Commit graph

109 commits

Author SHA1 Message Date
Simon Bärlocher
60464e6d23
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-27 23:12:23 +02:00
Simon Bärlocher
f0cd8ba432
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-27 23:12:23 +02:00
Simon Bärlocher
3855b3e0e7
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-27 23:12:23 +02:00
Simon Bärlocher
ce50bdb4d3
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-27 23:12:23 +02:00
Simon Bärlocher
6411f94cce
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-27 23:12:23 +02:00
Simon Bärlocher
99d8968a2e
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-27 23:12:23 +02:00
Simon Bärlocher
2104e5fe7d
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-27 23:12:23 +02:00
Simon Bärlocher
c3cf779532
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-27 23:12:22 +02:00
Simon Bärlocher
c11f019aae
fix(send): assert S3 credentials when storage backend is s3
When send_storage_backend=s3 the role previously deployed the container
with whatever was in send_s3_* (often empty strings from the defaults).
The container would then start, accept uploads, and fail to persist
anything silently. Same pattern as the validate blocks in coturn,
talk, bookstack and opnform: fail fast at task time with a clear error
that points at the four missing variables.

Skipped entirely when send_storage_backend=local (the default).
2026-05-26 15:40:21 +02:00
Simon Bärlocher
a492c3ee04
docs(send): add meta/argument_specs.yml
29 typed options with full defaults coverage (no required: true marks —
the role works with an empty S3 config when storage_backend=local).
Documents the send_domains list convention, the local-vs-s3 storage
choice, the timing/size limits and the Traefik / network wiring.

Loads through ansible-core's ArgumentSpecValidator. Matches the spec
convention used by the other roles in this collection.
2026-05-26 15:38:35 +02:00
Simon Bärlocher
b19ac2270a
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 15:38:34 +02:00
Simon Bärlocher
e1d604effc
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 15:38:34 +02:00
Simon Bärlocher
4655c8f037
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 15:38:34 +02:00
Simon Bärlocher
9a9039c4d3
docs(talk,coturn): add meta/argument_specs.yml
* coturn: 31 typed options including the 3 cert modes (acme/file/
  selfsigned), the RFC2136 acme.sh sidecar config and challenge alias
  subschema. coturn_static_auth_secret marked required.

* talk: 34 typed options covering the signaling/janus/nats triplet,
  TURN integration, MCU (janus) tuning, trusted-proxy CIDRs and the
  extra_hosts pin. talk_backend_secret, talk_turn_secret,
  talk_session_hashkey and talk_session_blockkey marked required.

Both specs load cleanly through ansible-core's ArgumentSpecValidator,
have 100% defaults/spec coverage, and match the convention introduced
for the other roles in this collection.
2026-05-26 15:35:19 +02:00
Simon Bärlocher
dc8f1e2ecd
fix(talk,coturn): correct vars file header (was 'httpbin')
Both new roles had 'vars file for httpbin' as the header comment in
vars/main.yml — copy-paste artefact from the httpbin role template.
Files are otherwise empty. Reviewer flagged both inline (PR review
comments 229 and 230).
2026-05-26 15:35:18 +02:00
05fb62c75d
feat(talk/turn/signaling/hpb): add role for Talk with backend services 2026-05-26 15:35:18 +02:00
Simon Bärlocher
2c2dbbc648
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 15:30:36 +02:00
951b1822fe
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 15:30:21 +02:00
Simon Bärlocher
30f3c16b59
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 14:58:36 +02:00
Simon Bärlocher
fb81f60f9d
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 14:58:18 +02:00
Simon Bärlocher
48d12a1b4a
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 14:58:10 +02:00
03af64ca2c
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 14:54:35 +02:00
53e80ad7be
chore: add new role for OpnForm 2026-05-26 14:47:57 +02:00
61193e26f4
refactor(homarr): extract layout packing to filter plugin 2026-05-19 11:19:29 +02:00
bbbd1c8940 fix: (Homarr) removed small mistakenly added entry in defaults 2026-05-18 10:47:06 +00: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
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
910986b808
feat: add drawio instance for nextcloud and opencloud
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-03-13 14:37:02 +01:00
f3f2b6d5b7
feat: add empty role skeleton for drawio role
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-03-13 13:44:53 +01:00
db21030a64
feat: add ldap backend to opencloud
Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
2026-03-13 11:43:11 +01:00