diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f9b81ff --- /dev/null +++ b/.env.example @@ -0,0 +1,29 @@ +# SimHub configuration template. +# +# Copy this file to ".env" next to compose.yaml and edit as needed: +# +# cp .env.example .env +# +# Docker Compose reads a sibling ".env" automatically, so any values set here +# are picked up by `docker compose up -d` with no extra flags. The real ".env" +# is gitignored -- never commit it. +# +# NOTE ON PASSWORDS: SimHub has no admin-password environment variable. It seeds +# a provisional admin account (admin@simhub.local / admin123) that you are +# forced to re-password in the browser at first sign-in. There is no secret to +# put in this file for that -- set it in the app after the first `up -d`. + +# Runtime environment. Leave as "production" for real deployments. +NODE_ENV=production + +# Set to "1" (or the number of proxy hops) ONLY when running behind a reverse +# proxy (nginx, Caddy, Traefik) so login rate-limiting sees real client IPs. +# Leave unset otherwise -- trusting proxy headers without a proxy lets clients +# spoof their IP. +# TRUST_PROXY=1 + +# Port SimHub listens on INSIDE the container. Changing this also requires +# updating the right-hand side of the port mapping in compose.yaml and the +# healthcheck URL, so most deployments leave it at 3000 and change only the +# host (left) side of the compose port mapping instead. +# PORT=3000 diff --git a/README.md b/README.md index fe4a6f3..4acfa23 100644 --- a/README.md +++ b/README.md @@ -148,15 +148,27 @@ docker exec simhub node seed.js ### Recommended — Docker Compose -The repository ships a ready-made [docker-compose.yml](docker-compose.yml). Copy it (or clone the repo) onto the server, then: +The repository ships a ready-made [compose.yaml](compose.yaml) that **pulls the published image** — you do **not** need the source checked out on the server. Grab just that one file onto the server and start it: ```bash -docker compose up -d # start +# Download the compose file next to where your data volume will live +curl -fsSL -o compose.yaml \ + https://raw.githubusercontent.com/authorTom/simhub/main/compose.yaml + +# Optional: configuration overrides (proxy, port). Compose auto-reads a sibling .env +curl -fsSL -o .env.example \ + https://raw.githubusercontent.com/authorTom/simhub/main/.env.example +cp .env.example .env # then edit .env if you need TRUST_PROXY, etc. + +docker compose up -d # start (pulls the image) +docker compose ps # confirm STATUS shows "(healthy)" docker compose exec simhub node seed.js # optional: load the example scenario docker compose logs -f simhub # watch the logs ``` -Compose gives you a declarative record of your deployment (port, volume, environment) that you can keep in your team's documentation. Uncomment `TRUST_PROXY: "1"` in the file when running behind a reverse proxy. +Then open `http://:3000` and sign in as `admin@simhub.local` / `admin123`; you are forced to set a real password at first sign-in. + +Compose gives you a declarative record of your deployment (port, volume, environment) that you can keep in your team's documentation. To run behind a reverse proxy, set `TRUST_PROXY=1` in `.env` (or uncomment it in `compose.yaml`). ### Your data lives in the volume @@ -173,7 +185,7 @@ docker run --rm -v simhub-data:/data -v "$PWD":/backup alpine \ docker compose pull && docker compose up -d ``` -That's it — the new container starts against the same data volume, and persisted sessions mean your faculty aren't even signed out. To be able to roll back, deploy a pinned tag (`ghcr.io/authortom/simhub:sha-` or a release version) instead of `latest`, and change the tag in `docker-compose.yml` when you upgrade. +That's it — the new container starts against the same data volume, and persisted sessions mean your faculty aren't even signed out. To be able to roll back, deploy a pinned tag (`ghcr.io/authortom/simhub:sha-` or a release version) instead of `latest`, and change the tag in `compose.yaml` when you upgrade. ### Building the image yourself @@ -246,7 +258,8 @@ simhub/ ├── CHANGELOG.md # Full change history ├── CONTRIBUTING.md # Contribution guide ├── Dockerfile # Production container image -├── docker-compose.yml # Departmental deployment recipe +├── compose.yaml # Departmental deployment recipe (pull-based) +├── .env.example # Config template -> copy to .env ├── LICENSE # MIT licence ├── scenario_template.md # ASPiH scenario blueprint (reference) ├── seed.js # Example dataset generator diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..170a378 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,59 @@ +# SimHub -- departmental deployment. +# +# docker compose up -d start (pulls the published image) +# docker compose pull && docker compose up -d update to the latest image +# docker compose exec simhub node seed.js load the example scenario +# docker compose ps check status (look for "healthy") +# +# Compose automatically reads a sibling ".env" file, so copy ".env.example" +# to ".env" and edit it to override any of the settings below. +# +# To build from local source instead of pulling the published image, comment +# out the `image:` line and uncomment the `build: .` line. Building needs the +# full source tree checked out next to this file; pulling does not. + +services: + simhub: + # Published automatically to GitHub Container Registry on every push to + # main. GHCR forces the owner/repo to lowercase, hence "authortom". + image: ghcr.io/authortom/simhub:latest + # build: . + container_name: simhub + + # Restart on failure and on daemon/host reboot, but not if an operator + # deliberately stopped it. + restart: unless-stopped + + # PID 1 that forwards signals and reaps zombies, so SIGTERM reaches node + # and the graceful-shutdown handler can flush state on `compose down`. + init: true + + ports: + # "host:container" -- change the LEFT number to serve on a different host + # port. Quoted so YAML never reinterprets it (e.g. 22:22 as base-60). + - "3000:3000" + + environment: + # Production defaults. Override in .env if needed. + NODE_ENV: production + # TRUST_PROXY: "1" # set when behind a reverse proxy (see .env.example) + + volumes: + # All scenarios, programmes, users and sessions live here. A named volume + # survives image updates and rebuilds; back it up regularly. + - simhub-data:/app/data + + healthcheck: + # Alpine/slim runtime images ship no curl or wget, so probe with Node's + # built-in fetch (Node 18+). Exec (list) form is used deliberately: it + # avoids a shell AND keeps YAML from misreading the "://" / colon-space + # in the script as a nested mapping. A bare `test: node -e "a: b"` string + # containing a colon-space WOULD need quoting; the list form sidesteps it. + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"] + interval: 30s + timeout: 5s + start_period: 10s + retries: 3 + +volumes: + simhub-data: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 1ccbe84..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,30 +0,0 @@ -# SimHub — departmental deployment. -# -# docker compose up -d start (pulls the published image) -# docker compose pull && docker compose up -d update to latest -# docker compose exec simhub node seed.js load the example scenario -# -# To build from local source instead of pulling the published image, -# comment out `image:` and uncomment `build: .` - -services: - simhub: - image: ghcr.io/authortom/simhub:latest - # build: . - container_name: simhub - restart: unless-stopped - ports: - # host:container — change the left side to serve on a different port - - "3000:3000" - volumes: - # All scenarios, programmes, users and sessions live here. - # A named volume survives image updates; back it up regularly. - - simhub-data:/app/data - environment: - # Uncomment when running behind a reverse proxy (nginx, Caddy, Traefik) - # so login rate-limiting sees real client IPs: - # TRUST_PROXY: "1" - NODE_ENV: production - -volumes: - simhub-data: