Make the app reproducible: a git repository that installs into any instance

Every stage so far produced files — document descriptions, mapping queries, CSV data, media. Kept in a repository with a small install script, they are the dataspace: versionable, diffable, reviewable, and replayable into any LinkedDataHub instance with ldh push. This is the layout the Northwind Traders repository uses:

northwind-traders/
├── root.ttl              # the target document itself
├── categories.ttl        # one RDF file per document...
├── categories/           # ...and a folder for the files it holds
│   ├── categories.csv
│   ├── categories.rq
│   └── *.jpg
├── admin/model/
│   ├── ns.ttl            # the namespace ontology
│   └── patch-ontology.ru # resets it before re-import
├── imports.csv           # the CSV import manifest
├── install.sh            # replays everything against $LDH_BASE
├── .ldhignore            # files that are neither documents nor uploads
└── Makefile

To follow along, clone the apps repository — git clone https://github.com/AtomGraph/LinkedDataHub-Apps, the demo lives in demo/northwind-traders — or assemble the files from the previous stages into a folder of the same shape.

The convention

The document URL is derived from the file path: categories.ttl installs to ${base}categories/, and root.ttl at the top of the tree is the target document ${base} itself. ldh push walks the tree applying that rule: an RDF file is PUT to the document its path spells, with its relative URIs resolved against that URL; every other file is uploaded into its folder's document; a subfolder recurses under the child document the RDF file beside it describes. Anything matched by .ldhignore is skipped:

admin/
Makefile
imports.csv
screenshot.gif
unesco-mappings.ttl
*.csv
*.sh

The patterns apply to the whole subtree below the ignore file, gitignore-style. A name pattern matches at any depth, so entries need not appear in the tree above: unesco-mappings.ttl excludes categories/unesco-mappings.ttl, which is import material rather than a document, and screenshot.gif excludes the repository's own screenshot, which is not app media. A trailing slash restricts a pattern to directories, so admin/ keeps the ontology sources out of the document tree. Nothing is excluded by default beyond hidden entries, so *.sh keeps the scripts from being uploaded, and *.csv leaves the data files to the CSV imports, which upload each one into the document it is imported into.

ldh push --dry-run "$LDH_BASE"   # print the plan, send nothing
ldh push "$LDH_BASE"

Idempotency

Re-running the install converges on the same documents instead of duplicating data. PUT replaces each document's description outright, and the one place that appends rather than replaces — the namespace ontology — is first reset by patch-ontology.ru, a SPARQL update that clears everything except the ontology document's own description. (The steps that create rather than replace — make-public and each CSV import — are POSTs, so a re-run records another authorization and another import; the documents the imports write are the same.)

PREFIX foaf: <http://xmlns.com/foaf/0.1/>

DELETE {
  ?s ?p ?o
}
WHERE {
  ?s ?p ?o
  # Exclude triples about the main resource itself
  MINUS {
    <> ?p ?o
    BIND(<> AS ?s)
  }
  # Exclude triples about the foaf:primaryTopic resource
  MINUS {
    <> foaf:primaryTopic ?primaryTopic .
    ?primaryTopic ?p ?o
    BIND(?primaryTopic AS ?s)
  }
}

Installing

make install

make install prompts for the base URI, certificate path, certificate password and an optional proxy URL, exports them as the LDH_BASE, LDH_CERT_FILE, LDH_CERT_PASSWORD and LDH_PROXY variables the CLI reads, then runs install.sh: ldh admin make-public, the namespace ontology install, ldh push, and one ldh import csv per row of the manifest. The entire tutorial, replayed in one command — against localhost today, against production tomorrow. Export the variables yourself and run ./install.sh to install without the prompts.

What you now see

The install prints a banner per step — ### Creating authorization to make the app public, ### Importing namespace ontology, ### Pushing documents and files (with one PUT or POST line per document or upload), ### Importing CSV data. When it finishes, open the base URI: the finished app renders, with no certificate required — the first step made it public.

How this works

  • Command line interface — one command per HTTP API operation, scriptable and replayable
  • HTTP API — the PUT semantics that make the replay idempotent

Next: Beyond low-code