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.