Huge role refactoring/cleanup. Other commits will propably follow. Because some bugs will exist. Still important for longrun and also for auto docs/help/slideshow generation

This commit is contained in:
2025-07-08 23:43:13 +02:00
parent 6b87a049d4
commit 563d5fd528
1242 changed files with 2301 additions and 1355 deletions

View File

@@ -0,0 +1,29 @@
# Mobilizon
## Description
Experience Mobilizon, an open-source event management platform that empowers communities to create, manage, and attend events with ease. Mobilizon puts privacy and decentralization first, giving you full control over your data and how you engage with your audience.
## Overview
This role deploys Mobilizon using Docker, automating the setup of your event management platform along with its underlying database. With support for health checks, persistent storage for uploads and configuration, and seamless integration with an Nginx reverse proxy, Mobilizon is configured to provide reliable and scalable event hosting for your community.
## Features
- **Event Scheduling:** Create and manage events with rich metadata and RSVP functionality.
- **Community-Driven:** Foster connections with built-in discussion and follow features for organizers and participants.
- **Privacy-First:** Self-hosted solution ensures data ownership and GDPR-compliance.
- **Customizable Setup:** Configure database connections, instance settings, and admin credentials via environment variables and a TOML configuration file.
- **Scalable Deployment:** Use Docker to ensure your event platform grows seamlessly with your communitys needs.
## Further Resources
- [Mobilizon Official Website](https://mobilizon.org)
## Credits
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [veen.world](https://www.veen.world).
Part of the [CyMaIS Project](https://github.com/kevinveenbirkenbach/cymais)
Licensed under [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl)

View File

@@ -0,0 +1,26 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Experience Mobilizon, an open-source event management platform that empowers communities to create, manage, and attend events with ease, prioritizing privacy and decentralization."
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
galaxy_tags:
- mobilizon
- docker
- event-management
- open-source
repository: "https://s.veen.world/cymais"
issue_tracker_url: "https://s.veen.world/cymaisissues"
documentation: "https://s.veen.world/cymais"
logo:
class: "fa-solid fa-calendar-days"
run_after:
- web-app-matomo
- web-app-keycloak
- web-app-mailu
- service-openldap
- service-rdbms-postgres

View File

@@ -0,0 +1,9 @@
credentials:
secret_key_base:
description: "Secret key base used to generate secrets for encrypting and signing data"
algorithm: "alphanumeric"
validation: "^[A-Za-z0-9]{64}$"
secret_key:
description: "Secret key used as a base to generate JWT tokens"
algorithm: "alphanumeric"
validation: "^[A-Za-z0-9]{64}$"

View File

@@ -0,0 +1,17 @@
---
- name: "include service-rdbms-central"
include_role:
name: service-rdbms-central
- name: "include role webserver-proxy-domain for {{application_id}}"
include_role:
name: webserver-proxy-domain
vars:
domain: "{{ domains | get_domain(application_id) }}"
http_port: "{{ ports.localhost.http[application_id] }}"
- name: add config.exs
template:
src: "config.exs.j2"
dest: "{{ mobilizon_host_conf_exs_file }}"
notify: docker compose up

View File

@@ -0,0 +1,278 @@
# Mobilizon instance configuration
import Config
import Mobilizon.Service.Config.Helpers
{:ok, _} = Application.ensure_all_started(:tls_certificate_check)
loglevels = [
:emergency,
:alert,
:critical,
:error,
:warning,
:notice,
:info,
:debug
]
loglevel_env = System.get_env("MOBILIZON_LOGLEVEL", "error")
loglevel =
if loglevel_env in Enum.map(loglevels, &to_string/1) do
String.to_existing_atom(loglevel_env)
else
:error
end
listen_ip = System.get_env("MOBILIZON_INSTANCE_LISTEN_IP", "0.0.0.0")
listen_ip =
case listen_ip |> to_charlist() |> :inet.parse_address() do
{:ok, listen_ip} -> listen_ip
_ -> raise "MOBILIZON_INSTANCE_LISTEN_IP does not match the expected IP format."
end
config :mobilizon, Mobilizon.Web.Endpoint,
server: true,
url: [host: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan")],
http: [
port: String.to_integer(System.get_env("MOBILIZON_INSTANCE_PORT", "4000")),
ip: listen_ip
],
secret_key_base: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY_BASE", "changethis")
config :mobilizon, Mobilizon.Web.Auth.Guardian,
secret_key: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY", "changethis")
config :mobilizon, :instance,
name: System.get_env("MOBILIZON_INSTANCE_NAME", "Mobilizon"),
description: "Change this to a proper description of your instance",
hostname: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan"),
registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN", "false") == "true",
registration_email_allowlist:
System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_EMAIL_ALLOWLIST", "")
|> String.split(",", trim: true),
registration_email_denylist:
System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_EMAIL_DENYLIST", "")
|> String.split(",", trim: true),
disable_database_login:
System.get_env("MOBILIZON_INSTANCE_DISABLE_DATABASE_LOGIN", "false") == "true",
default_language: System.get_env("MOBILIZON_INSTANCE_DEFAULT_LANGUAGE", "en"),
demo: System.get_env("MOBILIZON_INSTANCE_DEMO", "false") == "true",
allow_relay: System.get_env("MOBILIZON_INSTANCE_ALLOW_RELAY", "true") == "true",
federating: System.get_env("MOBILIZON_INSTANCE_FEDERATING", "true") == "true",
enable_instance_feeds:
System.get_env("MOBILIZON_INSTANCE_ENABLE_INSTANCE_FEEDS", "true") == "true",
email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"),
email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan")
config :mobilizon, Mobilizon.Storage.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("MOBILIZON_DATABASE_USERNAME", "username"),
password: System.get_env("MOBILIZON_DATABASE_PASSWORD", "password"),
database: System.get_env("MOBILIZON_DATABASE_DBNAME", "mobilizon"),
hostname: System.get_env("MOBILIZON_DATABASE_HOST", "postgres"),
port: System.get_env("MOBILIZON_DATABASE_PORT", "5432"),
ssl: System.get_env("MOBILIZON_DATABASE_SSL", "false") == "true",
pool_size: 10
config :logger, level: loglevel
config :mobilizon, Mobilizon.Web.Email.Mailer,
adapter: Swoosh.Adapters.SMTP,
relay: System.get_env("MOBILIZON_SMTP_SERVER", "localhost"),
port: System.get_env("MOBILIZON_SMTP_PORT", "25"),
username: System.get_env("MOBILIZON_SMTP_USERNAME", nil),
password: System.get_env("MOBILIZON_SMTP_PASSWORD", nil),
tls: System.get_env("MOBILIZON_SMTP_TLS", "if_available"),
tls_options:
:tls_certificate_check.options(System.get_env("MOBILIZON_SMTP_SERVER", "localhost")),
ssl: System.get_env("MOBILIZON_SMTP_SSL", "false"),
retries: 1,
no_mx_lookups: false,
auth: System.get_env("MOBILIZON_SMTP_AUTH", "if_available")
config :geolix,
databases: [
%{
id: :city,
adapter: Geolix.Adapter.MMDB2,
source: "/var/lib/mobilizon/geo_db/GeoLite2-City.mmdb"
}
]
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local,
uploads: System.get_env("MOBILIZON_UPLOADS", "/var/lib/mobilizon/uploads")
formats =
if System.get_env("MOBILIZON_EXPORTS_FORMAT_CSV_ENABLED", "true") == "true" do
[Mobilizon.Service.Export.Participants.CSV]
else
[]
end
formats =
if System.get_env("MOBILIZON_EXPORTS_FORMAT_PDF_ENABLED", "true") == "true" do
formats ++ [Mobilizon.Service.Export.Participants.PDF]
else
formats
end
formats =
if System.get_env("MOBILIZON_EXPORTS_FORMAT_ODS_ENABLED", "true") == "true" do
formats ++ [Mobilizon.Service.Export.Participants.ODS]
else
formats
end
config :mobilizon, :exports,
path: System.get_env("MOBILIZON_UPLOADS_EXPORTS", "/var/lib/mobilizon/uploads/exports"),
formats: formats
config :tz_world,
data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones")
config :tzdata, :data_dir, System.get_env("MOBILIZON_TZDATA_DIR", "/var/lib/mobilizon/tzdata")
config :web_push_encryption, :vapid_details,
subject: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_SUBJECT", nil),
public_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PUBLIC_KEY", nil),
private_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PRIVATE_KEY", nil)
geospatial_service =
case System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") do
"Nominatim" -> Mobilizon.Service.Geospatial.Nominatim
"Addok" -> Mobilizon.Service.Geospatial.Addok
"Photon" -> Mobilizon.Service.Geospatial.Photon
"GoogleMaps" -> Mobilizon.Service.Geospatial.GoogleMaps
"MapQuest" -> Mobilizon.Service.Geospatial.MapQuest
"Mimirsbrunn" -> Mobilizon.Service.Geospatial.Mimirsbrunn
"Pelias" -> Mobilizon.Service.Geospatial.Pelias
"Hat" -> Mobilizon.Service.Geospatial.Hat
end
config :mobilizon, Mobilizon.Service.Geospatial, service: geospatial_service
config :mobilizon, Mobilizon.Service.Geospatial.Nominatim,
endpoint:
System.get_env(
"MOBILIZON_GEOSPATIAL_NOMINATIM_ENDPOINT",
"https://nominatim.openstreetmap.org"
),
api_key: System.get_env("MOBILIZON_GEOSPATIAL_NOMINATIM_API_KEY", nil)
config :mobilizon, Mobilizon.Service.Geospatial.Addok,
endpoint:
System.get_env("MOBILIZON_GEOSPATIAL_ADDOK_ENDPOINT", "https://api-adresse.data.gouv.fr")
config :mobilizon, Mobilizon.Service.Geospatial.Photon,
endpoint: System.get_env("MOBILIZON_GEOSPATIAL_PHOTON_ENDPOINT", "https://photon.komoot.de")
config :mobilizon, Mobilizon.Service.Geospatial.GoogleMaps,
api_key: System.get_env("MOBILIZON_GEOSPATIAL_GOOGLE_MAPS_API_KEY", nil),
fetch_place_details: true
config :mobilizon, Mobilizon.Service.Geospatial.MapQuest,
api_key: System.get_env("MOBILIZON_GEOSPATIAL_MAP_QUEST_API_KEY", nil)
config :mobilizon, Mobilizon.Service.Geospatial.Mimirsbrunn,
endpoint: System.get_env("MOBILIZON_GEOSPATIAL_MIMIRSBRUNN_ENDPOINT", nil)
config :mobilizon, Mobilizon.Service.Geospatial.Pelias,
endpoint: System.get_env("MOBILIZON_GEOSPATIAL_PELIAS_ENDPOINT", nil)
sentry_dsn = System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_DSN", nil)
included_environments = if sentry_dsn, do: ["prod"], else: []
config :sentry,
dsn: sentry_dsn,
included_environments: included_environments,
release: to_string(Application.spec(:mobilizon, :vsn))
config :logger, Sentry.LoggerBackend,
capture_log_messages: true,
level: :error
if sentry_dsn != nil do
config :mobilizon, Mobilizon.Service.ErrorReporting,
adapter: Mobilizon.Service.ErrorReporting.Sentry
end
matomo_enabled = System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_ENABLED", "false") == "true"
matomo_endpoint = System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_ENDPOINT", nil)
matomo_site_id = System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_SITE_ID", nil)
matomo_tracker_file_name =
System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_TRACKER_FILE_NAME", "matomo")
matomo_host = host_from_uri(matomo_endpoint)
analytics_providers =
if matomo_enabled do
[Mobilizon.Service.FrontEndAnalytics.Matomo]
else
[]
end
analytics_providers =
if sentry_dsn != nil do
analytics_providers ++ [Mobilizon.Service.FrontEndAnalytics.Sentry]
else
analytics_providers
end
config :mobilizon, :analytics, providers: analytics_providers
matomo_csp =
if matomo_enabled and matomo_host do
[
connect_src: [matomo_host],
script_src: [matomo_host],
img_src: [matomo_host]
]
else
[]
end
config :mobilizon, Mobilizon.Service.FrontEndAnalytics.Matomo,
enabled: matomo_enabled,
host: matomo_endpoint,
siteId: matomo_site_id,
trackerFileName: matomo_tracker_file_name,
csp: matomo_csp
config :mobilizon, Mobilizon.Service.FrontEndAnalytics.Sentry,
enabled: sentry_dsn != nil,
dsn: sentry_dsn,
tracesSampleRate: 1.0,
organization: System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_ORGANISATION", nil),
project: System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_PROJECT", nil),
host: System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_HOST", nil),
csp: [
connect_src:
System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_HOST", "") |> String.split(" ", trim: true)
]
{% if applications | is_feature_enabled('oidc',application_id) %}
config :ueberauth,
Ueberauth,
providers: [
keycloak: {Ueberauth.Strategy.Keycloak, [default_scope: "openid profile email"]}
]
config :mobilizon, :auth,
oauth_consumer_strategies: [
{:keycloak, "{{ oidc.button_text }}"}
]
config :ueberauth, Ueberauth.Strategy.Keycloak.OAuth,
client_id: "{{ oidc.client.id }}",
client_secret: "{{ oidc.client.secret }}",
site: "{{ oidc.url }}",
authorize_url: "{{ oidc.client.authorize_url }}",
token_url: "{{ oidc.client.token_url }}",
userinfo_url: "{{ oidc.client.user_info_url }}",
token_method: :post
{% endif %}

View File

@@ -0,0 +1,18 @@
{% include 'roles/docker-compose/templates/base.yml.j2' %}
application:
image: "{{ applications[application_id].images[application_id] }}"
volumes:
- uploads:/var/lib/mobilizon/uploads
- {{ mobilizon_host_conf_exs_file }}:/etc/mobilizon/config.exs:ro
ports:
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}"
{% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %}
{% include 'roles/docker-container/templates/base.yml.j2' %}
{% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %}
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
uploads:
{% include 'roles/docker-compose/templates/networks.yml.j2' %}

View File

@@ -0,0 +1,101 @@
# Copy this file to .env, then update it with your own settings
######################################################
# Instance configuration #
######################################################
# The name for your instance
MOBILIZON_INSTANCE_NAME={{ applications[application_id].titel }}
# Your domain
MOBILIZON_INSTANCE_HOST={{ domains | get_domain(application_id) }}
# The IP to listen on (defaults to 0.0.0.0)
# MOBILIZON_INSTANCE_LISTEN_IP
# The port to listen on (defaults to 4000). Point your reverse proxy on this port.
MOBILIZON_INSTANCE_PORT={{ container_port }}
# Whether registrations are opened or closed. Can be changed in the admin settings UI as well.
# Make sure to moderate actively your instance if registrations are opened.
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=false
# From which email will the emails be sent
MOBILIZON_INSTANCE_EMAIL={{ users["no-reply"].email }}
# To which email with the replies be sent
MOBILIZON_REPLY_EMAIL={{ users["administrator"].email }}
# The loglevel setting.
# You can find accepted values here: https://hexdocs.pm/logger/Logger.html#module-levels
# Defaults to error
MOBILIZON_LOGLEVEL={% if enable_debug | bool %}debug{% else %}error{% endif %}
######################################################
# Database settings #
######################################################
# The values below will be given to both the PostGIS (PostgreSQL) and Mobilizon containers
# Use the next settings if you plan to use an existing external database
# The Mobilizon Database username. Defaults to $POSTGRES_USER.
# Change if using an external database.
MOBILIZON_DATABASE_USERNAME={{ database_username }}
# The Mobilizon Database password. Defaults to $POSTGRES_PASSWORD.
# Change if using an external database.
MOBILIZON_DATABASE_PASSWORD={{ database_password }}
# The Mobilizon Database name. Defaults to $POSTGRES_DB.
# Change if using an external database.
MOBILIZON_DATABASE_DBNAME={{ database_name }}
# The Mobilizon database host. Useful if using an external database.
MOBILIZON_DATABASE_HOST={{ database_host }}
# The Mobilizon database port. Useful if using an external database.
MOBILIZON_DATABASE_PORT={{ database_port }}
# Whether to use SSL to connect to the Mobilizon database. Useful if using an external database.
# MOBILIZON_DATABASE_SSL=false
######################################################
# Secrets #
######################################################
# A secret key used as a base to generate secrets for encrypting and signing data.
# Make sure it's long enough (~64 characters should be fine)
# You can run `openssl rand -base64 48` to generate such a secret
MOBILIZON_INSTANCE_SECRET_KEY_BASE={{ applications[application_id].credentials.secret_key_base }}
# A secret key used as a base to generate JWT tokens
# Make sure it's long enough (~64 characters should be fine)
# You can run `openssl rand -base64 48` to generate such a secret
MOBILIZON_INSTANCE_SECRET_KEY={{ applications[application_id].credentials.secret_key }}
######################################################
# Email settings #
######################################################
# The SMTP server
# Defaults to localhost
MOBILIZON_SMTP_SERVER={{system_email.host}}
MOBILIZON_SMTP_PORT={{system_email.port}}
MOBILIZON_SMTP_USERNAME={{ users['no-reply'].email }}
MOBILIZON_SMTP_PASSWORD={{ users['no-reply'].mailu_token }}
# Whether to use SSL for SMTP.
# Boolean
# Defaults to false
MOBILIZON_SMTP_SSL=false
# Deactivate Database Login if OIDC or LDAP are enabled
MOBILIZON_INSTANCE_DISABLE_DATABASE_LOGIN={{ (applications | is_feature_enabled('ldap',application_id) or applications | is_feature_enabled('oidc',application_id)) | lower }}
# Whether to use TLS for SMTP.
# Allowed values: always (TLS), never (Clear) and if_available (STARTTLS)
# Make sure to match the port value as well
# Defaults to "if_available"
MOBILIZON_SMTP_TLS={% if system_email.tls %}TLS{% elif system_email.start_tls %}STARTTLS{% else %}Clear{% endif %}

View File

@@ -0,0 +1,22 @@
titel: "Mobilizon on {{ primary_domain | upper }}"
images:
mobilizon: "docker.io/framasoft/mobilizon"
features:
central_database: true
oidc: true
matomo: true
csp:
flags:
script-src-elem:
unsafe-inline: true
script-src:
unsafe-eval: true
domains:
canonical:
- "event.{{ primary_domain }}"
aliases:
- "events.{{ primary_domain }}"
docker:
services:
database:
enabled: true

View File

@@ -0,0 +1,8 @@
application_id: mobilizon
database_type: "postgres"
database_gis_enabled: true
mobilizon_oidc_callback_url: "{{ domains | get_url(application_id, web_protocol) }}/auth/openid_connect/callback"
container_port: 4000
mobilizon_host_conf_exs_file: "{{docker_compose.directories.config}}config.exs"