diff --git a/2026/2026-08-17--decidim.md b/2026/2026-08-17--decidim.md index 688c4e3..3e5bac5 100644 --- a/2026/2026-08-17--decidim.md +++ b/2026/2026-08-17--decidim.md @@ -31,3 +31,11 @@ Stock Decidim still emits `#dc-dialog-wrapper` (Accept all / essential / Setting | 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. + +## Public example + isoc + +- Process **example** (published, not private): `https://decidim.hacktivism.ch/processes/example` +- One published meeting in that process (readable without login). +- Homepage block `highlighted_processes` published. +- Participant `isoc` on the host (`users.env`); password not in this repo. +- Seed: `scripts/decidim/seed-example.sh` / `configs/decidim/seed_example.rb`. diff --git a/configs/decidim/.env.example b/configs/decidim/.env.example index 303db55..9fe9974 100644 --- a/configs/decidim/.env.example +++ b/configs/decidim/.env.example @@ -11,3 +11,7 @@ SMTP_USERNAME=unused SMTP_PASSWORD=unused SMTP_PORT=25 QUEUE_ADAPTER=sidekiq +DECIDIM_ISOC_EMAIL=isoc@hacktivism.ch +DECIDIM_ISOC_PASSWORD=@inline-secret@ +DECIDIM_ISOC_NAME=isoc +DECIDIM_ISOC_NICKNAME=isoc diff --git a/configs/decidim/README.md b/configs/decidim/README.md index c0433f4..76c951e 100644 --- a/configs/decidim/README.md +++ b/configs/decidim/README.md @@ -18,8 +18,11 @@ Privacy: no third-party trackers. Stock GDPR banner is hidden (`#dc-dialog-wrapp cd ~/koopa-decidim && set -a && source .env && set +a && podman-compose up -d scripts/decidim/install-systemd.sh scripts/decidim/apply-branding.sh +scripts/decidim/seed-example.sh # public process /processes/example + optional isoc user ``` +Public example (no account): `https://decidim.hacktivism.ch/processes/example` + Caddy: `sudo /home/hernani/koopa-caddy/apply.sh` Daylog: `2026/2026-08-17--decidim.md`. diff --git a/configs/decidim/compose.yml b/configs/decidim/compose.yml index 282e1b3..5d25d65 100644 --- a/configs/decidim/compose.yml +++ b/configs/decidim/compose.yml @@ -44,6 +44,7 @@ services: - ./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 + - ./seed_example.rb:/code/lib/koopa_seed_example.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 diff --git a/configs/decidim/seed_example.rb b/configs/decidim/seed_example.rb new file mode 100644 index 0000000..ada85af --- /dev/null +++ b/configs/decidim/seed_example.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true +# Public example process + meeting (readable without an account). +# Optional participant from env: DECIDIM_ISOC_EMAIL / DECIDIM_ISOC_PASSWORD. +# Usage: bundle exec rails runner /code/lib/koopa_seed_example.rb + +def t(en, de, fr) + { "en" => en, "de" => de, "fr" => fr } +end + +def html(en, de, fr) + t("

#{en}

", "

#{de}

", "

#{fr}

") +end + +def fail_model!(record) + raise "#{record.class}: #{record.errors.full_messages.join("; ")}" +end + +host = ENV.fetch("DECIDIM_HOST", "decidim.hacktivism.ch") +org = Decidim::Organization.find_by!(host: host) +admin = Decidim::User.find_by!(email: ENV.fetch("DECIDIM_ADMIN_EMAIL"), organization: org) + +org.update!(force_users_to_authenticate_before_access_organization: false) + +slug = "example" +process = Decidim::ParticipatoryProcess.find_or_initialize_by(organization: org, slug: slug) +process.assign_attributes( + title: t("Example activity", "Beispielaktivität", "Activité exemple"), + subtitle: t( + "Public demo — no account needed to read", + "Öffentliches Beispiel — Lesen ohne Konto", + "Démo publique — lecture sans compte" + ), + short_description: html( + "A published participatory process so visitors can see Decidim without signing in.", + "Ein veröffentlichter Beteiligungsprozess, den Besucherinnen ohne Anmeldung lesen können.", + "Un processus participatif publié, lisible sans compte." + ), + description: html( + "This is a small public example on hacktivism. Meetings and the process page are visible to anyone. Signing in is only needed to take part.", + "Kleines öffentliches Beispiel auf hacktivism. Prozessseite und Treffen sind für alle sichtbar. Anmelden nur zum Mitmachen.", + "Petit exemple public sur hacktivism. La page du processus et la rencontre sont visibles sans compte. La connexion sert uniquement à participer." + ), + start_date: Date.current, + end_date: Date.current + 180, + published_at: process.published_at || Time.current, + private_space: false, + promoted: true, + scopes_enabled: false, + weight: 1 +) +process.save || fail_model!(process) +puts "process id=#{process.id} slug=#{process.slug} published=#{process.published_at}" + +step = process.steps.find_or_initialize_by(position: 1) +step.assign_attributes( + title: t("Open", "Offen", "Ouvert"), + description: html("Example step.", "Beispielphase.", "Étape exemple."), + start_date: Date.current, + end_date: Date.current + 180, + active: true +) +step.save || fail_model!(step) +puts "step id=#{step.id} active=#{step.active}" + +meetings = Decidim::Component.find_or_initialize_by( + participatory_space: process, + manifest_name: "meetings" +) +meetings.assign_attributes( + name: t("Meetings", "Treffen", "Rencontres"), + published_at: meetings.published_at || Time.current, + visible: true, + weight: 1 +) +meetings.save || fail_model!(meetings) +puts "meetings component id=#{meetings.id} published=#{meetings.published_at}" + +start_at = Time.current.utc.change(hour: 18, min: 0) + 7.days +finish_at = start_at + 2.hours +meeting = Decidim::Meetings::Meeting.find_or_initialize_by( + component: meetings, + author: admin +) +if meeting.new_record? || meeting.title.blank? + meeting.title = t("Open example meeting", "Offenes Beispieltreffen", "Rencontre exemple ouverte") +end +meeting.assign_attributes( + description: html( + "Anyone can open this meeting page without an account. It is a demo activity on decidim.hacktivism.ch.", + "Diese Treffen-Seite ist ohne Konto lesbar. Beispielaktivität auf decidim.hacktivism.ch.", + "Cette page de rencontre est lisible sans compte. Activité exemple sur decidim.hacktivism.ch." + ), + start_time: meeting.start_time || start_at, + end_time: meeting.end_time || finish_at, + address: "hacktivism", + location: t("Online / public", "Online / öffentlich", "En ligne / public"), + location_hints: t("No registration required to read.", "Zum Lesen keine Anmeldung.", "Pas d'inscription pour lire."), + type_of_meeting: "in_person", + registration_type: "registration_disabled", + registrations_enabled: false, + private_meeting: false, + transparent: true, + published_at: meeting.published_at || Time.current, + comments_enabled: true +) +meeting.save || fail_model!(meeting) +puts "meeting id=#{meeting.id} published=#{meeting.published_at} start=#{meeting.start_time}" + +block = Decidim::ContentBlock.find_or_initialize_by( + organization: org, + scope_name: "homepage", + manifest_name: "highlighted_processes" +) +block.weight ||= 45 +block.published_at ||= Time.current +block.save || fail_model!(block) +puts "homepage highlighted_processes published=#{block.published_at}" + +isoc_email = ENV["DECIDIM_ISOC_EMAIL"].to_s.strip +isoc_password = ENV["DECIDIM_ISOC_PASSWORD"].to_s +if isoc_email.empty? || isoc_password.empty? + puts "isoc user skipped (DECIDIM_ISOC_EMAIL / DECIDIM_ISOC_PASSWORD unset)" +else + isoc = Decidim::User.find_or_initialize_by(email: isoc_email, organization: org) + isoc.name = ENV.fetch("DECIDIM_ISOC_NAME", "isoc") + isoc.nickname = ENV.fetch("DECIDIM_ISOC_NICKNAME", "isoc") + isoc.password = isoc_password + isoc.password_confirmation = isoc_password + isoc.admin = false + isoc.confirmed_at ||= Time.current + isoc.tos_agreement = true + isoc.accepted_tos_version = org.tos_version + isoc.locale = org.default_locale + isoc.save || fail_model!(isoc) + puts "isoc user id=#{isoc.id} email=#{isoc.email} nick=#{isoc.nickname} admin=#{isoc.admin} confirmed=#{isoc.confirmed_at}" +end + +puts "public process: https://#{host}/processes/#{process.slug}" +puts "public meeting: https://#{host}/processes/#{process.slug}/f/#{meetings.id}/meetings/#{meeting.id}" diff --git a/scripts/decidim/seed-example.sh b/scripts/decidim/seed-example.sh new file mode 100755 index 0000000..89dbd05 --- /dev/null +++ b/scripts/decidim/seed-example.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Publish public example process/meeting + optional isoc user from users.env. +set -euo pipefail +cd "${HOME}/koopa-decidim" +set -a +# shellcheck disable=SC1091 +source .env +# shellcheck disable=SC1091 +source users.env +set +a + +src="" +if [ -f "${HOME}/src/koopa/koopa-admin-log/configs/decidim/seed_example.rb" ]; then + src="${HOME}/src/koopa/koopa-admin-log/configs/decidim/seed_example.rb" +elif [ -f ./seed_example.rb ]; then + src="./seed_example.rb" +else + echo "ERROR: seed_example.rb not found" >&2 + exit 1 +fi +podman cp "${src}" koopa-decidim:/code/lib/koopa_seed_example.rb + +podman exec \ + -e DECIDIM_HOST="${DECIDIM_HOST:-decidim.hacktivism.ch}" \ + -e DECIDIM_ADMIN_EMAIL \ + -e DECIDIM_ISOC_EMAIL \ + -e DECIDIM_ISOC_PASSWORD \ + -e DECIDIM_ISOC_NAME \ + -e DECIDIM_ISOC_NICKNAME \ + koopa-decidim bundle exec rails runner /code/lib/koopa_seed_example.rb +echo "seed done. public: https://decidim.hacktivism.ch/processes/example"