From 3046bec72fd26ee1c72cd312ee09fbe1e7373d51 Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Tue, 15 Sep 2026 12:40:14 +0200 Subject: [PATCH 1/7] body size Signed-off-by: Basil Bruhn --- conf.d/elog-lmx.conf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conf.d/elog-lmx.conf b/conf.d/elog-lmx.conf index a720b23..b182c86 100644 --- a/conf.d/elog-lmx.conf +++ b/conf.d/elog-lmx.conf @@ -7,13 +7,12 @@ server { server { listen 443 ssl; server_name elog-lmx.psi.ch; - - client_max_body_size 64M; + + client_max_body_size 0; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; - types_hash_max_size 4096; ssl_certificate /etc/nginx/certs/elog-lmx.psi.ch.crt; ssl_certificate_key /etc/nginx/private/elog-lmx.psi.ch.key; From 3d646f9bd5d7839fbfc07d51ff6ce0bee515ce18 Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Fri, 18 Sep 2026 16:14:14 +0200 Subject: [PATCH 2/7] health checks, validation and variable fix. Signed-off-by: Basil Bruhn --- .env.example | 28 ++++++++++++++ .gitignore | 2 + README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 79 +++++++++++++++++++++++--------------- 4 files changed, 171 insertions(+), 31 deletions(-) create mode 100644 .env.example create mode 100644 README.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..34d7502 --- /dev/null +++ b/.env.example @@ -0,0 +1,28 @@ +# Required non-SAP inputs to docker-compose.yaml. +# Schema only: empty values deliberately fail Compose validation. +# Puppet/Hiera owns the deployed .env. Do not replace it with this file. +# Supplier-managed SAP/BANF settings are not included here. +# Quote literal values containing dollar signs with single quotes in .env. + +# Swiss eCargo +ecargo_api_key= + +# HedgeDoc (application and database use the same database credentials) +hedgedoc_user= +hedgedoc_password= +bind_user= +bind_password= +session_secret= + +# IT strategy dashboard +IT_DASHBOARD_DB_USER= +IT_DASHBOARD_DB_PW= +IT_DASHBOARD_DB_ROOT_PW= +JWT_SECRET= +ADMIN_PW_HASH= + +# Panda Maintenance +PANDA_MAINTENANCE_DB_PW= + +# Tomcat Wiki +TOMCATWIKI_DB_SECRET= diff --git a/.gitignore b/.gitignore index 4c49bd7..1a8ac72 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .env +.env.* +*.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d111a6 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# WebHosting + +Docker Compose services and Nginx/Apache configuration for the shared web host. +Puppet deploys the host configuration and the environment file from Hiera-eyaml. + +## Environment configuration + +The existing variable names and single Puppet-managed `.env` file remain the +deployment interface. `.env.example` documents the required non-SAP variables; +its empty values are intentional and it must not replace the deployed file. +SAP/TIA, document-router, and BANF service configuration is unchanged. + +Non-SAP credentials use Compose's `${VARIABLE:?message}` syntax so missing or +empty values stop configuration loading. Compose loads the whole project even +when a command targets a single service, so provide the complete deployed env +file for these commands too. Supplier-managed variables retain their existing +behavior. + +The root `.env` supplies YAML interpolation values. Containers receive only +variables explicitly listed in their `environment` sections. Service-level +`env_file` is a different mechanism and does not supply YAML interpolation. +See [Docker's interpolation documentation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/). + +Preserve literal dollar signs in env-file values using single quotes, for example +`ADMIN_PW_HASH='$2b$12$example-not-a-real-hash'`. Use proper dotenv escaping for +embedded quotes; do not shell-source the file. Shell environment variables take +precedence over the env file, so run deployment from a controlled environment. +Keep actual credentials out of Git and logs. Puppet should restrict file access +and disable diffs and plaintext file backups for secrets (`show_diff => false`, +`backup => false`). + +## Validate and apply configuration + +From the existing deployment directory, validate using the full Puppet-managed +environment file. Keep the deployed Compose project name and directory unchanged. + +```sh +docker compose --env-file .env config --quiet +``` + +Use `--quiet`: plain `docker compose config` and `config --environment` can print +resolved secrets. Validation checks the Compose model; it does not test database +credentials, application behavior, host mounts, or Nginx configuration. + +After validation and the database checks below, use a targeted `docker compose +--env-file .env up -d ...` during the application's maintenance window. +Include its database when its definition changes, and inspect the dependency +chain first (the dashboard database depends on the frontend copy job). Avoid a +whole-project `down`/`up` for these changes. A plain `restart` does not apply changed +container environment variables; see [Docker's restart documentation](https://docs.docker.com/reference/cli/docker/compose/restart/). + +Application recreation can change its IP address. The current Nginx upstream +configuration resolves static service names at startup/reload. Once the selected +application is running, validate and reload the proxy under the existing host +deployment procedure, then check the affected site. The proxy is shared with SAP; +an Nginx reload affects the shared process even though its configuration is +unchanged. See [Nginx upstream resolution](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#resolve). + +## Database readiness and existing data + +HedgeDoc now uses separate `CMD_DB_*` fields so passwords containing URI-special +characters are passed literally. Its database receives the same username and +password from `.env`; the previous `{{ ... }}` expressions were literal strings +to Compose. The LDAP search filter's `{{username}}` is an application placeholder +and must remain. See [HedgeDoc configuration](https://docs.hedgedoc.org/configuration/). + +Before applying the HedgeDoc correction to an existing instance, confirm that +the actual database role/password match the Hiera values. PostgreSQL's +`POSTGRES_USER` and `POSTGRES_PASSWORD` initialize an empty data directory; changing +them does not update an existing database. Coordinate any required credential +change with the application owner and back up the database first. Do not delete +or reinitialize the data directory. See the [PostgreSQL image documentation](https://hub.docker.com/_/postgres). + +HedgeDoc and Tomcat Wiki wait for PostgreSQL to accept TCP connections before +starting. These `pg_isready` checks indicate server readiness, not successful +application authentication. The dashboard healthcheck authenticates to its +configured database and executes `SELECT 1`, using the existing container +environment rather than embedding the password in the healthcheck command. +An unhealthy status alone does not automatically restart a container. + +HedgeDoc's PostgreSQL 13 image remains a separate migration item. Version 13 is +unsupported, and a major upgrade requires a tested backup/restore or `pg_upgrade` +procedure compatible with HedgeDoc. Image versions, data paths, container names, +and networks are preserved in this configuration fix. See the +[PostgreSQL versioning policy](https://www.postgresql.org/support/versioning/). + +## Follow-up organization + +Split Puppet's output into service-specific env files or file secrets before +extracting independently operated applications into their own Compose projects. +That requires coordinated changes in the Hiera/Puppet repository. Preserve the +supplier's SAP/TIA/document-router/BANF definitions; they can later move together +into a dedicated directory without changing their configuration style. diff --git a/docker-compose.yaml b/docker-compose.yaml index 006c2ab..5b8b959 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -129,7 +129,7 @@ services: container_name: swiss-ecargo restart: always environment: - - HOSTED_API_KEY=${ecargo_api_key} + - HOSTED_API_KEY=${ecargo_api_key:?ecargo_api_key is required} networks: - backend @@ -139,26 +139,32 @@ services: image: quay.io/hedgedoc/hedgedoc:latest container_name: hedgedoc environment: - - CMD_DB_URL=postgres://${hedgedoc_user}:${hedgedoc_password}@hedgedoc_db:5432/hedgedoc + - CMD_DB_DIALECT=postgres + - CMD_DB_HOST=hedgedoc_db + - CMD_DB_PORT=5432 + - CMD_DB_DATABASE=hedgedoc + - CMD_DB_USERNAME=${hedgedoc_user:?hedgedoc_user is required} + - CMD_DB_PASSWORD=${hedgedoc_password:?hedgedoc_password is required} - CMD_DOMAIN=hedgedoc.psi.ch - CMD_URL_ADDPORT=false - CMD_PROTOCOL_USESSL=true - CMD_LDAP_URL=ldaps://dc00.d.psi.ch - - CMD_LDAP_BINDDN=${bind_user} - - CMD_LDAP_BINDCREDENTIALS=${bind_password} + - CMD_LDAP_BINDDN=${bind_user:?bind_user is required} + - CMD_LDAP_BINDCREDENTIALS=${bind_password:?bind_password is required} - CMD_LDAP_SEARCHBASE=OU=users,OU=psi,DC=d,DC=psi,DC=ch - CMD_LDAP_SEARCHFILTER=(&(objectcategory=person)(objectclass=user)(|(sAMAccountName={{username}})(mail={{username}}))) - CMD_LDAP_USERIDFIELD=sAMAccountName - - CMD_LDAP_PROVIDERNAME="PSI" + - CMD_LDAP_PROVIDERNAME=PSI - NODE_ENV=production - CMD_EMAIL=false - CMD_ALLOW_EMAIL_REGISTER=false - - CMD_SESSION_SECRET=${session_secret} + - CMD_SESSION_SECRET=${session_secret:?session_secret is required} volumes: - /opt/webcontent/sf-hedgedoc/uploads:/hedgedoc/public/uploads restart: always depends_on: - - hedgedoc_db + hedgedoc_db: + condition: service_healthy networks: - backend - hedgedoc_backend @@ -168,12 +174,18 @@ services: image: postgres:13.4-alpine container_name: hedgedoc_db environment: - - POSTGRES_USER={{ hedgedoc_user }} - - POSTGRES_PASSWORD={{ hedgedoc_password }} + - POSTGRES_USER=${hedgedoc_user:?hedgedoc_user is required} + - POSTGRES_PASSWORD=${hedgedoc_password:?hedgedoc_password is required} - POSTGRES_DB=hedgedoc volumes: - /opt/webcontent/sf-hedgedoc/data:/var/lib/postgresql/data restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s networks: - hedgedoc_backend @@ -254,10 +266,10 @@ services: - DB_HOST=it-strategy-dashboard-db - DB_PORT=3306 - DB_NAME=itstrategy - - DB_USER=${IT_DASHBOARD_DB_USER} - - DB_PASS=${IT_DASHBOARD_DB_PW} - - JWT_SECRET=${JWT_SECRET} - - ADMIN_PW_HASH=${ADMIN_PW_HASH} + - DB_USER=${IT_DASHBOARD_DB_USER:?IT_DASHBOARD_DB_USER is required} + - DB_PASS=${IT_DASHBOARD_DB_PW:?IT_DASHBOARD_DB_PW is required} + - JWT_SECRET=${JWT_SECRET:?JWT_SECRET is required} + - ADMIN_PW_HASH=${ADMIN_PW_HASH:?ADMIN_PW_HASH is required} depends_on: it-strategy-dashboard-db: condition: service_healthy @@ -270,10 +282,10 @@ services: container_name: it-strategy-dashboard-db restart: always environment: - - MYSQL_ROOT_PASSWORD=${IT_DASHBOARD_DB_ROOT_PW} + - MYSQL_ROOT_PASSWORD=${IT_DASHBOARD_DB_ROOT_PW:?IT_DASHBOARD_DB_ROOT_PW is required} - MYSQL_DATABASE=itstrategy - - MYSQL_USER=${IT_DASHBOARD_DB_USER} - - MYSQL_PASSWORD=${IT_DASHBOARD_DB_PW} + - MYSQL_USER=${IT_DASHBOARD_DB_USER:?IT_DASHBOARD_DB_USER is required} + - MYSQL_PASSWORD=${IT_DASHBOARD_DB_PW:?IT_DASHBOARD_DB_PW is required} volumes: - /opt/webcontent/it-strategy-dashboard/mysql/data:/var/lib/mysql depends_on: @@ -282,19 +294,17 @@ services: networks: - it_strategy_dashboard_backend healthcheck: + # Authenticate over TCP; do not embed the password in the command arguments. test: - [ - "CMD", - "mariadb-admin", - "ping", - "-h", - "localhost", - "-u${IT_DASHBOARD_DB_USER}", - "-p${IT_DASHBOARD_DB_PW}", - ] - interval: 2s + - CMD-SHELL + - >- + MYSQL_PWD="$${MYSQL_PASSWORD}" mariadb --protocol=TCP + -h 127.0.0.1 -u "$${MYSQL_USER}" "$${MYSQL_DATABASE}" + -e 'SELECT 1' >/dev/null + interval: 10s timeout: 5s retries: 5 + start_period: 30s # Christoph Hug , Simon Suter # Panda-Maintenance (OpenMaint) @@ -305,7 +315,7 @@ services: - /opt/webcontent/panda-maintenance/data:/var/lib/postgresql/data:rw environment: - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=${PANDA_MAINTENANCE_DB_PW} + - POSTGRES_PASSWORD=${PANDA_MAINTENANCE_DB_PW:?PANDA_MAINTENANCE_DB_PW is required} restart: always mem_limit: 4000m mem_reservation: 2000m @@ -330,7 +340,7 @@ services: - /opt/webcontent/panda-maintenance/docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh:ro environment: - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=${PANDA_MAINTENANCE_DB_PW} + - POSTGRES_PASSWORD=${PANDA_MAINTENANCE_DB_PW:?PANDA_MAINTENANCE_DB_PW is required} - POSTGRES_PORT=5432 - POSTGRES_HOST=panda-maintenance-db - POSTGRES_DB=openmaint @@ -485,10 +495,16 @@ services: restart: always environment: - POSTGRES_DB=wiki - - POSTGRES_PASSWORD=${TOMCATWIKI_DB_SECRET} + - POSTGRES_PASSWORD=${TOMCATWIKI_DB_SECRET:?TOMCATWIKI_DB_SECRET is required} - POSTGRES_USER=wiki volumes: - /opt/webcontent/tomcatwiki/db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s networks: - tomcatwiki_backend @@ -497,13 +513,14 @@ services: container_name: tomcatwiki restart: always depends_on: - - tomcatwikidb + tomcatwikidb: + condition: service_healthy environment: - DB_TYPE=postgres - DB_HOST=tomcatwikidb - DB_PORT=5432 - DB_USER=wiki - - DB_PASS=${TOMCATWIKI_DB_SECRET} + - DB_PASS=${TOMCATWIKI_DB_SECRET:?TOMCATWIKI_DB_SECRET is required} - DB_NAME=wiki networks: - tomcatwiki_backend From 491022beac6f9ae4e51c25d85e566d07ae6bc8c6 Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Fri, 18 Sep 2026 16:54:09 +0200 Subject: [PATCH 3/7] update compose to work with modules/stack Signed-off-by: Basil Bruhn --- .env.example | 34 +- README.md | 140 ++++- docker-compose.yaml | 589 ++----------------- stacks/dashboard/compose.yaml | 63 ++ stacks/elog/compose.yaml | 51 ++ stacks/excalidraw/compose.yaml | 10 + stacks/hedgedoc/compose.yaml | 58 ++ stacks/legacy-web/compose.yaml | 33 ++ stacks/n8n/compose.yaml | 22 + stacks/opticswiki/compose.yaml | 20 + stacks/panda-maintenance/compose.yaml | 59 ++ stacks/pepisco/compose.yaml | 10 + stacks/pikettlischte-beautifier/compose.yaml | 11 + stacks/proxy/compose.yaml | 25 + stacks/sap/compose.yaml | 118 ++++ stacks/scicat-rocrate/compose.yaml | 14 + stacks/slsbl/compose.yaml | 17 + stacks/summer-project-selection/compose.yaml | 13 + stacks/swiss-ecargo/compose.yaml | 13 + stacks/tomcatwiki/compose.yaml | 46 ++ 20 files changed, 738 insertions(+), 608 deletions(-) create mode 100644 stacks/dashboard/compose.yaml create mode 100644 stacks/elog/compose.yaml create mode 100644 stacks/excalidraw/compose.yaml create mode 100644 stacks/hedgedoc/compose.yaml create mode 100644 stacks/legacy-web/compose.yaml create mode 100644 stacks/n8n/compose.yaml create mode 100644 stacks/opticswiki/compose.yaml create mode 100644 stacks/panda-maintenance/compose.yaml create mode 100644 stacks/pepisco/compose.yaml create mode 100644 stacks/pikettlischte-beautifier/compose.yaml create mode 100644 stacks/proxy/compose.yaml create mode 100644 stacks/sap/compose.yaml create mode 100644 stacks/scicat-rocrate/compose.yaml create mode 100644 stacks/slsbl/compose.yaml create mode 100644 stacks/summer-project-selection/compose.yaml create mode 100644 stacks/swiss-ecargo/compose.yaml create mode 100644 stacks/tomcatwiki/compose.yaml diff --git a/.env.example b/.env.example index 34d7502..57622b8 100644 --- a/.env.example +++ b/.env.example @@ -1,28 +1,6 @@ -# Required non-SAP inputs to docker-compose.yaml. -# Schema only: empty values deliberately fail Compose validation. -# Puppet/Hiera owns the deployed .env. Do not replace it with this file. -# Supplier-managed SAP/BANF settings are not included here. -# Quote literal values containing dollar signs with single quotes in .env. - -# Swiss eCargo -ecargo_api_key= - -# HedgeDoc (application and database use the same database credentials) -hedgedoc_user= -hedgedoc_password= -bind_user= -bind_password= -session_secret= - -# IT strategy dashboard -IT_DASHBOARD_DB_USER= -IT_DASHBOARD_DB_PW= -IT_DASHBOARD_DB_ROOT_PW= -JWT_SECRET= -ADMIN_PW_HASH= - -# Panda Maintenance -PANDA_MAINTENANCE_DB_PW= - -# Tomcat Wiki -TOMCATWIKI_DB_SECRET= +# Optional nonsecret Compose setting; this is also the default when unset. +# Puppet/Hiera owns the deployed .env. Do not replace it with this example: +# SAP/BANF and other applications still use its existing settings. +# Migrated application credentials live in the nine service env files listed +# in README.md and are read with format: raw. +WEBHOSTING_ENV_DIR=/etc/webhosting diff --git a/README.md b/README.md index 9d111a6..14ee815 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,117 @@ # WebHosting Docker Compose services and Nginx/Apache configuration for the shared web host. -Puppet deploys the host configuration and the environment file from Hiera-eyaml. +Puppet deploys the host configuration and environment files from Hiera-eyaml. + +## Compose layout + +Requires **Docker Compose 2.30.0 or later** for raw service env files. The root +`docker-compose.yaml` is the deployment entrypoint and assembles these modules: + +```text +docker-compose.yaml +stacks/ + proxy/compose.yaml + legacy-web/compose.yaml + excalidraw/compose.yaml + elog/compose.yaml + pepisco/compose.yaml + swiss-ecargo/compose.yaml + hedgedoc/compose.yaml + opticswiki/compose.yaml + slsbl/compose.yaml + n8n/compose.yaml + dashboard/compose.yaml + panda-maintenance/compose.yaml + sap/compose.yaml + summer-project-selection/compose.yaml + tomcatwiki/compose.yaml + scicat-rocrate/compose.yaml + pikettlischte-beautifier/compose.yaml +``` + +Each application stays with its database and supporting containers. `legacy-web` +contains the shared Apache sites and scheduler; `elog` groups the four logbooks. +`sap` contains the existing TIA, document-router, and BANF definitions, moved +together without changes to their configuration. + +Run Compose through the root file, using the existing deployment directory and +project name. These modules belong to **one Compose project**; their shared +network definitions remain in the root file. They are not standalone deployment +entrypoints. For example, use `docker compose --env-file .env up -d excalidraw` +from the repository root to update that service. + +Every include sets `project_directory: .` so relative paths such as +`./apache/conf` and the default `.env` still resolve from the repository root. +The service names, container names, images, networks, and data paths are preserved. +See [Docker's include documentation](https://docs.docker.com/reference/compose-file/include/). ## Environment configuration -The existing variable names and single Puppet-managed `.env` file remain the -deployment interface. `.env.example` documents the required non-SAP variables; -its empty values are intentional and it must not replace the deployed file. -SAP/TIA, document-router, and BANF service configuration is unchanged. +Swiss eCargo, HedgeDoc, the dashboard, Panda Maintenance, and Tomcat Wiki read +their credentials from service-specific files under `/etc/webhosting`. In the +`hiera/data-lx` repository, `default/lx-docker-01.psi.ch.yaml` renders these files +from the existing `lx-data::docker-env::*` keys. Files are root-owned with mode +`0600`; their directories have mode `0700`. Run Compose as a deployment user +with permission to read them. -Non-SAP credentials use Compose's `${VARIABLE:?message}` syntax so missing or -empty values stop configuration loading. Compose loads the whole project even -when a command targets a single service, so provide the complete deployed env -file for these commands too. Supplier-managed variables retain their existing -behavior. +| File under `/etc/webhosting/` | Service | Required non-empty variables | +| --- | --- | --- | +| `swiss-ecargo/app.env` | `swiss-ecargo` | `HOSTED_API_KEY` | +| `hedgedoc/app.env` | `hedgedoc_app` | `CMD_DB_USERNAME`, `CMD_DB_PASSWORD`, `CMD_LDAP_BINDDN`, `CMD_LDAP_BINDCREDENTIALS`, `CMD_SESSION_SECRET` | +| `hedgedoc/db.env` | `hedgedoc_db` | `POSTGRES_USER`, `POSTGRES_PASSWORD` | +| `dashboard/backend.env` | `it-strategy-dashboard-backend` | `DB_USER`, `DB_PASS`, `JWT_SECRET`, `ADMIN_PW_HASH` | +| `dashboard/db.env` | `it-strategy-dashboard-db` | `MYSQL_ROOT_PASSWORD`, `MYSQL_USER`, `MYSQL_PASSWORD` | +| `panda-maintenance/app.env` | `panda-maintenance-app` | `POSTGRES_PASSWORD` | +| `panda-maintenance/db.env` | `panda-maintenance-db` | `POSTGRES_PASSWORD` | +| `tomcatwiki/app.env` | `tomcatwiki` | `DB_PASS` | +| `tomcatwiki/db.env` | `tomcatwikidb` | `POSTGRES_PASSWORD` | -The root `.env` supplies YAML interpolation values. Containers receive only -variables explicitly listed in their `environment` sections. Service-level -`env_file` is a different mechanism and does not supply YAML interpolation. -See [Docker's interpolation documentation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/). +Each service receives only its own file. Stable nonsecret settings remain in +`environment:`. The files use `format: raw`: write single-line literal values +without adding quotes or doubling dollar signs. Password hashes, quotes, and +URI-special characters pass through unchanged. These files are not shell scripts. +See [Docker's env-file reference](https://docs.docker.com/reference/compose-file/services/#env_file). -Preserve literal dollar signs in env-file values using single quotes, for example -`ADMIN_PW_HASH='$2b$12$example-not-a-real-hash'`. Use proper dotenv escaping for -embedded quotes; do not shell-source the file. Shell environment variables take -precedence over the env file, so run deployment from a controlled environment. -Keep actual credentials out of Git and logs. Puppet should restrict file access -and disable diffs and plaintext file backups for secrets (`show_diff => false`, -`backup => false`). +The optional `WEBHOSTING_ENV_DIR` variable overrides `/etc/webhosting`, for example +when validating a temporary set of files. `.env.example` documents this nonsecret +setting; it must not replace the production `.env`. + +The existing `/opt/WebHosting/.env` remains in place for SAP/BANF, applications +outside this repository, and rollback. The migrated services no longer reference +its old credential variables, including overrides of those variables in the shell. +There is no fallback to old credentials if a service env file is missing. + +Compose requires all nine files, including when validating the whole project for +a targeted service update. `required: true` verifies file existence only; it does +not enforce the keys or non-empty values listed above. Check those values as part +of deployment. This replaces the previous `${VARIABLE:?message}` checks for the +migrated credentials. + +The root `.env` still supplies YAML interpolation, including SAP/BANF variables +and `WEBHOSTING_ENV_DIR`. It follows normal dotenv quoting rules; service +`env_file` does not supply interpolation values to YAML. Keep credentials out of +Git and logs. See [Docker's interpolation documentation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/). ## Validate and apply configuration -From the existing deployment directory, validate using the full Puppet-managed -environment file. Keep the deployed Compose project name and directory unchanged. +Deploy the Hiera change first and let Puppet create all nine service env files. +Verify their permissions and the non-empty keys listed above before activating +this Compose change. Preserve the existing global env file for SAP/BANF and rollback. + +Deploy the complete `stacks/` directory alongside the root Compose file. Keep the +existing deployment directory and Compose project name. From that directory, +validate using the existing global env file and the new service env files: ```sh +docker compose version docker compose --env-file .env config --quiet +docker compose --env-file .env config --services ``` +Confirm the host runs Compose 2.30.0 or later and the assembled configuration lists +all 31 services before applying it. + Use `--quiet`: plain `docker compose config` and `config --environment` can print resolved secrets. Validation checks the Compose model; it does not test database credentials, application behavior, host mounts, or Nginx configuration. @@ -49,6 +123,12 @@ chain first (the dashboard database depends on the frontend copy job). Avoid a whole-project `down`/`up` for these changes. A plain `restart` does not apply changed container environment variables; see [Docker's restart documentation](https://docs.docker.com/reference/cli/docker/compose/restart/). +The credential source changes, but its values should match the previous +configuration. This is not a password rotation or database upgrade. To roll back, +restore the previous Compose revision and apply the affected services using the +retained global `.env`. Do not remove that file after this rollout; it still has +active consumers. + Application recreation can change its IP address. The current Nginx upstream configuration resolves static service names at startup/reload. Once the selected application is running, validate and reload the proxy under the existing host @@ -59,8 +139,8 @@ unchanged. See [Nginx upstream resolution](https://nginx.org/en/docs/http/ngx_ht ## Database readiness and existing data HedgeDoc now uses separate `CMD_DB_*` fields so passwords containing URI-special -characters are passed literally. Its database receives the same username and -password from `.env`; the previous `{{ ... }}` expressions were literal strings +characters are passed literally. Its app and database env files derive their +shared credentials from the same Hiera keys; the previous `{{ ... }}` expressions were literal strings to Compose. The LDAP search filter's `{{username}}` is an application placeholder and must remain. See [HedgeDoc configuration](https://docs.hedgedoc.org/configuration/). @@ -86,8 +166,8 @@ and networks are preserved in this configuration fix. See the ## Follow-up organization -Split Puppet's output into service-specific env files or file secrets before -extracting independently operated applications into their own Compose projects. -That requires coordinated changes in the Hiera/Puppet repository. Preserve the -supplier's SAP/TIA/document-router/BANF definitions; they can later move together -into a dedicated directory without changing their configuration style. +After verifying the migration on the host, obsolete non-SAP entries can be removed +from the global `.env` in a separate Hiera change. Preserve values used by SAP/BANF +and applications outside this repository. File secrets and independent Compose +projects can be considered separately. Preserve the supplier's definitions in +`stacks/sap/compose.yaml` without changing their configuration style. diff --git a/docker-compose.yaml b/docker-compose.yaml index 5b8b959..6086d7d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,554 +1,43 @@ -services: - # linux-eng@psi.ch - # Reverse Proxy handling all HTTP/HTTPS requests - # Only container that is exposed to the network - # Communication to other services is trough docker network - nginx: - image: nginx:latest - container_name: nginx_proxy - restart: always - ports: - - "80:80" - - "443:443" - volumes: - - /etc/letsencrypt/live:/etc/letsencrypt/live:ro - - /etc/letsencrypt/archive:/etc/letsencrypt/archive:ro - - /etc/pki/tls/certs:/etc/nginx/certs:ro - - /etc/pki/tls/private:/etc/nginx/private:ro - - /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - - /etc/nginx/conf.d:/etc/nginx/conf.d:ro - - /opt/webcontent/sinqstatus-test:/opt/webcontent/sinqstatus-test:ro - - /opt/webcontent/it-strategy-dashboard/frontend/:/opt/webcontent/it-strategy-dashboard/:ro - networks: - - public - - backend - - # rfwtools - # gfa-status-test - # fluid-eos - # spaceweather - apache: - image: gitea.psi.ch/images/php-apache:latest - container_name: apache_app - restart: always - volumes: - - /opt/webcontent/rfmwtools:/var/www/rfmwtools:ro - - /opt/webcontent/cas-status:/var/www/cas-status:rw - - /opt/webcontent/fluid-eos:/var/www/fluid-eos:rw - - /opt/webcontent/sls2:/var/www/sls2:rw - - /opt/webcontent/megwiki:/var/www/megwiki:rw - - /opt/webcontent/sinqimaging:/var/www/sinqimaging:rw - - /opt/webcontent/omny:/var/www/omny:rw - - /mount/srem/webhosting:/var/www/srem:rw - - ./apache/conf:/etc/apache2/sites-enabled:ro - networks: - - backend - - legacy_scheduleit: - # image: gitea.psi.ch/images/php-apache:latest - image: gitea.psi.ch/images/legacy_scheduleit:latest - restart: always - container_name: legacy-scheduleit - volumes: - - /opt/webcontent/lbr-scheduler/code:/var/www/lbr-scheduler:rw - - /opt/webcontent/lbr-scheduler/apache/conf:/etc/apache2/sites-enabled:ro - networks: - - backend - - # linux-eng@psi.ch - # Test app - excalidraw: - image: excalidraw/excalidraw:latest - container_name: excalidraw - restart: always - networks: - - backend - - # benjamin.brem@psi.ch - LAC - # Elog instance - lac-elog: - image: gitea.psi.ch/images/elog:3.1.5 - container_name: lac-elog - restart: always - volumes: - - /opt/logbooks/lac:/usr/local/elog/logbooks - - /opt/webcontent/lac/elog.cfg:/usr/local/elog/elogd.cfg - networks: - - backend - - # valerio.scagnoli@psi.ch - LMX - # Elog instance - lmx-elog: - image: gitea.psi.ch/images/elog:3.1.5 - container_name: lmx-elog - restart: always - volumes: - - /opt/logbooks/lmx:/usr/local/elog/logbooks - - /opt/webcontent/lmx/passwd.txt:/usr/local/elog/passwd.txt - - /opt/webcontent/lmx/elog.cfg:/usr/local/elog/elogd.cfg - networks: - - backend - - # Rostomyan Tigran - # INC0137443 - # Elog instance - pif-elog: - image: gitea.psi.ch/images/elog:3.1.5 - container_name: pif-elog - restart: always - volumes: - - /opt/logbooks/pif:/usr/local/elog/logbooks - - /opt/webcontent/pif/elog.cfg:/usr/local/elog/elogd.cfg - networks: - - backend - - # Krieger Jonas Andreas , Raselli Andrea-Raeto - # Elog as a Service PoC mit musr-elog.psi.ch? linux-eng@psi.ch - lmu-elog: - image: gitea.psi.ch/images/elog:3.1.5 - container_name: lmu-elog - restart: always - volumes: - - /opt/logbooks/LMU:/usr/local/elog/logbooks - - /opt/webcontent/LMU/elog.cfg:/usr/local/elog/elogd.cfg - networks: - - backend - - # PEPISCO - # PhotoElectron PhotoIon Spectral COmpendium - pepisco: - image: gitea.psi.ch/images/pepisco:v2026.03.13 - container_name: pepisco-test - restart: always - networks: - - backend - - # Romain Sacchi - swiss-ecargo: - image: gitea.psi.ch/sacchi_r/swiss-ecargo:0.1.2 - container_name: swiss-ecargo - restart: always - environment: - - HOSTED_API_KEY=${ecargo_api_key:?ecargo_api_key is required} - networks: - - backend - - # Augustin Sven - # POC for SiwssFel - hedgedoc_app: - image: quay.io/hedgedoc/hedgedoc:latest - container_name: hedgedoc - environment: - - CMD_DB_DIALECT=postgres - - CMD_DB_HOST=hedgedoc_db - - CMD_DB_PORT=5432 - - CMD_DB_DATABASE=hedgedoc - - CMD_DB_USERNAME=${hedgedoc_user:?hedgedoc_user is required} - - CMD_DB_PASSWORD=${hedgedoc_password:?hedgedoc_password is required} - - CMD_DOMAIN=hedgedoc.psi.ch - - CMD_URL_ADDPORT=false - - CMD_PROTOCOL_USESSL=true - - CMD_LDAP_URL=ldaps://dc00.d.psi.ch - - CMD_LDAP_BINDDN=${bind_user:?bind_user is required} - - CMD_LDAP_BINDCREDENTIALS=${bind_password:?bind_password is required} - - CMD_LDAP_SEARCHBASE=OU=users,OU=psi,DC=d,DC=psi,DC=ch - - CMD_LDAP_SEARCHFILTER=(&(objectcategory=person)(objectclass=user)(|(sAMAccountName={{username}})(mail={{username}}))) - - CMD_LDAP_USERIDFIELD=sAMAccountName - - CMD_LDAP_PROVIDERNAME=PSI - - NODE_ENV=production - - CMD_EMAIL=false - - CMD_ALLOW_EMAIL_REGISTER=false - - CMD_SESSION_SECRET=${session_secret:?session_secret is required} - volumes: - - /opt/webcontent/sf-hedgedoc/uploads:/hedgedoc/public/uploads - restart: always - depends_on: - hedgedoc_db: - condition: service_healthy - networks: - - backend - - hedgedoc_backend - - # Augustin Sven - hedgedoc_db: - image: postgres:13.4-alpine - container_name: hedgedoc_db - environment: - - POSTGRES_USER=${hedgedoc_user:?hedgedoc_user is required} - - POSTGRES_PASSWORD=${hedgedoc_password:?hedgedoc_password is required} - - POSTGRES_DB=hedgedoc - volumes: - - /opt/webcontent/sf-hedgedoc/data:/var/lib/postgresql/data - restart: always - healthcheck: - test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - networks: - - hedgedoc_backend - - # Flechsig Uwe - # opticswiki - # webcontent: - # lx-fs-01.psi.ch:/lx_webhosting - opticswiki: - image: gitea.psi.ch/images/opticswiki:1.8 - container_name: opticswiki - restart: always - volumes: - - /opt/webcontent/opticswiki/data:/usr/local/apache2/wiki/data - - /opt/webcontent/opticswiki/pub:/usr/local/apache2/wiki/pub - - /opt/webcontent/opticswiki/working:/usr/local/apache2/wiki/working - - /opt/webcontent/opticswiki/lib:/usr/local/apache2/wiki/lib - - /opt/webcontent/opticswiki/conf:/usr/local/apache2/conf - - /opt/webcontent/opticswiki/htdocs:/usr/local/apache2/htdocs - - /opt/webcontent/opticswiki/log:/var/log/apache2 - networks: - - backend - - # Flechsig Uwe - # slsbl - # webcontent: - # lx-fs-01.psi.ch:/lx_webhosting - slsbl: - image: gitea.psi.ch/images/slsbl:1.0 - container_name: slsbl - restart: always - volumes: - - /opt/webcontent/slsbl/cgi-bin:/usr/local/apache2/cgi-bin - - /opt/webcontent/slsbl/conf:/usr/local/apache2/conf - - /opt/webcontent/slsbl/htdocs:/usr/local/apache2/htdocs - - /opt/webcontent/slsbl/log:/var/log/apache2 - networks: - - backend - - # Angelo Sozzi INC0150655 angelo.sozzi@psi.ch - # POC Software - might be installed on docker-dmz - n8n: - image: docker.n8n.io/n8nio/n8n - container_name: n8n - restart: always - environment: - - N8N_HOST=n8n - - N8N_PORT=5678 - - N8N_PROTOCOL=http - - NODE_ENV=production - - WEBHOOK_URL=https://n8n.psi.ch - - GENERIC_TIMEZONE=Europe/Zurich - - DB_SQLITE_POOL_SIZE=4 - - N8N_RUNNERS_ENABLED=true - volumes: - - /opt/webcontent/n8n/n8n_data:/home/node/.n8n - - /opt/webcontent/n8n/local_files:/files - networks: - - backend - - # Ritter Tom - # It-Strategy monitoring dashboard - - it-strategy-dashboard-frontend: - image: gitea.psi.ch/9501/it-strategy-dashboard-frontend:4.0.4 - container_name: it-strategy-dashboard-frontend - command: ["sh", "-c", "/usr/local/bin/copyData.sh"] - volumes: - - /opt/webcontent/it-strategy-dashboard/frontend:/opt/webcontent/it-strategy-dashboard/frontend - restart: "no" - networks: - - it_strategy_dashboard_backend - - it-strategy-dashboard-backend: - image: gitea.psi.ch/9501/it-strategy-dashboard-backend:4.0.5 - container_name: it-strategy-dashboard-backend - restart: always - environment: - - DB_HOST=it-strategy-dashboard-db - - DB_PORT=3306 - - DB_NAME=itstrategy - - DB_USER=${IT_DASHBOARD_DB_USER:?IT_DASHBOARD_DB_USER is required} - - DB_PASS=${IT_DASHBOARD_DB_PW:?IT_DASHBOARD_DB_PW is required} - - JWT_SECRET=${JWT_SECRET:?JWT_SECRET is required} - - ADMIN_PW_HASH=${ADMIN_PW_HASH:?ADMIN_PW_HASH is required} - depends_on: - it-strategy-dashboard-db: - condition: service_healthy - networks: - - it_strategy_dashboard_backend - - backend - - it-strategy-dashboard-db: - image: mariadb:12 - container_name: it-strategy-dashboard-db - restart: always - environment: - - MYSQL_ROOT_PASSWORD=${IT_DASHBOARD_DB_ROOT_PW:?IT_DASHBOARD_DB_ROOT_PW is required} - - MYSQL_DATABASE=itstrategy - - MYSQL_USER=${IT_DASHBOARD_DB_USER:?IT_DASHBOARD_DB_USER is required} - - MYSQL_PASSWORD=${IT_DASHBOARD_DB_PW:?IT_DASHBOARD_DB_PW is required} - volumes: - - /opt/webcontent/it-strategy-dashboard/mysql/data:/var/lib/mysql - depends_on: - it-strategy-dashboard-frontend: - condition: service_completed_successfully - networks: - - it_strategy_dashboard_backend - healthcheck: - # Authenticate over TCP; do not embed the password in the command arguments. - test: - - CMD-SHELL - - >- - MYSQL_PWD="$${MYSQL_PASSWORD}" mariadb --protocol=TCP - -h 127.0.0.1 -u "$${MYSQL_USER}" "$${MYSQL_DATABASE}" - -e 'SELECT 1' >/dev/null - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - - # Christoph Hug , Simon Suter - # Panda-Maintenance (OpenMaint) - panda-maintenance-db: - image: postgis/postgis:17-3.5-alpine - container_name: panda-maintenance-db - volumes: - - /opt/webcontent/panda-maintenance/data:/var/lib/postgresql/data:rw - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=${PANDA_MAINTENANCE_DB_PW:?PANDA_MAINTENANCE_DB_PW is required} - restart: always - mem_limit: 4000m - mem_reservation: 2000m - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 80s - networks: - - panda_maintenance_backend - panda-maintenance-app: - image: itmicus/cmdbuild:om-2.4-4.1.0 - container_name: panda-maintenance-app - links: - - panda-maintenance-db - depends_on: - panda-maintenance-db: - condition: service_healthy - restart: always - volumes: - - /opt/webcontent/panda-maintenance/docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh:ro - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=${PANDA_MAINTENANCE_DB_PW:?PANDA_MAINTENANCE_DB_PW is required} - - POSTGRES_PORT=5432 - - POSTGRES_HOST=panda-maintenance-db - - POSTGRES_DB=openmaint - - CMDBUILD_DUMP=empty.dump.xz - - JAVA_OPTS=-Xmx6000m -Xms3000m - mem_limit: 6000m - mem_reservation: 3500m - healthcheck: - test: ["CMD", "curl", "-f", "-L", "http://localhost:8080/cmdbuild/ui"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 120s - networks: - - backend - - panda_maintenance_backend - - # michael.schmidt@psi.ch - # SAP Archiv Programm von der Firma KGS supportabteilung@kgs-software.com - tiacore-test: - container_name: tiacore-test - image: images.kgs-cloud.de/tia/core/saphttp:3.17.1 - restart: always - volumes: - - /opt/webcontent/tiacore-test/config/license:/application/license - - /opt/webcontent/tiacore-test/config/keystore:/application/ssl - - /opt/webcontent/tiacore-test/config:/application/config/ - - /mount/saparchive/testdata:/application/data - environment: - LOGGING_LEVEL_ROOT: INFO - # Authentifizierung für WebUI - WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin - WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" - networks: - - backend - - # michael.schmidt@psi.ch - # SAP Archiv Programm von der Firma KGS supportabteilung@kgs-software.com - tiacore-prod: - container_name: tiacore-prod - image: images.kgs-cloud.de/tia/core/saphttp:3.17.1 - restart: always - volumes: - - /opt/webcontent/tiacore-prod/config/license:/application/license - - /opt/webcontent/tiacore-prod/config/keystore:/application/ssl - - /opt/webcontent/tiacore-prod/config:/application/config/ - - /mount/saparchive/proddata:/application/data - environment: - LOGGING_LEVEL_ROOT: INFO - # Authentifizierung für WebUI - WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin - WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" - networks: - - backend - - # michael.schmidt@psi.ch - # SAP Document Router von der Firma KGS supportabteilung@kgs-software.com - documentrouter-prod: - container_name: tiadocrouter-prod - image: images.kgs-cloud.de/tia-documentrouter/tia-documentrouter:5.2.0 - restart: always - volumes: - - /opt/webcontent/tiadocrouter-prod/work:/application/work - - /opt/webcontent/tiadocrouter-prod/input:/application/input - - /opt/webcontent/tiadocrouter-prod/log:/application/log - - /opt/webcontent/tiadocrouter-prod/lib:/application/lib - - /opt/webcontent/tiadocrouter-prod/config:/application/config - environment: - LOGGING_LEVEL_ROOT: INFO - # Authentifizierung für WebUI - WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin - WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" - CLASSPATH: "/application/lib/sapjco3.jar" - LD_LIBRARY_PATH: "/usr/glibc-compat/lib:/application/lib" - networks: - - backend - banf-fetcher-prod: - container_name: banf-fetcher-prod - image: python:3.12 - restart: always - volumes: - - /opt/webcontent/tiadocrouter-prod/banf-data:/data - - /opt/webcontent/tiadocrouter-prod/input:/data/banf-dr - environment: - - SCP_HOST=filetransfer.openordering.de - - SCP_PORT=22 - - SCP_USER=PSIProduktiv - - SCP_PASSWORD=${BANF_FETCHER_PROD_PASSWORD} - - SCP_REMOTE_DIR=Anlagen - - SCP_HOSTKEY_FINGERPRINT="ssh-rsa 1024 fingerprint" - command: > - sh -c "pip install paramiko scp lxml && - while true; do python /data/script/banf_ftpload_convert.py; sleep 120; done" - - # michael.schmidt@psi.ch - # SAP Document Router von der Firma KGS supportabteilung@kgs-software.com - documentrouter-test: - container_name: tiadocrouter-test - image: images.kgs-cloud.de/tia-documentrouter/tia-documentrouter:5.2.0 - restart: always - volumes: - - /opt/webcontent/tiadocrouter-test/work:/application/work - - /opt/webcontent/tiadocrouter-test/input:/application/input - - /opt/webcontent/tiadocrouter-test/log:/application/log - - /opt/webcontent/tiadocrouter-test/lib:/application/lib - - /opt/webcontent/tiadocrouter-test/config:/application/config - environment: - LOGGING_LEVEL_ROOT: INFO - # Authentifizierung für WebUI - WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin - WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" - CLASSPATH: "/application/lib/sapjco3.jar" - LD_LIBRARY_PATH: "/usr/glibc-compat/lib:/application/lib" - - networks: - - backend - banf-fetcher-test: - container_name: banf-fetcher-test - image: python:3.12 - restart: always - volumes: - - /opt/webcontent/tiadocrouter-test/banf-data:/data - - /opt/webcontent/tiadocrouter-test/input:/data/banf-dr - environment: - - SCP_HOST=filetransfer.test.openordering.de - - SCP_PORT=22 - - SCP_USER=PSITest - - SCP_PASSWORD=${BANF_FETCHER_TEST_PASSWORD} - - SCP_REMOTE_DIR=Anlagen - - SCP_HOSTKEY_FINGERPRINT="ssh-rsa 1024 fingerprint" - command: > - sh -c "pip install paramiko scp lxml && - while true; do python /data/script/banf_ftpload_convert.py; sleep 120; done" - - # Lange Clemens - # summer-project-selection.psi.ch - summer-project-selection: - image: gitea.psi.ch/lange_c/summer-project-selection:latest - restart: always - volumes: - - /opt/webcontent/summer-project-selection/data:/app/data - - /opt/webcontent/summer-project-selection/projects:/app/projects - - /opt/webcontent/summer-project-selection/applicants:/app/applicants - networks: - - backend - - # Tomcat Beamline Wiki INC0159147 tomcatwiki.psi.ch - # goran.lovric@psi.ch - tomcatwikidb: - image: postgres:15-alpine - container_name: tomcatwiki_db - restart: always - environment: - - POSTGRES_DB=wiki - - POSTGRES_PASSWORD=${TOMCATWIKI_DB_SECRET:?TOMCATWIKI_DB_SECRET is required} - - POSTGRES_USER=wiki - volumes: - - /opt/webcontent/tomcatwiki/db-data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - networks: - - tomcatwiki_backend - - tomcatwiki: - image: ghcr.io/requarks/wiki:2 - container_name: tomcatwiki - restart: always - depends_on: - tomcatwikidb: - condition: service_healthy - environment: - - DB_TYPE=postgres - - DB_HOST=tomcatwikidb - - DB_PORT=5432 - - DB_USER=wiki - - DB_PASS=${TOMCATWIKI_DB_SECRET:?TOMCATWIKI_DB_SECRET is required} - - DB_NAME=wiki - networks: - - tomcatwiki_backend - - backend - - scicat-rocrate-dev: - image: gitea.psi.ch/data-catalog-services/scicat-rocrate:2.6.0 - container_name: scicat-rocrate-dev - restart: always - networks: - - backend - environment: - - QUARKUS_REST_CLIENT_SCICAT_URL=https://scicat.development.psi.ch - - QUARKUS_REST_CLIENT_S3BROKER_URL=https://scicat-s3-broker.development.psi.ch - - QUARKUS_HTTP_LIMITS_MAX_BODY_SIZE=10G - volumes: - - /opt/webcontent/rocrate-dev/dev:/ro-crate:rw - - # Aimo Altorfer / Roger Kalt - RF - # rf-pikett.psi.ch - Pikettlischte Beautifier - # Image is built from https://gitea.psi.ch/RF/pikettlischte-beautifier - pikettlischte-beautifier: - image: gitea.psi.ch/rf/pikettlischte-beautifier:latest - container_name: pikettlischte-beautifier - restart: always - networks: - - backend +# Requires Docker Compose 2.30.0 or later (raw service env files). +# Run Compose from this directory to keep the existing project and .env. +# All modules resolve relative paths and their default .env from this root. +include: + - path: stacks/proxy/compose.yaml + project_directory: . + - path: stacks/legacy-web/compose.yaml + project_directory: . + - path: stacks/excalidraw/compose.yaml + project_directory: . + - path: stacks/elog/compose.yaml + project_directory: . + - path: stacks/pepisco/compose.yaml + project_directory: . + - path: stacks/swiss-ecargo/compose.yaml + project_directory: . + - path: stacks/hedgedoc/compose.yaml + project_directory: . + - path: stacks/opticswiki/compose.yaml + project_directory: . + - path: stacks/slsbl/compose.yaml + project_directory: . + - path: stacks/n8n/compose.yaml + project_directory: . + - path: stacks/dashboard/compose.yaml + project_directory: . + - path: stacks/panda-maintenance/compose.yaml + project_directory: . + - path: stacks/sap/compose.yaml + project_directory: . + - path: stacks/summer-project-selection/compose.yaml + project_directory: . + - path: stacks/tomcatwiki/compose.yaml + project_directory: . + - path: stacks/scicat-rocrate/compose.yaml + project_directory: . + - path: stacks/pikettlischte-beautifier/compose.yaml + project_directory: . +# Network definitions remain shared by the single Compose project. networks: public: backend: diff --git a/stacks/dashboard/compose.yaml b/stacks/dashboard/compose.yaml new file mode 100644 index 0000000..12bd763 --- /dev/null +++ b/stacks/dashboard/compose.yaml @@ -0,0 +1,63 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Ritter Tom + # It-Strategy monitoring dashboard + + it-strategy-dashboard-frontend: + image: gitea.psi.ch/9501/it-strategy-dashboard-frontend:4.0.4 + container_name: it-strategy-dashboard-frontend + command: ["sh", "-c", "/usr/local/bin/copyData.sh"] + volumes: + - /opt/webcontent/it-strategy-dashboard/frontend:/opt/webcontent/it-strategy-dashboard/frontend + restart: "no" + networks: + - it_strategy_dashboard_backend + + it-strategy-dashboard-backend: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/dashboard/backend.env + required: true + format: raw + image: gitea.psi.ch/9501/it-strategy-dashboard-backend:4.0.5 + container_name: it-strategy-dashboard-backend + restart: always + environment: + - DB_HOST=it-strategy-dashboard-db + - DB_PORT=3306 + - DB_NAME=itstrategy + depends_on: + it-strategy-dashboard-db: + condition: service_healthy + networks: + - it_strategy_dashboard_backend + - backend + + it-strategy-dashboard-db: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/dashboard/db.env + required: true + format: raw + image: mariadb:12 + container_name: it-strategy-dashboard-db + restart: always + environment: + - MYSQL_DATABASE=itstrategy + volumes: + - /opt/webcontent/it-strategy-dashboard/mysql/data:/var/lib/mysql + depends_on: + it-strategy-dashboard-frontend: + condition: service_completed_successfully + networks: + - it_strategy_dashboard_backend + healthcheck: + # Authenticate over TCP; do not embed the password in the command arguments. + test: + - CMD-SHELL + - >- + MYSQL_PWD="$${MYSQL_PASSWORD}" mariadb --protocol=TCP + -h 127.0.0.1 -u "$${MYSQL_USER}" "$${MYSQL_DATABASE}" + -e 'SELECT 1' >/dev/null + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s diff --git a/stacks/elog/compose.yaml b/stacks/elog/compose.yaml new file mode 100644 index 0000000..ae89900 --- /dev/null +++ b/stacks/elog/compose.yaml @@ -0,0 +1,51 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # benjamin.brem@psi.ch - LAC + # Elog instance + lac-elog: + image: gitea.psi.ch/images/elog:3.1.5 + container_name: lac-elog + restart: always + volumes: + - /opt/logbooks/lac:/usr/local/elog/logbooks + - /opt/webcontent/lac/elog.cfg:/usr/local/elog/elogd.cfg + networks: + - backend + + # valerio.scagnoli@psi.ch - LMX + # Elog instance + lmx-elog: + image: gitea.psi.ch/images/elog:3.1.5 + container_name: lmx-elog + restart: always + volumes: + - /opt/logbooks/lmx:/usr/local/elog/logbooks + - /opt/webcontent/lmx/passwd.txt:/usr/local/elog/passwd.txt + - /opt/webcontent/lmx/elog.cfg:/usr/local/elog/elogd.cfg + networks: + - backend + + # Rostomyan Tigran + # INC0137443 + # Elog instance + pif-elog: + image: gitea.psi.ch/images/elog:3.1.5 + container_name: pif-elog + restart: always + volumes: + - /opt/logbooks/pif:/usr/local/elog/logbooks + - /opt/webcontent/pif/elog.cfg:/usr/local/elog/elogd.cfg + networks: + - backend + + # Krieger Jonas Andreas , Raselli Andrea-Raeto + # Elog as a Service PoC mit musr-elog.psi.ch? linux-eng@psi.ch + lmu-elog: + image: gitea.psi.ch/images/elog:3.1.5 + container_name: lmu-elog + restart: always + volumes: + - /opt/logbooks/LMU:/usr/local/elog/logbooks + - /opt/webcontent/LMU/elog.cfg:/usr/local/elog/elogd.cfg + networks: + - backend diff --git a/stacks/excalidraw/compose.yaml b/stacks/excalidraw/compose.yaml new file mode 100644 index 0000000..acbbfbc --- /dev/null +++ b/stacks/excalidraw/compose.yaml @@ -0,0 +1,10 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # linux-eng@psi.ch + # Test app + excalidraw: + image: excalidraw/excalidraw:latest + container_name: excalidraw + restart: always + networks: + - backend diff --git a/stacks/hedgedoc/compose.yaml b/stacks/hedgedoc/compose.yaml new file mode 100644 index 0000000..62b9d1e --- /dev/null +++ b/stacks/hedgedoc/compose.yaml @@ -0,0 +1,58 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Augustin Sven + # POC for SiwssFel + hedgedoc_app: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/hedgedoc/app.env + required: true + format: raw + image: quay.io/hedgedoc/hedgedoc:latest + container_name: hedgedoc + environment: + - CMD_DB_DIALECT=postgres + - CMD_DB_HOST=hedgedoc_db + - CMD_DB_PORT=5432 + - CMD_DB_DATABASE=hedgedoc + - CMD_DOMAIN=hedgedoc.psi.ch + - CMD_URL_ADDPORT=false + - CMD_PROTOCOL_USESSL=true + - CMD_LDAP_URL=ldaps://dc00.d.psi.ch + - CMD_LDAP_SEARCHBASE=OU=users,OU=psi,DC=d,DC=psi,DC=ch + - CMD_LDAP_SEARCHFILTER=(&(objectcategory=person)(objectclass=user)(|(sAMAccountName={{username}})(mail={{username}}))) + - CMD_LDAP_USERIDFIELD=sAMAccountName + - CMD_LDAP_PROVIDERNAME=PSI + - NODE_ENV=production + - CMD_EMAIL=false + - CMD_ALLOW_EMAIL_REGISTER=false + volumes: + - /opt/webcontent/sf-hedgedoc/uploads:/hedgedoc/public/uploads + restart: always + depends_on: + hedgedoc_db: + condition: service_healthy + networks: + - backend + - hedgedoc_backend + + # Augustin Sven + hedgedoc_db: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/hedgedoc/db.env + required: true + format: raw + image: postgres:13.4-alpine + container_name: hedgedoc_db + environment: + - POSTGRES_DB=hedgedoc + volumes: + - /opt/webcontent/sf-hedgedoc/data:/var/lib/postgresql/data + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - hedgedoc_backend diff --git a/stacks/legacy-web/compose.yaml b/stacks/legacy-web/compose.yaml new file mode 100644 index 0000000..1029d51 --- /dev/null +++ b/stacks/legacy-web/compose.yaml @@ -0,0 +1,33 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # rfwtools + # gfa-status-test + # fluid-eos + # spaceweather + apache: + image: gitea.psi.ch/images/php-apache:latest + container_name: apache_app + restart: always + volumes: + - /opt/webcontent/rfmwtools:/var/www/rfmwtools:ro + - /opt/webcontent/cas-status:/var/www/cas-status:rw + - /opt/webcontent/fluid-eos:/var/www/fluid-eos:rw + - /opt/webcontent/sls2:/var/www/sls2:rw + - /opt/webcontent/megwiki:/var/www/megwiki:rw + - /opt/webcontent/sinqimaging:/var/www/sinqimaging:rw + - /opt/webcontent/omny:/var/www/omny:rw + - /mount/srem/webhosting:/var/www/srem:rw + - ./apache/conf:/etc/apache2/sites-enabled:ro + networks: + - backend + + legacy_scheduleit: + # image: gitea.psi.ch/images/php-apache:latest + image: gitea.psi.ch/images/legacy_scheduleit:latest + restart: always + container_name: legacy-scheduleit + volumes: + - /opt/webcontent/lbr-scheduler/code:/var/www/lbr-scheduler:rw + - /opt/webcontent/lbr-scheduler/apache/conf:/etc/apache2/sites-enabled:ro + networks: + - backend diff --git a/stacks/n8n/compose.yaml b/stacks/n8n/compose.yaml new file mode 100644 index 0000000..57e1b11 --- /dev/null +++ b/stacks/n8n/compose.yaml @@ -0,0 +1,22 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Angelo Sozzi INC0150655 angelo.sozzi@psi.ch + # POC Software - might be installed on docker-dmz + n8n: + image: docker.n8n.io/n8nio/n8n + container_name: n8n + restart: always + environment: + - N8N_HOST=n8n + - N8N_PORT=5678 + - N8N_PROTOCOL=http + - NODE_ENV=production + - WEBHOOK_URL=https://n8n.psi.ch + - GENERIC_TIMEZONE=Europe/Zurich + - DB_SQLITE_POOL_SIZE=4 + - N8N_RUNNERS_ENABLED=true + volumes: + - /opt/webcontent/n8n/n8n_data:/home/node/.n8n + - /opt/webcontent/n8n/local_files:/files + networks: + - backend diff --git a/stacks/opticswiki/compose.yaml b/stacks/opticswiki/compose.yaml new file mode 100644 index 0000000..665c81a --- /dev/null +++ b/stacks/opticswiki/compose.yaml @@ -0,0 +1,20 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Flechsig Uwe + # opticswiki + # webcontent: + # lx-fs-01.psi.ch:/lx_webhosting + opticswiki: + image: gitea.psi.ch/images/opticswiki:1.8 + container_name: opticswiki + restart: always + volumes: + - /opt/webcontent/opticswiki/data:/usr/local/apache2/wiki/data + - /opt/webcontent/opticswiki/pub:/usr/local/apache2/wiki/pub + - /opt/webcontent/opticswiki/working:/usr/local/apache2/wiki/working + - /opt/webcontent/opticswiki/lib:/usr/local/apache2/wiki/lib + - /opt/webcontent/opticswiki/conf:/usr/local/apache2/conf + - /opt/webcontent/opticswiki/htdocs:/usr/local/apache2/htdocs + - /opt/webcontent/opticswiki/log:/var/log/apache2 + networks: + - backend diff --git a/stacks/panda-maintenance/compose.yaml b/stacks/panda-maintenance/compose.yaml new file mode 100644 index 0000000..2d9a639 --- /dev/null +++ b/stacks/panda-maintenance/compose.yaml @@ -0,0 +1,59 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Christoph Hug , Simon Suter + # Panda-Maintenance (OpenMaint) + panda-maintenance-db: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/panda-maintenance/db.env + required: true + format: raw + image: postgis/postgis:17-3.5-alpine + container_name: panda-maintenance-db + volumes: + - /opt/webcontent/panda-maintenance/data:/var/lib/postgresql/data:rw + environment: + - POSTGRES_USER=postgres + restart: always + mem_limit: 4000m + mem_reservation: 2000m + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 80s + networks: + - panda_maintenance_backend + panda-maintenance-app: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/panda-maintenance/app.env + required: true + format: raw + image: itmicus/cmdbuild:om-2.4-4.1.0 + container_name: panda-maintenance-app + links: + - panda-maintenance-db + depends_on: + panda-maintenance-db: + condition: service_healthy + restart: always + volumes: + - /opt/webcontent/panda-maintenance/docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh:ro + environment: + - POSTGRES_USER=postgres + - POSTGRES_PORT=5432 + - POSTGRES_HOST=panda-maintenance-db + - POSTGRES_DB=openmaint + - CMDBUILD_DUMP=empty.dump.xz + - JAVA_OPTS=-Xmx6000m -Xms3000m + mem_limit: 6000m + mem_reservation: 3500m + healthcheck: + test: ["CMD", "curl", "-f", "-L", "http://localhost:8080/cmdbuild/ui"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s + networks: + - backend + - panda_maintenance_backend diff --git a/stacks/pepisco/compose.yaml b/stacks/pepisco/compose.yaml new file mode 100644 index 0000000..1a7a9fb --- /dev/null +++ b/stacks/pepisco/compose.yaml @@ -0,0 +1,10 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # PEPISCO + # PhotoElectron PhotoIon Spectral COmpendium + pepisco: + image: gitea.psi.ch/images/pepisco:v2026.03.13 + container_name: pepisco-test + restart: always + networks: + - backend diff --git a/stacks/pikettlischte-beautifier/compose.yaml b/stacks/pikettlischte-beautifier/compose.yaml new file mode 100644 index 0000000..115f55e --- /dev/null +++ b/stacks/pikettlischte-beautifier/compose.yaml @@ -0,0 +1,11 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Aimo Altorfer / Roger Kalt - RF + # rf-pikett.psi.ch - Pikettlischte Beautifier + # Image is built from https://gitea.psi.ch/RF/pikettlischte-beautifier + pikettlischte-beautifier: + image: gitea.psi.ch/rf/pikettlischte-beautifier:latest + container_name: pikettlischte-beautifier + restart: always + networks: + - backend diff --git a/stacks/proxy/compose.yaml b/stacks/proxy/compose.yaml new file mode 100644 index 0000000..8caae65 --- /dev/null +++ b/stacks/proxy/compose.yaml @@ -0,0 +1,25 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # linux-eng@psi.ch + # Reverse Proxy handling all HTTP/HTTPS requests + # Only container that is exposed to the network + # Communication to other services is trough docker network + nginx: + image: nginx:latest + container_name: nginx_proxy + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - /etc/letsencrypt/live:/etc/letsencrypt/live:ro + - /etc/letsencrypt/archive:/etc/letsencrypt/archive:ro + - /etc/pki/tls/certs:/etc/nginx/certs:ro + - /etc/pki/tls/private:/etc/nginx/private:ro + - /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - /etc/nginx/conf.d:/etc/nginx/conf.d:ro + - /opt/webcontent/sinqstatus-test:/opt/webcontent/sinqstatus-test:ro + - /opt/webcontent/it-strategy-dashboard/frontend/:/opt/webcontent/it-strategy-dashboard/:ro + networks: + - public + - backend diff --git a/stacks/sap/compose.yaml b/stacks/sap/compose.yaml new file mode 100644 index 0000000..c21ea8f --- /dev/null +++ b/stacks/sap/compose.yaml @@ -0,0 +1,118 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # michael.schmidt@psi.ch + # SAP Archiv Programm von der Firma KGS supportabteilung@kgs-software.com + tiacore-test: + container_name: tiacore-test + image: images.kgs-cloud.de/tia/core/saphttp:3.17.1 + restart: always + volumes: + - /opt/webcontent/tiacore-test/config/license:/application/license + - /opt/webcontent/tiacore-test/config/keystore:/application/ssl + - /opt/webcontent/tiacore-test/config:/application/config/ + - /mount/saparchive/testdata:/application/data + environment: + LOGGING_LEVEL_ROOT: INFO + # Authentifizierung für WebUI + WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin + WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" + networks: + - backend + + # michael.schmidt@psi.ch + # SAP Archiv Programm von der Firma KGS supportabteilung@kgs-software.com + tiacore-prod: + container_name: tiacore-prod + image: images.kgs-cloud.de/tia/core/saphttp:3.17.1 + restart: always + volumes: + - /opt/webcontent/tiacore-prod/config/license:/application/license + - /opt/webcontent/tiacore-prod/config/keystore:/application/ssl + - /opt/webcontent/tiacore-prod/config:/application/config/ + - /mount/saparchive/proddata:/application/data + environment: + LOGGING_LEVEL_ROOT: INFO + # Authentifizierung für WebUI + WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin + WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" + networks: + - backend + + # michael.schmidt@psi.ch + # SAP Document Router von der Firma KGS supportabteilung@kgs-software.com + documentrouter-prod: + container_name: tiadocrouter-prod + image: images.kgs-cloud.de/tia-documentrouter/tia-documentrouter:5.2.0 + restart: always + volumes: + - /opt/webcontent/tiadocrouter-prod/work:/application/work + - /opt/webcontent/tiadocrouter-prod/input:/application/input + - /opt/webcontent/tiadocrouter-prod/log:/application/log + - /opt/webcontent/tiadocrouter-prod/lib:/application/lib + - /opt/webcontent/tiadocrouter-prod/config:/application/config + environment: + LOGGING_LEVEL_ROOT: INFO + # Authentifizierung für WebUI + WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin + WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" + CLASSPATH: "/application/lib/sapjco3.jar" + LD_LIBRARY_PATH: "/usr/glibc-compat/lib:/application/lib" + networks: + - backend + banf-fetcher-prod: + container_name: banf-fetcher-prod + image: python:3.12 + restart: always + volumes: + - /opt/webcontent/tiadocrouter-prod/banf-data:/data + - /opt/webcontent/tiadocrouter-prod/input:/data/banf-dr + environment: + - SCP_HOST=filetransfer.openordering.de + - SCP_PORT=22 + - SCP_USER=PSIProduktiv + - SCP_PASSWORD=${BANF_FETCHER_PROD_PASSWORD} + - SCP_REMOTE_DIR=Anlagen + - SCP_HOSTKEY_FINGERPRINT="ssh-rsa 1024 fingerprint" + command: > + sh -c "pip install paramiko scp lxml && + while true; do python /data/script/banf_ftpload_convert.py; sleep 120; done" + + # michael.schmidt@psi.ch + # SAP Document Router von der Firma KGS supportabteilung@kgs-software.com + documentrouter-test: + container_name: tiadocrouter-test + image: images.kgs-cloud.de/tia-documentrouter/tia-documentrouter:5.2.0 + restart: always + volumes: + - /opt/webcontent/tiadocrouter-test/work:/application/work + - /opt/webcontent/tiadocrouter-test/input:/application/input + - /opt/webcontent/tiadocrouter-test/log:/application/log + - /opt/webcontent/tiadocrouter-test/lib:/application/lib + - /opt/webcontent/tiadocrouter-test/config:/application/config + environment: + LOGGING_LEVEL_ROOT: INFO + # Authentifizierung für WebUI + WEBAPP_SECURITY_AUTH_BASIC_USERNAME: psiadmin + WEBAPP_SECURITY_AUTH_BASIC_PASSWORD: "$$2y$$10$$LA.TjBKRIDM/c6AJ86BZ/elpa9InzVbzqzLsyfc1Wt7h3S3N8MUPm" + CLASSPATH: "/application/lib/sapjco3.jar" + LD_LIBRARY_PATH: "/usr/glibc-compat/lib:/application/lib" + + networks: + - backend + banf-fetcher-test: + container_name: banf-fetcher-test + image: python:3.12 + restart: always + volumes: + - /opt/webcontent/tiadocrouter-test/banf-data:/data + - /opt/webcontent/tiadocrouter-test/input:/data/banf-dr + environment: + - SCP_HOST=filetransfer.test.openordering.de + - SCP_PORT=22 + - SCP_USER=PSITest + - SCP_PASSWORD=${BANF_FETCHER_TEST_PASSWORD} + - SCP_REMOTE_DIR=Anlagen + - SCP_HOSTKEY_FINGERPRINT="ssh-rsa 1024 fingerprint" + command: > + sh -c "pip install paramiko scp lxml && + while true; do python /data/script/banf_ftpload_convert.py; sleep 120; done" diff --git a/stacks/scicat-rocrate/compose.yaml b/stacks/scicat-rocrate/compose.yaml new file mode 100644 index 0000000..b00509d --- /dev/null +++ b/stacks/scicat-rocrate/compose.yaml @@ -0,0 +1,14 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + scicat-rocrate-dev: + image: gitea.psi.ch/data-catalog-services/scicat-rocrate:2.6.0 + container_name: scicat-rocrate-dev + restart: always + networks: + - backend + environment: + - QUARKUS_REST_CLIENT_SCICAT_URL=https://scicat.development.psi.ch + - QUARKUS_REST_CLIENT_S3BROKER_URL=https://scicat-s3-broker.development.psi.ch + - QUARKUS_HTTP_LIMITS_MAX_BODY_SIZE=10G + volumes: + - /opt/webcontent/rocrate-dev/dev:/ro-crate:rw diff --git a/stacks/slsbl/compose.yaml b/stacks/slsbl/compose.yaml new file mode 100644 index 0000000..6748d96 --- /dev/null +++ b/stacks/slsbl/compose.yaml @@ -0,0 +1,17 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Flechsig Uwe + # slsbl + # webcontent: + # lx-fs-01.psi.ch:/lx_webhosting + slsbl: + image: gitea.psi.ch/images/slsbl:1.0 + container_name: slsbl + restart: always + volumes: + - /opt/webcontent/slsbl/cgi-bin:/usr/local/apache2/cgi-bin + - /opt/webcontent/slsbl/conf:/usr/local/apache2/conf + - /opt/webcontent/slsbl/htdocs:/usr/local/apache2/htdocs + - /opt/webcontent/slsbl/log:/var/log/apache2 + networks: + - backend diff --git a/stacks/summer-project-selection/compose.yaml b/stacks/summer-project-selection/compose.yaml new file mode 100644 index 0000000..d978db1 --- /dev/null +++ b/stacks/summer-project-selection/compose.yaml @@ -0,0 +1,13 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Lange Clemens + # summer-project-selection.psi.ch + summer-project-selection: + image: gitea.psi.ch/lange_c/summer-project-selection:latest + restart: always + volumes: + - /opt/webcontent/summer-project-selection/data:/app/data + - /opt/webcontent/summer-project-selection/projects:/app/projects + - /opt/webcontent/summer-project-selection/applicants:/app/applicants + networks: + - backend diff --git a/stacks/swiss-ecargo/compose.yaml b/stacks/swiss-ecargo/compose.yaml new file mode 100644 index 0000000..6f5f7b7 --- /dev/null +++ b/stacks/swiss-ecargo/compose.yaml @@ -0,0 +1,13 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Romain Sacchi + swiss-ecargo: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/swiss-ecargo/app.env + required: true + format: raw + image: gitea.psi.ch/sacchi_r/swiss-ecargo:0.1.2 + container_name: swiss-ecargo + restart: always + networks: + - backend diff --git a/stacks/tomcatwiki/compose.yaml b/stacks/tomcatwiki/compose.yaml new file mode 100644 index 0000000..8ad4851 --- /dev/null +++ b/stacks/tomcatwiki/compose.yaml @@ -0,0 +1,46 @@ +# Included by ../../docker-compose.yaml; use the root Compose entrypoint. +services: + # Tomcat Beamline Wiki INC0159147 tomcatwiki.psi.ch + # goran.lovric@psi.ch + tomcatwikidb: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/tomcatwiki/db.env + required: true + format: raw + image: postgres:15-alpine + container_name: tomcatwiki_db + restart: always + environment: + - POSTGRES_DB=wiki + - POSTGRES_USER=wiki + volumes: + - /opt/webcontent/tomcatwiki/db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - tomcatwiki_backend + + tomcatwiki: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/tomcatwiki/app.env + required: true + format: raw + image: ghcr.io/requarks/wiki:2 + container_name: tomcatwiki + restart: always + depends_on: + tomcatwikidb: + condition: service_healthy + environment: + - DB_TYPE=postgres + - DB_HOST=tomcatwikidb + - DB_PORT=5432 + - DB_USER=wiki + - DB_NAME=wiki + networks: + - tomcatwiki_backend + - backend From 075721cc1d0792c30e88fe2b3cdb6ed4f347b5d0 Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Fri, 18 Sep 2026 17:01:19 +0200 Subject: [PATCH 4/7] update compose and README to not forget where i left Signed-off-by: Basil Bruhn --- .env.example | 4 ++-- README.md | 38 +++++++++++++++++++++++--------------- stacks/sap/compose.yaml | 10 ++++++++-- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 57622b8..3256566 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # Optional nonsecret Compose setting; this is also the default when unset. # Puppet/Hiera owns the deployed .env. Do not replace it with this example: -# SAP/BANF and other applications still use its existing settings. -# Migrated application credentials live in the nine service env files listed +# Other applications and rollback still use its existing settings. +# Migrated application credentials live in the eleven service env files listed # in README.md and are read with format: raw. WEBHOSTING_ENV_DIR=/etc/webhosting diff --git a/README.md b/README.md index 14ee815..925d0cd 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ stacks/ Each application stays with its database and supporting containers. `legacy-web` contains the shared Apache sites and scheduler; `elog` groups the four logbooks. -`sap` contains the existing TIA, document-router, and BANF definitions, moved -together without changes to their configuration. +`sap` contains the TIA, document-router, and BANF definitions. TIA and +document-router keep their existing inline WebUI authentication entries; only +the BANF passwords move to separate test and production env files. Run Compose through the root file, using the existing deployment directory and project name. These modules belong to **one Compose project**; their shared @@ -48,7 +49,7 @@ See [Docker's include documentation](https://docs.docker.com/reference/compose-f ## Environment configuration -Swiss eCargo, HedgeDoc, the dashboard, Panda Maintenance, and Tomcat Wiki read +Swiss eCargo, HedgeDoc, the dashboard, Panda Maintenance, Tomcat Wiki, and BANF read their credentials from service-specific files under `/etc/webhosting`. In the `hiera/data-lx` repository, `default/lx-docker-01.psi.ch.yaml` renders these files from the existing `lx-data::docker-env::*` keys. Files are root-owned with mode @@ -66,6 +67,13 @@ with permission to read them. | `panda-maintenance/db.env` | `panda-maintenance-db` | `POSTGRES_PASSWORD` | | `tomcatwiki/app.env` | `tomcatwiki` | `DB_PASS` | | `tomcatwiki/db.env` | `tomcatwikidb` | `POSTGRES_PASSWORD` | +| `sap/banf-test.env` | `banf-fetcher-test` | `SCP_PASSWORD` | +| `sap/banf-prod.env` | `banf-fetcher-prod` | `SCP_PASSWORD` | + +The BANF files reuse the existing `banf_fetcher_password_test` and +`banf_fetcher_password_prod` Hiera keys. The `WEBAPP_SECURITY_AUTH_BASIC_USERNAME` +and `WEBAPP_SECURITY_AUTH_BASIC_PASSWORD` entries remain inline and unchanged +in all four TIA/document-router services. Each service receives only its own file. Stable nonsecret settings remain in `environment:`. The files use `format: raw`: write single-line literal values @@ -77,27 +85,27 @@ The optional `WEBHOSTING_ENV_DIR` variable overrides `/etc/webhosting`, for exam when validating a temporary set of files. `.env.example` documents this nonsecret setting; it must not replace the production `.env`. -The existing `/opt/WebHosting/.env` remains in place for SAP/BANF, applications -outside this repository, and rollback. The migrated services no longer reference +The existing `/opt/WebHosting/.env` remains in place for applications outside +this repository and rollback, including the old BANF entries. Migrated services no longer reference its old credential variables, including overrides of those variables in the shell. There is no fallback to old credentials if a service env file is missing. -Compose requires all nine files, including when validating the whole project for +Compose requires all eleven files, including when validating the whole project for a targeted service update. `required: true` verifies file existence only; it does not enforce the keys or non-empty values listed above. Check those values as part of deployment. This replaces the previous `${VARIABLE:?message}` checks for the migrated credentials. -The root `.env` still supplies YAML interpolation, including SAP/BANF variables -and `WEBHOSTING_ENV_DIR`. It follows normal dotenv quoting rules; service +The root `.env` still supplies YAML interpolation for `WEBHOSTING_ENV_DIR`. +It follows normal dotenv quoting rules; service `env_file` does not supply interpolation values to YAML. Keep credentials out of Git and logs. See [Docker's interpolation documentation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/). ## Validate and apply configuration -Deploy the Hiera change first and let Puppet create all nine service env files. +Deploy the Hiera change first and let Puppet create all eleven service env files. Verify their permissions and the non-empty keys listed above before activating -this Compose change. Preserve the existing global env file for SAP/BANF and rollback. +this Compose change. Preserve the existing global env file for other applications and rollback. Deploy the complete `stacks/` directory alongside the root Compose file. Keep the existing deployment directory and Compose project name. From that directory, @@ -166,8 +174,8 @@ and networks are preserved in this configuration fix. See the ## Follow-up organization -After verifying the migration on the host, obsolete non-SAP entries can be removed -from the global `.env` in a separate Hiera change. Preserve values used by SAP/BANF -and applications outside this repository. File secrets and independent Compose -projects can be considered separately. Preserve the supplier's definitions in -`stacks/sap/compose.yaml` without changing their configuration style. +After verifying the migration on the host, obsolete migrated entries (including +the two BANF passwords) can be removed from the global `.env` in a separate Hiera +change. Preserve values used by applications outside this repository. File secrets +and independent Compose projects can be considered separately. Keep the supplier's +inline WebUI authentication entries in `stacks/sap/compose.yaml` unchanged. diff --git a/stacks/sap/compose.yaml b/stacks/sap/compose.yaml index c21ea8f..03848ba 100644 --- a/stacks/sap/compose.yaml +++ b/stacks/sap/compose.yaml @@ -60,6 +60,10 @@ services: networks: - backend banf-fetcher-prod: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/sap/banf-prod.env + required: true + format: raw container_name: banf-fetcher-prod image: python:3.12 restart: always @@ -70,7 +74,6 @@ services: - SCP_HOST=filetransfer.openordering.de - SCP_PORT=22 - SCP_USER=PSIProduktiv - - SCP_PASSWORD=${BANF_FETCHER_PROD_PASSWORD} - SCP_REMOTE_DIR=Anlagen - SCP_HOSTKEY_FINGERPRINT="ssh-rsa 1024 fingerprint" command: > @@ -100,6 +103,10 @@ services: networks: - backend banf-fetcher-test: + env_file: + - path: ${WEBHOSTING_ENV_DIR:-/etc/webhosting}/sap/banf-test.env + required: true + format: raw container_name: banf-fetcher-test image: python:3.12 restart: always @@ -110,7 +117,6 @@ services: - SCP_HOST=filetransfer.test.openordering.de - SCP_PORT=22 - SCP_USER=PSITest - - SCP_PASSWORD=${BANF_FETCHER_TEST_PASSWORD} - SCP_REMOTE_DIR=Anlagen - SCP_HOSTKEY_FINGERPRINT="ssh-rsa 1024 fingerprint" command: > From 2bcc1dfed8ed27d1690d150a295c26cf2e8b45b8 Mon Sep 17 00:00:00 2001 From: Basil Bruhn Date: Fri, 18 Sep 2026 19:03:28 +0200 Subject: [PATCH 5/7] update readme and networks Signed-off-by: Basil Bruhn --- README.md | 192 ++++++------------------------------------------------ 1 file changed, 19 insertions(+), 173 deletions(-) diff --git a/README.md b/README.md index 925d0cd..0fa2712 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,27 @@ # WebHosting -Docker Compose services and Nginx/Apache configuration for the shared web host. -Puppet deploys the host configuration and environment files from Hiera-eyaml. +Shared web applications behind Nginx/Apache, deployed in `/opt/WebHosting`. +Requires Docker Compose **2.30.0 or later**. -## Compose layout +- `docker-compose.yaml` combines `stacks//compose.yaml` into one Compose project. + Run commands from the repository root; relative paths resolve from there. +- Only Nginx publishes host ports (`80`/`443`) and joins `public` and `backend`. + Web applications share `backend`; databases use their separate application networks. + These are bridge networks with outbound access, without `internal: true`. +- Puppet manages secrets through `hiera/data-lx/default/lx-docker-01.psi.ch.yaml`. + The service env files live under `/etc/webhosting/`, separately from this repo. +- Env files are root-owned (`0600`, directories `0700`) and use `format: raw`: + keep values literal, without adding quotes or escaping dollar signs. -Requires **Docker Compose 2.30.0 or later** for raw service env files. The root -`docker-compose.yaml` is the deployment entrypoint and assembles these modules: - -```text -docker-compose.yaml -stacks/ - proxy/compose.yaml - legacy-web/compose.yaml - excalidraw/compose.yaml - elog/compose.yaml - pepisco/compose.yaml - swiss-ecargo/compose.yaml - hedgedoc/compose.yaml - opticswiki/compose.yaml - slsbl/compose.yaml - n8n/compose.yaml - dashboard/compose.yaml - panda-maintenance/compose.yaml - sap/compose.yaml - summer-project-selection/compose.yaml - tomcatwiki/compose.yaml - scicat-rocrate/compose.yaml - pikettlischte-beautifier/compose.yaml -``` - -Each application stays with its database and supporting containers. `legacy-web` -contains the shared Apache sites and scheduler; `elog` groups the four logbooks. -`sap` contains the TIA, document-router, and BANF definitions. TIA and -document-router keep their existing inline WebUI authentication entries; only -the BANF passwords move to separate test and production env files. - -Run Compose through the root file, using the existing deployment directory and -project name. These modules belong to **one Compose project**; their shared -network definitions remain in the root file. They are not standalone deployment -entrypoints. For example, use `docker compose --env-file .env up -d excalidraw` -from the repository root to update that service. - -Every include sets `project_directory: .` so relative paths such as -`./apache/conf` and the default `.env` still resolve from the repository root. -The service names, container names, images, networks, and data paths are preserved. -See [Docker's include documentation](https://docs.docker.com/reference/compose-file/include/). - -## Environment configuration - -Swiss eCargo, HedgeDoc, the dashboard, Panda Maintenance, Tomcat Wiki, and BANF read -their credentials from service-specific files under `/etc/webhosting`. In the -`hiera/data-lx` repository, `default/lx-docker-01.psi.ch.yaml` renders these files -from the existing `lx-data::docker-env::*` keys. Files are root-owned with mode -`0600`; their directories have mode `0700`. Run Compose as a deployment user -with permission to read them. - -| File under `/etc/webhosting/` | Service | Required non-empty variables | -| --- | --- | --- | -| `swiss-ecargo/app.env` | `swiss-ecargo` | `HOSTED_API_KEY` | -| `hedgedoc/app.env` | `hedgedoc_app` | `CMD_DB_USERNAME`, `CMD_DB_PASSWORD`, `CMD_LDAP_BINDDN`, `CMD_LDAP_BINDCREDENTIALS`, `CMD_SESSION_SECRET` | -| `hedgedoc/db.env` | `hedgedoc_db` | `POSTGRES_USER`, `POSTGRES_PASSWORD` | -| `dashboard/backend.env` | `it-strategy-dashboard-backend` | `DB_USER`, `DB_PASS`, `JWT_SECRET`, `ADMIN_PW_HASH` | -| `dashboard/db.env` | `it-strategy-dashboard-db` | `MYSQL_ROOT_PASSWORD`, `MYSQL_USER`, `MYSQL_PASSWORD` | -| `panda-maintenance/app.env` | `panda-maintenance-app` | `POSTGRES_PASSWORD` | -| `panda-maintenance/db.env` | `panda-maintenance-db` | `POSTGRES_PASSWORD` | -| `tomcatwiki/app.env` | `tomcatwiki` | `DB_PASS` | -| `tomcatwiki/db.env` | `tomcatwikidb` | `POSTGRES_PASSWORD` | -| `sap/banf-test.env` | `banf-fetcher-test` | `SCP_PASSWORD` | -| `sap/banf-prod.env` | `banf-fetcher-prod` | `SCP_PASSWORD` | - -The BANF files reuse the existing `banf_fetcher_password_test` and -`banf_fetcher_password_prod` Hiera keys. The `WEBAPP_SECURITY_AUTH_BASIC_USERNAME` -and `WEBAPP_SECURITY_AUTH_BASIC_PASSWORD` entries remain inline and unchanged -in all four TIA/document-router services. - -Each service receives only its own file. Stable nonsecret settings remain in -`environment:`. The files use `format: raw`: write single-line literal values -without adding quotes or doubling dollar signs. Password hashes, quotes, and -URI-special characters pass through unchanged. These files are not shell scripts. -See [Docker's env-file reference](https://docs.docker.com/reference/compose-file/services/#env_file). - -The optional `WEBHOSTING_ENV_DIR` variable overrides `/etc/webhosting`, for example -when validating a temporary set of files. `.env.example` documents this nonsecret -setting; it must not replace the production `.env`. - -The existing `/opt/WebHosting/.env` remains in place for applications outside -this repository and rollback, including the old BANF entries. Migrated services no longer reference -its old credential variables, including overrides of those variables in the shell. -There is no fallback to old credentials if a service env file is missing. - -Compose requires all eleven files, including when validating the whole project for -a targeted service update. `required: true` verifies file existence only; it does -not enforce the keys or non-empty values listed above. Check those values as part -of deployment. This replaces the previous `${VARIABLE:?message}` checks for the -migrated credentials. - -The root `.env` still supplies YAML interpolation for `WEBHOSTING_ENV_DIR`. -It follows normal dotenv quoting rules; service -`env_file` does not supply interpolation values to YAML. Keep credentials out of -Git and logs. See [Docker's interpolation documentation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/). - -## Validate and apply configuration - -Deploy the Hiera change first and let Puppet create all eleven service env files. -Verify their permissions and the non-empty keys listed above before activating -this Compose change. Preserve the existing global env file for other applications and rollback. - -Deploy the complete `stacks/` directory alongside the root Compose file. Keep the -existing deployment directory and Compose project name. From that directory, -validate using the existing global env file and the new service env files: +Example: validate the project and apply an Excalidraw update on the host: ```sh -docker compose version -docker compose --env-file .env config --quiet -docker compose --env-file .env config --services +cd /opt/WebHosting +docker compose config --quiet && docker compose up -d excalidraw +docker compose ps excalidraw +docker compose logs --tail=50 excalidraw ``` -Confirm the host runs Compose 2.30.0 or later and the assembled configuration lists -all 31 services before applying it. - -Use `--quiet`: plain `docker compose config` and `config --environment` can print -resolved secrets. Validation checks the Compose model; it does not test database -credentials, application behavior, host mounts, or Nginx configuration. - -After validation and the database checks below, use a targeted `docker compose ---env-file .env up -d ...` during the application's maintenance window. -Include its database when its definition changes, and inspect the dependency -chain first (the dashboard database depends on the frontend copy job). Avoid a -whole-project `down`/`up` for these changes. A plain `restart` does not apply changed -container environment variables; see [Docker's restart documentation](https://docs.docker.com/reference/cli/docker/compose/restart/). - -The credential source changes, but its values should match the previous -configuration. This is not a password rotation or database upgrade. To roll back, -restore the previous Compose revision and apply the affected services using the -retained global `.env`. Do not remove that file after this rollout; it still has -active consumers. - -Application recreation can change its IP address. The current Nginx upstream -configuration resolves static service names at startup/reload. Once the selected -application is running, validate and reload the proxy under the existing host -deployment procedure, then check the affected site. The proxy is shared with SAP; -an Nginx reload affects the shared process even though its configuration is -unchanged. See [Nginx upstream resolution](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#resolve). - -## Database readiness and existing data - -HedgeDoc now uses separate `CMD_DB_*` fields so passwords containing URI-special -characters are passed literally. Its app and database env files derive their -shared credentials from the same Hiera keys; the previous `{{ ... }}` expressions were literal strings -to Compose. The LDAP search filter's `{{username}}` is an application placeholder -and must remain. See [HedgeDoc configuration](https://docs.hedgedoc.org/configuration/). - -Before applying the HedgeDoc correction to an existing instance, confirm that -the actual database role/password match the Hiera values. PostgreSQL's -`POSTGRES_USER` and `POSTGRES_PASSWORD` initialize an empty data directory; changing -them does not update an existing database. Coordinate any required credential -change with the application owner and back up the database first. Do not delete -or reinitialize the data directory. See the [PostgreSQL image documentation](https://hub.docker.com/_/postgres). - -HedgeDoc and Tomcat Wiki wait for PostgreSQL to accept TCP connections before -starting. These `pg_isready` checks indicate server readiness, not successful -application authentication. The dashboard healthcheck authenticates to its -configured database and executes `SELECT 1`, using the existing container -environment rather than embedding the password in the healthcheck command. -An unhealthy status alone does not automatically restart a container. - -HedgeDoc's PostgreSQL 13 image remains a separate migration item. Version 13 is -unsupported, and a major upgrade requires a tested backup/restore or `pg_upgrade` -procedure compatible with HedgeDoc. Image versions, data paths, container names, -and networks are preserved in this configuration fix. See the -[PostgreSQL versioning policy](https://www.postgresql.org/support/versioning/). - -## Follow-up organization - -After verifying the migration on the host, obsolete migrated entries (including -the two BANF passwords) can be removed from the global `.env` in a separate Hiera -change. Preserve values used by applications outside this repository. File secrets -and independent Compose projects can be considered separately. Keep the supplier's -inline WebUI authentication entries in `stacks/sap/compose.yaml` unchanged. +Puppet must provision all required env files before Compose validation or deployment. +Use `config --quiet` to avoid printing secrets. After an update, check the application; +if its container IP changed, validate and reload Nginx using the host's deployment procedure. From 70b8e5f0ca0074927f16ba359695d819d554b0b2 Mon Sep 17 00:00:00 2001 From: bruhn_b Date: Fri, 18 Sep 2026 19:04:52 +0200 Subject: [PATCH 6/7] Update README.md --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 0fa2712..91b4502 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,6 @@ Requires Docker Compose **2.30.0 or later**. - Env files are root-owned (`0600`, directories `0700`) and use `format: raw`: keep values literal, without adding quotes or escaping dollar signs. -Example: validate the project and apply an Excalidraw update on the host: - -```sh -cd /opt/WebHosting -docker compose config --quiet && docker compose up -d excalidraw -docker compose ps excalidraw -docker compose logs --tail=50 excalidraw -``` - Puppet must provision all required env files before Compose validation or deployment. Use `config --quiet` to avoid printing secrets. After an update, check the application; if its container IP changed, validate and reload Nginx using the host's deployment procedure. From 71240df3e875ea1037098f37a683c5700c822900 Mon Sep 17 00:00:00 2001 From: bruhn_b Date: Fri, 18 Sep 2026 19:05:06 +0200 Subject: [PATCH 7/7] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 91b4502..6438d43 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # WebHosting Shared web applications behind Nginx/Apache, deployed in `/opt/WebHosting`. -Requires Docker Compose **2.30.0 or later**. - `docker-compose.yaml` combines `stacks//compose.yaml` into one Compose project. Run commands from the repository root; relative paths resolve from there.