Files
MobilityOps/deploy/unraid/README.md
T
NuklearRabbit 444e61253b
MobilityOps acceptance / backend (push) Failing after 18s
MobilityOps acceptance / frontend (push) Successful in 27s
MobilityOps acceptance / e2e (push) Skipped
M55: restore private RAGcore routing
2026-08-24 03:45:09 +02:00

5.5 KiB

Unraid deployment

MobilityOps is deployed from a committed source archive; the server does not need Gitea credentials. The portable compose.yaml is combined with compose.unraid.yaml; of the MobilityOps-owned services, only the web application is reachable from the LAN. The host's existing shared n8n remains available on its established port 5678.

Server layout

  • Directory: /mnt/user/appdata/mobilityops
  • Compose project: mobilityops
  • Public web: https://fleetops.itworx.tech (TLS reverse proxy to host port 1236)
  • API and PostgreSQL: Compose network only
  • Shared n8n: http://192.168.10.150:5678 (outside the MobilityOps Compose project)

Deploy

Create .env from .env.example, replace every placeholder secret, set MOBILITYOPS_ENV=production, set both public URLs to https://fleetops.itworx.tech, set SESSION_COOKIE_SECURE=true, and configure KNOWLEDGE_PROVIDER=ragcore only after the RAGcore health and source inventory checks pass. The internal 1236 listener is an upstream for the TLS proxy, not a user-facing URL.

When RAGcore runs as a separate Compose project on the same host, keep its HTTP listener private and configure Fleet Ops through the existing shared Docker network, for example:

RAGCORE_BASE_URL=http://ragcore-app:8080
RAGCORE_DOCKER_NETWORK=ragcore-proxy

The release deployer validates the network, attaches only the API replicas and takes the non-secret RAGcore URL from the server .env on every promotion. Tokens and the remaining resolved API environment continue to be inherited without being printed.

cd /mnt/user/appdata/mobilityops
./deploy/unraid/configure-env.sh \
  https://fleetops.itworx.tech \
  https://n8n.itworx.tech/webhook/mobilityops-return
git archive --format=tar.gz -o /tmp/mobilityops-source.tar.gz HEAD
sha256sum /tmp/mobilityops-source.tar.gz
# Copy the archive and run deploy-release.sh with its SHA-256 and full Git SHA.

deploy-release.sh stages a clean, commit-named release, builds OCI-labelled immutable API/web images, starts two candidate replicas per stateless service behind a stable gateway, and removes the previous replicas only after public readiness passes. It promotes without a seed/reset and leaves the database, backups and monitoring untouched during routine app releases. Run python -m app.cli seed --reset only for initial setup or an explicit synthetic-demo reset; it is never part of a routine deployment.

Refresh the pinned stateful and monitoring containers explicitly after reviewing their version/configuration changes:

./deploy/unraid/refresh-infrastructure.sh

Migrations run automatically in the API entrypoint. Import and publish the MobilityOps workflow into the existing n8n container:

./deploy/unraid/setup-existing-n8n.sh \
  n8n \
  https://fleetops.itworx.tech/api/v1/integrations/n8n/return-callback

The callback token remains server-side and is never written to the repository. The bundled n8n service is retained only as a standalone fallback behind the explicit bundled-n8n Compose profile; it is not started in this deployment.

Operate

cd /mnt/user/appdata/mobilityops
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml ps
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml logs --tail=200
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec api alembic current
docker logs --tail=200 n8n

Backup and restore

The backup service creates a backup immediately and then every 24 hours. Every dump is checked by SHA-256 and pg_restore --list; at least weekly the newest dump is also restored into a disposable database and its migration revision and core table counts are verified. Backups are retained for 30 days with at least seven copies protected from pruning. Its healthcheck becomes unhealthy when the daily backup or eight-day restore-drill SLA is missed.

For this synthetic demo, OneDrive is the supported real off-site target. The optional worker uploads the newest locally verified dump and checksum, downloads both again, verifies the round trip, and performs a weekly restore into a disposable database. Its OAuth token remains in the untracked mode-0600 rclone configuration directory. Configure it once from an interactive server terminal (Microsoft browser authorization is required):

./deploy/unraid/configure-onedrive-backup.sh onedrive
docker inspect --format '{{.State.Health.Status}}' mobilityops-offsite-backup-1

Set RCLONE_ONEDRIVE_PATH to change the default FleetOps/backups folder. Do not set BACKUP_SECONDARY_DESTINATION to another path on the same server and call that off-site; that legacy mount remains available only for an independently mounted filesystem.

Create an additional on-demand backup, verify the newest backup, or execute the isolated restore drill:

./deploy/unraid/backup-postgres.sh
./deploy/unraid/verify-postgres-backups.sh
docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T backup \
  /opt/mobilityops/restore-drill-postgres.sh /backups/<backup>.dump

A restore is deliberately guarded and creates an additional safety backup before replacing the database:

./deploy/unraid/restore-postgres.sh \
  backups/postgres/mobilityops-YYYYMMDDTHHMMSSZ.dump \
  RESTORE-MOBILITYOPS

The restore stops the API, recreates only the configured MobilityOps database, restarts API/web and verifies the active Alembic revision. Test restores in a disposable environment before using a production backup for incident recovery.