From b220593def9f40d0693e3b7e1ac2b9cbe2173f19 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Mon, 17 Aug 2026 14:58:35 +0200 Subject: [PATCH] feat(stacks): migrate rss --- stacks/rss.opytex.org/README.md | 52 +++++++++++++++++++++++ stacks/rss.opytex.org/docker-compose.yml | 53 ++++++++++++++++++++++++ stacks/rss.opytex.org/env_file | 26 ++++++++++++ stacks/rss.opytex.org/secrets.env.yaml | 26 ++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 stacks/rss.opytex.org/README.md create mode 100644 stacks/rss.opytex.org/docker-compose.yml create mode 100644 stacks/rss.opytex.org/env_file create mode 100644 stacks/rss.opytex.org/secrets.env.yaml diff --git a/stacks/rss.opytex.org/README.md b/stacks/rss.opytex.org/README.md new file mode 100644 index 0000000..f2ec426 --- /dev/null +++ b/stacks/rss.opytex.org/README.md @@ -0,0 +1,52 @@ +# FreshRSS — rss.opytex.org + +Agrégateur RSS, PostgreSQL dédié, authentification SSO via Authentik. + +## Déploiement + +Depuis ce dossier, sur chioggia : + +```bash +sops exec-env secrets.env.yaml 'docker compose up -d' +``` + +Le nom du dossier (`rss.opytex.org`) donne le nom de projet Compose +`rssopytexorg`, dont dérivent les noms de volumes +(`rssopytexorg_freshrss_data`, …) et le nom du dump de base +(`rssopytexorg.sql.gz`). **Renommer ce dossier ferait repartir la stack sur +des volumes vides.** + +Après une rotation de secret, `docker compose up -d` répond « up to date » et +garde l'ancienne valeur : + +```bash +sops exec-env secrets.env.yaml 'docker compose up -d --force-recreate' +``` + +## Volumes + +| Volume | Contenu | Sauvegarde | +| ---------------------------------- | -------------------------------- | -------------------------------------------------- | +| `rssopytexorg_pgsql_data` | base PostgreSQL | dump SQL (`backup.type=postgres` sur le conteneur) | +| `rssopytexorg_freshrss_data` | config, comptes, articles locaux | label `backup.enable=true` sur le volume | +| `rssopytexorg_freshrss_extensions` | extensions installées | label `backup.enable=true` sur le volume | + +Les deux volumes FreshRSS portent `backup.enable=true` **depuis leur +création** : le label est donc conservé dans le compose. Le retirer, comme +l'ajouter à un volume qui ne l'a pas, ferait diverger la déclaration du volume +réel — Compose proposerait alors de le recréer, donc de vider FreshRSS. + +Ils figurent en plus dans `MANUAL_VOLUMES` (`backup.conf`), ce qui les fait +sauvegarder deux fois : le script empile les chemins de `MANUAL_VOLUMES` puis +ceux des volumes labellisés, sans dédoublonner. Redondance héritée de l'ancien +dépôt, sans conséquence (restic déduplique) — à nettoyer quand `backup.conf` +sera migré dans `ops/backup/`. + +`pgsql_data` n'a délibérément aucun label : sauvegarder les fichiers d'un +PostgreSQL en cours d'écriture ne donne pas une base restaurable, c'est le dump +SQL qui le couvre. + +Références : + +- https://freshrss.github.io/FreshRSS/en/admins/16_OpenID-Connect-Authentik.html +- https://docs.goauthentik.io/integrations/services/freshrss/ diff --git a/stacks/rss.opytex.org/docker-compose.yml b/stacks/rss.opytex.org/docker-compose.yml new file mode 100644 index 0000000..9ea8540 --- /dev/null +++ b/stacks/rss.opytex.org/docker-compose.yml @@ -0,0 +1,53 @@ +services: + postgresql: + image: postgres:16.2 + container_name: freshrss-db + restart: unless-stopped + env_file: + - env_file + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?} + volumes: + - pgsql_data:/var/lib/postgresql/data + networks: + - default + labels: + - backup.enable=true + - backup.type=postgres + + freshrss: + image: freshrss/freshrss:1.28.1 + restart: unless-stopped + depends_on: + - postgresql + env_file: + - env_file + environment: + OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:?} + volumes: + - freshrss_data:/var/www/FreshRSS/data + - freshrss_extensions:/var/www/FreshRSS/extensions + networks: + - default + - traefik-proxy + labels: + - traefik.enable=true + - traefik.docker.network=traefik-proxy + - traefik.http.routers.freshrss.entrypoints=web-secure + - traefik.http.routers.freshrss.rule=Host(`rss.opytex.org`) + - traefik.http.routers.freshrss.tls.certresolver=letsencrypt + - traefik.http.routers.freshrss.middlewares=crowdsec@file + - traefik.http.services.freshrss.loadbalancer.server.port=80 + +volumes: + pgsql_data: + freshrss_data: + labels: + backup.enable: true + freshrss_extensions: + labels: + backup.enable: true + +networks: + traefik-proxy: + external: true diff --git a/stacks/rss.opytex.org/env_file b/stacks/rss.opytex.org/env_file new file mode 100644 index 0000000..1d028ac --- /dev/null +++ b/stacks/rss.opytex.org/env_file @@ -0,0 +1,26 @@ +# ============================================================================= +# FRESHRSS — configuration NON sensible +# ============================================================================= +# POSTGRES_PASSWORD et OIDC_CLIENT_SECRET sont dans secrets.env.yaml et +# injectés par `sops exec-env`. +# +# Ce fichier ne contient QUE ce que les conteneurs consomment. Le domaine et +# la version d'image sont en dur dans docker-compose.yml : `env_file:` +# n'alimente pas l'interpolation ${...}, et une version en dur est détectée +# par Renovate. +# ============================================================================= + +# --- Base de données --- +POSTGRES_USER=freshrss +POSTGRES_DB=freshrss + +# --- Conteneur FreshRSS --- +CRON_MIN=*/20 +TZ=Europe/Paris + +# --- SSO OpenID Connect (Authentik) --- +OIDC_ENABLED=1 +OIDC_PROVIDER_METADATA_URL=https://sso.opytex.org/application/o/freshrss/.well-known/openid-configuration +OIDC_CLIENT_ID=3YF3MIapyVz45EQ8BcFGwk2q47BTstmPMhRwwPlZ +OIDC_X_FORWARDED_HEADERS="X-Forwarded-Port X-Forwarded-Proto X-Forwarded-Host" +OIDC_SCOPES="openid email profile" diff --git a/stacks/rss.opytex.org/secrets.env.yaml b/stacks/rss.opytex.org/secrets.env.yaml new file mode 100644 index 0000000..94c03ec --- /dev/null +++ b/stacks/rss.opytex.org/secrets.env.yaml @@ -0,0 +1,26 @@ +POSTGRES_PASSWORD: ENC[AES256_GCM,data:2QAKvkuKOxME53FF3YGugKZSxVjfgF4NEXRMFNmSP6Z9ICkUHIQvFlxcFCKIJ1L8,iv:shO6GH4mlqPpEXO9Q/0LgUb7l/zDOghDbTxMzlkJBjg=,tag:vlBr1hE4GimoobZ8+ejgrg==,type:str] +OIDC_CLIENT_SECRET: ENC[AES256_GCM,data:GqKQQHL7X8CMd+vflksqGLjg0vIsceQMP5p5sCAJaYjKMiiaFoSRefbOVseTmo3zGdAfLz6tFNpWi6Q7BQCg6+i0fH7B5shZf/s/GxnerbJ1qC9UVNQ49dea88f/sC85ByGxgZ/9HGTQ/li2DDMkTnt1iNF46o5WolRdhmXgV0k=,iv:Taxpju1C2eGc4FGyLDONo1E+n2EkBWrHfdxi1ca0neg=,tag:C3TNHS0eIlcTwwNMKcDU9w==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNUS9xaUMxV2EreEVwdmFV + c1kzbkNOOCt2WisvQjhvU0wxb0hSUkRkMUVRCnFxUmNXNUhoemRKWjlpN2E0Z0kz + cWorU3ZINm5ISEhMWWRsVVFJRUFqZ1EKLS0tIGVMZUloa2RxK0daWHVFNW1CSzZZ + NVhNZUxjcmZTYTk5UjNZUmFrRnlXeW8KQVe3zJMofheTzg6vDtKSEYefRFz9ND9v + CPTWmjv0tGAsWX6sdnKeBniw29A9QRKlAKubvES7hl4cf2vEtruYzQ== + -----END AGE ENCRYPTED FILE----- + recipient: age18m2zl3pwgw97djshzcf4wmhrqxksl4erqse6edkpxpkg5rcz0ujs8u4c7s + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsZEVuejgwMXhCV1BCaHlp + RG5TZklLa0xQYWVTTU40VVhOSEVsb2xHZXlVClJiYXhtZ2ZCRklRTGRGSVBtaUFP + VXFjME9WZHhyRXJDazR4VnV2R0VXaWMKLS0tIE9LcmZjMFFhRWJqbU52S1BHSldx + Wk8wcis2bzgwSWpFVTUwOC9mT3UzVjQK4X6/iYoec/k1r/yLIQoT0XhfYryJ75KN + X7aDRlHniIPPPUg9tdIjGIUKpRBidOBw7s1PYImxOMTqjs0CUoIRzw== + -----END AGE ENCRYPTED FILE----- + recipient: age1fh70nny5hzz8a8g9077kgfca0lhlh3wsjf4fjrv4klx5ucdtpdmsazuqdj + lastmodified: "2026-08-17T04:27:36Z" + mac: ENC[AES256_GCM,data:mvGIwaflMEAT+QuVCIiU3PHRJf7gC8rIA0093WXSYV92HdK8eRX6Y624DTMxThL6BKhaEWojsARhd2ldDsz/iQdbSJEmfcOTIw8SCatByRucK9wen6L7AZqsAB5lZVXTQQp4PfwxbrVj/pYG5K8twYSrt7JJiHWZnZ//gJZPTAM=,iv:eK765KsM7eNgWy3Y3OGqol+99rbop/rYYs+w3Rn4Brc=,tag:2P7QQdxjj/5kCL1THSnUDg==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.3