Commit graph

116 commits

Author SHA1 Message Date
Simon Bärlocher
1715533729
fix(send): use Traefik v3 OR-syntax for multi-domain Host rule
The router rule joined send_domains with ', ' which is the v2 syntax
('Host(`a`, `b`)'). Traefik v3 expects each Host() to be its own
matcher joined with the explicit '||' OR operator. With v3 the comma
form is silently ignored — only the first host actually matches.

Match the pattern already used in the authentik, drawio and nextcloud
roles in this collection.
2026-05-26 16:16:36 +02:00
Simon Bärlocher
6ee7c2328b
fix(send): self-review fixes (FQCN, min_ansible_version str)
* tasks/main.yml: prefix all builtin modules with ansible.builtin
  (file, template) — silences ansible-lint fqcn[action-core] and
  matches the convention used by the other roles in this collection.

* meta/main.yml: change min_ansible_version from the float 2.14 to
  the string '2.14'. ansible-galaxy's schema requires a string here
  (ansible-lint schema[meta] complains otherwise — same fix I just
  applied to the opnform role).
2026-05-26 16:16:36 +02:00
Simon Bärlocher
e1879e9686
feat(send): add role for self-hosted Send file-share service
Deploys timvisee/send with a Redis backend behind Traefik. Supports
local-disk or S3 storage (e.g. via the garage role). Uses the shared
`*_domains` list convention so the router can accept internal *.int.*
names alongside the canonical BASE_URL host.
2026-05-26 16:16:36 +02:00
Simon Bärlocher
9cbfab7080
docs(bookstack): add meta/argument_specs.yml
47 typed options covering the full defaults file plus the OIDC and
backup-timer subsystems. The three secrets the role asserts on
(db_root_password, db_password, admin_password) are marked
required: true so ansible refuses the play with a clear error before
the validate task even runs.

Loads cleanly through ansible-core's ArgumentSpecValidator with 100%
defaults/spec coverage. Matches the spec convention used by traefik,
authentik, drawio, garage, nextcloud, opnform, coturn, talk and send.
2026-05-26 16:16:35 +02:00
4fe9d6b177
feat(bookstack): add role for self-hosted BookStack deployment
Deploy BookStack with linuxserver.io images behind Traefik, including
Entra ID OIDC SSO support and a daily backup timer.

Stack:
- lscr.io/linuxserver/bookstack:version-v26.03.3
- lscr.io/linuxserver/mariadb:11.4.9
- Traefik labels for websecure entrypoint on internal network
- Healthcheck via mariadb-admin ping (LSIO image lacks healthcheck.sh)

Features:
- Persistent APP_KEY generated on first run, stored in volume dir
- Optional OIDC SSO via Microsoft Entra ID (configurable per-instance)
- Idempotent admin user creation with DB-based existence check
- Daily systemd timer backup (DB dump + uploads tar + APP_KEY)
  with configurable retention

Implementation notes:
- DB queries use --protocol=tcp with the app user because root@localhost
  uses unix_socket auth in the LSIO MariaDB image (no password) and
  root@% does not exist
- docker_container_exec uses argv: (list) instead of command: (string)
  to avoid argument-splitting issues
- Migration-wait task ensures users table exists before admin check,
  since /login returns 200 before Laravel migrations complete
- no_log: true on all tasks that reference DB or admin passwords
- artisan absolute path (/app/www/artisan) because LSIO image WORKDIR
  is not the app directory

Adds bookstack route to DMZ Traefik service registry.
2026-05-26 16:16:35 +02:00
Simon Bärlocher
611964f7d6
docs(opnform): add meta/argument_specs.yml
50 typed options covering the full defaults file plus the OIDC subschema
(group_role_mappings with idp_group + role choices). Required secrets
(app_key, jwt_secret, front_api_secret, db_password) marked
required: true so ansible refuses the play with a clear error before
the validate task even runs.

Loads cleanly through ansible-core's ArgumentSpecValidator. Matches the
spec convention introduced for traefik, authentik, drawio, garage and
nextcloud.
2026-05-26 16:16:35 +02:00
Simon Bärlocher
c1951468ba
fix(opnform): drop production-looking secrets from defaults
opnform_app_key, opnform_jwt_secret, opnform_front_api_secret and
opnform_db_password shipped as real base64 strings in defaults — they
look like production secrets that just happen to be public. Set all
four to '' and rely on the existing Validate task (and the new
argument_specs marking them required) to fail fast when an inventory
forgets to override them.

Mirror the docstring comment to show how to generate each one with
openssl.
2026-05-26 16:16:35 +02:00
Simon Bärlocher
a99f491fc7
fix(opnform): address review feedback on vars header and meta boilerplate
* vars/main.yml: header was 'vars file for homarr' (copy-paste from the
  homarr role). Fixed to 'vars file for opnform'. File body is empty.
* meta/main.yml: replace ansible-galaxy init boilerplate with real
  metadata — author, description, license (MIT-0), min_ansible_version
  set to '2.15' as a string (galaxy schema requires str), galaxy_tags
  for discovery, and an empty dependencies list.

The third inline finding (dead roles/opnform/templates/compose.yml.j2)
is resolved by dropping the WIP commit a6f301e during the rebase rather
than removing it in a separate commit — the file no longer exists in
the rebased history.
2026-05-26 16:16:35 +02:00
256a82df1f
feat(opnform)!: add admin and OIDC bootstrap, rename role to lowercase
Rename roles/OpnForm → roles/opnform so the role resolves as
  digitalboard.core.opnform (Ansible collection convention is
  lowercase). Update tests/test.yml reference accordingly.

  Add automated admin user creation via POST /api/register, gated on
  opnform_admin_email + opnform_admin_password. Idempotent through a
  prior login probe. Without these vars the manual setup page flow is
  preserved.

  Add automated OIDC IdentityConnection setup via the per-workspace
  /api/open/workspaces/{id}/oidc-connections endpoint, gated on
  opnform_oidc_enabled. Hard-coupled to the admin bootstrap (the API
  requires an authenticated admin token); validation block fails fast
  if OIDC is enabled without admin credentials. Supports both an
  explicit opnform_oidc_group_role_mappings list and a fallback
  opnform_oidc_admin_group convenience var.

  Convert opnform_oidc_scopes from space-separated string to YAML list
  to match OpnForm's API expectation. Rewrite README "First login" and
  "OIDC setup" sections to reflect that self-hosted OpnForm does not
  ship a pre-seeded admin and to document the new bootstrap paths.
  BREAKING CHANGE: opnform_oidc_scopes changed from space-separated
  string to YAML list. Inventories that override it must update from
  "openid profile email" to [openid, profile, email].
2026-05-26 16:16:35 +02:00
eb51b6a054
chore: add new role for OpnForm 2026-05-26 16:16:35 +02:00
Simon Bärlocher
14c81657d7
docs(roles): add argument_specs and README for traefik, authentik, drawio, garage, nextcloud
Each of the five roles touched in this branch now ships:

* meta/argument_specs.yml: typed schema for every variable in
  defaults/main.yml plus the optional inputs surfaced via this
  branch (traefik_extra_hosts, authentik_host_rewrite_domains,
  authentik_proxy_apps.mode / .allowed_groups, drawio_extra_domains,
  drawio_authentik_forward_auth*, garage_webui_authentik_forward_auth*).
  All five specs load cleanly through ansible-core's
  ArgumentSpecValidator.

* README.md: replaces the ansible-galaxy boilerplate (where it was
  still in place) with a focused write-up — service vars, required
  secrets, ForwardAuth/idempotency notes, dependencies, and a working
  example playbook. authentik and garage READMEs are rewritten to cover
  the new knobs while preserving their existing content.
2026-05-26 14:16:47 +02:00
Simon Bärlocher
1f9292cc9a
feat(drawio): support extra hostnames via drawio_extra_domains
Add drawio_extra_domains (list, default empty). The traefik Host rule
on the drawio router now expands to Host(<canonical>) || Host(<extra>)
... so the same container can answer on additional FQDNs — e.g. an
internal *.int.* name so a DMZ reverse-proxy can reach drawio via a
backend hostname covered by the local traefik cert.

Empty by default; behaviour unchanged for existing inventories.
2026-05-26 14:16:23 +02:00
Simon Bärlocher
d476bca4f5
fix(nextcloud): in-container patch for UserConfig::getValueBool TypeError
nextcloud/server#59629: under PHP 8.x with OPcache,
UserConfig::getValueBool() passes a non-string from getTypedValue()
straight into strtolower(), throwing a TypeError on every authenticated
request once user_ldap is involved. Fix landed in master (PR #59646)
but no stable33 backport made it into 33.0.4.

Discover all compose-managed nextcloud containers, check whether the
`strtolower((string)` cast is already present, and `sed` it into
`lib/private/Config/UserConfig.php` on the ones that still ship the
broken version. Idempotent via grep guard so re-runs are no-ops.

Remove this block once the deployed image >= 33.0.4 ships the upstream fix.
2026-05-26 14:04:33 +02:00
Simon Bärlocher
aea6dec081
fix(nextcloud): make occ-driven config tasks idempotent
Every `occ config:app:set` / `ldap:set-config` / `notify_push:setup`
call previously fired on every play, marking changed even when the
stored value already matched. Now we read the current value first and
only invoke the setter when it differs:

* richdocuments (collabora): pre-read wopi_url, public_wopi_url,
  disable_certificate_verification, wopi_allowlist into a fact map;
  guard each `config:app:set` and tag `richdocuments:activate-config`
  with `changed_when: false` since it's a discovery refresh.

* drawio: same pattern for DrawioUrl, DrawioTheme, DrawioOffline,
  comparing as strings (occ stores booleans as "1"/"0").

* user_ldap: pre-read `ldap:show-config s01 --output=json`, parse JSON
  defensively (occ logs interleave on stderr), and skip per-key
  `ldap:set-config` calls when the stored value already equals the
  desired one.

* notify_push: skip `notify_push:setup` when the stored base_endpoint
  already matches the computed URL.

* plugins: `app:install`/`app:enable` were treating "already installed/
  enabled" output as a change. Add the negative match to `changed_when`
  so re-runs of a fully-provisioned site report ok rather than changed.
2026-05-26 14:04:17 +02:00
Simon Bärlocher
1157448d59
fix(garage): make bootstrap & provision idempotent across reruns
* bootstrap: `garage layout show` truncates node IDs to 16 chars, but
  the membership check compared against the full hex. After the first
  successful join, subsequent runs no longer found the short ID in
  `layout show` and re-issued `layout assign`, marking the task
  changed every time. Compare against both the truncated and the full
  form so a configured node stays detected. Also tag the read-only
  `garage node id` / `layout show` probes with `changed_when: false`.

* provision keys: the old parser sliced `stdout_lines[1:]` to drop the
  header but missed that INFO log lines and ANSI escapes can interleave
  with table rows. Replace with an explicit `^GK[0-9a-fA-F]+` filter
  after stripping ANSI, so probe-output noise no longer corrupts the
  existing-keys set and triggers spurious `key new` calls.

* provision buckets: same class of fix — match `^[0-9a-f]{16}\s` data
  rows instead of slicing `[2:]`, which broke when the table header
  wasn't exactly two lines.

* provision permissions: pre-read `bucket info` for each (key, bucket)
  pair and only run `bucket allow` when the current `RWO` flag set for
  that key ID doesn't already match the desired permissions. Previously
  `bucket allow` ran unconditionally and reported changed every play.

* `changed_when: false` on all read-only probes (`key list`, `key info`,
  `bucket list`).
2026-05-26 14:03:58 +02:00
Simon Bärlocher
c27584cd9c
feat(drawio,garage): optional Authentik ForwardAuth in front of UIs
Add `*_authentik_forward_auth` + `*_authentik_forward_auth_url` knobs to
both roles. When enabled:

* drawio: traefik attaches a ForwardAuth middleware pointing at the
  authentik embedded outpost; unauthenticated requests get redirected
  to log in and downstream sees X-Authentik-* identity headers.

* garage WebUI: same ForwardAuth wiring, and `AUTH_USER_PASS` is dropped
  from the container env so authentik is the only gate. Tasks now key
  the htpasswd hash workflow off `_garage_webui_htpasswd_active`
  (`webui_enabled AND NOT authentik_forward_auth`); when authentik
  fronts the UI we skip hashing entirely. htpasswd hash is also now
  cached on disk and re-verified via `htpasswd -vbB` so unchanged
  passwords stop showing as `changed=true` on every run.

Both knobs default to `false`, preserving existing htpasswd/plain behaviour.
2026-05-26 14:03:38 +02:00
Simon Bärlocher
da103a59f2
feat(authentik): split-horizon host rewrite + proxy-app mode/group bindings
* `authentik_host_rewrite_domains`: extra hostnames that reach the
  authentik container but make it generate URLs (OIDC issuer, reset
  links) as if requested from the canonical `authentik_domains[0]`.
  Each entry gets its own traefik router and a URL-based loadbalancer
  service that disables passHostHeader and pins X-Forwarded-Host via
  middleware, so server-to-server calls on internal FQDNs keep traffic
  in the LAN while the iss claim stays aligned with the public host.
  Uses a network alias on the canonical FQDN so traefik (sharing the
  network) resolves the URL upstream to this very container.

* proxy-app blueprint:
  - `mode` (default `forward_single`) lets callers pick between proxy,
    forward_single and forward_domain providers in one template.
  - `allowed_groups`: when set, emit one PolicyBinding per group on
    the application; authentik OR-evaluates bindings, so users in any
    listed group pass and others are denied.

Existing inventories with an empty list see no behavioural change.
2026-05-26 14:03:05 +02:00
Simon Bärlocher
afe5950d77
feat(traefik): configurable extra_hosts for container DNS overrides
Add `traefik_extra_hosts` (list of `host:ip`) that maps straight into
the traefik container's compose `extra_hosts`. Needed when a downstream
middleware (e.g. ForwardAuth to authentik on a sibling LAN) has to
resolve a public FQDN to an internal IP because the DMZ doesn't hairpin
the public address back inside.

Empty by default; behaviour unchanged for existing inventories.
2026-05-26 14:02:43 +02:00
Simon Bärlocher
02d45026a5
feat: drop blanket recreates, ACME-DNS knobs, notify_push override
- Drop `recreate: always` from collabora/drawio/homarr/opencloud/traefik
  handlers and the authentik_outpost_ldap start task. `up -d` with
  `state: present` already recreates exactly the services whose
  compose definition changed; the blanket recreate was forcing
  restarts even when nothing relevant moved.
- Rewrite the `*_domains` Traefik Host loop to the `Host(\`a\`) ||
  Host(\`b\`)` form across authentik/collabora/garage/nextcloud so the
  rule still matches when traefik can't normalize the comma-form into
  the same canonical shape.
- Traefik: add `traefik_acme_tcp_only` (sets LEGO_EXPERIMENTAL_DNS_TCP_ONLY)
  and `traefik_acme_disable_ans_checks` (disables lego's authoritative-NS
  propagation check) for environments where the DNS path between the
  traefik container and the zone's nameservers is constrained.
- Traefik DMZ collector: two-step merge so a `traefik_dmz_exposed_services`
  entry that sets its own `backend_host` wins over the host fallback;
  lets a route target an internal FQDN covered by the backend cert's
  SANs instead of the raw IP.
- Nextcloud: add `nextcloud_notify_push_domain` override for the
  `occ notify_push:setup` call so the setup check can hit an internal
  FQDN instead of hairpinning through the DMZ. Push router now matches
  every entry in `nextcloud_domains`.
- Nextcloud: also %2F-escape slashes in the postgres user/password
  inside the notify_push DATABASE_URL.
2026-05-20 22:44:41 +02:00
Simon Bärlocher
36e3a4b688
feat: domain list refactor + demo-gymburgdorf fixes
- Refactor: collapse `*_domain` + `*_extra_domains` into a single
  `*_domains` list across authentik, collabora, garage and nextcloud
  roles. First entry is the canonical FQDN (used for OVERWRITEHOST,
  BASE_URL, notify_push setup and garage root_domain).
- Authentik blueprint: guard the OAuth sources block so an empty
  `authentik_login_sources` no longer renders an invalid YAML key.
- Nextcloud: introduce `nextcloud_collabora_public_domain` and set
  Collabora's `public_wopi_url` separately from the server-to-server
  `wopi_url` so browsers can reach Collabora via the public name while
  Nextcloud still talks to it on the internal one.
- Nextcloud: URL-encode the postgres user/password in DATABASE_URL.
2026-05-20 22:13:34 +02:00
78095cca1d Merge remote-tracking branch 'origin/main' 2026-05-20 12:41:52 +02:00
61193e26f4
refactor(homarr): extract layout packing to filter plugin 2026-05-19 11:19:29 +02:00
27ed51ee95 chore: ignore python bytecode cache 2026-05-18 18:02:59 +02:00
e0cb1ac68c Merge branch 'feature/homarr' 2026-05-18 17:25:15 +02:00
bbbd1c8940 fix: (Homarr) removed small mistakenly added entry in defaults 2026-05-18 10:47:06 +00:00
1c7ecabcaf Merge remote-tracking branch 'origin/feature/homarr' into feature/homarr 2026-05-18 09:45:28 +02:00
9fc34dfb29
Merge branch 'feature/homarr' 2026-05-13 15:40:52 +02:00
2aa1df8614
chore(homarr): added readme and removed test env contents 2026-05-13 15:37:13 +02:00
308bf50122
chore(homarr): remove digitalboard-specific defaults 2026-05-13 15:37:12 +02:00
c1c1a84591
feat(homarr): make apps list configurable with auto-layout 2026-05-13 15:37:12 +02:00
d4eaa5f12c
refactor(homarr): extract seed SQL into template 2026-05-13 15:37:12 +02:00
3c35b8782e
fix: reomved remnants of removed env / fixed encription key validatiion 2026-05-13 15:37:11 +02:00
f4084ba078
refactor(homarr): drop service_name var and rename db_dir to db
- homarr_service_name removed, replaced with fixed "homarr" string
- homarr_db_dir renamed to homarr_db (variable points to a file, not a dir)
2026-05-13 15:37:11 +02:00
123769a4f4
feat(homarr): use handler for restart, validate encryption key 2026-05-13 15:37:11 +02:00
bdb1b03a18
refactor(homarr): align vars with homarr_ prefix, EN-only strings 2026-05-13 15:37:11 +02:00
c060d6136a
fix(homarr): salt column, bcrypt newline, transaction safety 2026-05-13 15:37:10 +02:00
23ea8dafc9 Chore: add admin user and seed staging
added creation of the admin user, the basic homeboard and all basic setup tasks.
Todo: Cleanup
2026-05-13 13:30:34 +00:00
5608daadaa chore: base config and deployment for role homarr 2026-05-13 13:30:34 +00:00
1fcb433aae chore: add new boilerplate role for homarr 2026-05-13 13:30:34 +00:00
967ffb0c2d
fix: leading space in extra networks
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-04-10 14:34:15 +02:00
c27b4d9488
feat: add blueprints for authentik ldap outpost and render values directly instead of using env vars
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-04-10 14:33:52 +02:00
d25f1c5304
chore: add authentik outpost deployment
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-04-10 11:27:07 +02:00
dbcccc090b
feat: ability to set extra networks for opencloud
needed for ldap outpost

Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-04-10 11:19:10 +02:00
e2fae25592
feat: make nextcloud_notify_push_image configurable
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-04-10 11:18:28 +02:00
468ed34550
feat: ability to set extra networks for nextcloud
needed for ldap outpost

Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-04-10 11:17:42 +02:00
422b196831
Chore: add admin user and seed staging
added creation of the admin user, the basic homeboard and all basic setup tasks.
Todo: Cleanup
2026-04-07 16:58:28 +02:00
77484f1944
chore: add new empty role skeleton for authentik_outpost_ldap
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-04-02 11:51:58 +02:00
aa8baad630
feat: opencloud group provisioning via oidc
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-03-13 16:43:02 +01:00
6f4cc2bdb3
feat: nextcloud ability to get groups from ldap backend
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-03-13 15:37:33 +01:00
d517f77b6c
feat: add file_lock and notify_push configuration to nextcloud role
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-03-13 15:22:09 +01:00