diff --git a/2026/2026-08-17--decidim.md b/2026/2026-08-17--decidim.md new file mode 100644 index 0000000..688c4e3 --- /dev/null +++ b/2026/2026-08-17--decidim.md @@ -0,0 +1,33 @@ +# Decidim — decidim.hacktivism.ch (2026-08-17) + +Site: **https://decidim.hacktivism.ch/** +Port **9027** → Caddy. Stack: `koopa-decidim` + `koopa-decidim-sidekiq` + `koopa-decidim-db` + `koopa-decidim-redis`. Image `decidim/decidim:0.30.8`. + +Live: `~/koopa-decidim/` (compose mirror `configs/decidim/`). Secrets only on host (`.env`, `users.env`). + +Instance name **hacktivism**. Theme: exchange-dark (gold `#e8a838`, bg `#1a1410`) via `theme/hacktivism.css`. Analog: `configs/lemmy/`. + +Admin users are on the host (`~/koopa-decidim/users.env`), not in this repo. + +Caddy: `sudo /home/hernani/koopa-caddy/apply.sh` +Boot: `scripts/decidim/install-systemd.sh` (linger already yes on koopa). + +## Cookie / privacy + +Stock Decidim still emits `#dc-dialog-wrapper` (Accept all / essential / Settings). This instance has no third-party trackers; privacy is default. + +- CSS: `#dc-dialog-wrapper, .cookies__container { display: none !important; }` +- JS: `/hacktivism-consent.js` sets `decidim-consent={"essential":true}` if missing +- Live 2026-08-17: HTML 200, CSS/JS 200, hide rules in public `/hacktivism.css` + +## Reboot (checked 2026-08-17) + +| Item | Value | +|------|--------| +| linger `hernani` | yes | +| `container-koopa-decidim-db` | enabled + active | +| `container-koopa-decidim` | enabled + active | +| `container-koopa-lemmy-db` / `container-koopa-lemmy` | enabled + active | +| listen | `:9027` `:9026` | + +login-health: probes + containers + linger + these units enabled. Loopback HTTP on :9027 is **301** (app wants HTTPS; TLS is Caddy). Probe allows 301. After reboot: login-health only unless asked. diff --git a/SECRETS.md b/SECRETS.md index 7500471..5b48906 100644 --- a/SECRETS.md +++ b/SECRETS.md @@ -19,6 +19,7 @@ Live passwords/keys live in sibling **`koopa-admin-secrets`**. |------|--------|------| | Castopod | `koopa/home-hernani/koopa-castopod/{.env,users.env}` (+ compose mirror in admin-log `configs/castopod/`) | `~/koopa-castopod/` | | Lemmy | host-only `~/koopa-lemmy/{.env,lemmy.hjson}` (compose mirror in admin-log `configs/lemmy/`) | `~/koopa-lemmy/` | +| Decidim | host-only `~/koopa-decidim/{.env,users.env}` (compose mirror in admin-log `configs/decidim/`) | `~/koopa-decidim/` | | Bonfire (`FEDERATE=true`) | `koopa/home-hernani/koopa-bonfire/{.env,users.env,compose.yml}` | `~/koopa-bonfire/` | | Prime (Jellyfin + qBittorrent) | `koopa/home-hernani/koopa-prime/{.env,users.env}` | `~/koopa-prime/` | | Forgejo (**rootless**, git.hacktivism.ch) | `koopa/home-hernani/koopa-forgejo/{.env,users.env,compose.yml}` | `~/koopa-forgejo/` | diff --git a/configs/caddy/Caddyfile b/configs/caddy/Caddyfile index 58fb8a5..bacff1e 100644 --- a/configs/caddy/Caddyfile +++ b/configs/caddy/Caddyfile @@ -1,7 +1,7 @@ # Internal only (not in the browser URL): # 9010 merchant API | 9011 exchange API | 9012 bank API # 9013 bank landing | 9014 exchange landing | 9015 merchant landing -# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9200 forgejo-ssh +# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9200 forgejo-ssh # 9090 tops ng1 | 9091 tops ng2 | 9092 tops ng3 { @@ -39,28 +39,28 @@ taler.hacktivism.ch { # surface + aptdeploy HTML (taler.hacktivism.ch only) handle /taler-monitoring-surface_err { - redir * /taler-monitoring-surface_err/ 302 + redir /taler-monitoring-surface_err/ 302 } handle /taler-monitoring-surface_err* { root * /var/www/monitoring-sites/taler.hacktivism.ch file_server } handle /taler-monitoring-surface { - redir * /taler-monitoring-surface/ 302 + redir /taler-monitoring-surface/ 302 } handle /taler-monitoring-surface* { root * /var/www/monitoring-sites/taler.hacktivism.ch file_server } handle /taler-monitoring-aptdeploy_err { - redir * /taler-monitoring-aptdeploy_err/ 302 + redir /taler-monitoring-aptdeploy_err/ 302 } handle /taler-monitoring-aptdeploy_err* { root * /var/www/monitoring-sites/taler.hacktivism.ch file_server } handle /taler-monitoring-aptdeploy { - redir * /taler-monitoring-aptdeploy/ 302 + redir /taler-monitoring-aptdeploy/ 302 } handle /taler-monitoring-aptdeploy* { root * /var/www/monitoring-sites/taler.hacktivism.ch @@ -68,14 +68,14 @@ taler.hacktivism.ch { } handle /taler-monitoring-mattermost_err { - redir * /taler-monitoring-mattermost_err/ 302 + redir /taler-monitoring-mattermost_err/ 302 } handle /taler-monitoring-mattermost_err* { root * /var/www/monitoring-sites/taler.hacktivism.ch file_server } handle /taler-monitoring-mattermost { - redir * /taler-monitoring-mattermost/ 302 + redir /taler-monitoring-mattermost/ 302 } handle /taler-monitoring-mattermost* { root * /var/www/monitoring-sites/taler.hacktivism.ch @@ -83,14 +83,14 @@ taler.hacktivism.ch { } handle /taler-monitoring-mail_err { - redir * /taler-monitoring-mail_err/ 302 + redir /taler-monitoring-mail_err/ 302 } handle /taler-monitoring-mail_err* { root * /var/www/monitoring-sites/taler.hacktivism.ch file_server } handle /taler-monitoring-mail { - redir * /taler-monitoring-mail/ 302 + redir /taler-monitoring-mail/ 302 } handle /taler-monitoring-mail* { root * /var/www/monitoring-sites/taler.hacktivism.ch @@ -99,14 +99,14 @@ taler.hacktivism.ch { # taler-monitoring console HTML (host static — not in app containers) handle /monitoring_err { - redir * /monitoring_err/ 302 + redir /monitoring_err/ 302 } handle /monitoring_err* { root * /var/www/monitoring-sites/taler.hacktivism.ch file_server } handle /monitoring { - redir * /monitoring/ 302 + redir /monitoring/ 302 } handle /monitoring* { root * /var/www/monitoring-sites/taler.hacktivism.ch @@ -143,14 +143,14 @@ exchange.hacktivism.ch { # taler-monitoring console HTML (host static — not in app containers) handle /monitoring_err { - redir * /monitoring_err/ 302 + redir /monitoring_err/ 302 } handle /monitoring_err* { root * /var/www/monitoring-sites/exchange.hacktivism.ch file_server } handle /monitoring { - redir * /monitoring/ 302 + redir /monitoring/ 302 } handle /monitoring* { root * /var/www/monitoring-sites/exchange.hacktivism.ch @@ -177,14 +177,14 @@ bank.hacktivism.ch { # taler-monitoring console HTML (host static — not in app containers) handle /monitoring_err { - redir * /monitoring_err/ 302 + redir /monitoring_err/ 302 } handle /monitoring_err* { root * /var/www/monitoring-sites/bank.hacktivism.ch file_server } handle /monitoring { - redir * /monitoring/ 302 + redir /monitoring/ 302 } handle /monitoring* { root * /var/www/monitoring-sites/bank.hacktivism.ch @@ -315,7 +315,24 @@ lemmy.hacktivism.ch { } } -http://taler.hacktivism.ch, http://exchange.hacktivism.ch, http://bank.hacktivism.ch, http://castopod.hacktivism.ch, http://bonfire.hacktivism.ch, http://prime.hacktivism.ch, http://bt.hacktivism.ch, http://git.hacktivism.ch, http://paivana.hacktivism.ch, http://tops.ng1.hacktivism.ch, http://tops.ng2.hacktivism.ch, http://tops.ng3.hacktivism.ch, http://lemmy.hacktivism.ch { +# 9027 decidim (rails) +decidim.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9027 { + header_up Host {host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Proto {scheme} + header_up X-Forwarded-Port 443 + flush_interval -1 + transport http { + read_timeout 3600s + write_timeout 3600s + } + } +} + +http://taler.hacktivism.ch, http://exchange.hacktivism.ch, http://bank.hacktivism.ch, http://castopod.hacktivism.ch, http://bonfire.hacktivism.ch, http://prime.hacktivism.ch, http://bt.hacktivism.ch, http://git.hacktivism.ch, http://paivana.hacktivism.ch, http://tops.ng1.hacktivism.ch, http://tops.ng2.hacktivism.ch, http://tops.ng3.hacktivism.ch, http://lemmy.hacktivism.ch, http://decidim.hacktivism.ch { handle /.well-known/acme-challenge/* { root * /var/www/acme file_server diff --git a/configs/decidim/.env.example b/configs/decidim/.env.example new file mode 100644 index 0000000..303db55 --- /dev/null +++ b/configs/decidim/.env.example @@ -0,0 +1,13 @@ +POSTGRES_PASSWORD=@inline-secret@ +SECRET_KEY_BASE=@inline-secret@ +DECIDIM_APPLICATION_NAME=hacktivism +DECIDIM_MAILER_SENDER=noreply@hacktivism.ch +DECIDIM_AVAILABLE_LOCALES=en,de,fr +DECIDIM_DEFAULT_LOCALE=en +DECIDIM_HOST=decidim.hacktivism.ch +SMTP_ADDRESS=127.0.0.1 +SMTP_DOMAIN=hacktivism.ch +SMTP_USERNAME=unused +SMTP_PASSWORD=unused +SMTP_PORT=25 +QUEUE_ADAPTER=sidekiq diff --git a/configs/decidim/README.md b/configs/decidim/README.md new file mode 100644 index 0000000..c0433f4 --- /dev/null +++ b/configs/decidim/README.md @@ -0,0 +1,25 @@ +# Decidim — decidim.hacktivism.ch + +| Item | Value | +|------|--------| +| Live | `/home/hernani/koopa-decidim/` | +| Compose | `compose.yml` (mirror, no secrets) | +| Port | **9027** → Caddy | +| Site | https://decidim.hacktivism.ch/ | +| Image | `decidim/decidim:0.30.8` + postgres 16 + redis 7 + sidekiq | + +Secrets: host `~/koopa-decidim/{.env,users.env}` (mode 600). Not in this repo. + +Theme: `theme/hacktivism.css` + `theme/hacktivism.rb` (same exchange-dark palette as Lemmy/Bonfire). Analog stack: `configs/lemmy/`. + +Privacy: no third-party trackers. Stock GDPR banner is hidden (`#dc-dialog-wrapper`); `theme/hacktivism-consent.js` sets essential-only `decidim-consent`. + +```bash +cd ~/koopa-decidim && set -a && source .env && set +a && podman-compose up -d +scripts/decidim/install-systemd.sh +scripts/decidim/apply-branding.sh +``` + +Caddy: `sudo /home/hernani/koopa-caddy/apply.sh` + +Daylog: `2026/2026-08-17--decidim.md`. diff --git a/configs/decidim/assets/img/favicon.svg b/configs/decidim/assets/img/favicon.svg new file mode 100644 index 0000000..e1d3dc3 --- /dev/null +++ b/configs/decidim/assets/img/favicon.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/configs/decidim/assets/img/logo.svg b/configs/decidim/assets/img/logo.svg new file mode 100644 index 0000000..e1d3dc3 --- /dev/null +++ b/configs/decidim/assets/img/logo.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/configs/decidim/bootstrap.rb b/configs/decidim/bootstrap.rb new file mode 100644 index 0000000..8ec96a1 --- /dev/null +++ b/configs/decidim/bootstrap.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +# Create system admin + organization + org admin. Idempotent. +# Usage: bundle exec rails runner /code/lib/koopa_bootstrap.rb + +host = ENV.fetch("DECIDIM_HOST", "decidim.hacktivism.ch") +sys_email = ENV.fetch("DECIDIM_SYSTEM_EMAIL") +sys_password = ENV.fetch("DECIDIM_SYSTEM_PASSWORD") +admin_email = ENV.fetch("DECIDIM_ADMIN_EMAIL") +admin_password = ENV.fetch("DECIDIM_ADMIN_PASSWORD") +admin_name = ENV.fetch("DECIDIM_ADMIN_NAME", "hacktivism") +org_name = ENV.fetch("DECIDIM_APPLICATION_NAME", "hacktivism") + +sys = Decidim::System::Admin.find_or_initialize_by(email: sys_email) +sys.password = sys_password +sys.password_confirmation = sys_password +sys.save! +puts "system admin: #{sys.email}" + +org = Decidim::Organization.find_by(host: host) +if org.nil? + attrs = { + host: host, + default_locale: ENV.fetch("DECIDIM_DEFAULT_LOCALE", "en"), + available_locales: ENV.fetch("DECIDIM_AVAILABLE_LOCALES", "en,de,fr").split(","), + reference_prefix: "HCK", + time_zone: "Europe/Zurich", + users_registration_mode: "enabled", + force_users_to_authenticate_before_access_organization: false, + available_authorizations: [], + tos_version: Time.current, + colors: { + "primary" => "#e8a838", + "secondary" => "#3ecfbf", + "tertiary" => "#3d3128", + "success" => "#16a34a", + "warning" => "#f0d090", + "alert" => "#e7131a" + }, + header_snippets: %() + } + name_val = { "en" => org_name, "de" => org_name, "fr" => org_name } + desc_val = { "en" => "hacktivism participatory platform", "de" => "hacktivism Partizipationsplattform", "fr" => "plateforme participative hacktivism" } + begin + org = Decidim::Organization.new(attrs.merge(name: name_val, description: desc_val)) + org.save! + rescue StandardError + org = Decidim::Organization.new(attrs.merge(name: org_name, description: "hacktivism participatory platform")) + org.save! + end + Decidim::System::CreateDefaultPages.call(org) if defined?(Decidim::System::CreateDefaultPages) + Decidim::System::CreateDefaultContentBlocks.call(org) if defined?(Decidim::System::CreateDefaultContentBlocks) + puts "organization created host=#{org.host}" +else + org.colors = { + "primary" => "#e8a838", + "secondary" => "#3ecfbf", + "tertiary" => "#3d3128", + "success" => "#16a34a", + "warning" => "#f0d090", + "alert" => "#e7131a" + } + org.header_snippets = %() + org.save! + puts "organization updated host=#{org.host}" +end + +user = Decidim::User.find_or_initialize_by(email: admin_email, organization: org) +user.name = admin_name +user.nickname = admin_name.downcase.gsub(/[^a-z0-9_]/, "")[0, 20] +user.password = admin_password +user.password_confirmation = admin_password +user.admin = true +user.confirmed_at ||= Time.current +user.tos_agreement = true +user.accepted_tos_version = org.tos_version +user.locale = org.default_locale +user.save! +puts "org admin: #{user.email}" diff --git a/configs/decidim/compose.yml b/configs/decidim/compose.yml new file mode 100644 index 0000000..282e1b3 --- /dev/null +++ b/configs/decidim/compose.yml @@ -0,0 +1,132 @@ +# koopa-decidim — Decidim behind host Caddy on :9027 +# Docs: https://docs.decidim.org/en/develop/install/ +# Image: official pre-generated app (core modules only). No secrets in this file. + +x-logging: &default-logging + driver: "json-file" + options: + max-size: "50m" + max-file: "4" + +x-app-env: &app-env + RAILS_ENV: production + RAILS_SERVE_STATIC_FILES: "true" + RAILS_LOG_TO_STDOUT: "true" + DATABASE_URL: postgres://decidim:${POSTGRES_PASSWORD}@postgres:5432/decidim + REDIS_URL: redis://redis:6379/0 + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + DECIDIM_APPLICATION_NAME: ${DECIDIM_APPLICATION_NAME:-hacktivism} + DECIDIM_MAILER_SENDER: ${DECIDIM_MAILER_SENDER:-noreply@hacktivism.ch} + DECIDIM_AVAILABLE_LOCALES: ${DECIDIM_AVAILABLE_LOCALES:-en,de,fr} + DECIDIM_DEFAULT_LOCALE: ${DECIDIM_DEFAULT_LOCALE:-en} + SMTP_ADDRESS: ${SMTP_ADDRESS:-127.0.0.1} + SMTP_DOMAIN: ${SMTP_DOMAIN:-hacktivism.ch} + SMTP_USERNAME: ${SMTP_USERNAME:-unused} + SMTP_PASSWORD: ${SMTP_PASSWORD:-unused} + SMTP_PORT: ${SMTP_PORT:-25} + SMTP_AUTHENTICATION: ${SMTP_AUTHENTICATION:-plain} + QUEUE_ADAPTER: ${QUEUE_ADAPTER:-sidekiq} + +services: + decidim: + image: docker.io/decidim/decidim:0.30.8 + container_name: koopa-decidim + hostname: decidim + ports: + - "9027:3000" + env_file: + - .env + environment: + <<: *app-env + volumes: + - ./entrypoint.sh:/usr/local/bin/koopa-decidim-entrypoint.sh:ro + - ./theme/hacktivism.rb:/code/config/initializers/hacktivism.rb:ro + - ./theme/hacktivism.css:/code/public/hacktivism.css:ro + - ./theme/hacktivism-consent.js:/code/public/hacktivism-consent.js:ro + - ./bootstrap.rb:/code/lib/koopa_bootstrap.rb:ro + - ./assets/img/logo.svg:/code/public/hacktivism-logo.svg:ro + - ./assets/img/favicon.svg:/code/public/hacktivism-favicon.svg:ro + - decidim-storage:/code/storage + - decidim-uploads:/code/public/uploads + - decidim-tmp:/code/tmp + entrypoint: ["/bin/bash", "/usr/local/bin/koopa-decidim-entrypoint.sh"] + command: ["bundle", "exec", "rails", "s", "-b", "0.0.0.0", "-p", "3000"] + restart: unless-stopped + logging: *default-logging + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_started + labels: + org.hacktivism.service: decidim + org.hacktivism.host_port: "9027" + org.hacktivism.site: decidim.hacktivism.ch + org.hacktivism.managed_by: koopa-admin + + sidekiq: + image: docker.io/decidim/decidim:0.30.8 + container_name: koopa-decidim-sidekiq + hostname: sidekiq + env_file: + - .env + environment: + <<: *app-env + volumes: + - ./entrypoint.sh:/usr/local/bin/koopa-decidim-entrypoint.sh:ro + - ./theme/hacktivism.rb:/code/config/initializers/hacktivism.rb:ro + - decidim-storage:/code/storage + - decidim-uploads:/code/public/uploads + - decidim-tmp:/code/tmp + entrypoint: ["/bin/bash", "/usr/local/bin/koopa-decidim-entrypoint.sh"] + command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"] + restart: unless-stopped + logging: *default-logging + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_started + labels: + org.hacktivism.service: decidim + org.hacktivism.managed_by: koopa-admin + + postgres: + image: docker.io/library/postgres:16-alpine + container_name: koopa-decidim-db + hostname: postgres + environment: + - POSTGRES_USER=decidim + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=decidim + volumes: + - decidim-db:/var/lib/postgresql/data + restart: unless-stopped + logging: *default-logging + healthcheck: + test: ["CMD-SHELL", "pg_isready -U decidim -d decidim"] + interval: 5s + timeout: 5s + retries: 12 + labels: + org.hacktivism.service: decidim + org.hacktivism.managed_by: koopa-admin + + redis: + image: docker.io/library/redis:7-alpine + container_name: koopa-decidim-redis + hostname: redis + volumes: + - decidim-redis:/data + restart: unless-stopped + logging: *default-logging + labels: + org.hacktivism.service: decidim + org.hacktivism.managed_by: koopa-admin + +volumes: + decidim-db: + decidim-redis: + decidim-storage: + decidim-uploads: + decidim-tmp: diff --git a/configs/decidim/container-koopa-decidim-db.service b/configs/decidim/container-koopa-decidim-db.service new file mode 100644 index 0000000..c15c8c6 --- /dev/null +++ b/configs/decidim/container-koopa-decidim-db.service @@ -0,0 +1,21 @@ +# user systemd — Decidim postgres + redis +[Unit] +Description=Decidim postgres/redis (koopa-decidim-db) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +RestartSec=20 +TimeoutStartSec=300 +TimeoutStopSec=120 +WorkingDirectory=/home/hernani/koopa-decidim +ExecStart=/bin/bash -lc 'set -a && source .env && set +a && /usr/bin/podman-compose up -d postgres redis' +ExecStop=/usr/bin/podman stop -t 30 koopa-decidim-db koopa-decidim-redis +Type=oneshot +RemainAfterExit=yes + +[Install] +WantedBy=default.target diff --git a/configs/decidim/container-koopa-decidim.service b/configs/decidim/container-koopa-decidim.service new file mode 100644 index 0000000..c84ec49 --- /dev/null +++ b/configs/decidim/container-koopa-decidim.service @@ -0,0 +1,21 @@ +# user systemd — Decidim stack (app + sidekiq) +[Unit] +Description=Decidim stack (koopa-decidim) +Wants=network-online.target container-koopa-decidim-db.service +After=network-online.target container-koopa-decidim-db.service +RequiresMountsFor=%t/containers + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +RestartSec=20 +TimeoutStartSec=600 +TimeoutStopSec=120 +WorkingDirectory=/home/hernani/koopa-decidim +ExecStart=/bin/bash -lc 'set -a && source .env && set +a && /usr/bin/podman-compose up -d' +ExecStop=/usr/bin/podman stop -t 30 koopa-decidim koopa-decidim-sidekiq +Type=oneshot +RemainAfterExit=yes + +[Install] +WantedBy=default.target diff --git a/configs/decidim/entrypoint.sh b/configs/decidim/entrypoint.sh new file mode 100755 index 0000000..b7af36d --- /dev/null +++ b/configs/decidim/entrypoint.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Wait for Postgres, migrate, then exec CMD (rails or sidekiq). +set -euo pipefail + +export RAILS_ENV="${RAILS_ENV:-production}" + +echo "koopa-decidim: waiting for postgres..." +i=0 +until bundle exec rails runner 'ActiveRecord::Base.connection.execute("SELECT 1")' >/dev/null 2>&1; do + i=$((i + 1)) + if [ "$i" -ge 60 ]; then + echo "koopa-decidim: postgres not ready after ${i} tries" >&2 + exit 1 + fi + sleep 3 +done + +echo "koopa-decidim: db:prepare" +bundle exec rails db:prepare + +echo "koopa-decidim: exec $*" +exec "$@" diff --git a/configs/decidim/theme/hacktivism-consent.js b/configs/decidim/theme/hacktivism-consent.js new file mode 100644 index 0000000..796f5d7 --- /dev/null +++ b/configs/decidim/theme/hacktivism-consent.js @@ -0,0 +1,10 @@ +/* Essential-only consent; banner hidden via CSS. */ +(function () { + var name = "decidim-consent"; + if (document.cookie.indexOf(name + "=") !== -1) return; + document.cookie = + name + + "=" + + encodeURIComponent(JSON.stringify({ essential: true })) + + "; path=/; max-age=31536000; SameSite=Lax"; +})(); diff --git a/configs/decidim/theme/hacktivism.css b/configs/decidim/theme/hacktivism.css new file mode 100644 index 0000000..f38e96d --- /dev/null +++ b/configs/decidim/theme/hacktivism.css @@ -0,0 +1,98 @@ +/* hacktivism — same palette as bonfire/git/lemmy (exchange-dark) */ +:root, +html { + --primary: #e8a838; + --primary-rgb: 232, 168, 56; + --secondary: #3ecfbf; + --secondary-rgb: 62, 207, 191; + --tertiary: #3d3128; + --success: #16a34a; + --warning: #f0d090; + --alert: #e7131a; + --highlight: #e8a838; +} + +html, +body { + background-color: #1a1410 !important; + background-image: + radial-gradient(ellipse 90% 55% at 50% 108%, rgba(26, 107, 110, 0.35) 0%, transparent 55%), + radial-gradient(circle 420px at 12% 18%, rgba(232, 168, 56, 0.22) 0%, transparent 62%), + linear-gradient(165deg, #2c1e14 0%, #1a1410 38%, #12181a 72%, #0e1c1e 100%) !important; + background-attachment: fixed !important; + color: #fff6e8 !important; +} + +a { + color: #e8a838 !important; +} +a:hover { + color: #f0d090 !important; +} + +.title-bar, +.main-bar, +.navbar, +.topbar, +header.header, +.main-nav, +.footer, +.mini-footer, +.off-canvas, +.sidebar { + background-color: #14110e !important; + color: #fff6e8 !important; + border-color: #2a2018 !important; +} + +.card, +.card__content, +.callout, +.flash, +.dialog, +.reveal, +.accordion-item, +.comment-thread, +.process-header, +.hero, +.home-section { + background-color: #221c16 !important; + color: #fff6e8 !important; + border-color: #2a2018 !important; +} + +.button, +.button.primary, +.button--sc, +input[type="submit"].button { + background-color: #e8a838 !important; + border-color: #e8a838 !important; + color: #1a1410 !important; +} + +.button.secondary { + background-color: #3d3128 !important; + border-color: #3d3128 !important; + color: #fff6e8 !important; +} + +h1, h2, h3, h4, h5, h6, +.title-bar__title, +.card__title { + color: #f0c86a !important; +} + +input, +textarea, +select, +.input-group-field { + background-color: #221c16 !important; + color: #fff6e8 !important; + border-color: #3d3128 !important; +} + +/* No third-party trackers; hide stock GDPR banner (privacy by default). */ +#dc-dialog-wrapper, +.cookies__container { + display: none !important; +} diff --git a/configs/decidim/theme/hacktivism.rb b/configs/decidim/theme/hacktivism.rb new file mode 100644 index 0000000..762e5d8 --- /dev/null +++ b/configs/decidim/theme/hacktivism.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +# hacktivism theme + host allowlist for decidim.hacktivism.ch + +Rails.application.config.hosts << "decidim.hacktivism.ch" +Rails.application.config.hosts << "127.0.0.1" +Rails.application.config.hosts << "localhost" + +if Rails.application.config.respond_to?(:assume_ssl=) + Rails.application.config.assume_ssl = true +end + +Decidim.configure do |config| + config.enable_html_header_snippets = true if config.respond_to?(:enable_html_header_snippets=) +end diff --git a/configs/ports.md b/configs/ports.md index f8bdc8d..872d09c 100644 --- a/configs/ports.md +++ b/configs/ports.md @@ -18,12 +18,13 @@ | **9024** | podman **`koopa-forgejo`** (HTTP) → Caddy **`git.hacktivism.ch`** | | **9025** | podman **`koopa-paivana`** (paivana-httpd) → Caddy **`paivana.hacktivism.ch`** | | **9026** | podman **`koopa-lemmy-proxy`** → Caddy **`lemmy.hacktivism.ch`** | +| **9027** | podman **`koopa-decidim`** → Caddy **`decidim.hacktivism.ch`** | | **9200** | podman **Forgejo git-SSH** (host-direct; not Caddy) | | 9090–9092 | podman `koopa-tops-ng1` … `ng3` → Caddy `tops.ng{1,2,3}.hacktivism.ch` | | 8080 | Tor ORPort | VeciGate: WAN **80→9000**, WAN **443→9001**. -Public apps: Caddy vhosts on **9001** → 127.0.0.1:{9010–9015, 9020–9026, 9090–9092}. +Public apps: Caddy vhosts on **9001** → 127.0.0.1:{9010–9015, 9020–9027, 9090–9092}. Git SSH needs separate NAT/firewall **9200/tcp** if exposed to WAN. | **1789** | podman **`koopa-nym`** mixnet (nym.com nym-node) | diff --git a/host/overview/LIVE.md b/host/overview/LIVE.md index 46ee303..11120be 100644 --- a/host/overview/LIVE.md +++ b/host/overview/LIVE.md @@ -36,6 +36,7 @@ ss -lntp | grep -E '90[0-9]{2}|9200|8080|9080|1789' | `koopa-forgejo` (+ postgres) | `forgejo:11-rootless` | **9024**, **9200** | `git.hacktivism.ch` | | **`koopa-paivana`** (+ upstream) | `localhost/koopa-paivana:latest` | **9025** | `paivana.hacktivism.ch` | | `koopa-lemmy-proxy` (+ lemmy, ui, pictrs, db) | `lemmy:0.19.20` | **9026** | `lemmy.hacktivism.ch` | +| `koopa-decidim` (+ postgres, redis, sidekiq) | `decidim/decidim:0.30.8` | **9027** | `decidim.hacktivism.ch` | | **`koopa-tor-relay`** | `localhost/koopa-tor-relay:latest` | **8080**, **9051** (host net) | Tor OR (non-exit) | | **`koopa-nym`** | `localhost/koopa-nym:latest` | **9080**, **1789**, **1790** (+ opt. WG) | Nym mixnet node (nym.com) | | `koopa-tops-ng1` | `nginx` | **9090** | `tops.ng1.hacktivism.ch` | @@ -61,6 +62,7 @@ Managed from **`/home/hernani/ansible-taler-exchange/`** (see `configs/taler-exc | `git.hacktivism.ch` | 9024 (HTTP); git-SSH **9200** host-direct | | `paivana.hacktivism.ch` | 9025 (paivana-httpd GOA paywall) | | `lemmy.hacktivism.ch` | 9026 | +| `decidim.hacktivism.ch` | 9027 | | `tops.ng1.hacktivism.ch` | 9090 | | `tops.ng2.hacktivism.ch` | 9091 | | `tops.ng3.hacktivism.ch` | 9092 | @@ -74,6 +76,7 @@ Config: `/etc/caddy/Caddyfile` (mirror `configs/caddy/Caddyfile`). | `~/ansible-taler-exchange/` | Ansible + scripts for exchange-ansible container | | `~/koopa-castopod/` | Castopod compose | | `~/koopa-lemmy/` | Lemmy compose | +| `~/koopa-decidim/` | Decidim compose | | `~/koopa-bonfire/` | Bonfire compose + gitbot | | `~/koopa-prime/` | Jellyfin + qBittorrent | | `~/koopa-forgejo/` | Forgejo rootless | @@ -90,6 +93,8 @@ Config: `/etc/caddy/Caddyfile` (mirror `configs/caddy/Caddyfile`). | Taler merchant/bank | root `start_base_services_*` → service user → `/usr/local/bin/start_*.sh` | | Exchange (GOA) | `~/ansible-taler-exchange/run-container-koopa.sh` then `deploy-hacktivism-goa.sh` | | User apps | `cd ~/koopa-* && podman-compose up -d` | +| Lemmy | user units `container-koopa-lemmy-db` + `container-koopa-lemmy` (linger) | +| Decidim | user units `container-koopa-decidim-db` + `container-koopa-decidim` (linger) | | tops (ng1–ng3) | user unit `container-koopa-tops.service` (linger) | ## Config mirrors in this repo (`configs/`) @@ -111,3 +116,4 @@ Config: `/etc/caddy/Caddyfile` (mirror `configs/caddy/Caddyfile`). | Forgejo | `2026/2026-07-10--forgejo-rootless.md` | | Bonfire public feeds | `configs/bonfire/public-feeds.md` | | Castopod content | `2026/2026-07-09--castopod-content.md` | +| Decidim | `2026/2026-08-17--decidim.md` · `configs/decidim/` | diff --git a/host/overview/services.md b/host/overview/services.md index 0c9daf2..6c72548 100644 --- a/host/overview/services.md +++ b/host/overview/services.md @@ -37,6 +37,7 @@ flowchart TB Forgejo["koopa-forgejo ROOTLESS\n:9024 HTTP / :9200 SSH"] Paivana["koopa-paivana\n:9025"] Lemmy["koopa-lemmy-proxy\n:9026"] + Decidim["koopa-decidim\n:9027"] end Tor["tor ORPort :8080"] @@ -63,6 +64,7 @@ flowchart TB CaddyHTTPS -->|git.hacktivism.ch| Forgejo CaddyHTTPS -->|paivana.hacktivism.ch| Paivana CaddyHTTPS -->|lemmy.hacktivism.ch| Lemmy + CaddyHTTPS -->|decidim.hacktivism.ch| Decidim ``` ## Port table @@ -84,6 +86,7 @@ flowchart TB | **9024** | podman | **forgejo** HTTP → `git.hacktivism.ch` | | **9025** | podman | **paivana** → `paivana.hacktivism.ch` | | **9026** | podman | **lemmy** → `lemmy.hacktivism.ch` | +| **9027** | podman | **decidim** → `decidim.hacktivism.ch` | | **9200** | podman | **forgejo** git-SSH (host-direct) | | 9090–9092 | podman | **tops** `koopa-tops-ng1`…`ng3` | | 8080 | tor | ORPort | diff --git a/scripts/decidim/apply-branding.sh b/scripts/decidim/apply-branding.sh new file mode 100755 index 0000000..6327fe3 --- /dev/null +++ b/scripts/decidim/apply-branding.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Apply hacktivism colors + header CSS on the live organization. +set -euo pipefail +cd "${HOME}/koopa-decidim" +set -a +# shellcheck disable=SC1091 +source .env +# shellcheck disable=SC1091 +source users.env +set +a +podman exec \ + -e DECIDIM_HOST="${DECIDIM_HOST:-decidim.hacktivism.ch}" \ + -e DECIDIM_SYSTEM_EMAIL \ + -e DECIDIM_SYSTEM_PASSWORD \ + -e DECIDIM_ADMIN_EMAIL \ + -e DECIDIM_ADMIN_PASSWORD \ + -e DECIDIM_ADMIN_NAME \ + -e DECIDIM_APPLICATION_NAME \ + -e DECIDIM_DEFAULT_LOCALE \ + -e DECIDIM_AVAILABLE_LOCALES \ + koopa-decidim bundle exec rails runner /code/lib/koopa_bootstrap.rb +echo "branding applied. public: https://decidim.hacktivism.ch/" diff --git a/scripts/decidim/install-systemd.sh b/scripts/decidim/install-systemd.sh new file mode 100755 index 0000000..01934d3 --- /dev/null +++ b/scripts/decidim/install-systemd.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Install Decidim user systemd units on koopa (boot via linger). +set -euo pipefail +HERE="$(cd "$(dirname "$0")" && pwd)" +LIVE="${HOME}/koopa-decidim" +# admin-log tree (scripts/decidim → ../..) or live ~/koopa-decidim/bin +if [ -f "${HERE}/../../configs/decidim/container-koopa-decidim.service" ]; then + UNIT_SRC="${HERE}/../../configs/decidim" +elif [ -f "${LIVE}/container-koopa-decidim.service" ]; then + UNIT_SRC="${LIVE}" +else + echo "ERROR: decidim unit files not found" >&2 + exit 1 +fi +UNIT_DIR="${HOME}/.config/systemd/user" + +mkdir -p "${UNIT_DIR}" +cp "${UNIT_SRC}/container-koopa-decidim-db.service" "${UNIT_DIR}/" +cp "${UNIT_SRC}/container-koopa-decidim.service" "${UNIT_DIR}/" + +systemctl --user daemon-reload +systemctl --user enable \ + container-koopa-decidim-db.service \ + container-koopa-decidim.service + +if ! podman ps --format '{{.Names}}' | grep -qx koopa-decidim; then + (cd "${HOME}/koopa-decidim" && set -a && source .env && set +a && podman-compose up -d) +fi + +systemctl --user start \ + container-koopa-decidim-db.service \ + container-koopa-decidim.service + +systemctl --user --no-pager --lines=0 status \ + container-koopa-decidim-db.service \ + container-koopa-decidim.service || true + +if command -v loginctl >/dev/null 2>&1; then + if [ "$(loginctl show-user "$(id -un)" -p Linger --value 2>/dev/null || echo no)" != "yes" ]; then + echo "NOTE: enable linger so units start after reboot without login:" + echo " sudo loginctl enable-linger $(id -un)" + else + echo "linger already enabled for $(id -un)" + fi +fi + +echo "OK. Public: https://decidim.hacktivism.ch/" diff --git a/scripts/decidim/up.sh b/scripts/decidim/up.sh new file mode 100755 index 0000000..21a6a9d --- /dev/null +++ b/scripts/decidim/up.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail +DIR="${HOME}/koopa-decidim" +cd "${DIR}" +set -a +# shellcheck disable=SC1091 +source .env +set +a +podman-compose up -d +echo "== wait for :9027 ==" +ok=0 +for i in $(seq 1 80); do + code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 "http://127.0.0.1:9027/" || true) + echo "try $i: $code" + case "$code" in + 2*|3*) ok=1; break ;; + esac + sleep 5 +done +[[ "$ok" -eq 1 ]] || { echo "ERROR: no answer on 9027"; podman-compose ps; exit 1; } +echo "local ok. public: https://decidim.hacktivism.ch/" diff --git a/scripts/lemmy/install-systemd.sh b/scripts/lemmy/install-systemd.sh index 626dc3b..f064f9f 100755 --- a/scripts/lemmy/install-systemd.sh +++ b/scripts/lemmy/install-systemd.sh @@ -1,12 +1,21 @@ #!/usr/bin/env bash # Install Lemmy user systemd units on koopa (boot via linger). set -euo pipefail -ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +HERE="$(cd "$(dirname "$0")" && pwd)" +LIVE="${HOME}/koopa-lemmy" +if [ -f "${HERE}/../../configs/lemmy/container-koopa-lemmy.service" ]; then + UNIT_SRC="${HERE}/../../configs/lemmy" +elif [ -f "${LIVE}/container-koopa-lemmy.service" ]; then + UNIT_SRC="${LIVE}" +else + echo "ERROR: lemmy unit files not found" >&2 + exit 1 +fi UNIT_DIR="${HOME}/.config/systemd/user" mkdir -p "${UNIT_DIR}" -cp "${ROOT}/configs/lemmy/container-koopa-lemmy-db.service" "${UNIT_DIR}/" -cp "${ROOT}/configs/lemmy/container-koopa-lemmy.service" "${UNIT_DIR}/" +cp "${UNIT_SRC}/container-koopa-lemmy-db.service" "${UNIT_DIR}/" +cp "${UNIT_SRC}/container-koopa-lemmy.service" "${UNIT_DIR}/" systemctl --user daemon-reload systemctl --user enable \ @@ -25,4 +34,13 @@ systemctl --user --no-pager --lines=0 status \ container-koopa-lemmy-db.service \ container-koopa-lemmy.service || true +if command -v loginctl >/dev/null 2>&1; then + if [ "$(loginctl show-user "$(id -un)" -p Linger --value 2>/dev/null || echo no)" != "yes" ]; then + echo "NOTE: enable linger so units start after reboot without login:" + echo " sudo loginctl enable-linger $(id -un)" + else + echo "linger already enabled for $(id -un)" + fi +fi + echo "OK. Public: https://lemmy.hacktivism.ch/" diff --git a/scripts/login-health/README.md b/scripts/login-health/README.md index 04b1a6b..0003217 100644 --- a/scripts/login-health/README.md +++ b/scripts/login-health/README.md @@ -65,7 +65,9 @@ overall: OK ok=42 warn=0 fail=0 | Git SSH | TCP `:9200` | **Critical** (→ overall FAIL): merchant, exchange, bank, forgejo, edge/critical ports. -**Optional** (→ WARN): castopod, bonfire, lemmy, prime, bt, paivana, tops, tor, nym. +**Optional** (→ WARN): castopod, bonfire, lemmy, decidim, prime, bt, paivana, tops, tor, nym. + +Collect also checks **linger** and that Lemmy/Decidim user units are **enabled** (reboot survival). Read-only. No restarts, no secrets. diff --git a/scripts/login-health/koopa-login-health.sh b/scripts/login-health/koopa-login-health.sh index aa9c2db..309a782 100755 --- a/scripts/login-health/koopa-login-health.sh +++ b/scripts/login-health/koopa-login-health.sh @@ -151,6 +151,7 @@ EXPECT_CONTAINERS=( koopa-castopod koopa-bonfire koopa-lemmy-proxy + koopa-decidim koopa-prime-jellyfin koopa-prime-qbittorrent koopa-forgejo @@ -173,6 +174,7 @@ OPTIONAL_CONTAINERS=( koopa-castopod koopa-bonfire koopa-lemmy-proxy + koopa-decidim koopa-paivana ) @@ -185,6 +187,7 @@ PROBES=( "castopod|http://127.0.0.1:9020/|200,302,303,307,308" "bonfire|http://127.0.0.1:9021/|200,302,303,307,308" "lemmy|http://127.0.0.1:9026/|200,302,303,307,308" + "decidim|http://127.0.0.1:9027/|200,301,302,303,307,308" "prime|http://127.0.0.1:9022/|200,302,303,307,308" "bt|http://127.0.0.1:9023/|200,302,303,307,308,401" "paivana|http://127.0.0.1:9025/|200,302,303,307,308" @@ -193,7 +196,7 @@ PROBES=( "tops-ng3|http://127.0.0.1:9092/|200,302,303" ) -OPTIONAL_PROBES=(castopod bonfire lemmy prime bt paivana tops-ng1 tops-ng2 tops-ng3) +OPTIONAL_PROBES=(castopod bonfire lemmy decidim prime bt paivana tops-ng1 tops-ng2 tops-ng3) C_OK=; C_WARN=; C_FAIL=; C_DIM=; C_RST=; C_BOLD= # colors only when writing for a tty later; snapshots are plain + optional ANSI if COLLECT_COLOR=1 @@ -277,6 +280,34 @@ do_collect() { record WARN "systemctl not available" fi + # Reboot survival: linger + enabled user units (public stacks). + if command -v loginctl >/dev/null 2>&1; then + linger_st=$(loginctl show-user "$(id -un)" -p Linger --value 2>/dev/null || echo no) + if [ "${linger_st}" = "yes" ]; then + record OK "linger=yes ($(id -un))" + else + record FAIL "linger=${linger_st} (user units will not start after reboot without login)" + fi + else + record WARN "loginctl not available (cannot check linger)" + fi + if command -v systemctl >/dev/null 2>&1 && [ "$(id -u 2>/dev/null)" != "0" ]; then + local u en + for u in \ + container-koopa-lemmy-db.service \ + container-koopa-lemmy.service \ + container-koopa-decidim-db.service \ + container-koopa-decidim.service + do + en=$(systemctl --user is-enabled "${u}" 2>/dev/null || echo unknown) + if [ "${en}" = "enabled" ]; then + record OK "unit ${u} enabled" + else + record WARN "unit ${u} is ${en} (will not start on reboot)" + fi + done + fi + if command -v ss >/dev/null 2>&1; then listen_blob=$(ss -lnt 2>/dev/null | awk '{print $4}' || true) for port in 9000 9001 9010 9011 9012 9024 9200; do @@ -286,7 +317,7 @@ do_collect() { record FAIL "nothing listening on :${port}" fi done - for port in 9020 9021 9022 9023 9025 9026 9090 9091 9092 8080 9080; do + for port in 9020 9021 9022 9023 9025 9026 9027 9090 9091 9092 8080 9080; do if printf '%s\n' "${listen_blob}" | grep -qE "[:.]${port}\$"; then record OK "listen :${port} (optional)" else @@ -323,7 +354,7 @@ do_collect() { fi fi done - for dep in koopa-forgejo-db koopa-castopod-mariadb koopa-castopod-redis koopa-bonfire-db koopa-lemmy-db; do + for dep in koopa-forgejo-db koopa-castopod-mariadb koopa-castopod-redis koopa-bonfire-db koopa-lemmy-db koopa-decidim-db koopa-decidim-redis; do if printf '%s\n' "${running_names}" | grep -qxF "${dep}"; then record INFO "dep ${dep} running" else