diff --git a/INSTALLATION.md b/INSTALLATION.md new file mode 100644 index 0000000..a7092c1 --- /dev/null +++ b/INSTALLATION.md @@ -0,0 +1,220 @@ +# Installing Recapp + +This guide describes how to self-host Recapp on a single server using its +Docker Compose stack with **Caddy** as the reverse proxy. Caddy terminates TLS +(automatic Let's Encrypt certificates), serves the frontend, and routes +`/api` and WebSocket traffic to the backend over an internal Docker network — +so only Caddy is exposed to the public internet. + +This guide covers a production/staging deployment on a server. Running the +packages locally for development (hot-reload, no Docker for the app code) is a +separate workflow and is not covered here. + +## Overview + +A deployment consists of three containers, orchestrated by `deployment.sh`: + +| Container | Role | +|---|---| +| `caddy` | Reverse proxy, TLS termination, static frontend serving | +| `backend` | Node.js API + WebSocket server | +| `recapp-db` | MongoDB, data persisted on a host bind mount | + +`deployment.sh` pulls the chosen branch, runs `npm ci`, builds the images, and +starts the stack. Only ports `80` and `443` are published on the host. + +## 1. Prerequisites + +- A server (physical or virtual) reachable from the internet on ports `80`/`443`, + running a recent Linux. Tested on Ubuntu 24.04 / 26.04 LTS. +- A **domain name** you control, with a DNS `A` (and optionally `AAAA`) record + pointing at the server's public IP. Set this up **before** deploying — Caddy + needs the domain to resolve to the server to obtain a Let's Encrypt + certificate. +- An **OpenID Connect (OIDC) provider** (e.g. Keycloak, Authentik, or your + institution's SSO) for teacher/student login. Anonymous guest participation + works without it, but at least one OIDC client is required for authenticated + accounts. + +### Install the software prerequisites + +```bash +sudo apt-get update +sudo apt-get install -y nodejs npm docker.io docker-compose-v2 +``` + +- **Node.js 20+** is required — `deployment.sh` runs `npm ci` on the host. + Check with `node -v`; if your distribution ships an older version, install + Node 20 via [NodeSource](https://github.com/nodesource/distributions) or nvm. +- **`docker-compose-v2`** provides the `docker compose` subcommand. The + `docker.io` package alone does **not** include it, and `deployment.sh` will + fail with `unknown command: docker compose` without it. +- The deploying user needs **passwordless `sudo` for Docker**. `deployment.sh` + runs every Docker command as `sudo docker …` and aborts at the start if + passwordless `sudo` is unavailable. It does *not* assume rootless Docker or + `docker`-group membership; `npm ci` is the only step that runs unprivileged. + +### Firewall + +Expose only what you need: + +- `22/tcp` — SSH (administration) +- `80/tcp`, `443/tcp` — Caddy (HTTP challenge + HTTPS) + +The backend, frontend, and database publish **no** host ports; they are only +reachable over the internal Docker network. + +## 2. Clone the repository + +`deployment.sh` expects the repository at `$HOME/recapp`: + +```bash +git clone https://github.com/ecomod-code/recapp.git "$HOME/recapp" +cd "$HOME/recapp" +``` + +## 3. Configure the environment + +Copy the template and fill in real values: + +```bash +cp .env.template .env.production +``` + +Edit `.env.production`. The values that **must** change from the template: + +| Variable | Set to | Notes | +|---|---|---| +| `DOMAIN` | your public hostname | Must match the DNS record from step 1 | +| `FRONTEND_URI` | `https://` | No path suffix | +| `BACKEND_URI` | `https:///api` | **Must** include `/api` — Caddy strips the prefix before forwarding | +| `OPENID_PROVIDER` | your OIDC provider base URL | | +| `OID_CLIENT_ID` / `OID_CLIENT_SECRET` | OIDC client credentials | | +| `ISSUER` | OIDC issuer path segment | Appended to `OPENID_PROVIDER` | +| `REDIRECT_URI` | `/auth/callback` | OIDC callback path, appended to `BACKEND_URI` | +| `REQUIRES_OFFLINE_SCOPE` | `false` (usually) | Set `true` only if your provider needs the `offline_access` scope to issue refresh tokens. Some providers (e.g. certain Keycloak setups) reject it — leave `false` there. | +| `API_KEYS` | comma-separated UUIDs | Shared secrets for internal actor authentication; generate fresh ones | +| `MONGODB_USER` / `MONGODB_PW` | database credentials | | +| `JWT_SECRET` | a long random string | Signs temporary (guest) account tokens | + +> **Do not leave `BACKEND_URI` / `FRONTEND_URI` at the `localhost` template +> defaults.** `BACKEND_URI` is baked into the frontend bundle at build time (it +> is used for the login link and the WebSocket URL) *and* used to build the OIDC +> redirect. A `localhost` value produces a broken login link and a dead auth +> flow. + +### Register the OIDC callback + +In your OIDC provider, add the callback as an allowed redirect URI for the +client: + +``` +/auth/callback e.g. https:///api/auth/callback +``` + +If this is missing, login fails at the callback step rather than at the login +link. + +## 4. Deploy + +```bash +bash deployment.sh +``` + +Use the branch you want to deploy (e.g. `main`). The script fetches the branch, +does a clean install, builds the images, starts the stack, and health-checks the +backend. **The first run takes longer than usual** while Caddy obtains its +Let's Encrypt certificate. + +A few things the script assumes, worth knowing before you run it: + +- It reads **`.env.production`** specifically — the filename is fixed, not + configurable. +- With **no argument** it deploys the `production` branch; always pass the + branch you mean. +- It runs `git reset --hard origin/` in `$HOME/recapp`, so **any + uncommitted local changes in that directory are discarded**. Your + `.env.production` is safe — it is gitignored and untracked — but don't keep + other local edits there. + +## 5. Verify + +```bash +sudo docker compose --env-file .env.production \ + -f docker/docker-compose.yaml -f docker/docker-compose.prod.yaml ps +``` + +- All services should be `running` / healthy. +- `https://` loads the frontend. +- `https:///api/ping` returns HTTP 200. + +## Updating + +To deploy a newer version, just re-run the script — it resets the working tree +to the exact remote state of the branch, rebuilds, and recreates the containers: + +```bash +bash deployment.sh +``` + +## Backups + +Two host directories hold all persistent state and should be included in backups: + +- `docker/mongo/data/db` — the MongoDB data. +- `docker/caddy/` — Caddy's ACME account and issued certificates. + +> **Do not delete `docker/caddy/`.** Wiping it forces certificate re-issuance, +> which Let's Encrypt rate-limits. Both directories are bind-mounted and +> gitignored. + +## Appendix: migrating data from an existing instance + +Because MongoDB data lives on a host bind mount, a physical copy is the simplest +faithful migration (it carries users, indexes, and everything else). Compose +commands are abbreviated below as `docker compose …` — use the same +`--env-file`/`-f` flags shown in step 5. + +**Physical copy (full move):** + +```bash +# SOURCE — stop the DB for a consistent snapshot, then archive the data dir +sudo docker compose … stop recapp-db +sudo tar czf recapp-db.tar.gz -C docker/mongo/data db +scp recapp-db.tar.gz :~/ + +# NEW — after at least one deploy has created the stack, stop, swap, restart +sudo docker compose … stop +sudo rm -rf docker/mongo/data/db +sudo tar xzf ~/recapp-db.tar.gz -C docker/mongo/data +sudo docker compose … up -d +``` + +**Logical dump** — use if the MongoDB versions differ or for a selective copy: + +```bash +# SOURCE +sudo docker compose … exec -T recapp-db mongodump --archive --gzip > recapp.archive.gz +# NEW +cat recapp.archive.gz | sudo docker compose … exec -T recapp-db mongorestore --archive --gzip --drop +``` + +## Appendix: Docker MTU on overlay networks + +On some cloud platforms the VM sits on a VXLAN/overlay network with a reduced +MTU (commonly **1450**). Docker's default bridge uses 1500, so network calls +made *during an image build* (e.g. `apt-get`) can silently time out even though +image pulls succeed. If builds hang on network operations, set the daemon +default: + +```json +// /etc/docker/daemon.json +{ "mtu": 1450 } +``` + +```bash +sudo systemctl restart docker +``` + +If your network has a normal MTU (most bare-metal and many cloud setups), you do +not need this. diff --git a/README.md b/README.md index 158a1a7..90451b9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## What is Recapp? -Recapp is a collaborative quiz tool designed for university teaching. It is built around a +Recapp is a collaborative quiz tool designed for teaching. It is built around a learning-through-teaching approach: instead of the teacher writing all the questions, **students write the quiz questions themselves** — and then answer each other's questions when the teacher starts the quiz session. @@ -22,33 +22,24 @@ start the session, and monitor live statistics. **For students:** write questions during the editing phase, then answer your fellow students' questions during the quiz run. You can also comment on and upvote questions. -Participation works either via university OpenID Connect accounts or anonymously via +Participation works either via OpenID Connect accounts or anonymously via browser fingerprint — no registration required for guests. -## Setup instructions +## Documentation -Run `npm install`und init your repo with `lerna bootstrap`. Note that this repo uses _npm workspaces_, so make sure to always add packages with `-w MODULE` to the individual projects. - -Also make sure to create a `.env.development` (or `.env.production`) file from the `.env.template` in the repo. - -Make sure to edit the `.env` file in the frontend project to the proper URIs. - -### Model generator - -There is simple generator for new data schemas/models if needed. It can be called via `npx pinion generators/model.template.ts`. - -See also our [Installation Guide](INSTALLATION.de.md) (at the moment, German only) +- **[User Guide](https://ecomod-code.github.io/recapp/)** — how to use Recapp as a teacher or student. +- **[Installation Guide](INSTALLATION.md)** — self-hosting Recapp on your own server (Docker Compose + Caddy). ## Contributing -**recapp** is a free and open-source project. Therefore, we warmly welcome your suggestions, bug reports, or, in the best case, code contributions! For more information, see -[CONTRIBUTING](CONTRIBUTING.md). +**recapp** is a free and open-source project. Therefore, we warmly welcome your suggestions, bug reports, or, in the best case, code contributions! For more information, see [CONTRIBUTING](CONTRIBUTING.md). -Maintainers and contributors must follow this repository’s [CODE OF -CONDUCT](CODE_OF_CONDUCT.md). +Maintainers and contributors must follow this repository’s [CODE OF CONDUCT](CODE_OF_CONDUCT.md). -## ENTWICKLUNG UND FÖRDERUNG +## Development & Funding Die Entwicklung von RECAPP wurde mit Mitteln des Niedersächsischen Ministeriums für Wissenschaft und Kultur gefördert. Die initiale Entwicklung erfolgte an der Georg-August-Universität Göttingen. +*The development of RECAPP was funded by the Lower Saxony Ministry of Science and Culture (Niedersächsisches Ministerium für Wissenschaft und Kultur). Initial development took place at the University of Göttingen.* + MWK Wappen Logo Uni Goettingen diff --git a/package.json b/package.json index 573bab9..a88fc0f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "start:docker:prod": "docker-compose --env-file .env.production -f docker/docker-compose.yaml -f docker/docker-compose.prod.yaml up -d", "stop:docker:dev": "docker-compose --env-file .env.development -f docker/docker-compose.yaml -f docker/docker-compose.dev.yaml down", "stop:docker:prod": "docker-compose --env-file .env.production -f docker/docker-compose.yaml -f docker/docker-compose.prod.yaml down", - "bootstrap": "lerna bootstrap", "dev": "lerna run dev", "build": "lerna run build", "docs": "typedoc",