The drift check that runs on an unchanged vhost escalated on any status the
app does not accept. That escalation notifies restart-openresty, whose handler
runs container service update --force at a measured 40.32s mean over 186 fires,
and openresty sets service_update_order stop-first, so the update stops the
serving task before the replacement starts.
A 5xx cannot justify that. OpenResty emits one only after a server block
matched and proxied, so the vhost is loaded and the proxy is not at fault.
Restarting it takes the front end away from every other vhost and leaves the
backend exactly where it was.
Require the status to be a stale-config signature as well, the same [-1, 404]
the sibling escalation has used since 2c192ab7e6. Ansible's uri returns -1 when
the TLS handshake is rejected, which is what default_server.conf.j2 does when no
server block matches, and 404 is what that same default server returns on port
80. Both mean the vhost is not loaded, which is the only case a task redeploy
repairs.
The sibling's reload arm is deliberately not copied. It reads openresty_reload,
which reload.yml registers only when a reload ran. This branch notifies nothing,
so the variable would carry the previous domain's result.
default(-1) keeps the test total. The probe has no when of its own and shares
the block guard with the escalation, so site_check is always registered and a
missing status can only mean a probe that got no response.
Across 98 job logs the block executed 6 times, answering 200 four times and 405
twice, and all six keep their outcome. The other 488 of 494 evaluations skipped
the block outright, because a fresh CI host renders every vhost as changed. A
repeat deploy of the same inventory is what reaches this branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The post-reload probe escalated on any status the app does not accept, and a
forced OpenResty task redeploy costs a measured 40.32s. In four CI job logs it
fired 11 times per job on 502 and 500 only, never on 404 and never on a
connection failure.
Those statuses refute the escalation rather than justify it. OpenResty emits
5xx after it has matched a server block and proxied, so they prove the vhost is
loaded and the reload took effect. The default server cannot emit them at all:
it returns 404 on port 80 and rejects the handshake on 443, and it carries no
proxy_pass. In eight of eight round-one escalations the upstream app had not
been deployed yet when its vhost was probed, the deploy trigger following the
502 by up to 52 seconds.
The log also holds a control. A full 40s redeploy completes three to six
seconds before every one of the 28 post-reload probes, notified by another
pending restart in the same flush. Seventeen then pass and eleven do not, same
proxy, same recency, so the differentiator is the backend. The escalation
answers that by running the same redeploy again, and nothing re-probes after it.
Narrow the trigger to the two signatures that do prove a stale configuration,
plus the case where the reload never landed because the container was
mid-recreate, which reload.yml tolerates. Worth 438 to 470 seconds per job.
The status test also gains the connection-failure case. Today the
`status is defined` clause excludes it, so the one symptom a genuinely stale
config produces could never fire the escalation at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lookup(tls, <application_id>) resolves through resolve_primary_domain_from_app, so every iteration of a multi-vhost loop probed the same primary domain. For seaweedfs that meant checking s3. three times and filer. and master. never, right after writing their vhosts.
Passing the domain instead returns it unchanged (utils/tls_common.py:221-227). Resolve the probe URL and the accepted status codes from that one term in a second set_fact, so the URL and the codes cannot disagree even when the lookup falls back to the primary domain.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The front proxy hardcoded [200, 301, 302] and escalated to a full OpenResty redeploy on anything else. Apps that answer 403, 404 or 405 by design tripped it on every run: 19 escalations in one Nextcloud job at a measured median of 40.5s each.
roles/<app>/meta/server.yml already declares status_codes per canonical key, and sys-ctl-hlth-webserver already reads it. Move the resolution into utils/roles/applications/status_codes.py so both consumers share one definition of DEFAULT_OK, and expose it to templates as the accepted_status_codes filter.
The proxy gate takes the union of the default and the declaration rather than replacing it. Most declarations are narrower than the triple, so replacing would escalate vhosts that answer perfectly well. Contract checking stays with sys-ctl-hlth-webserver, which keeps replacement semantics.
_normalize_codes and _valid_http_code became dead in the health filter and are removed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both escalation tasks decide on a registered probe that carries failed_when: false, so the status never reaches the log and every probe prints ok:. Measured in run 33828309745 job 100890535287: the site-down branch fired 4 times and the post-reload fallback 15 times across 30 domains, which is 19 of the jobs 27 full redeploys at roughly 38s each. Nothing in the artifact says what any of them answered.
Ansible renders a task name before it evaluates when:, so carrying the status in the name reports it for all 30 domains while the skipping or changed line below says which one escalated. That costs no extra task.
The names stay under the 80 characters test_name_emoji_and_length allows by dropping the domain, which the neighbouring verify task already names.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A handler name was doing two jobs at once: the line an operator reads in the
play output, and the identifier a `notify:` binds to. The two pull in opposite
directions -- the log wants "restart the SSH service, with diagnostics", the
binding wants a token that survives quoting, YAML flow style and a shell
round-trip -- and a name that changes for readability silently changes which
handler runs.
Every handler that a notify reached by name now carries a `listen:` with that
exact wording, so the binding is explicit before anything is renamed. The
topics themselves become identifiers: `compose up` reads `compose-up`,
`Restart NFS ganesha` reads `restart-nfs-ganesha`, 22 of them across both
sides of every notify. Where the topic is assembled at runtime, both branches
moved together: the conditional in sys-service carries two literals, and the
LDAP renderer interpolates `import-{{ folder }}-ldif-files`, which is what
makes those handlers reachable at all.
Freed from the binding, the names take the emoji prefix every other task line
already has.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A deployment that keeps the shipped DOMAIN_PRIMARY serves names under
infinito.localhost. Those exist in no zone - coredns only knows the outer
stack's domain - so anything reaching the deployment's own domain from the host
depends on the myhostname NSS module, which maps *.localhost to loopback per RFC
6761. 3ebc4d152 established that and titled it "on every target", but the role
carrying it is reachable from exactly one place, sys-svc-mail, so a deploy
without mail never gets the guarantee.
web-app-bigbluebutton is where that surfaces. Job 94846729503 of run
31823654078 replays the role's README per distro; fedora and arch answered its
Greenlight gate with 200, and debian failed 30 attempts with elapsed: 0 and
"<urlopen error [Errno -2] Name or service not known>". In that distro's window
sys-hostname does not appear at all - the play reaches sys-stk-front-proxy at
18:27:45 and the gate at 18:30:06, both before anything pulls the role in. The
asymmetry is declared, not incidental: meta/packages.yml gives Debian
libnss-myhostname and both other families an empty list, because there the
module ships inside systemd.
The front proxy is the chokepoint every domain-serving role passes through, and
it stands up two domain probes of its own, so the guarantee belongs in front of
it. Including sys-hostname there would have been wrong: half that role is
gated only on DOCKER_IN_CONTAINER, so on a production target it would newly run
ansible.builtin.hostname on every domain-serving deploy and rename hosts whose
inventory entry is an alias. CI cannot show this - it always runs in a
container, where exactly those tasks skip.
So the resolution half moves to sys-svc-nss and sys-hostname includes it,
leaving the mail path unchanged while the front proxy takes only what it needs.
required_by names both callers rather than taking the coverage opt-out
sys-hostname still carries as a TODO.
Verified: tests/lint 335 passed / 307 subtests, tests/integration/roles 107
passed / 610 subtests, make readme-check clean. The role itself is unchanged in
behaviour and was already exercised on all five CI distros in job 94636437319 -
ubuntu and debian install the package, fedora, arch and centos skip it, and the
nsswitch line is a no-op everywhere.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adding a domain redeployed the whole OpenResty task. Measured in the green onion job odoo 0: 26 redeploys at 41 to 43 seconds each, 18.1 minutes of a 4h44m job. A reload costs one to two seconds.
It is safe for a vhost because of where the file lands. nginx.py builds files.domain as servers_dir + protocol + domain.conf, and servers_dir is /etc/nginx/conf.d/servers/ - inside the conf.d DIRECTORY bind. A directory keeps its inode across the atomic write-and-rename that stack_host_template performs, so the running master already sees the new file. Only nginx.conf is bound as a FILE, whose inode the task pins at create time; that path keeps the redeploy, as do the default server config and the site-is-down case.
History matters here: a9ca2ae68 once made the handler reload for everything, including nginx.conf, and f9dea42c6 replaced it with the force-recreate. That replacement rode along in a commit about apt mirrors and explains itself nowhere, so the only recorded reason is the file header - the file bind and its pinned inode. That reason does not apply to a vhost in the directory bind, which is why this reload is confined to that case.
The reload cannot prove it took, so it does not have to. After the flush the domain is probed once, and anything other than 200, 301 or 302 notifies the full redeploy. The worst case is therefore one HTTP probe on top of the behaviour this replaces, never a domain left unserved behind a green deploy.
OpenResty runs replicated 1/1 (services.txt of a real deployment), so the exec reaches the only instance and no replica can keep stale config.
Not verified: the reload itself was never executed against a running stack.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The swarm front-proxy pointed at openproject_web on the entity's ports.internal.http (80), which the Apache proxy sidecar and its compose publish mapping use, but the Puma web service listens on 8080, so OpenResty got a 502. Add services.openproject.proxy.internal_port=8080 and resolve front_proxy_internal_port per-app from services.<entity>.proxy.internal_port (mirroring how service_key already resolves), so it stays entity-scoped and cannot leak into co-located apps like the reverted proxy_internal_port var did. Compose is unaffected (resolve_upstream uses the local published port there).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tests/lint/repository/test_name_emoji_and_length.py: every Ansible task
name and GitHub Actions workflow/job/step name must start with an emoji (first
char not 0-9/A-Za-z) and be <= 80 chars.
- emit emoji names at the source: cli/build/include generator now writes
"🔧 setup <app>" / "🚿 flush handlers after <app>" so generated
tasks/groups/*-roles.yml satisfy the convention too
- convert existing task/step/job/workflow names to emoji-prefixed, choosing a
task-fitting icon per action (📦 install, ✅ ensure, 🔍 probe, 🥾 bootstrap,
🚀 deploy, 🧩 service-module load, ⬇️ checkout, 📂 include-routines, ...)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve 69 conflicts across ~70 files: union main's image-version bumps and new service fields (bond, resource limits) with the swarm branch's default_placement / NFS / IS_STACK_HOST config; retire the three per-deploy-type test workflows main still edited (consolidated into compose+swarm); adopt main's run-once 01_core dispatchers while re-applying each role's swarm guard. Fix the post-merge lint surfaced by main's stricter rules: bond + resource-budget keys, container-exec stack-host gating + resolver routing, leaky/dead proxy vars, suitecrm once-flag, xwiki variant nocheck, nextcloud oversized-block to gated include, matrix-mdad capped at 3g. make autoformat clean, make test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In swarm the bluesky api/web/view vhosts proxied to the wrong upstream: the
front proxy used the app entity name as the service key and resolve_upstream
hard-failed without an internal port (bluesky has no
services.bluesky.ports.internal). Pass the swarm service keys (pds /
bluesky-broker / view) and the internal ports (added to
services.{api,web,view,broker}) per mode, and freeze + re-pass
proxy_internal_port in sys-stk-front-proxy so it survives the nested role loads
and reaches html.conf.j2 (mirrors the existing front_proxy_service_key freeze).
The sys-stk-front-proxy change is compose-neutral (resolve_upstream ignores
internal_port in compose) and a no-op for the 9 non-passing apps (internal_port
stays ''); it also co-fixes web-svc-coturn, whose deliberate
proxy_internal_port: 80 was being lost (coturn has no
services.coturn.ports.internal, so swarm resolve_upstream hard-failed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A role's vars/main.yml is auto-loaded into play scope and persists, so proxy
override vars (webserver_*) leaked into the central front-proxy render of OTHER
roles (re-evaluated against the wrong application_id) — silently applying one
role's value elsewhere, or hard-failing when the lazy lookup hit a role lacking
the key (the seaweedfs<->matrix client_max_body_size crash).
- Templates (sys-svc-proxy html/upload/ws_generic, matrix synapse homeserver)
resolve client_max_body_size from the role's own server config
(lookup('config', application_id, 'server.client_max_body_size', '100m')).
- Move every role-level webserver_* out of vars/main.yml into task-scoped
include params (9 roles); they apply only to that role's proxy and can't leak.
- sys-stk-front-proxy resets webserver_* to safe defaults via task-scoped vars
on the render tasks, so a leaked value from any other source is neutralized;
a role's own include params (#20) still override the reset (precedence-tested,
direct + nested).
- Add test_no_leaky_proxy_role_vars lint forbidding webserver_* in vars/main.yml
(suppressable via '# nocheck: leaky-proxy-var').
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Centralise the nginx proxy_pass directive in one source of truth (utils/networks/proxy.py + plugins/lookup/proxy_pass.py). Compose keeps the literal 127.0.0.1:<local> upstream; swarm resolves the service over the shared overlay at request time (variable upstream + http resolver) so a rescheduling backend never blocks openresty startup. Converts the html/media/upload/ws location partials, the keycloak oauth2 and login-iframe endpoints, and the logout endpoint to the lookup; adds the swarm resolver and the front-proxy service_key plumbing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a single grammar for per-line, per-block, and per-file
suppression markers across the test suite, replacing seven ad-hoc
syntaxes that had drifted apart.
Helper
utils/annotations/suppress.py exposes is_suppressed_at,
is_suppressed_in_head, is_suppressed_anywhere, line_has_rule, and
suppressed_line_numbers. Markers may use either `noqa` or `nocheck`
(case-insensitive synonyms), live inside `#`, `//`, `{# ... #}`, or
`<\!-- ... -->` comments, and combine multiple kebab-case rules
comma-separated. 17 unit tests cover the grammar in isolation.
Refactored consumers
- tests/external/repository/test_urls_reachable.py (rule: url)
- tests/external/docker/test_image_versions.py via
utils/docker/version_updater.py (rule: docker-version)
- tests/lint/repository/test_no_direct_yaml_calls.py (rule:
direct-yaml)
- tests/lint/ansible/test_service_shared_consistency.py (rule:
shared)
- tests/lint/ansible/roles/test_web_app_email_integration.py (rule:
email)
- tests/lint/repository/test_python_file_size.py (rule: file-size)
- tests/lint/ansible/test_run_once_tags.py +
tests/integration/roles/run_once/test_schema.py (rules: run-once,
run-once-suffix)
Marker migration
12 role tasks/main.yml files: `# run_once_<key>: deactivated`
-> `# nocheck: run-once`. roles/web-app-suitecrm/meta/services.yml
drops a malformed inline `noqa: intentional legacy pin` from a TODO
comment so the new parser does not pick it up as a stray rule.
Caching wins
Test files that previously loaded YAML / read files via raw
yaml.safe_load + Path.read_text now route through utils.cache.yaml
(load_yaml_any / load_yaml_str) and utils.cache.files.read_text:
- test_service_shared_consistency.py
- test_web_app_email_integration.py
- tests/integration/roles/run_once/test_schema.py
- utils/docker/version_updater.py
This both speeds up repeated reads and stays consistent with the
direct-yaml lint policy.
Documentation
New SPOT page at docs/contributing/actions/testing/suppression.md
with grammar, position semantics, the catalog of rules, examples,
and an "adding a new rule" checklist. lint.md and external.md drop
their per-rule tables and link to the new page. All touched docs
conform to docs/contributing/documentation.md (no em-dash thought
breaks, no SPOT self-labels, RFC-2119 keywords used consistently).
Settings sort tooling
scripts/lint/sort_claude_settings.sh wraps utils/claude/settings_sort.py
and is wired into scripts/lint/autoformat.sh. The tooling fixes the
ASCII-ascending requirement enforced by
tests/lint/repository/test_claude_settings_sorted.py; the test's
failure message now points operators at the script. .claude/settings.json
is committed in sorted form.
Single atomic landing for three back-to-back role-meta refactor requirements
(see docs/requirements/008-role-meta-layout.md, 009-per-role-networks-and-ports.md,
010-role-meta-runafter-lifecycle-migration.md).
req-008 — role meta layout
* Move every role's metadata under meta/<topic>.yml with file-root
convention (no wrapping key matching the basename):
- schema/main.yml + config/main.yml.credentials → meta/schema.yml
- users/main.yml → meta/users.yml
- config/main.yml.server → meta/server.yml
- config/main.yml.rbac → meta/rbac.yml
- config/main.yml.compose.services + every other top-level key
of config/main.yml inlined under <entity>.<key>
→ meta/services.yml
- config/main.yml.compose.volumes → meta/volumes.yml
* Drop the legacy schema/, users/, config/ directories from every role.
* meta/schema.yml: support nested credential keys, optional `default:`
field (Jinja literal, not rendered/validated/regenerated), implicit
`algorithm: plain`.
* Materialised paths drop the `compose.` prefix:
compose.services.<e>.<…> → services.<e>.<…>
compose.volumes.<k> → volumes.<k>
<former-top-level>.<…> → services.<entity_name>.<former-top-level>.<…>
* Update every Python consumer (utils/cache, utils/manager, utils/applications,
plugins/lookup, plugins/filter, cli/*) plus every Jinja template, vars/*.yml
and task file to the new paths.
* No legacy fallback. Repository-wide grep for `compose.services.` /
`compose.volumes.` / `schema/main.yml` / `users/main.yml` / `config/main.yml`
is empty outside docs/requirements/ and tasks/utils/migrate_meta_layout.py.
* Add tests/lint/repository/test_role_meta_layout.py guarding against the
old paths returning, and an explicit lint guard that meta/variants.yml
files do not carry a `compose:` wrapper.
req-009 — per-role networks/ports
* Move group_vars/all/08_networks.yml.defaults_networks.local.<role>.{subnet,
dns_resolver} into roles/<role>/meta/server.yml.networks.local.{subnet,
dns_resolver}.
* Delete group_vars/all/09_ports.yml entirely; ports now live on the
service entity that exposes them at
roles/<role>/meta/services.yml.<entity>.ports.{inter,local,public}.
* Rename + int-cast every former top-level `services.<entity>.port`
field to `services.<entity>.ports.inter` (single int).
* Always-category-keyed maps for ports.local / ports.public; relay
spans use {start, end} integers.
* Add PORT_BANDS map to group_vars/all/08_networks.yml — single source
of truth for the per-<scope>.<category> band ranges, consumed by both
the new CLI helpers and the lint test.
* New CLI helpers (cli meta ports suggest, cli meta networks suggest)
walk the live role tree gap-first then increment-fallback, with
deterministic output and non-zero exit on capacity overflow.
* cli create role consumes both helpers when scaffolding a new role.
* Lint extensions: bare-int port shape rejection, host-bound port
collision check (single ints + relay span integers, inter excluded),
band-membership check with documented allow-list for the legacy
web-app-bigbluebutton.bigbluebutton.local.http: 48087 entry.
req-010 — run_after / lifecycle migration
* Move galaxy_info.run_after and galaxy_info.lifecycle out of
meta/main.yml into meta/services.yml.<primary_entity>.{run_after,
lifecycle}; <primary_entity> is the value returned by
get_entity_name(role_name).
* For multi-entity roles whose primary entity has no compose service
(web-app-bluesky → bluesky, web-app-matrix → matrix, desk/dev/drv/
gen roles), create a dedicated metadata-only top-level entry under
<primary_entity>.
* Introduce utils/roles/meta_lookup.py with get_role_run_after /
get_role_lifecycle that resolve <primary_entity> once and read from
meta/services.yml; degrade gracefully when meta/services.yml is
absent OR the field is absent; raise MetaServicesShapeError when the
file exists but is malformed.
* Update every consumer (utils/service_registry, utils/invokable,
cli/meta/applications/resolution/run_after/__main__, cli/meta/roles/
lifecycle_filter/__main__, cli/meta/applications/resolution/combined,
cli/build/{tree,graph,role_include}, cli/deploy/development/{common,
init,deps,deploy}, plugins/filter/canonical_domains_map, etc.) to
use the helper.
* Lint guards: no run_after/lifecycle in meta/main.yml; lifecycle
values restricted to {planned,pre-alpha,alpha,beta,stable,deprecated};
run_after/lifecycle never on a non-primary entity; empty run_after
lists rejected.
Also folded into this change set
* services_disabler now writes `services.<svc>.{enabled,shared}` into
host_vars instead of the old `compose.services.<svc>.{...}` path; the
discover/find pair walks meta/services.yml and only treats entries
carrying enabled/shared/provides as provider declarations (so
metadata-only entries from req-010 do not create spurious provider
rows).
* service_registry skips `run_after` deps whose target was filtered
out via SERVICES_DISABLED instead of failing the whole load.
* cli/mirror/resolver and cli/create/inventory/mirror_overrides emit/
consume the new applications.<role>.services.<svc>.image shape
(mirror.md updated to match).
* utils/domains/list.build_applications_from_roles now reads
meta/server.yml directly (it had been silently returning {} since
config/main.yml stopped existing).
* utils/roles/resource_validation.filter_roles_by_min_storage reads
meta/services.yml.<entity>.min_storage instead of the legacy
compose.services path.
* web-app-bluesky pre-clones the social-app build context BEFORE
sys-svc-compose so the build context is on disk by the time
sys-stk-front-proxy/tasks/main.yml flushes the docker build handler.
* 4 meta/variants.yml files (svc-ai-ollama, web-app-phpmyadmin,
web-app-wordpress, web-app-keycloak) lose their `compose: services:`
wrapper so deep-merge over meta/services.yml lands on the new
file-root shape.
Validation
* Unit tests: 1571 passed.
* Lint tests: 50 passed.
* Integration tests: 161 passed (1 skipped).
* Full-cycle fresh-purged-apps deploy succeeded for the 8-app validation
set: web-app-{nextcloud,wordpress,yourls,bigbluebutton,bluesky,gitea,
discourse,mattermost}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces inline `applications`/`users` merging with dedicated lookup
plugins and runtime hydration. Introduces `add_csp_hash` filter,
integrates sort into `find_dock_val_by_bkp_entr`, splits the Keycloak
one-shot realm import into `05a_realm_import_run.yml`, and fixes the
Mastodon administrator task YAML. Removes obsolete `csp_hashes` filter,
`sys-token-store` Jinja filter, and empty CLI setup modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The TLS lookup has been renamed from `tls_resolve` to `tls` to better
reflect its responsibility as a strict TLS state resolver.
This refactor updates all usages across the codebase, including:
- lookup plugins and internal helpers
- roles, vars, tasks, and templates
- tests, stubs, and error messages
- documentation and changelog references
Behavior, lookup API, and semantics remain unchanged.
https://chatgpt.com/share/6985e635-5a94-800f-b794-937e566ee308
- Remove deprecated filter_plugins/tls.py and all dependent filters
- Introduce strict lookup plugin lookup_plugins/tls_resolve.py as single TLS authority
- Migrate all roles, templates, vars, and tasks to use tls_resolve (protocols, ports, URLs, cert paths)
- Enforce explicit TLS context (domains, applications, TLS_ENABLED, TLS_MODE)
- Support letsencrypt and self_signed with SAN handling and per-app overrides
- Update TLS provider roles to assert resolver state instead of setting facts
- Add comprehensive unit tests for tls_resolve lookup
https://chatgpt.com/share/6972b49b-b074-800f-a323-dd9fe69d02eb
with explicit per-application TLS resolution.
Key changes:
- Introduce sys-front-tls-layer as the single TLS abstraction
- Centralize certificate paths via TLS_CERT_FILE / TLS_KEY_FILE / TLS_CA_FILE
- Replace sys-svc-letsencrypt SSL templates with generic TLS layer includes
- Add tls_* Jinja filters (enabled, mode, protocol, port, websocket)
- Resolve TLS mode per application (letsencrypt | self_signed | off)
- Remove implicit defaults and WEB_* globals to fail fast on misconfiguration
- Support self-signed certificates with SAN awareness
- Make TLS behavior explicit and consistent across all roles
This decouples application logic from certificate providers and
allows switching TLS flavor without touching app roles.
https://chatgpt.com/share/69727c61-8e68-800f-a267-473a7e627978
Refactor proxy/webserver configuration variables to a consistent webserver_* naming scheme across roles. Replace legacy variables like proxy_extra_configuration, client_max_body_size, vhost_flavour, location_ws and ws_port with webserver_extra_configuration, webserver_client_max_body_size, webserver_vhost_flavour, webserver_websocket_location and webserver_websocket_port. Update NGINX vhost and location templates (html, upload, ws, basic, ws_generic) as well as callers (sys-front-inj-all, sys-stk-front-proxy, various web-app-* and web-svc-* roles) to use the new naming.
Tighten docker-compose Git repository handling by making docker_git_repository_pull depend on docker_git_repository_address being defined, a string and non-empty. This avoids accidental Git operations when the repository address is unset or of the wrong type.
Refactor the BigBlueButton role structure and fix deployment bugs: introduce 01_core.yml to orchestrate docker/proxy setup, database seeding, websocket map deployment, docker-compose overrides and admin/bootstrap logic in a single once-executed entrypoint. Rename supporting task files (02_docker-compose.yml, 03_administrator.yml, 04_dependencies.yml) and update tasks/main.yml to delegate via include_tasks with run_once_web_app_bigbluebutton. Improve Greenlight admin creation behavior by treating the 'Email has already been taken' error as a non-fatal, unchanged outcome and running user:set_admin_role as a fallback, both for the primary password and the OIDC starred-password path.
Also standardize vhost flavour selection for services like Mailu, Discourse, CDN, Collabora, Coturn, OnlyOffice, Simpleicons and web-svc-logout by explicitly passing webserver_vhost_flavour where needed and aligning client_max_body_size and websocket configuration with the new webserver_* variables.
Reference: ChatGPT conversation https://chatgpt.com/share/6931c530-bba8-800f-9997-dd61dc1d497b
- Renamed role `srv-tls-core` → `sys-svc-certs`
- Renamed role `srv-https-stack` → `sys-stk-front-pure`
- Renamed role `sys-stk-front` → `sys-stk-front-proxy`
- Updated all includes, READMEs, meta, and dependent roles accordingly
This improves clarity and consistency of naming conventions for certificate management and proxy orchestration.
See: https://chatgpt.com/share/68b19f2c-22b0-800f-ba9b-3f2c8fd427b0