[Unit] Description=Local CI/CD — web dashboard (ci web serve) Documentation=file:///opt/ci/local-ci-cd-system/docs/WEB-UI.md After=network-online.target Wants=network-online.target [Service] Type=simple User=ci-runner Group=ci-runner WorkingDirectory=/opt/ci/local-ci-cd-system # CI_CONFIG (and any other CI_* override) lives here, one KEY=VALUE per # line, no quoting. The "-" prefix means the unit still starts if the file # is absent (config.toml is then auto-discovered at /var/lib/ci/config.toml # — see config.py's resolution order). Rendered/edited by setup-web-linux.sh # — never put secrets in this file, only the config path. EnvironmentFile=-/etc/ci/web.env # The bind address below is filled in by setup-web-linux.sh at install time # (a LAN IP, or left as 127.0.0.1 for loopback-only) — never edit this file # by hand on the target host, re-run the installer instead. ExecStart=/opt/ci/venv/bin/python -m ci_orchestrator web serve --host __CI_WEB_BIND_HOST__ --port 8800 Restart=on-failure RestartSec=10 TimeoutStartSec=30s # Hardening. ProtectHome=read-only (NOT "yes"/true): the OIDC/session # secrets are read from ci-runner's headless keyring backend # (keyrings.alt.file.PlaintextKeyring, ~/.local/share/python_keyring/ — # see docs/WEB-UI.md's keyring-headless note); "yes" would hide /home # entirely and every login would fail with a keyring lookup error. NoNewPrivileges=true ProtectSystem=strict ProtectHome=read-only PrivateTmp=true ReadWritePaths=/var/lib/ci [Install] WantedBy=multi-user.target