Deploy the site

Deploy the site

Put MOF++ on the internet, and keep it there without anybody minding it.

The whole deployment is three files in deploy/ and one command. It is written for a project with no dedicated maintainer, so every choice below prefers "nothing to remember" over "nothing to waste".


What it looks like when it is running

internet ──▶ nginx :80/:443 ──▶ uvicorn 127.0.0.1:8000 ──▶ mofpp.db (SQLite)
             TLS, one domain      systemd unit,              one file
             certbot renews       restarts itself

The application listens on loopback only. Nothing reaches it except through nginx, so there is exactly one thing exposed and one place to configure TLS.


Install

You need a DNS A record pointing your chosen name at this server, and nginx installed. Then:

cd ~/sandbox/mofpp
sudo MOFPP_HOST=nets.example.org deploy/install.sh

That writes /etc/mofpp.env, installs the systemd unit and the nginx site, reloads both, and smoke-tests the result — failing loudly rather than leaving you half-deployed. It is idempotent: run it again after a code change, a reboot, or a change of hostname.

Then, once, add TLS:

sudo certbot --nginx -d nets.example.org

Certbot edits the nginx site in place, adds the HTTP→HTTPS redirect, and installs its own renewal timer. Nobody has to remember to renew a certificate, which is the only reason TLS is not done by install.sh itself — certbot's timer is lower-maintenance than anything we would write.


Day to day

After a code change sudo systemctl restart mofpp
Logs journalctl -u mofpp -f
Is it up? systemctl status mofpp
After a data reload nothing — the app reads the database per request

There is no logfile to rotate (the journal handles it), no cache to invalidate, no worker pool to tune, and no migration tool. That is the maintenance surface, in full.


The choices, and why

Each of these is a decision to have less to maintain, not less to run.

SQLite, not PostgreSQL. The dataset is 3077 nets, read almost exclusively. SQLite is a file: backing the site up is copying mofpp.db and var/files/. A database server is a second service to patch, secure and monitor. Revisit when concurrent writes appear — a deposition workflow, say. The models are plain SQLAlchemy and port without a rewrite; Vertex.vs is Text precisely so that a PostgreSQL cutover does not truncate a long vertex symbol.

One uvicorn worker. Read-mostly traffic over a local file. A worker pool is a number to tune and therefore a number to get wrong. Raise --workers in deploy/mofpp.service if a measurement — not an intuition — says to.

systemd, not a container. The unit is 40 lines and restarts on crash and on boot. A container adds an image to rebuild, a registry to keep, and a second place for the Python version to drift.

certbot's timer, not our cron. It already exists, it already works, and a forgotten certificate renewal is the single most likely way this site goes dark.

No cache layer. Record pages embed their own geometry and the data changes when somebody runs an ingest. A cache would be a thing that goes stale and gets debugged.


The two settings that matter

Both live in /etc/mofpp.env, which install.sh writes and which is not in the repository — it holds a secret, and it is the only file that differs between a laptop and the server.

MOFPP_BASE_URL is the base of every record URI the site serves. FAIR F1 requires those URIs to resolve, so it must be the public https:// address. Get it wrong and the site publishes 3077 identifiers pointing at localhost — the pages still work, which is what makes it easy to miss. To check:

curl -s https://nets.example.org/net/pcu.json | grep -o '"uri":"[^"]*"'

MOFPP_SECRET_KEY signs session cookies. install.sh generates it once and keeps it across re-runs: regenerating it on every deploy would sign every user out on every deploy.


Backups

Two paths hold everything the site cannot regenerate:

mofpp.db        # the records
var/files/      # the deposited .mfpx files

var/ingest_cache/ is a retrieval cache and can be refetched from MOF+. var/reports/ and var/state/ are run artefacts. Copy the two paths above somewhere else on a schedule and the site is recoverable; a fresh checkout plus those two paths is the whole restore procedure.


Reloading the data

The ingest is idempotent and restartable, so this is safe to re-run:

python3 scripts/fetch_mofplus_nets.py     # ~13 min, read-only, resumable
.venv/bin/mofpp ingest-nets               # converges; does not duplicate

Then apply the licence decision, which is a separate, auditable step rather than a side effect of loading:

.venv/bin/python -c "
from mofpp.db import SessionLocal
from mofpp.services import ingest
with SessionLocal() as s:
    print(ingest.apply_licence_decision(s).as_dict()['by_source']); s.commit()"

No restart is needed: the application reads the database per request.


If it does not come up

install.sh prints the journal and exits non-zero rather than reporting success, so start with what it showed you. Otherwise, in order:

systemctl status mofpp            # did the unit start
journalctl -u mofpp -n 50         # why not
sudo nginx -t                     # is the site config valid
curl -I http://127.0.0.1:8000/    # is the app answering at all

An app that answers on 8000 but not through nginx is a proxy problem; an app that does not answer on 8000 is in the journal.