From f4f9066e36fa86ec5e2e8c2543bc12de03d18bb5 Mon Sep 17 00:00:00 2001 From: Krstan Vjestica Date: Tue, 11 Aug 2026 14:05:00 +0200 Subject: [PATCH 1/3] fix: nginx never started, because nothing ever generated a certificate nginx terminates TLS and refuses to start when the certificate it is configured with is missing. nginx/ssl/ ships empty (just a .gitkeep), and nothing in the compose path puts anything there, so a clean git clone && cp .env.example .env && docker compose up -d left nginx in a restart loop with nothing serving on 80 or 443 -- the whole stack was up and unreachable. The instructions for producing a certificate exist, but in README-prod.md, which the quickstart never sends you to. Generate a self-signed pair on first start when nginx/ssl/ is empty, and say so loudly in the log. A certificate that is already there is never touched, so this changes nothing for an operator who supplies a real one. --- docker-compose.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 8da3e61..fb40613 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -196,6 +196,33 @@ services: restart: unless-stopped # ── External nginx (TLS termination + routing) ───────── + # ── TLS certificate bootstrap ───────────────────────── + # nginx terminates TLS and refuses to start without a certificate, so a + # clean `docker compose up -d` used to leave it in a restart loop with + # nothing serving on 80 or 443. Generate a self-signed pair when none is + # present; an operator-supplied certificate in nginx/ssl is never touched. + # Self-signed is for getting started only -- see README-prod.md for the + # real thing. + nginx-cert-init: + image: alpine:3.20 + command: + - sh + - -c + - | + if [ -f /ssl/fullchain.pem ] && [ -f /ssl/privkey.pem ]; then + echo "TLS certificate present, leaving it alone." + exit 0 + fi + apk add --no-cache openssl >/dev/null + openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout /ssl/privkey.pem -out /ssl/fullchain.pem \ + -subj "/CN=${FORAIL_TLS_CN:-localhost}" >/dev/null 2>&1 + echo "WARNING: generated a self-signed TLS certificate for ${FORAIL_TLS_CN:-localhost}." + echo "WARNING: browsers will not trust it. Replace nginx/ssl/ before production." + volumes: + - ./nginx/ssl:/ssl + restart: "no" + nginx: image: nginx:1.27-alpine ports: @@ -207,6 +234,8 @@ services: networks: - forail depends_on: + nginx-cert-init: + condition: service_completed_successfully forail-web: condition: service_healthy forail-frontend: From ef43a42dcab0e5b681b41571b7a51c53ae0035df Mon Sep 17 00:00:00 2001 From: Krstan Vjestica Date: Wed, 12 Aug 2026 10:40:00 +0200 Subject: [PATCH 2/3] fix: the pinned image tag could not run a job .env.example pinned 2026.07.0, which predates 0cb32fe. On that image the node registers as `control` instead of the requested `hybrid`, and the `default` queue is created as a Kubernetes container group. On Compose there is no Kubernetes, so project updates fail with unknown work type kubernetes-incluster-auth and every job launched afterwards sits in `pending` forever. The env var asking for a hybrid node was simply ignored. 2026.07.1 is not a fix either: it was published for the backend only, so pinning it makes the frontend image unresolvable. 2026.07.2-rc1 is the lowest tag that exists for both images and contains the fix. Also add FORAIL_TLS_CN for the generated certificate. --- .env.example | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index c956563..354d08c 100644 --- a/.env.example +++ b/.env.example @@ -39,10 +39,20 @@ FORAIL_NODE_TYPE=hybrid #FORAIL_TASK_CGROUP=host # ── Docker images ──────────────────────────────────────────────────────────── -# Pin to a release tag in production (e.g., 2026.04.0), not latest. +# Pin to a release tag in production, not latest. The tag has to exist for +# BOTH images -- 2026.07.1 was published for the backend only, so pinning it +# leaves the frontend unresolvable. +# +# Do not go below 2026.07.2-rc1 for a single-node install: 2026.07.0 registers +# the node as `control` and makes the `default` queue a Kubernetes container +# group, so every job sits in `pending` forever on Compose. FORAIL_BACKEND_IMAGE=ghcr.io/forail-platform/forail-backend FORAIL_FRONTEND_IMAGE=ghcr.io/forail-platform/forail-frontend -FORAIL_TAG=2026.07.0 +FORAIL_TAG=2026.07.2-rc1 + +# Subject name for the self-signed certificate generated on first start when +# nginx/ssl/ is empty. Ignored once a real certificate is in place. +FORAIL_TLS_CN=localhost # ── Nginx / Networking ────────────────────────────────────────────────────── #NGINX_HTTP_PORT=80 From bdc2cacf7d841e86562925b86d4d908a02eb90d0 Mon Sep 17 00:00:00 2001 From: Krstan Vjestica Date: Thu, 13 Aug 2026 16:15:00 +0200 Subject: [PATCH 3/3] docs: the quickstart described a stack that cannot execute anything Two things the Quick Start asserted were not true on a clean machine. It said every service should report healthy after `docker compose up -d`; nginx could not, because no certificate existed. And it never mentioned that job execution is off by default -- so a reader who followed it exactly got an API and a UI where projects never sync and jobs never leave `pending`, with nothing pointing at why. Say where the UI is and how to log in, describe the self-signed certificate, and document FORAIL_TASK_PRIVILEGED together with the reason it defaults to off. --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e2fe489..f25d0a5 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ cd forail-devops # 2. Configure environment cp .env.example .env -# Edit .env with real values +# Set the four `changeme` values in .env -- the stack will not come up without +# them. The rest has working defaults. # 3. Start docker compose up -d @@ -33,6 +34,29 @@ docker compose up -d docker compose ps # every service should report (healthy) ``` +The UI is then on ; `http://` redirects to it. Log in with +`FORAIL_ADMIN_USER` / `FORAIL_ADMIN_PASSWORD` from your `.env`. + +On first start, when `nginx/ssl/` is empty, a **self-signed** certificate is +generated so nginx has something to serve — your browser will warn about it. +Drop a real certificate into `nginx/ssl/` and it is left alone; see +[README-prod.md](README-prod.md). + +### Running jobs + +The stack above serves the API and the UI, but it **cannot execute anything** — +projects will not sync and every job stays in `pending`. Job execution runs +playbooks through podman inside the task container, which needs cgroup, mount +and user-namespace privileges: + +```bash +FORAIL_TASK_PRIVILEGED=true FORAIL_TASK_CGROUP=host docker compose up -d +``` + +This is **off by default on purpose**: a privileged container is a trivial +escape to host root. Turn it on only on a host you are willing to treat as a +dedicated job runner. This is tracked as H4 in the security notes. + The scripts under `scripts/` are the container health probes — Compose mounts them into the containers and runs them there (`bash /etc/forail/healthcheck-web.sh`), so they call `forail-manage` and only work inside the image. Run one by hand with: