update ProdBuild
Build Remsly / Build-Remsly-Backend (push) Successful in 2m36s
Build Remsly / Build-Remsly-Frontend (push) Successful in 1m28s

This commit is contained in:
boxi committed 2026-07-06 19:27:13 +02:00
1 parent 5c92129fba
commit 6a50099849
176 files changed
+408 -395

No files matched your search

+43
View File
@@ -58,6 +58,49 @@ jobs:
echo "versioned: gitea.tail276105.ts.net/${REPOSITORY_LC}-backend:${GITHUB_RUN_NUMBER}"
echo "latest: gitea.tail276105.ts.net/${REPOSITORY_LC}-backend:latest"
- name: Log out from Docker registry
if: always()
run: docker logout gitea.tail276105.ts.net
Build-Remsly-Frontend:
runs-on: ubuntu-latest
container:
image: gitea.tail276105.ts.net/remsly/ci-image:5
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Build FE
run: |
cd frontend
npm ci
npm run buildProd
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: gitea.tail276105.ts.net
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build Docker
run: |
REPOSITORY_LC=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')
(
cd frontend;
docker build -t gitea.tail276105.ts.net/${REPOSITORY_LC}-frontend:${GITHUB_RUN_NUMBER} .;
docker tag gitea.tail276105.ts.net/${REPOSITORY_LC}-frontend:${GITHUB_RUN_NUMBER} gitea.tail276105.ts.net/${REPOSITORY_LC}-frontend:latest
)
- name: Push Docker Images
run: |
REPOSITORY_LC=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')
docker push gitea.tail276105.ts.net/${REPOSITORY_LC}-frontend:${GITHUB_RUN_NUMBER}
docker push gitea.tail276105.ts.net/${REPOSITORY_LC}-frontend:latest
- name: Docker imageTags
run: |
REPOSITORY_LC=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')
echo "versioned: gitea.tail276105.ts.net/${REPOSITORY_LC}-frontend:${GITHUB_RUN_NUMBER}"
echo "latest: gitea.tail276105.ts.net/${REPOSITORY_LC}-frontend:latest"
- name: Log out from Docker registry
if: always()
run: docker logout gitea.tail276105.ts.net
+1
View File
@@ -5,6 +5,7 @@
/frontend/build/
!gradle/wrapper/gradle-wrapper.jar
http-client.private.env.json
deploy/prod/.env
/backend/src/main/resources/*.properties
src/gen
+80
View File
@@ -0,0 +1,80 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Boxit (also called "Remsly" in CI) is a box/inventory management app:
- **backend/** — Spring Boot 2.6 (Kotlin, Java 17, Jersey + Spring MVC), PostgreSQL via jOOQ, Elasticsearch for search, Keycloak for auth. Runs on port **5050**.
- **frontend/** — Angular 13 app (port **4200**), proxies `/api` to the backend via `proxy-conf.json`.
- **JooqHelper/** — small Gradle module providing `BoxitNamingStrategy` used by jOOQ code generation.
- **deploy/localDev/boxit/** — docker-compose for local infrastructure (Postgres on 6543, Keycloak on 8081, Apache OIDC proxy, Elasticsearch, Prometheus).
- **deploy/prod/** — production docker-compose + Let's Encrypt scripts.
- **clients/** — IntelliJ HTTP client files for the REST API, Keycloak, and Elasticsearch (env in `http-client.private.env.json`, template provided).
## Commands
### Backend (Gradle, from repo root)
```bash
./gradlew build # build + tests (requires running Postgres, see below)
./gradlew test # tests only (JUnit 5; integration tests use Testcontainers)
./gradlew generateJooq # run Flyway migrations, then regenerate jOOQ classes
./gradlew lintKotlin # kotlinter lint
./gradlew formatKotlin # kotlinter autoformat
./gradlew bootRun # run the backend
```
Run a single test class: `./gradlew test --tests "ch.boxi.boxit.SomeTestClass"`
DB connection for Flyway/jOOQ codegen is parameterized via Gradle properties
`-PDB_HOST_PORT -PDB_USER -PDB_PASSWD -PDB_NAME` (defaults: `localhost:6543`, `gradle`/`gradlepw`/`gradle` — matches the localDev docker-compose Postgres).
### Frontend (from frontend/)
```bash
npm start # dev server on :4200 with /api proxy to :5050
npm run build # dev build
npm run buildProd # production build (also copies index.html -> 404.html)
npm test # Karma/Jasmine tests
npm run lint # ng lint
```
### Local infrastructure
```bash
cd deploy/localDev/boxit && docker compose up -d
```
Provides Postgres (port 6543, multiple DBs: `boxit`, `keycloak`, `gradle`, `test`), Keycloak (8081), Apache OIDC proxy, and Elasticsearch. `backend/src/main/resources/application.properties` is not committed — create it from `application.properties-Template`.
## Architecture
### Backend data flow: Flyway → jOOQ codegen
The DB schema is the source of truth. Migrations live in `backend/src/main/resources/db/migration` (Flyway, `V*.sql`). `generateJooq` depends on `flywayMigrate` and generates records/POJOs/DAOs into `backend/src/gen/java` (package `ch.boxi.boxit.gen`, deleted on `clean`, regenerated on compilation). Naming is customized by `JooqHelper`'s `BoxitNamingStrategy`. **After changing a migration, re-run `./gradlew generateJooq` against a running Postgres.**
### Backend layers (`ch.boxi.boxit`)
- `restapi/` — REST controllers, DTOs, mappers, pagination (`Paged`, `PaginationDto`, infinite scroll support). Organized by feature (folder, note, task, user, search).
- `domain/` — business logic per feature (attachment, folder, location, note, task, user). Uses a Visitor pattern (`Visitable`/`Visitor`) and `Augmentor` abstractions.
- `db/` — jOOQ-based repositories built on `SimpleCrudRepository` / `AbstractSimpleCrudRepositoryImpl` plus composable `JooqFilter` classes (`CombinedAndFilter`, `CombinedOrFilter`, ...).
- `searchindex/` — Elasticsearch index per feature (folder, note, task) with its own migration support; `db/elasticsearchSync` + `domain/elasticSearchSync` implement DB→ES synchronization.
- `clientapi/` — outbound HTTP clients (e.g. OpenStreetMap geocoding).
- `security/` — Keycloak/OIDC config and SSL client config (truststore at `backend/src/main/resources/truststore.jks`; Elasticsearch CA certs must be imported there — see readme.md).
### Frontend layers (`frontend/src/app`)
- `api/` — `boxit.api.ts` + DTOs + HTTP error interceptor (mirror of the backend REST API).
- `business/` — services/models per feature (item, attachment, location, user), notification and global error handling.
- `gui/` — components (header, page, search, filters, item, location, responsive helpers).
- `auth/` — `auth.service.ts` (Keycloak-based login).
### Testing
Backend integration tests (`BoxitIntegrationTest`) use Testcontainers. For container reuse, add `testcontainers.reuse.enabled=true` to `~/.testcontainers.properties`.
### CI
Gitea Actions (`.gitea/workflows/`) builds on push to `main`: Gradle build with a Postgres service container, then builds/pushes Docker images (`*-backend`, `*-frontend`) to `gitea.tail276105.ts.net` and triggers a prod compose redeploy. `bitbucket-pipelines.yml` and `.space.kts` are legacy.
+23
View File
@@ -0,0 +1,23 @@
# Template for deploy/prod/.env
# Copy to .env and fill in the values: cp .env-template .env
# The .env file itself is git-ignored (it holds secrets).
# --- Database / Keycloak passwords ---
REMSLY_PSQL_PASSWORD=
KC_PSQL_PASSWORD=
KC_ADMIN_PASSWORD=
# --- Tailscale ---
# Reusable, tag:server pre-authorized auth key from the Tailscale admin console.
TS_AUTHKEY=
# --- Apache OIDC (mod_auth_openidc) ---
OIDC_CRYPTO_PASSPHRASE=
OIDC_CLIENT_SECRET=
# --- Elasticsearch ---
ELASTIC_PASSWORD=
# --- Backend ---
# Keycloak client id (resource) the bearer-only backend expects.
KEYCLOAK_RESOURCE=
@@ -0,0 +1,37 @@
# Production config for the backend, mounted into the container and activated via
# SPRING_CONFIG_ADDITIONAL_LOCATION (see docker-compose.yml). Secrets are injected
# from the environment (.env) through ${...} placeholders, so this file is safe to commit.
spring.profiles.active=PROD
server.port=5050
# Postgres runs as service "postgres" on remslyNet; db/user "remsly" (see POSTGRES_MULTIPLE_DATABASES).
spring.datasource.url=jdbc:postgresql://postgres:5432/remsly
spring.datasource.username=remsly
spring.datasource.password=${REMSLY_PSQL_PASSWORD}
# Elasticsearch runs as service "elasticsearch" on remslyNet, plain HTTP on 9200.
boxit.elasticsearch.host=elasticsearch
boxit.elasticsearch.port=9200
boxit.elasticsearch.user=elastic
boxit.elasticsearch.pass=${ELASTIC_PASSWORD}
spring.jackson.date-format="yyyy-MM-dd HH:mm:ss"
# SSLClientConfig overrides the JVM trust store unconditionally, so point it at the JDK
# default CA bundle. That keeps outbound HTTPS (e.g. fetching Keycloak's JWKS over the
# Let's Encrypt / tailscale cert) working without shipping a custom truststore.
boxit.server.ssl.trust-store=/opt/java/openjdk/lib/security/cacerts
boxit.server.ssl.trust-store-password=changeit
management.endpoint.metrics.enabled=true
management.endpoints.web.exposure.include=*
management.endpoint.prometheus.enabled=true
management.metrics.export.prometheus.enabled=true
logging.level.root=INFO
keycloak.realm=remsly
keycloak.bearer-only=true
keycloak.auth-server-url=https://remsly-id.tail276105.ts.net
keycloak.resource=${KEYCLOAK_RESOURCE}
-1
View File
@@ -1 +0,0 @@
*.pem
@@ -1 +0,0 @@
{"creation_dt": "2023-03-24T14:43:02Z", "creation_host": "20e2283fb076"}
@@ -1 +0,0 @@
{"n": "u6an4MsyALnC0Ik2b8tcVpikTwzWgtASLGEUg7wEs7_piMKI7wNwroRbCesHujhJu2Ha_hDMrJmCEvT0T2fO4JzoAt1bLE3zWM4sXnWEpTXCrWxayjXbGTblrJcTIQPb7cbiTvyoAQvcThApZcvQEBkKZrlOp3aB62thM2zSI6XFVcbmF3CRCVJdIYN2K3ioQjkkxiAFwQ0EUNWtHzaDvjwFBvwAD2iDDqZh0mHYWWnhwV0okuJ3FBX8fVUr2oskq5cHhc8t8r8l1Iob9lEcgwkRJE93hQ1CMxYT4RX_rbJtyIFbixVkxM99x16nlyJnAWlPzEZrTz_fdcdFeQHPrw", "e": "AQAB", "d": "F7GUUvL1LlhGO_SHVhhUYW7GmGCjmULSeJ4NbBDKezPJNgZyKdPCX5bAtADkKeQ_kxRpBVlzrnt3jBM34erWmU_Wj74JbA-voU_vFEI8bGvcV-9ti0i_kHB5EZDFzo5hL6XEH3sRb4hi2cx6narda5rt0EinoMd64hHd5RNheTSWsVcSbBwzgDqKNB-OTr2vGzK3O3ZmY-I6IE5nVTf1tLkkI3GlPwKOSix1nDFtwHvKV0QTazsBop9MkMDwrBNVYJB_0G6AEtC8RXn9z4FIBZc9BAk4G8bygATwixu3WfUuM5LI3qavwILmQVFzmBfojHHWYf-eQBXf-kwR4KeX2Q", "p": "7MgYLilx05H3tvpy0M5ty2R6i3cE9vzl-eiNPM_rBF-eu-cAjkCtLViKg6VTdr0I8qV4B-NCshPpnF50cuFupF6jTgWZJeAfiOoEWQC6mqNxngX4N98mxDm7lf_3QqRsdhh2cS_5IOJ4cpaJ5WlX89WZvpfAKbCISQ_nejSypQM", "q": "yuG2pUTwPi4QazqX6w3oMAb9C-R6kDoYMNhRm2qCkSMQ9jJlaW80sIypYNzeD2JbF6pAjhF2mN2ehorMwVs97flNrSjGTdMts63JMcI6-w5MOWHmI938knxrBn5ku88QsJpjMdY4f25Oynb5LG3YSiNqQVztc_aC3qElrJ4CvOU", "dp": "PbwBye9WVSImotdzngtkRvZgmQIkEj1nvJusT2GqVc6UdUtKMsycGkmOe7pcI4Yv0gvgWrYfCoz0iKd71wP9s-9Uxp_hCSYBMtJI5eexv2XD1r6Zs_-DfP_Mb9R35DE4ciX4o5QtcSx62O108JJhsxpK5o1sNbJoJhx2br9UoH8", "dq": "TBt6UetXmGvdWuxjGxlfdczZVoHCfKJeesxbaMr-6gVWkpT7OB8wi95ol96jKvBBrdH_X4wrilLw6hXdk9dvlvtD6WZ3ZbCH33SdGh5707xcWdZg6CmZWzZzS79UeKPH5gg0l_h9v0gZJJiXBtS6I62_aX-70Bm0pGGdaXDpJAU", "qi": "L4HAI6WvpBjiwvGGH9IAoxsxqlnCton8uUUf2IQwpZMKe-d9LXPsOdWsyaZneCnc2C_AQsgLkpZIWd47koybwch4vTkbmLrNwtaaazVk53Axdg3ksFT-uWad4YTiqCH-hIt_2xXxvensMqHwRvIk44MHk0kq485nPfGx4E9zvDE", "kty": "RSA"}
@@ -1 +0,0 @@
{"body": {}, "uri": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/94925864"}
@@ -1 +0,0 @@
{"creation_dt": "2023-03-24T14:44:14Z", "creation_host": "ed2dd2431723"}
@@ -1 +0,0 @@
{"n": "5JoKajoOVE7y5Bcchd4KPlVarooN-XLHtEbFRy0zrFG4mAY_Qf-H1eZkmHvPQwv-tDEPFRlabrxXqC3uWoy9uOmqXea7rbnOqQHYRSAhIiW8I0JbuzGHga-ZwujShQDj85eimjBXRKhjD6r3zRElchTWXyeT0_dRrOZ099zAE0qNCnVRxel6IXdFEKz9YrBvIqvEzJC2DBWwF0W6ZBlpiM8RXq1guBU22k0XkoDHE8d_LL6AMrhtXSCPwiL9YOxiyvSTgLHciy4c-WP1-mypy65oK5YLCiVagX4kM8qbxW_0nmFtdYCdhhEBrHktxofHUaps4cPGK_XDkbz5mti9vQ", "e": "AQAB", "d": "D53KSllu8yl1hElj5G03q-xuxm-d5sWFiPYwsd0Po5ztjE0Y8yw-nkkf5B7JGN5v5uVXD2zs9ZWeM1n3522n5ELqJYIGniaMTJVYrdg1Cz0irhHxbEdE54IUcfEvuHBIIjLgo8HbpFAQfFiElxitwy5TOqrceWVyyenwYaJN6MNU6h5_TReay7Smye1NQYnv3E63jDX3Od_rQyrLBV0UE3Y7BOPLGmqHTeeEaDo8zut0HgXIS0ecRrfTt8RkDpv_Xc9AAVzZaPXLNo4Kxo7YWCpoi32oLwUhsBELS6G3i2rQxPTveMnkeU0-1qo6hfoGZFMKpAivl4QX86_D_41tzQ", "p": "_dmRjJn2dVbPNOgvXeencRykjCHk5aRDYDCJ6BKYEsXpOdOZCGH2HlX-43Loh1w5qHcCXlSCX5T1GsOv-EAye2T9Eg-xfpkPHUJ9JlKRk1Oiy6FjFfBr_ObUzK_rC-WyHzcIaU50hyzwoKDhICRdbQvfJnOp02tE1Hlxm15qO28", "q": "5om5xnQAv6RRzkCdsO4Sk2yFXwoTvpL8KS9LG1RqcPoAvy1ey5oEPJLIUyFUmf8-hr5RW2OBgEwGCR3HQgaXXiyxeCSdoM0roEJaEcerk2i8JW_w_S52evvigJb4mOajzjY--WsSa12LpJTTKvSlf-9eWNaNeb7vv9GT1atWs5M", "dp": "lL_gFTbG_909kDAaSXmMWbDCD0dxPMuzm3L17KKwsBd-4h7HBu14bjBQKvHTpjX5p2kNXz9AIkhEFEnlHKBptHrp_KJrjNadz7R9Z25s1Zrlw1Mvn9SBQvH0cnkHT7JqXPY_nI2XpPpFiqqbIN2OTW7udTbDphque58qXjPGJks", "dq": "hP4o1Mpw8B1sL-7oyXvd7XwqSJR4pXUH0P5-doqxSPGAYslaqVv1W7hVpldRBfHQcUzzhCQYPhodVT7_hyBkTCi-TI_a-zvBEheU_DLS0RwXhgVY4cXfBb2vgf0V9qb_EmQK2johixyRBWXxmBNAMli1dcx4GybCCkdt4FKSls8", "qi": "Y_CRKqA-X8fnYAU9WIBVB2mH08m5y_3UhcmzJpcDAeaPygrh4hItYKdBSk9R9IBRiME84nIDW2Xor9c_GPpXcEItsHAHYHiF12hZtZHcEXyp9fFsJzprTWhXr1JRohlsPzCK6pzcB1Hk2ZwUvc0G4wkcaeOfo9LTVQj5eWRlnWQ", "kty": "RSA"}
@@ -1 +0,0 @@
{"body": {}, "uri": "https://acme-v02.api.letsencrypt.org/acme/acct/1025448747"}
-14
View File
@@ -1,14 +0,0 @@
This directory contains your keys and certificates.
`[cert name]/privkey.pem` : the private key for your certificate.
`[cert name]/fullchain.pem`: the certificate file used in most server software.
`[cert name]/chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
`[cert name]/cert.pem` : will break many server configurations, and should not be used
without reading further documentation (see link below).
WARNING: DO NOT MOVE OR RENAME THESE FILES!
Certbot expects these files to remain in this location in order
to function properly!
We recommend not moving these files. For more information, see the Certbot
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
@@ -1,14 +0,0 @@
This directory contains your keys and certificates.
`privkey.pem` : the private key for your certificate.
`fullchain.pem`: the certificate file used in most server software.
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
`cert.pem` : will break many server configurations, and should not be used
without reading further documentation (see link below).
WARNING: DO NOT MOVE OR RENAME THESE FILES!
Certbot expects these files to remain in this location in order
to function properly!
We recommend not moving these files. For more information, see the Certbot
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
@@ -1,13 +0,0 @@
# renew_before_expiry = 30 days
version = 0.40.1
archive_dir = /etc/letsencrypt/archive/app.boxi.ch
cert = /etc/letsencrypt/live/app.boxi.ch/cert.pem
privkey = /etc/letsencrypt/live/app.boxi.ch/privkey.pem
chain = /etc/letsencrypt/live/app.boxi.ch/chain.pem
fullchain = /etc/letsencrypt/live/app.boxi.ch/fullchain.pem
# Options used in the renewal process
[renewalparams]
account = ea5cf7614a600d23b821c0af24b17a1d
authenticator = standalone
server = https://acme-v02.api.letsencrypt.org/directory
@@ -1,132 +0,0 @@
<VirtualHost *:80>
DocumentRoot "/var/www/app"
ServerName app.boxi.ch
# ServerAlias www.${server_name}
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
LogLevel debug
<Directory />
AllowOverride none
Options -Indexes -Includes
Require all granted
<LimitExcept GET POST HEAD>
deny from all
</LimitExcept>
</Directory>
<Directory "/var/www/app">
AllowOverride None
Options -Indexes -Includes
Require all granted
</Directory>
<Location /status >
SetHandler server-status
</Location>
<Location / >
Redirect / https://app.boxi.ch/
</Location>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
DocumentRoot "/var/www/app"
ServerName app.boxi.ch
#ServerAlias www.${server_name}
LogLevel info
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Content-Security-Policy "default-src 'self' ; \
script-src 'self' 'unsafe-inline' 'unsafe-eval'; \
style-src 'self' use.fontawesome.com 'unsafe-inline'; \
font-src 'self' data: use.fontawesome.com; \
base-uri 'self'; \
form-action 'self'; \
frame-ancestors 'none'; \
img-src 'self' data:; \
upgrade-insecure-requests" "expr=%{CONTENT_TYPE} =~ m#text\/(html|javascript)|application\/pdf|xml#i"
Header always append X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy "same-origin"
# this feature is not yet used and must be extended if needed, currently only a sample config
Header always set Feature-Policy "payment 'none';"
<Directory />
AllowOverride none
Options -Indexes -Includes
Require all granted
<LimitExcept GET POST HEAD>
deny from all
</LimitExcept>
</Directory>
<Directory "/var/www/app">
AllowOverride None
Options -Indexes -Includes
Require all granted
</Directory>
<Location /status >
SetHandler server-status
</Location>
RequestHeader set X-Forwarded-User expr=%{REMOTE_USER}
RequestHeader set Authorization "Bearer %{oidc_access_token}e"
Header set User-Details "%{REMOTE_USER}e %{oidc_claim_email}e"
OIDCProviderMetadataURL ${KC_ADDR}/realms/${KC_REALM}/.well-known/openid-configuration
OIDCClientID ${OIDC_CLIENT_ID}
OIDCClientSecret ${OIDC_CLIENT_SECRET}
OIDCRefreshAccessTokenBeforeExpiry 10
OIDCRedirectURI ${OIDC_REDIRECT_URI}
OIDCCryptoPassphrase ${OIDC_CRYPTO_PASSPHRASE}
OIDCInfoHook ${OIDC_INFO_HOOK}
OIDCDefaultLoggedOutURL ${KC_ADDR}/realms/${KC_REALM}/protocol/openid-connect/logout?redirect_uri=https%3A%2F%2Fapp.boxi.ch
OIDCRemoteUserClaim preferred_username
OIDCStateMaxNumberOfCookies 3 true
<Location /status >
SetHandler server-status
</Location>
<Location />
AuthType openid-connect
Require valid-user
</Location>
# If the browser is allowed to cache index.html, it starts loading assets/scripts/images before apache had a chance
# to check whether or not the user is even authenticated, leading to many failed requests.
# Disabling cache for index.html forces the browser to fetch it first, and therefore ensures that the full
# authentication mechanic is being run.
# See also https://github.com/zmartzone/mod_auth_openidc/issues/113
<filesMatch "index.html">
FileETag None
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "Mon, 21 Mar 1983 14:10:00 UTC"
</filesMatch>
#ProxyPass /ws ${FRONTEND_WS_URL}:${FRONTEND_PORT}/ws retry=0 timeout=86400
#ProxyPassReverse /ws ws://${FRONTEND_URL}:${FRONTEND_PORT}/ws
#ProxyPass /api/ ${BACKEND_URL}:${BACKEND_PORT}/api/ retry=0
#ProxyPassReverse /api/ ${BACKEND_URL}:${BACKEND_PORT}/api/
#ProxyPass / ${FRONTEND_URL}:${FRONTEND_PORT}/ retry=0
#ProxyPassReverse / ${FRONTEND_URL}:${FRONTEND_PORT}
SSLEngine On
# SSLUseStapling On
SSLProtocol -all +TLSv1.2
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder on
SSLCertificateFile /etc/letsencrypt/live/app.boxi.ch/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.boxi.ch/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/app.boxi.ch/fullchain.pem
</VirtualHost>
</IfModule>
@@ -1,79 +0,0 @@
<VirtualHost *:80>
DocumentRoot "/var/www/id"
ServerName id.boxi.ch
# ServerAlias www.${server_name}
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
LogLevel info
<Directory />
AllowOverride none
Options -Indexes -Includes
Require all granted
<LimitExcept GET POST HEAD>
deny from all
</LimitExcept>
</Directory>
<Directory "/var/www/id">
AllowOverride None
Options -Indexes -Includes
Require all granted
</Directory>
<Location /status >
SetHandler server-status
</Location>
<Location / >
Redirect / https://id.boxi.ch/
</Location>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
DocumentRoot "/var/www/id"
ServerName id.boxi.ch
#ServerAlias www.${server_name}
LogLevel info
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Content-Security-Policy "default-src 'self' ; \
style-src 'self' 'unsafe-inline'; \
script-src 'self' 'unsafe-inline'; \
img-src 'self'; \
connect-src 'self'; \
form-action 'self'; \
frame-src http:; \
frame-ancestors 'self'; \
object-src 'none'; \
upgrade-insecure-requests;"
#ProxyRequests Off
#ProxyPreserveHost On
#ProxyAddHeaders On
#SSLProxyEngine On
#SSLProxyVerify none
#SSLProxyCheckPeerName off
#SSLProxyCheckPeerCN off
#SSLProxyCheckPeerName off
#SSLProxyCheckPeerExpire off
#RequestHeader set X-Forwarded-Proto "https"
#RequestHeader set X-Forwarded-Port "443"
#RequestHeader set X-Forwarded-Host "id.boxi.ch"
#ProxyPass / http://keycloak:8080/ retry=0
#ProxyPassReverse / http://keycloak:8080
<Location / >
Redirect / https://id.boxi.ch:8443/
</Location>
SSLEngine On
SSLProtocol -all +TLSv1.2
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder on
SSLCertificateFile /etc/letsencrypt/live/app.boxi.ch/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.boxi.ch/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/app.boxi.ch/fullchain.pem
</VirtualHost>
</IfModule>
-23
View File
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boxi the APP</title>
<style>
body {
margin: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
height: 100vh;
align-items: center;
}
</style>
</head>
<body>
<h1>This is the webserver for app.boxi.ch</h1>
</body>
</html>
-23
View File
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boxi the ID</title>
<style>
body {
margin: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
height: 100vh;
align-items: center;
}
</style>
</head>
<body>
<h1>This is the webserver for id.boxi.ch</h1>
</body>
</html>
+107 -60
View File
@@ -1,98 +1,145 @@
version: '3'
volumes:
postgres:
driver: local
elasticsearch:
driver: local
tailscale-id:
driver: local
tailscale-apache:
driver: local
networks:
remslyNet:
name: remslyNet
external: false
services:
postgres:
image: postgres:14-alpine
restart: always
networks:
boxitNet:
ipv4_address: 10.5.1.1
- remslyNet
environment:
POSTGRES_USER: 'boxitadmin'
POSTGRES_PASSWORD: 'kQsQKCQoa.yf.XiGCh.3'
POSTGRES_USER: 'remsly'
POSTGRES_PASSWORD: ${REMSLY_PSQL_PASSWORD}
PGDATA: '/var/lib/postgresql/data/pgdata'
POSTGRES_MULTIPLE_DATABASES: 'boxit:q_VpgQaeXcjns9PqPmGj:boxit,keycloak:roraJK_ds3KDiHkMRLqf:keycloak'
POSTGRES_MULTIPLE_DATABASES: 'remsly:${REMSLY_PSQL_PASSWORD}:remsly,keycloak:${KC_PSQL_PASSWORD}:keycloak'
volumes:
- ./data/postgres:/var/lib/postgresql/data/pgdata
- ./config/postgres/create-multiple-postgresql-databases.sh:/docker-entrypoint-initdb.d/create-multiple-postgresql-databases.sh
- postgres:/var/lib/postgresql/data/pgdata
- ./postgres/create-multiple-postgresql-databases.sh:/docker-entrypoint-initdb.d/create-multiple-postgresql-databases.sh
keycloak:
image: gitea.tail276105.ts.net/remsly/keycloak:4
networks:
boxitNet:
ipv4_address: 10.5.0.20
restart: always
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: 'admin'
KC_BOOTSTRAP_ADMIN_PASSWORD: 'dzVTry4QhwKBnmM8X2PWZRZA'
KC_HTTPS_CERTIFICATE_FILE: '/opt/keycloak/certs/fullchain.pem'
KC_HTTPS_CERTIFICATE_KEY_FILE: '/opt/keycloak/certs/privkey.pem'
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
KC_HTTP_ENABLED: 'true'
KC_DB: postgres
KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak'
KC_DB_USERNAME: 'keycloak'
KC_DB_PASSWORD: 'roraJK_ds3KDiHkMRLqf'
KC_HOSTNAME: 'id.boxi.ch'
# KC_HOSTNAME_STRICT: 'false'
# KC_PROXY_HEADERS: xforwarded
# KC_HTTP_ENABLED: 'true'
# KC_PROXY: edge
KC_DB_PASSWORD: ${KC_PSQL_PASSWORD}
# TLS is terminated by the tailscale-id sidecar; Keycloak runs plain HTTP (8080) behind it
KC_HOSTNAME: 'https://remsly-id.tail276105.ts.net'
KC_HOSTNAME_URL: 'https://remsly-id.tail276105.ts.net'
KC_HOSTNAME_ADMIN_URL: 'https://remsly-id.tail276105.ts.net'
command:
- start
- '--optimized'
# - '--hostname-debug=true' # https://id.boxi.ch:8443/realms/master/hostname-debug
# - '--hostname-debug=true' #https://remsly-id.tail276105.ts.net/realms/master/hostname-debug
volumes:
- ./config/keycloak/theme:/opt/keycloak/themes/boxit
- ./config/keycloak/theme.v2:/opt/keycloak/themes/boxit.v2
- ./certbot/etc/live/app.boxi.ch/fullchain.pem:/opt/keycloak/certs/fullchain.pem:ro
- ./certbot/etc/live/app.boxi.ch/privkey.pem:/opt/keycloak/certs/privkey.pem:ro
ports:
- '8443:8443'
- ./keycloak/theme:/opt/keycloak/themes/boxit
- ./keycloak/theme.v2:/opt/keycloak/themes/boxit.v2
network_mode: service:tailscale-id
depends_on:
- postgres
- tailscale-id
apache-oidc:
image: gitea.tail276105.ts.net/remsly/apacheoidc:3
restart: always
networks:
boxitNet:
ipv4_address: 10.5.0.30
environment:
OIDC_CRYPTO_PASSPHRASE: '39938b0a-1bd2-4352-937d-3cf1e2023727'
KC_ADDR: 'https://id.boxi.ch:8443'
KC_REALM: 'boxit'
OIDC_CRYPTO_PASSPHRASE: ${OIDC_CRYPTO_PASSPHRASE}
KC_ADDR: 'https://remsly-id.tail276105.ts.net'
KC_REALM: 'remsly'
OIDC_CLIENT_ID: 'apacheOIDC'
OIDC_CLIENT_SECRET: 'Ymye3T55SnH1YfssvZZi6tRIIZ0PJTsK'
OIDC_REDIRECT_URI: 'https://app.boxi.ch/openidc'
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
OIDC_REDIRECT_URI: 'https://remsly.tail276105.ts.net/openidc'
APACHE_DEBUG_LEVEL: 'warn'
BACKEND_URL: 'backend'
BACKEND_PORT: '5050'
volumes:
- ./config/frontend/apache2.conf:/etc/apache2/apache2.conf
- ./certbot/etc:/etc/letsencrypt
- ./data/www:/var/www
- ./config/frontend/app.boxi.ch.conf:/etc/apache2/sites-enabled/app.boxi.ch.conf
- ./config/frontend/id.boxi.ch.conf:/etc/apache2/sites-enabled/id.boxi.ch.conf
ports:
- '80:80'
- '443:443'
- ./frontend/apache2.conf:/etc/apache2/apache2.conf
- ./frontend/app.boxi.ch.conf:/etc/apache2/sites-enabled/app.boxi.ch.conf
network_mode: service:tailscale-apache
depends_on:
- keycloak
- tailscale-apache
#backend:
#image: boxi.registry.jetbrains.space/p/boxit/containers/boxit-backend:2023.1.36
# restart: always
# networks:
# boxitNet:
# ipv4_address: 10.5.0.40
# depends_on:
# - postgres
# - keycloak
backend:
image: gitea.tail276105.ts.net/remsly/remsly-backend:3
restart: always
networks:
- remslyNet
environment:
# The CI-built image ships no application.properties, so mount one and activate it.
SPRING_CONFIG_ADDITIONAL_LOCATION: 'file:/config/application.properties'
# Secrets referenced as ${...} placeholders inside the mounted properties file.
REMSLY_PSQL_PASSWORD: ${REMSLY_PSQL_PASSWORD}
ELASTIC_PASSWORD: ${ELASTIC_PASSWORD}
KEYCLOAK_RESOURCE: ${KEYCLOAK_RESOURCE}
volumes:
- ./backend/application.properties:/config/application.properties:ro
depends_on:
- postgres
- elasticsearch
- tailscale-id
networks:
boxitNet:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.5.0.0/16
elasticsearch:
image: gitea.tail276105.ts.net/remsly/elasticsearch:3
restart: always
volumes:
- elasticsearch:/usr/share/elasticsearch/data
networks:
- remslyNet
environment:
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- discovery.type=single-node
- xpack.license.self_generated.type=basic
tailscale-id:
image: tailscale/tailscale:latest
hostname: remsly-id
environment:
TS_AUTHKEY: ${TS_AUTHKEY}
TS_USERSPACE: "true"
TS_STATE_DIR: /var/lib/tailscale
TS_SERVE_CONFIG: /config/serve.json
TS_EXTRA_ARGS: --advertise-tags=tag:server
volumes:
- tailscale-id:/var/lib/tailscale
- ./tailscale/serve-id.json:/config/serve.json
networks:
remslyNet:
# Let the backend reach Keycloak via its external URL over the internal network:
# the FQDN resolves to this sidecar, hits tailscale serve on :443 (valid LE cert)
# and is proxied to Keycloak. Keeps the token issuer == auth-server-url.
aliases:
- remsly-id.tail276105.ts.net
restart: unless-stopped
tailscale-apache:
image: tailscale/tailscale:latest
hostname: remsly
environment:
TS_AUTHKEY: ${TS_AUTHKEY}
TS_USERSPACE: "true"
TS_STATE_DIR: /var/lib/tailscale
TS_SERVE_CONFIG: /config/serve.json
TS_EXTRA_ARGS: --advertise-tags=tag:server
volumes:
- tailscale-apache:/var/lib/tailscale
- ./tailscale/serve-apache.json:/config/serve.json
networks:
- remslyNet
restart: unless-stopped
File renamed without changes.
+90
View File
@@ -0,0 +1,90 @@
<VirtualHost *:80>
DocumentRoot "/var/www/app"
ServerName remsly.tail276105.ts.net
# ServerAlias www.${server_name}
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
LogLevel info
# TLS is terminated by the tailscale sidecar; make mod_auth_openidc and the
# app aware that the external scheme is https.
RequestHeader set X-Forwarded-Proto "https"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Content-Security-Policy "default-src 'self' ; \
script-src 'self' 'unsafe-inline' 'unsafe-eval'; \
style-src 'self' use.fontawesome.com 'unsafe-inline'; \
font-src 'self' data: use.fontawesome.com; \
base-uri 'self'; \
form-action 'self'; \
frame-ancestors 'none'; \
img-src 'self' data:; \
upgrade-insecure-requests" "expr=%{CONTENT_TYPE} =~ m#text\/(html|javascript)|application\/pdf|xml#i"
Header always append X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy "same-origin"
# this feature is not yet used and must be extended if needed, currently only a sample config
Header always set Feature-Policy "payment 'none';"
<Directory />
AllowOverride none
Options -Indexes -Includes
Require all granted
<LimitExcept GET POST HEAD>
deny from all
</LimitExcept>
</Directory>
<Directory "/var/www/app">
AllowOverride None
Options -Indexes -Includes
Require all granted
</Directory>
RequestHeader set X-Forwarded-User expr=%{REMOTE_USER}
RequestHeader set Authorization "Bearer %{oidc_access_token}e"
Header set User-Details "%{REMOTE_USER}e %{oidc_claim_email}e"
OIDCProviderMetadataURL ${KC_ADDR}/realms/${KC_REALM}/.well-known/openid-configuration
OIDCClientID ${OIDC_CLIENT_ID}
OIDCClientSecret ${OIDC_CLIENT_SECRET}
OIDCRefreshAccessTokenBeforeExpiry 10
OIDCRedirectURI ${OIDC_REDIRECT_URI}
OIDCCryptoPassphrase ${OIDC_CRYPTO_PASSPHRASE}
OIDCInfoHook ${OIDC_INFO_HOOK}
OIDCDefaultLoggedOutURL ${KC_ADDR}/realms/${KC_REALM}/protocol/openid-connect/logout?redirect_uri=https%3A%2F%2Fremsly.tail276105.ts.net
OIDCRemoteUserClaim preferred_username
OIDCStateMaxNumberOfCookies 3 true
<Location /status >
SetHandler server-status
</Location>
<Location />
AuthType openid-connect
Require valid-user
</Location>
# If the browser is allowed to cache index.html, it starts loading assets/scripts/images before apache had a chance
# to check whether or not the user is even authenticated, leading to many failed requests.
# Disabling cache for index.html forces the browser to fetch it first, and therefore ensures that the full
# authentication mechanic is being run.
# See also https://github.com/zmartzone/mod_auth_openidc/issues/113
<filesMatch "index.html">
FileETag None
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "Mon, 21 Mar 1983 14:10:00 UTC"
</filesMatch>
#ProxyPass /ws ${FRONTEND_WS_URL}:${FRONTEND_PORT}/ws retry=0 timeout=86400
#ProxyPassReverse /ws ws://${FRONTEND_URL}:${FRONTEND_PORT}/ws
#ProxyPass /api/ ${BACKEND_URL}:${BACKEND_PORT}/api/ retry=0
#ProxyPassReverse /api/ ${BACKEND_URL}:${BACKEND_PORT}/api/
#ProxyPass / ${FRONTEND_URL}:${FRONTEND_PORT}/ retry=0
#ProxyPassReverse / ${FRONTEND_URL}:${FRONTEND_PORT}
</VirtualHost>
File renamed without changes.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Loaded 100 of 176 files, more files were not shown because too many files have changed in this diff. Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.