Repository navigation
feat(console): one image for every instance, published beside the worker, and a service of the compose file (FRU-222) - #290
Conversation
…ker, and a service of the compose file (FRU-222) The address of the worker was compiled into the console. The container now reads FRUITBACK_API when it starts, writes it in /config.js, which the page loads before its bundle, and its origin in the Content-Security-Policy. The value is held to the letters of an address first; no address, or what is not one, stops the container with a sentence. The build argument of the Cloud is still taken, as the address of a container that is given none. The image runs as user 101 on the slim nginx base with its packages upgraded, writes under /tmp only, and holds nothing of the repository but the built files. release-image.yml gets two jobs for it, check-console and publish-console, behind the gates of the worker. docker-compose.yml gets a console service behind a profile. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
Score de confiance : 4/5 · ✅ Rien de bloquant · Mergeable en l'état : je n'ai trouvé aucun défaut certain dans le diff. Les points non vérifiés (csp.mjs, les tests de la console, les références à check-api.sh supprimé) restent à confirmer par l'équipe.
Niveau lite · anthropic/claude-haiku-5.5 · 0,012 $ · Le résumé, le diagramme et la liste des constats sont à la fin de la description de la PR.
|
@sakuga-review review heavy |
There was a problem hiding this comment.
Score de confiance : 4/5 ·
Niveau heavy · anthropic/claude-opus-5.5 · 0,543 $ · Le résumé, le diagramme et la liste des constats sont à la fin de la description de la PR.
For Mathieu to read: CI, publication and the compose file. Merging this to
mainpublishes a new package,ghcr.io/sakuga-software/fruitback-console, at the next run ofrelease-image.yml. Nothing was published, dispatched or pushed to a registry from this branch.Refs FRU-222 (not
Closes: the sign-in by the link of the first admin cannot be measured before FRU-221, and the image is not published yet)Three things to check before merging
app.fruitback.comwithVITE_FRUITBACK_APIand gives the container no variable. That path is kept and tested in CI, and the policy it serves is the same (below). Two things change for it all the same: the base image isnginx:1.29-alpine-slimwithapk upgrade, and nginx runs as user101, still on port80. Docker lets any user open that port (measured here: uid101listens on80); not measured on Dokploy itself.fruitback-consolewill need the visibility and the link to the repository thatfruitback-workerhas, ordocker compose --profile consolecannot pull it.docker compose updoes not change. The console is behind a profile, like the database it needs.The choices, the simplest each time
FRUITBACK_API.entrypoint.shwrites it in/config.js, which the page loads in<head>before its bundle (app/instance.ts), and its origin in theconnect-srcof the policy. No rebuild, noenvsubston the bundle.8080and the console on8081; behind a proxy, two names of one domain (api.andapp.). Reason: it is what the Cloud runs and what the worker is written and tested for. The worker answers the origin ofFRUITBACK_CONSOLE_URLand no other, with credentials, and the session cookie is the worker's,SameSite=Strict, which a browser sends between two names of one site. One name with two paths would need a proxy that knows every path of the worker; nobody measured it (FRU-200), so it is not offered.FRUITBACK_PUBLIC_URL, which the worker already has. Two new variables only, both for Compose:FRUITBACK_CONSOLE_IMAGEandFRUITBACK_CONSOLE_PORT.What changes
apps/console/Dockerfile,entrypoint.sh(replacescheck-api.sh),nginx.conf,nginx.conf.template:FRUITBACK_APIis held to the letters of an address (scheme, host, port, path) before anything is written: it goes into a header and into a script. No address, or what is not one, stops the container with a sentence and exit1.101:101, annginx.confwith nouserline, everything written under/tmp; the entry scripts of the base image are removed.FRUITBACK_CONSOLE_LISTEN(default80) for a runtime that keeps low ports to root.nginx:1.29-alpine-slim+apk upgrade: the release gate would have refused the image on the base of before (Trivy found fixable HIGH findings inpcre2,nghttp2-libs,tiffofnginx:1.29-alpine; on this image: 0).server_tokens off.apps/console/app/instance.ts(+ test),transport.ts,root.tsx(one<script src="/config.js">in<head>; no screen is touched),public/config.js(sets nothing:pnpm devand the E2E suite are as before)..github/workflows/ci.yml, jobdocker imageonly (nothing added toe2e): the image built with no address and started for two workers; eight refusals; not root, no source file, nothing writable outside/tmp; a read-only root with no capability; the Cloud build started with no variable; theconsoleprofile of the compose file..github/workflows/release-image.yml:check-console(both architectures, booted, Trivy) andpublish-console(needs: [ci, check-console], theifofpublish, the same tags, SBOM, provenance, attestation). Every action is pinned by SHA, the ones the worker's jobs use.docker-compose.yml: aconsoleservice,profiles: [console], one published port,read_only,tmpfs: /tmp,cap_drop: ALL,no-new-privileges, two variables and no secret..env.example,compose.test.ts(five new cases),docs/self-hosting.md(a short section; the whole guide is FRU-227),docs/decisions/image.md,docs/decisions/console.md,SECURITY.md.Measured, on images built from this branch (Docker Desktop, arm64)
docker image inspect), of which 1.7 MB are the files of the console. With the fullnginx:1.29-alpinebase it was 26.2 MB.https://api.acme.devand withhttp://localhost:8080/, each servesconnect-src 'self' <its own origin>andglobalThis.__FRUITBACK__ = { api: "<its own address>" }, the slash removed.Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-…' ×4; style-src 'self' 'unsafe-inline'; connect-src 'self' <worker>; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'self',Cache-Control: no-store,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrer,Server: nginx. On/config.js:application/javascript,no-store,nosniff.https://a.example; script-src *; a quote;"};alert(1);//;user:pw@; a query;javascript:; two lines.idanswersuid=101(nginx), every nginx process isnginx, nothing is writable outside/tmp. It serves on80as that user, and it starts and ishealthyon8080with--read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges..ts,.tsx,.map,package.json, lockfile,.envornode_modulesin the image; its environment holdsFRUITBACK_APIandFRUITBACK_CONSOLE_LISTEN.VITE_FRUITBACK_API=https://api.fruitback.comand started with no variable: the same headers on every route, but for two things:Server: nginxin place ofnginx/1.29.8, and one of the four script hashes (the inline script that names the files of the build, which changed withroot.tsx).connect-srcand every other directive are the same.env -i: the compose file and a.envfrom the example,docker compose --profile postgres --profile console up -d --wait: worker, database and console healthy. In a real Chromium, the sign-in screen of the console served by the image reads its address from/config.js, calls the worker (GET /auth/providers200,GET /console/me401, both withAccess-Control-Allow-Originof the console and credentials), and reports no violation of the policy; afetchof another origin planted in the page is blocked byconnect-src(the control). The worker answers403 origin-not-allowedto another origin.FRUITBACK_PUBLIC_URLin that directory:up --waitfails in two seconds and the log holds the sentence.zizmor1.30.1 offline on.github/workflows: no finding. The three new shell steps ofci.ymlwere extracted and run here as written: exit 0.pnpm test(7 projects): green.Not verified
http://localhost, where the cookie isSecurewith no TLS: Chromium and Firefox are documented to accept it there, Safari is not. Two names of one domain over HTTPS is what the Cloud runs.linux/amd64was not built here (the machine is arm64):check-consolebuilds and boots both, and it has not run.release-image.ymlitself has not run with these jobs: no dispatch, no push.🤖 Generated with Claude Code
Sakuga Review · Score de confiance : 4/5
Fusion possible : aucun défaut bloquant trouvé dans l'image, l'entrypoint ou la lecture de l'adresse côté bundle ; seules trois vérifications négatives de la CI ne peuvent jamais échouer.
Constats
! grepne font jamais échouer l'étape.github/workflows/ci.yml:468▶Résumé
La PR fait de la console une image unique et publiée.
entrypoint.shvalideFRUITBACK_API, puis écrit/tmp/fruitback/config.jset la politique CSP. Le bundle lit l'adresse viaapiFrom, et le compose ajoute un serviceconsolesous profil. J'ai vérifié : le motif de validation (la vérification multi-ligne rattrapegrepligne à ligne), l'absence de caractère spécial poursed, l'ordre de chargement de/config.jsavant le module, le repli sur l'adresse du build pour le Cloud, le healthcheck avec le port variable, les tests decompose.test.tsface au fichier, et la dépendance depublish-consoleàci. Une seule réserve dansci.yml: sousbash -e, une commande niée par!ne déclenche jamais errexit.Diagramme
%%{init: {'theme': 'neutral'}}%% flowchart LR A["docker-compose.yml / docker run"] --> B["entrypoint.sh"] B --> C["/tmp/fruitback/config.js"] B --> D["console.conf (CSP connect-src)"] D --> E["nginx"] C --> E E --> F["root.tsx script /config.js"] F --> G["instance.ts apiFrom"] G --> H["transport.ts API"]Non relu
<script src="/config.js">(hash d'un script vide ou erreur).Revues (2) · Dernier commit relu : feat(console): one image for every instance, pu... · Niveau
heavy·anthropic/claude-opus-5.5· 0,543 $