mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-03 07:32:02 +02:00
Finished Mobilizon OIDC implementation
This commit is contained in:
parent
3ce6e958b4
commit
4cffddab51
@ -1,15 +1,14 @@
|
|||||||
# filter_plugins/docker_image.py
|
def get_docker_image(applications, application_id, image_key:str=None):
|
||||||
|
image_key = image_key if image_key else application_id
|
||||||
|
docker = applications.get(application_id, {}).get("docker", {})
|
||||||
|
version = docker.get("versions", {}).get(image_key)
|
||||||
|
image = docker.get("images", {}).get(image_key)
|
||||||
|
|
||||||
def get_docker_image(applications, application_id, image_key):
|
if not image:
|
||||||
app = applications.get(application_id, {})
|
raise ValueError(f"Missing image for {application_id}:{image_key}")
|
||||||
docker = app.get("docker", {})
|
|
||||||
images = docker.get("images", {})
|
|
||||||
versions = docker.get("versions", {})
|
|
||||||
version = versions.get(image_key) or app.get("version")
|
|
||||||
image = images.get(image_key)
|
|
||||||
|
|
||||||
if not image or not version:
|
if not version:
|
||||||
raise ValueError(f"Missing image or version for {application_id}:{image_key}")
|
raise ValueError(f"Missing version for {application_id}:{image_key}")
|
||||||
|
|
||||||
return f"{image}:{version}"
|
return f"{image}:{version}"
|
||||||
|
|
||||||
|
@ -9,10 +9,17 @@
|
|||||||
|
|
||||||
## Helper Variables:
|
## Helper Variables:
|
||||||
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}"
|
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}"
|
||||||
_oidc_client_issuer_url: "{{ web_protocol }}://{{domains | get_domain('keycloak')}}/realms/{{_oidc_client_realm}}"
|
_oidc_url: "{{
|
||||||
|
(oidc.url
|
||||||
|
if (oidc is defined and oidc.url is defined)
|
||||||
|
else web_protocol ~ '://' ~ (domains | get_domain('keycloak'))
|
||||||
|
)
|
||||||
|
}}"
|
||||||
|
_oidc_client_issuer_url: "{{ _oidc_url }}/realms/{{_oidc_client_realm}}"
|
||||||
_oidc_client_id: "{{ oidc.client.id if oidc.client is defined and oidc.client.id is defined else primary_domain }}"
|
_oidc_client_id: "{{ oidc.client.id if oidc.client is defined and oidc.client.id is defined else primary_domain }}"
|
||||||
|
|
||||||
defaults_oidc:
|
defaults_oidc:
|
||||||
|
url: "{{ _oidc_url }}"
|
||||||
client:
|
client:
|
||||||
id: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain
|
id: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain
|
||||||
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
|
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
|
||||||
|
@ -41,7 +41,7 @@ FUNKWHALE_WEB_WORKERS=4
|
|||||||
# your instance. It cannot be changed after initial deployment
|
# your instance. It cannot be changed after initial deployment
|
||||||
# without breaking your instance.
|
# without breaking your instance.
|
||||||
FUNKWHALE_HOSTNAME={{domains | get_domain(application_id)}}
|
FUNKWHALE_HOSTNAME={{domains | get_domain(application_id)}}
|
||||||
FUNKWHALE_PROTOCOL=https
|
FUNKWHALE_PROTOCOL={{ web_protocol }}
|
||||||
|
|
||||||
# Log level (debug, info, warning, error, critical)
|
# Log level (debug, info, warning, error, critical)
|
||||||
LOGLEVEL={% if enable_debug | bool %}debug{% else %}error{% endif %}
|
LOGLEVEL={% if enable_debug | bool %}debug{% else %}error{% endif %}
|
||||||
@ -60,7 +60,7 @@ LOGLEVEL={% if enable_debug | bool %}debug{% else %}error{% endif %}
|
|||||||
# (returns `noreply%40youremail.host`)
|
# (returns `noreply%40youremail.host`)
|
||||||
# EMAIL_CONFIG=smtp://user:password@youremail.host:25
|
# EMAIL_CONFIG=smtp://user:password@youremail.host:25
|
||||||
# EMAIL_CONFIG=smtp+ssl://user:password@youremail.host:465
|
# EMAIL_CONFIG=smtp+ssl://user:password@youremail.host:465
|
||||||
EMAIL_CONFIG=smtp+tls://no-reply:{{ users['no-reply'].mailu_token }}@{{system_email.host}}:{{system_email.port}}
|
EMAIL_CONFIG=smtp+tls://{{ users['no-reply'].username }}:{{ users['no-reply'].mailu_token }}@{{system_email.host}}:{{system_email.port}}
|
||||||
|
|
||||||
# Make e-mail verification mandatory before using the service
|
# Make e-mail verification mandatory before using the service
|
||||||
# Doesn't apply to admins.
|
# Doesn't apply to admins.
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
# Todo
|
|
||||||
- Implement
|
|
@ -10,4 +10,10 @@
|
|||||||
domain: "{{ domains | get_domain(application_id) }}"
|
domain: "{{ domains | get_domain(application_id) }}"
|
||||||
http_port: "{{ ports.localhost.http[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
|
||||||
|
|
||||||
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
|
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
|
||||||
|
278
roles/docker-mobilizon/templates/config.exs.j2
Normal file
278
roles/docker-mobilizon/templates/config.exs.j2
Normal 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 %}
|
@ -1,14 +1,12 @@
|
|||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
{% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %}
|
{% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %}
|
||||||
|
|
||||||
mobilizon:
|
application:
|
||||||
image: "{{ applications[application_id].images[application_id] }}"
|
image: "{{ applications[application_id].images[application_id] }}"
|
||||||
volumes:
|
volumes:
|
||||||
- uploads:/var/lib/mobilizon/uploads
|
- uploads:/var/lib/mobilizon/uploads
|
||||||
# - ./config.exs:/etc/mobilizon/config.exs:ro
|
- {{ mobilizon_host_conf_exs_file }}:/etc/mobilizon/config.exs:ro
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ mobilizon_exposed_docker_port }}"
|
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ mobilizon_exposed_docker_port }}"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
@ -67,12 +67,12 @@ MOBILIZON_DATABASE_PORT={{ database_port }}
|
|||||||
# A secret key used as a base to generate secrets for encrypting and signing data.
|
# 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)
|
# Make sure it's long enough (~64 characters should be fine)
|
||||||
# You can run `openssl rand -base64 48` to generate such a secret
|
# You can run `openssl rand -base64 48` to generate such a secret
|
||||||
MOBILIZON_INSTANCE_SECRET_KEY_BASE={{ applications[application_id].secret_key_base }}
|
MOBILIZON_INSTANCE_SECRET_KEY_BASE={{ applications[application_id].credentials.secret_key_base }}
|
||||||
|
|
||||||
# A secret key used as a base to generate JWT tokens
|
# A secret key used as a base to generate JWT tokens
|
||||||
# Make sure it's long enough (~64 characters should be fine)
|
# Make sure it's long enough (~64 characters should be fine)
|
||||||
# You can run `openssl rand -base64 48` to generate such a secret
|
# You can run `openssl rand -base64 48` to generate such a secret
|
||||||
MOBILIZON_INSTANCE_SECRET_KEY={{ applications[application_id].secret_key }}
|
MOBILIZON_INSTANCE_SECRET_KEY={{ applications[application_id].credentials.secret_key }}
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
######################################################
|
||||||
@ -96,34 +96,3 @@ MOBILIZON_SMTP_SSL=false
|
|||||||
# Make sure to match the port value as well
|
# Make sure to match the port value as well
|
||||||
# Defaults to "if_available"
|
# Defaults to "if_available"
|
||||||
MOBILIZON_SMTP_TLS={% if system_email.tls %}TLS{% elif system_email.start_tls %}STARTTLS{% else %}Clear{% endif %}
|
MOBILIZON_SMTP_TLS={% if system_email.tls %}TLS{% elif system_email.start_tls %}STARTTLS{% else %}Clear{% endif %}
|
||||||
|
|
||||||
{% if applications | is_feature_enabled('oidc',application_id) %}
|
|
||||||
####################################
|
|
||||||
# ▶️ Mobilizon OIDC Configuration
|
|
||||||
####################################
|
|
||||||
|
|
||||||
AUTHENTICATION_STRATEGIES=open_id_connect
|
|
||||||
|
|
||||||
# Display name of the OIDC login button
|
|
||||||
UEBERAUTH_OPENID_CONNECT_DISPLAY_NAME="{{ oidc.button_text }}"
|
|
||||||
|
|
||||||
# Use discovery to automatically fetch OIDC provider settings
|
|
||||||
UEBERAUTH_OPENID_CONNECT_DISCOVERY_DOCUMENT={{ oidc.client.discovery_document }}
|
|
||||||
|
|
||||||
# OIDC OAuth2 client credentials
|
|
||||||
UEBERAUTH_OPENID_CONNECT_CLIENT_ID={{ oidc.client.id }}
|
|
||||||
UEBERAUTH_OPENID_CONNECT_CLIENT_SECRET={{ oidc.client.secret }}
|
|
||||||
|
|
||||||
# Redirect URI for the OIDC callback
|
|
||||||
UEBERAUTH_OPENID_CONNECT_REDIRECT_URI={{ mobilizon_oidc_callback_url }}
|
|
||||||
|
|
||||||
# Scope and response type for OIDC
|
|
||||||
UEBERAUTH_OPENID_CONNECT_SCOPE=openid email profile
|
|
||||||
UEBERAUTH_OPENID_CONNECT_RESPONSE_TYPE=code
|
|
||||||
|
|
||||||
# Claim/field used to uniquely identify the user
|
|
||||||
UEBERAUTH_OPENID_CONNECT_UID_FIELD={{ oidc.attributes.username }}
|
|
||||||
|
|
||||||
# Optional email verification behavior
|
|
||||||
UEBERAUTH_OPENID_CONNECT_ASSUME_EMAIL_IS_VERIFIED=true
|
|
||||||
{% endif %}
|
|
||||||
|
@ -4,3 +4,14 @@ images:
|
|||||||
features:
|
features:
|
||||||
central_database: true
|
central_database: true
|
||||||
oidc: true
|
oidc: true
|
||||||
|
csp:
|
||||||
|
flags:
|
||||||
|
script-src-elem:
|
||||||
|
unsafe-inline: true
|
||||||
|
script-src:
|
||||||
|
unsafe-eval: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "event.{{ primary_domain }}"
|
||||||
|
aliases:
|
||||||
|
- "events.{{ primary_domain }}"
|
@ -1,4 +1,8 @@
|
|||||||
application_id: mobilizon
|
application_id: mobilizon
|
||||||
database_type: "mariadb"
|
|
||||||
|
database_type: "postgres"
|
||||||
|
database_gis_enabled: true
|
||||||
|
|
||||||
mobilizon_oidc_callback_url: "{{ web_protocol }}://{{ domains | get_domain(application_id) }}/auth/openid_connect/callback"
|
mobilizon_oidc_callback_url: "{{ web_protocol }}://{{ domains | get_domain(application_id) }}/auth/openid_connect/callback"
|
||||||
mobilizon_exposed_docker_port: 4000
|
mobilizon_exposed_docker_port: 4000
|
||||||
|
mobilizon_host_conf_exs_file: "{{docker_compose.directories.config}}config.exs"
|
@ -9,7 +9,7 @@
|
|||||||
- name: Install PostgreSQL
|
- name: Install PostgreSQL
|
||||||
docker_container:
|
docker_container:
|
||||||
name: "{{ applications.postgres.hostname }}"
|
name: "{{ applications.postgres.hostname }}"
|
||||||
image: "postgres:{{applications.postgres.version}}"
|
image: "{{ applications | get_docker_image(application_id) }}"
|
||||||
detach: yes
|
detach: yes
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: "{{ applications.postgres.credentials.postgres_password }}"
|
POSTGRES_PASSWORD: "{{ applications.postgres.credentials.postgres_password }}"
|
||||||
@ -118,6 +118,21 @@
|
|||||||
GRANT CREATE ON SCHEMA public TO {{ database_username }};
|
GRANT CREATE ON SCHEMA public TO {{ database_username }};
|
||||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO {{ database_username }};
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO {{ database_username }};
|
||||||
|
|
||||||
|
- name: Ensure PostGIS-related extensions are installed
|
||||||
|
community.postgresql.postgresql_ext:
|
||||||
|
db: "{{ database_name }}"
|
||||||
|
ext: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
login_user: postgres
|
||||||
|
login_password: "{{ applications.postgres.credentials.postgres_password }}"
|
||||||
|
login_host: 127.0.0.1
|
||||||
|
login_port: "{{ database_port }}"
|
||||||
|
loop:
|
||||||
|
- postgis
|
||||||
|
- pg_trgm
|
||||||
|
- unaccent
|
||||||
|
when: database_gis_enabled is defined and database_gis_enabled
|
||||||
|
|
||||||
- name: Run the docker_postgres tasks once
|
- name: Run the docker_postgres tasks once
|
||||||
set_fact:
|
set_fact:
|
||||||
run_once_docker_postgres: true
|
run_once_docker_postgres: true
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
# Please set an version in your inventory file - Rolling release for postgres isn't recommended
|
|
||||||
version: "latest"
|
|
||||||
hostname: "central-postgres"
|
hostname: "central-postgres"
|
||||||
|
docker:
|
||||||
|
images:
|
||||||
|
# Postgis is necessary for mobilizon
|
||||||
|
postgres: postgis/postgis
|
||||||
|
versions:
|
||||||
|
# Please set an version in your inventory file!
|
||||||
|
# Rolling release isn't recommended
|
||||||
|
postgres: "latest"
|
||||||
|
|
||||||
|
@ -30,11 +30,15 @@ class TestDeprecatedVersionKey(unittest.TestCase):
|
|||||||
uses_version = 'version' in config
|
uses_version = 'version' in config
|
||||||
uses_images = 'images' in config
|
uses_images = 'images' in config
|
||||||
|
|
||||||
if uses_version and not uses_images:
|
if uses_version:
|
||||||
warnings.append(
|
warnings.append(
|
||||||
f"[DEPRECATION WARNING] {role_path.name}/vars/configuration.yml: "
|
f"[DEPRECATION WARNING] {role_path.name}/vars/configuration.yml: "
|
||||||
f"'version:' is set, but 'images:' is missing. "
|
f"'version' is deprecated. Replace it by docker.versions[version]."
|
||||||
f"'version' is deprecated and must only be set if 'images' is present."
|
)
|
||||||
|
if uses_images:
|
||||||
|
warnings.append(
|
||||||
|
f"[DEPRECATION WARNING] {role_path.name}/vars/configuration.yml: "
|
||||||
|
f"'images' is deprecated. Replace it by docker.images[image]."
|
||||||
)
|
)
|
||||||
|
|
||||||
if warnings:
|
if warnings:
|
||||||
|
@ -33,55 +33,15 @@ class TestDockerRoleImagesConfiguration(unittest.TestCase):
|
|||||||
errors.append(f"{role_path.name}: YAML parse error: {e}")
|
errors.append(f"{role_path.name}: YAML parse error: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
images = config.get("images")
|
images = config.get("docker",{}).get("images")
|
||||||
if not images:
|
if not images:
|
||||||
warnings.append(f"[WARNING] {role_path.name}: No 'images' key in configuration.yml")
|
warnings.append(f"[WARNING] {role_path.name}: No 'docker.images' key in configuration.yml")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not isinstance(images, dict):
|
if not isinstance(images, dict):
|
||||||
errors.append(f"{role_path.name}: 'images' must be a dict in configuration.yml")
|
errors.append(f"{role_path.name}: 'images' must be a dict in configuration.yml")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for key, value in images.items():
|
|
||||||
if not key or not value or not isinstance(key, str) or not isinstance(value, str):
|
|
||||||
errors.append(f"{role_path.name}: images['{key}'] is invalid (must be non-empty string key and value)")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Improved regex: matches both ' and " and allows whitespace
|
|
||||||
pattern = (
|
|
||||||
r'image:\s*["\']\{\{\s*applications\[application_id\]\.images\.' + re.escape(key) + r'\s*\}\}["\']'
|
|
||||||
)
|
|
||||||
|
|
||||||
# innerhalb Deines Loops
|
|
||||||
pattern2 = (
|
|
||||||
r'image:\s*["\']\{\{\s*' # image: "{{
|
|
||||||
r'applications\[\s*application_id\s*\]\.images' # applications[ application_id ].images
|
|
||||||
r'\[\s*application_id\s*\]\s*' # [ application_id ]
|
|
||||||
r'\}\}["\']' # }}" oder }}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
for tmpl_file in [
|
|
||||||
role_path / "templates" / "docker-compose.yml.j2",
|
|
||||||
role_path / "templates" / "env.j2",
|
|
||||||
]:
|
|
||||||
if not tmpl_file.exists():
|
|
||||||
continue
|
|
||||||
content = tmpl_file.read_text("utf-8")
|
|
||||||
if re.search(pattern, content):
|
|
||||||
break
|
|
||||||
if key == main.get('application_id') and re.search(pattern2, content):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Dieser Block wird nur ausgeführt, wenn kein `break` ausgelöst wurde
|
|
||||||
errors.append(
|
|
||||||
f"{role_path.name}: image key '{key}' is not referenced as "
|
|
||||||
f"image: \"{{{{ applications[application_id].images.{key} }}}}\" or "
|
|
||||||
f"\"{{{{ applications[application_id].images[application_id] }}}}\" "
|
|
||||||
"in docker-compose.yml.j2 or env.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# OPTIONAL: Check if the image is available locally via docker images
|
# OPTIONAL: Check if the image is available locally via docker images
|
||||||
# from shutil import which
|
# from shutil import which
|
||||||
# import subprocess
|
# import subprocess
|
||||||
|
@ -18,27 +18,14 @@ class TestGetDockerImage(unittest.TestCase):
|
|||||||
"akaunting": {
|
"akaunting": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"docker": {
|
"docker": {
|
||||||
"images": { "akaunting": "docker.io/akaunting/akaunting" },
|
"images": {"akaunting": "docker.io/akaunting/akaunting"},
|
||||||
"versions": { "akaunting": "2.0.0" }
|
"versions": {"akaunting": "2.0.0"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = self.get_docker_image(applications, "akaunting", "akaunting")
|
result = self.get_docker_image(applications, "akaunting", "akaunting")
|
||||||
self.assertEqual(result, "docker.io/akaunting/akaunting:2.0.0")
|
self.assertEqual(result, "docker.io/akaunting/akaunting:2.0.0")
|
||||||
|
|
||||||
def test_fallback_to_application_version(self):
|
|
||||||
applications = {
|
|
||||||
"akaunting": {
|
|
||||||
"version": "1.2.3",
|
|
||||||
"docker": {
|
|
||||||
"images": { "akaunting": "ghcr.io/akaunting/akaunting" },
|
|
||||||
"versions": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = self.get_docker_image(applications, "akaunting", "akaunting")
|
|
||||||
self.assertEqual(result, "ghcr.io/akaunting/akaunting:1.2.3")
|
|
||||||
|
|
||||||
def test_missing_image_raises_error(self):
|
def test_missing_image_raises_error(self):
|
||||||
applications = {
|
applications = {
|
||||||
"akaunting": {
|
"akaunting": {
|
||||||
@ -56,7 +43,7 @@ class TestGetDockerImage(unittest.TestCase):
|
|||||||
applications = {
|
applications = {
|
||||||
"akaunting": {
|
"akaunting": {
|
||||||
"docker": {
|
"docker": {
|
||||||
"images": { "akaunting": "some/image" },
|
"images": {"akaunting": "some/image"},
|
||||||
"versions": {}
|
"versions": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,5 +51,45 @@ class TestGetDockerImage(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.get_docker_image(applications, "akaunting", "akaunting")
|
self.get_docker_image(applications, "akaunting", "akaunting")
|
||||||
|
|
||||||
|
# --- new: Default image_key uses application_id if none provided ---
|
||||||
|
def test_default_image_key_uses_application_id(self):
|
||||||
|
applications = {
|
||||||
|
"myapp": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"docker": {
|
||||||
|
"images": {"myapp": "registry/myapp"},
|
||||||
|
"versions": {"myapp": "4.5.6"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# No image_key argument → falls back to application_id
|
||||||
|
result = self.get_docker_image(applications, "myapp")
|
||||||
|
self.assertEqual(result, "registry/myapp:4.5.6")
|
||||||
|
|
||||||
|
# --- new: Alternate image_key lookup ---
|
||||||
|
def test_alternate_image_key(self):
|
||||||
|
applications = {
|
||||||
|
"service": {
|
||||||
|
"version": "9.9.9",
|
||||||
|
"docker": {
|
||||||
|
"images": {
|
||||||
|
"service": "registry/service",
|
||||||
|
"db": "registry/service-db"
|
||||||
|
},
|
||||||
|
"versions": {
|
||||||
|
"db": "2.2.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.get_docker_image(applications, "service", "db")
|
||||||
|
self.assertEqual(result, "registry/service-db:2.2.2")
|
||||||
|
|
||||||
|
# --- new: Missing application raises error ---
|
||||||
|
def test_missing_application_raises_error(self):
|
||||||
|
applications = {}
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.get_docker_image(applications, "does_not_exist")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user