diff --git a/.gitattributes b/.gitattributes index 2b15509289..b80eff143c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,3 +7,6 @@ # Dockerfiles Dockerfile text eol=lf *.dockerfile text eol=lf + +# Front-end image files are copied verbatim into a Linux image and served from it +clients/*/docker/** text eol=lf diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 13440884e6..09ef7c7668 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -111,10 +111,58 @@ jobs: path: clients/${{ matrix.app }}/playwright-report retention-days: 7 + # The nginx image renders /config.json at every start. Restarting the same container + # has to bring it back: the entrypoint once deleted its own template after the first + # render, so any restart crash-looped, and nothing else here builds or runs the image. + container: + name: Container (${{ matrix.app }}) + needs: changes + if: needs.changes.outputs.frontend == 'true' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + app: [admin, dashboard] + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Build image + run: docker build -t fsh-${{ matrix.app }}:ci clients/${{ matrix.app }} + + - name: Smoke test start and restart + env: + IMAGE: fsh-${{ matrix.app }}:ci + run: | + set -euo pipefail + docker run -d --name smoke -p 8080:80 \ + -e FSH_API_URL=http://api.example.test -e FSH_DEFAULT_TENANT=acme \ + -e FSH_DASHBOARD_URL=http://app.example.test "$IMAGE" + check() { + rm -f config.json + for _ in $(seq 1 20); do + curl -fsS http://localhost:8080/config.json -o config.json && break + sleep 1 + done + [ "$(docker inspect -f '{{.State.Running}} {{.RestartCount}}' smoke)" = "true 0" ] + jq -e '.apiBase == "http://api.example.test" and .defaultTenant == "acme"' config.json + } + check + if curl -fsS http://localhost:8080/config.json.template | grep -qF '${FSH_API_URL}'; then + echo "::error::config.json.template is served from the web root" + exit 1 + fi + docker restart smoke + check + + - name: Container logs + if: failure() + run: docker logs smoke + # Single required status check — see backend.yml for the rationale. frontend-ci: name: Frontend CI - needs: [changes, lint-build, e2e] + needs: [changes, lint-build, e2e, container] if: always() runs-on: ubuntu-latest steps: diff --git a/clients/admin/Dockerfile b/clients/admin/Dockerfile index ff97828574..4f7bc42ca9 100644 --- a/clients/admin/Dockerfile +++ b/clients/admin/Dockerfile @@ -23,9 +23,10 @@ RUN apk add --no-cache gettext RUN rm -rf ./* /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf -# Copy the built bundle, the runtime config template, and the entrypoint +# Copy the built bundle, the runtime config template (outside the web root so +# it is never served), and the entrypoint COPY --from=build /app/dist/ ./ -COPY docker/config.json.template ./config.json.template +COPY docker/config.json.template /etc/fsh/config.json.template COPY docker/docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh diff --git a/clients/admin/docker/docker-entrypoint.sh b/clients/admin/docker/docker-entrypoint.sh index 3908a3444a..8e6bce4f3a 100644 --- a/clients/admin/docker/docker-entrypoint.sh +++ b/clients/admin/docker/docker-entrypoint.sh @@ -10,10 +10,8 @@ set -e export FSH_API_URL FSH_DASHBOARD_URL FSH_DEFAULT_TENANT -# Render the runtime config from the template, writing into nginx's web root. -envsubst < /usr/share/nginx/html/config.json.template > /usr/share/nginx/html/config.json - -# Drop the template so it isn't served accidentally. -rm /usr/share/nginx/html/config.json.template +# Render the runtime config into nginx's web root on every start. The template +# lives outside the web root, so it is never served and survives a restart. +envsubst < /etc/fsh/config.json.template > /usr/share/nginx/html/config.json exec nginx -g 'daemon off;' diff --git a/clients/dashboard/Dockerfile b/clients/dashboard/Dockerfile index 97eb73c4bd..bf452b8b0d 100644 --- a/clients/dashboard/Dockerfile +++ b/clients/dashboard/Dockerfile @@ -13,7 +13,7 @@ RUN apk add --no-cache gettext RUN rm -rf ./* /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist/ ./ -COPY docker/config.json.template ./config.json.template +COPY docker/config.json.template /etc/fsh/config.json.template COPY docker/docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh EXPOSE 80 diff --git a/clients/dashboard/docker/docker-entrypoint.sh b/clients/dashboard/docker/docker-entrypoint.sh index 0c19ef4e24..372542e99f 100644 --- a/clients/dashboard/docker/docker-entrypoint.sh +++ b/clients/dashboard/docker/docker-entrypoint.sh @@ -6,8 +6,7 @@ set -e export FSH_API_URL FSH_DEFAULT_TENANT -envsubst < /usr/share/nginx/html/config.json.template \ +envsubst < /etc/fsh/config.json.template \ > /usr/share/nginx/html/config.json -rm /usr/share/nginx/html/config.json.template exec nginx -g 'daemon off;'