Skip to content

Development environment setup#6

Open
kzndotsh wants to merge 45 commits intomainfrom
cursor/development-environment-setup-635d
Open

Development environment setup#6
kzndotsh wants to merge 45 commits intomainfrom
cursor/development-environment-setup-635d

Conversation

@kzndotsh
Copy link
Contributor

Harden IRC/XMPP stack configurations, streamline env vars, and add a production deployment guide.

This PR addresses multiple security and best practice findings from comprehensive audits of UnrealIRCd, Atheme, and Prosody. It cleans up environment variable usage, clarifies dev/prod setup, and provides a detailed guide for deploying the stack with Nginx Proxy Manager in a two-server architecture.


Open in Web Open in Cursor 

Add except ban block for 172.16.0.0/12 (Docker bridge subnets) to prevent
bridge and other internal containers from being G-Lined by DNSBL checks.

Also fix antirandom except to use CIDR 172.16.0.0/12 instead of 172.17.*
to cover all Docker bridge network subnets.
The test_sends_relaymsg_when_capability_negotiated test was not mocking
cfg.irc_relaymsg_clean_nicks, causing it to pick up the env var from
.env.dev (BRIDGE_RELAYMSG_CLEAN_NICKS=true) and skip the /d suffix.
Now explicitly patches cfg to False, matching the companion clean_nick test.
Cross-reference audit of apps/unrealircd/config/unrealircd.conf.template
against UnrealIRCd 6.x upstream example.conf and modules.default.conf.

Covers: modules (A), TLS (B), set blocks (C), allow (D), listen (E),
link (F), log (G), except ban (H), blacklist (I), oper (J),
anti-flood/connthrottle (K), drpass (L), plaintext/outdated-tls (M),
websocket (N), spamfilter/badwords (O), third-party modules (P).

Findings: 3 critical, 8 warnings, 35+ OK, 12 suggestions.
Cross-reference audit of atheme.conf.template against upstream
atheme.conf.example (master branch) covering all 16 audit categories.

Key findings:
- 2 CRITICAL: example operator left in config; orphaned chanfix block
- 4 WARNING: debug loglevel, missing pbkdf2 v1 compat, duplicate scram ref
- 35+ OK: vast majority matches or exceeds upstream
- 8+ SUGGESTIONS: scram_mechanisms, exttarget modules, log separation
Critical fixes:
- Add drpass block for /DIE and /RESTART protection (IRC_DRPASS env var)
- Template WEBIRC password via ATL_WEBIRC_PASSWORD env var (was hardcoded)
- Restrict admin oper mask from *@* to Docker network (172.16.0.0/12)
- Require TLS for oper authentication (require-modes z)

Warning fixes:
- Add connthrottle config for connection throttling during attacks
- Add spamfilter defaults (ban-time, ban-reason, virus-help-channel)
- Add allow-user-stats to restrict sensitive stats to opers
- Restrict link block incoming mask to Docker network + localhost
- Restrict RPC user match to Docker network + localhost
- Tighten connect-flood from 20:10 to 5:60 for production
Critical fixes:
- Remove insecure 'operator jilles' block (SRA to anyone with that nick)
- Comment out orphaned chanfix{} block (chanfix/main module not loaded)

Warning fixes:
- Reduce loglevel from debug to production-appropriate categories
- Remove duplicate commented saslserv/scram reference
From reading every page on the UnrealIRCd wiki (Set_block, Security, Configuration):

- Add set::gline-address (was missing; only kline-address was set)
- Add set::snomask-on-oper +bBcdfkqsSo (recommended default for full server notice visibility)
- Add +H to modes-on-oper (hides oper idle time from non-opers)
- Add restrict-commands for private-message and private-notice with
  120s connect-delay for unidentified users (Security page recommendation
  to combat spam bots, exempts identified/webirc/reputation users)
Browsed every page on prosody.im/doc and modules.prosody.im, read
the full config/security/certificates/ports/modules/storage/MAM/
file_share/MUC docs plus community module pages.

Critical fixes:
- .env.example: flip TLS/auth defaults to secure (c2s_require_encryption,
  s2s_require_encryption, s2s_secure_auth = true; unencrypted_plain_auth = false)
- .env.dev.example: add insecure overrides for local dev (self-signed certs)
- Uncomment global ssl block (protocol tlsv1_2+, strong ciphers, secp384r1)
- Restrict http_status_allow_cidr from 0.0.0.0/0 to Docker network

Warning fixes:
- Disable legacyauth module (use SASL SCRAM-SHA-256 instead)
- Enable dont_archive_namespaces to exclude typing indicators and Jingle

Added: docs/audits/prosody-config-audit.md with full findings

Note: luacheck warnings on Prosody globals (dont_archive_namespaces, https_ssl)
are expected - these are valid Prosody config directives, not Lua standard globals.
Scanned all sources: .env.example, .env.dev.example, all compose files,
config templates (UnrealIRCd, Atheme, Prosody, Bridge, The Lounge),
scripts (init.sh, prepare-config.sh, cert-manager/run.sh),
web app env, and bridge Python code.

Comprehensive table with 150+ variables cross-referenced across 6 columns.

Findings:
- Category A: 37 orphaned vars (defined but never consumed)
- Category B: 33 undefined vars (consumed but not in .env.example)
- Category C: 6 inconsistency issues (XMPP port naming mismatch, etc.)
- Category D: 12 hardcoded values that could be env vars
- Category E: ~30 dead vars (PostgreSQL, Adminer, Nginx with no compose service)
- Category F: 14 duplicate variable pairs

Priority fixes: port naming mismatch, missing critical vars, dead service sections.
Full cross-reference audit of 150+ env vars across all sources (compose,
config templates, scripts, code). Changes:

Removed (orphaned/dead — no compose service or consumer):
- ATL_PROJECT_NAME, ATL_BASE_DOMAIN (never consumed)
- PROSODY_ENV (nothing reads it; ATL_ENVIRONMENT is the discriminator)
- ATHEME_UPLINK_SSL_PORT, ATHEME_LOG_LEVEL (not consumed by templates)
- THELOUNGE_DOMAIN (not consumed anywhere)
- ATHEME_CHANFIX_* (4 vars for commented-out module)
- PROSODY_ACCOUNT_DELETION_CONFIRMATION (consumed code is commented out)
- All POSTGRES_* vars (12 vars — no PostgreSQL compose service)
- All ADMINER_* vars (7 vars — no Adminer compose service)
- NGINX_HTTP_PORT, NGINX_HTTPS_PORT (no nginx compose service)
- ATL_SENTRY_DSN, ATL_INTERNAL_SECRET_IRC/XMPP (empty, never consumed)

Fixed (naming mismatch):
- Renamed XMPP_C2S_PORT → PROSODY_C2S_PORT (and S2S, HTTP, HTTPS) to
  match what xmpp.yaml compose actually consumes. The old XMPP_* names
  were silently ignored.

Fixed (broken interpolation):
- IRC_UNREAL_RPC_USER/PASSWORD: replaced ${WEBPANEL_RPC_USER} with
  literal values. Docker Compose does not expand ${VAR} inside .env files.

Added (previously undefined but consumed):
- CLOUDFLARE_DNS_API_TOKEN (cert-manager)
- PROSODY_STORAGE (docker-entrypoint.sh)
- PROSODY_C2S_DIRECT_TLS_PORT, PROSODY_S2S_DIRECT_TLS_PORT, PROSODY_PROXY65_PORT
- TURN_SECRET, TURN_EXTERNAL_HOST

Reorganized:
- Clear section headers with 12-factor notes
- Portal-consumed vars separated with explicit comment
- Grouped by service lifecycle, not arbitrary categories

Added: docs/audits/env-var-audit.md with full 150+ var cross-reference
Critical fixes:
- just prod/staging now run init.sh and pass --env-file .env (was bare
  docker compose with no init, no dirs, no config generation)
- prepare-config.sh only sources .env.dev when ATL_ENVIRONMENT=dev,
  preventing dev overrides from leaking into prod config generation
- Prosody .env.example cert paths changed from 'localhost' to 'atl.chat'
  (dev override in .env.dev correctly uses xmpp.localhost)

Cleanup:
- Removed meaningless --profile staging/prod from down commands (no
  services use these profiles; staging/prod are identical to default)

Added: docs/audits/dev-prod-lifecycle-audit.md with full trace of both
flows covering env, scripts, Docker, TLS, DNS, and networking
New: docs/deployment.md — comprehensive guide for deploying the atl.chat
stack on Server B behind an Nginx Proxy Manager on Server A, connected
via Tailscale mesh. Covers:

- Full architecture diagram and prerequisites
- Step-by-step Server B setup (.env configuration)
- DNS records (A, CNAME, SRV for XMPP federation)
- NPM proxy host config (HTTPS services: BOSH, WebSocket, Lounge, WebPanel)
- NPM stream config (TCP passthrough: IRC TLS, XMPP C2S/S2S)
- Certificate renewal and reload automation
- Security checklist, backup strategy, troubleshooting

Config changes:
- Prosody: add Tailscale CIDR (100.64.0.0/10) to trusted_proxies
- UnrealIRCd: add proxy 'npm-forwarded' block trusting Forwarded headers
  from gateway IP and Tailscale range

Note: UnrealIRCd does not support HAProxy PROXY protocol on raw TCP
listeners (upstream limitation). Direct IRC clients through NPM TCP
streams appear as the proxy IP. Web clients use WEBIRC for real IPs.
Per the UnrealIRCd Proxy block wiki docs (pasted by user):

- Migrated old `webirc {}` block (pre-6.1.1 syntax) to new `proxy {}`
  block with `type webirc` — we're on 6.2.0.1
- Changed `type forwarded` to `type x-forwarded` since NPM sends
  X-Forwarded-For headers (not RFC 7239 Forwarded headers)
- Added clear doc comments explaining each proxy block's purpose
- Noted that x-forwarded/forwarded types auto-exempt matching hosts
  from connect floods and blacklist checks (6.1.8+ feature)
- Updated deployment guide to explain the three real-IP mechanisms:
  X-Forwarded-For (WebSocket), WEBIRC (web gateways), none (raw TCP)
Portal's mod_http_admin_api requires Bearer tokens (mod_tokenauth).
This adds mod_http_oauth2 to enable OAuth2 token generation:

- Added mod_http_oauth2 to modules.list (Prosody community module)
- Enabled 'http_oauth2' and 'invites' in global modules_enabled
- Configured OAuth2: password grant, 24h access tokens, 30d refresh
- Added oauth2_registration_key for dynamic client registration

Token generation flow for Portal:
  1. Register client: POST /oauth2/register
  2. Get token: POST /oauth2/token (grant_type=password, scope=prosody:operator)
  3. Use token: Authorization: Bearer <token>

Verified end-to-end: create user (200), get user, delete user (200).
@cursor
Copy link

cursor bot commented Feb 27, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @kzndotsh, your pull request is larger than the review limit of 150000 diff characters

@gitguardian
Copy link

gitguardian bot commented Feb 27, 2026

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
26362067 Triggered Generic Password 9a6705a .env.example View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Important

Review skipped

Too many files!

This PR contains 172 files, which is 22 over the limit of 150.

📥 Commits

Reviewing files that changed from the base of the PR and between 5df7a67 and 4523ed4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (172)
  • .env.example
  • .gitignore
  • .gitleaks.toml
  • AGENTS.md
  • CHANGELOG.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • apps/atheme/config/atheme.conf.template
  • apps/docs/alchemy.run.ts
  • apps/docs/app/[[...slug]]/layout.tsx
  • apps/docs/app/[[...slug]]/page.tsx
  • apps/docs/app/api/search/route.ts
  • apps/docs/app/global.css
  • apps/docs/app/layout.tsx
  • apps/docs/app/llms-full.txt/route.ts
  • apps/docs/app/llms.mdx/docs/[[...slug]]/route.ts
  • apps/docs/app/llms.txt/route.ts
  • apps/docs/components/ai/page-actions.tsx
  • apps/docs/components/mdx/mermaid.tsx
  • apps/docs/content/docs/architecture/data-model.mdx
  • apps/docs/content/docs/architecture/index.mdx
  • apps/docs/content/docs/architecture/meta.json
  • apps/docs/content/docs/architecture/networking.mdx
  • apps/docs/content/docs/development/adding-a-service.mdx
  • apps/docs/content/docs/development/contributing.mdx
  • apps/docs/content/docs/development/meta.json
  • apps/docs/content/docs/development/testing.mdx
  • apps/docs/content/docs/getting-started/index.mdx
  • apps/docs/content/docs/getting-started/local-development.mdx
  • apps/docs/content/docs/getting-started/meta.json
  • apps/docs/content/docs/index.mdx
  • apps/docs/content/docs/meta.json
  • apps/docs/content/docs/operations/backups.mdx
  • apps/docs/content/docs/operations/deployment.mdx
  • apps/docs/content/docs/operations/meta.json
  • apps/docs/content/docs/operations/monitoring.mdx
  • apps/docs/content/docs/operations/security.mdx
  • apps/docs/content/docs/operations/ssl-tls.mdx
  • apps/docs/content/docs/operations/troubleshooting.mdx
  • apps/docs/content/docs/reference/api.mdx
  • apps/docs/content/docs/reference/environment-variables.mdx
  • apps/docs/content/docs/reference/faq.mdx
  • apps/docs/content/docs/reference/glossary.mdx
  • apps/docs/content/docs/reference/meta.json
  • apps/docs/content/docs/reference/ports.mdx
  • apps/docs/content/docs/services/atheme/configuration.mdx
  • apps/docs/content/docs/services/atheme/index.mdx
  • apps/docs/content/docs/services/atheme/meta.json
  • apps/docs/content/docs/services/atheme/operations.mdx
  • apps/docs/content/docs/services/bridge/configuration.mdx
  • apps/docs/content/docs/services/bridge/index.mdx
  • apps/docs/content/docs/services/bridge/meta.json
  • apps/docs/content/docs/services/bridge/operations.mdx
  • apps/docs/content/docs/services/irc/configuration.mdx
  • apps/docs/content/docs/services/irc/dns.mdx
  • apps/docs/content/docs/services/irc/index.mdx
  • apps/docs/content/docs/services/irc/meta.json
  • apps/docs/content/docs/services/irc/modules.mdx
  • apps/docs/content/docs/services/irc/operations.mdx
  • apps/docs/content/docs/services/irc/troubleshooting.mdx
  • apps/docs/content/docs/services/irc/user-modes.mdx
  • apps/docs/content/docs/services/meta.json
  • apps/docs/content/docs/services/thelounge/configuration.mdx
  • apps/docs/content/docs/services/thelounge/index.mdx
  • apps/docs/content/docs/services/thelounge/meta.json
  • apps/docs/content/docs/services/thelounge/operations.mdx
  • apps/docs/content/docs/services/web/development.mdx
  • apps/docs/content/docs/services/web/index.mdx
  • apps/docs/content/docs/services/web/meta.json
  • apps/docs/content/docs/services/webpanel/configuration.mdx
  • apps/docs/content/docs/services/webpanel/index.mdx
  • apps/docs/content/docs/services/webpanel/meta.json
  • apps/docs/content/docs/services/xmpp/configuration.mdx
  • apps/docs/content/docs/services/xmpp/dns.mdx
  • apps/docs/content/docs/services/xmpp/index.mdx
  • apps/docs/content/docs/services/xmpp/meta.json
  • apps/docs/content/docs/services/xmpp/modules.mdx
  • apps/docs/content/docs/services/xmpp/operations.mdx
  • apps/docs/lib/cn.ts
  • apps/docs/lib/layout.shared.tsx
  • apps/docs/lib/source.ts
  • apps/docs/mdx-components.tsx
  • apps/docs/next.config.mjs
  • apps/docs/open-next.config.ts
  • apps/docs/package.json
  • apps/docs/postcss.config.mjs
  • apps/docs/scripts/lint.ts
  • apps/docs/source.config.ts
  • apps/docs/tsconfig.json
  • apps/docs/types/env.d.ts
  • apps/prosody/config/prosody.cfg.lua
  • apps/unrealircd/config/unrealircd.conf.template
  • apps/unrealircd/justfile
  • docs/AGENTS.md
  • docs/README.md
  • docs/architecture/README.md
  • docs/architecture/ci-cd.md
  • docs/architecture/new-service.md
  • docs/bridges/README.md
  • docs/examples/cloudflare-credentials.ini.example
  • docs/examples/nginx-docker.conf
  • docs/examples/nginx-docker.dev.conf
  • docs/examples/prometheus-scrape-config.yml
  • docs/infra/containerization.md
  • docs/infra/data-structure.md
  • docs/infra/networking.md
  • docs/infra/ssl.md
  • docs/onboarding/README.md
  • docs/services/MODULES.md
  • docs/services/irc/API.md
  • docs/services/irc/ATHEME.md
  • docs/services/irc/BACKUP_RECOVERY.md
  • docs/services/irc/CI_CD.md
  • docs/services/irc/CONFIG.md
  • docs/services/irc/DEVELOPMENT.md
  • docs/services/irc/DOCKER.md
  • docs/services/irc/MAKE.md
  • docs/services/irc/MODULES.md
  • docs/services/irc/README.md
  • docs/services/irc/SCRIPTS.md
  • docs/services/irc/SECRET_MANAGEMENT.md
  • docs/services/irc/SSL.md
  • docs/services/irc/TESTING.md
  • docs/services/irc/TODO.md
  • docs/services/irc/TROUBLESHOOTING.md
  • docs/services/irc/UNREALIRCD.md
  • docs/services/irc/USERMODES.md
  • docs/services/irc/WEBPANEL.md
  • docs/services/irc/examples/atheme/atheme.conf.example
  • docs/services/irc/examples/atheme/atheme.motd.example
  • docs/services/irc/examples/unrealircd/aliases/aliases.conf
  • docs/services/irc/examples/unrealircd/aliases/anope.conf
  • docs/services/irc/examples/unrealircd/aliases/atheme.conf
  • docs/services/irc/examples/unrealircd/aliases/auspice.conf
  • docs/services/irc/examples/unrealircd/aliases/cygnus.conf
  • docs/services/irc/examples/unrealircd/aliases/epona.conf
  • docs/services/irc/examples/unrealircd/aliases/generic.conf
  • docs/services/irc/examples/unrealircd/aliases/genericstats.conf
  • docs/services/irc/examples/unrealircd/aliases/ircservices.conf
  • docs/services/irc/examples/unrealircd/aliases/operstats.conf
  • docs/services/irc/examples/unrealircd/badwords.conf
  • docs/services/irc/examples/unrealircd/dccallow.conf
  • docs/services/irc/examples/unrealircd/examples/example.conf
  • docs/services/irc/examples/unrealircd/examples/example.es.conf
  • docs/services/irc/examples/unrealircd/examples/example.fr.conf
  • docs/services/irc/examples/unrealircd/examples/example.pt.conf
  • docs/services/irc/examples/unrealircd/examples/example.tr.conf
  • docs/services/irc/examples/unrealircd/help/help.conf
  • docs/services/irc/examples/unrealircd/help/help.de.conf
  • docs/services/irc/examples/unrealircd/help/help.es.conf
  • docs/services/irc/examples/unrealircd/help/help.fr.conf
  • docs/services/irc/examples/unrealircd/help/help.it.conf
  • docs/services/irc/examples/unrealircd/help/help.nl.conf
  • docs/services/irc/examples/unrealircd/help/help.pl.conf
  • docs/services/irc/examples/unrealircd/help/help.ru.conf
  • docs/services/irc/examples/unrealircd/help/help.tr.conf
  • docs/services/irc/examples/unrealircd/modules.default.conf
  • docs/services/irc/examples/unrealircd/modules.optional.conf
  • docs/services/irc/examples/unrealircd/modules.sources.list
  • docs/services/irc/examples/unrealircd/operclass.default.conf
  • docs/services/irc/examples/unrealircd/rpc-class.default.conf
  • docs/services/irc/examples/unrealircd/rpc.modules.default.conf
  • docs/services/irc/examples/unrealircd/snomasks.default.conf
  • docs/services/irc/examples/unrealircd/spamfilter.conf
  • docs/services/irc/examples/unrealircd/unrealircd.conf
  • docs/services/web/README.md
  • docs/services/xmpp/DNS.md
  • docs/services/xmpp/README.md
  • docs/services/xmpp/TODO.md
  • package.json
  • scripts/download_references.py
  • scripts/prepare-config.sh

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds OAuth2 to Prosody, tightens network ACLs and Docker subnet allowances, renames bridge identity from atl-bridgebridge, gates .env.dev loading to dev only, introduces dev TLS toggles in .env.dev.example, updates bridge tests and adapters, and adds multiple audits and deployment/docs.

Changes

Cohort / File(s) Summary
Environment templates & loader
/.env.example, /.env.dev.example, scripts/prepare-config.sh, justfile, .cursorignore
Streamlined .env.example, added Prosody TLS/dev toggles in .env.dev.example (C2S/S2S TLS flags, S2S auth, plain auth), gate .env.dev loading to ATL_ENVIRONMENT=dev, updated prod init/workflow in justfile, and removed .env.staging ignores.
Prosody (XMPP)
apps/prosody/config/prosody.cfg.lua, apps/prosody/modules.list, .luacheckrc
Enabled mod_http_oauth2 and added OAuth2 config (grant/response types, TTLs, registration key), added dont_archive_namespaces, restricted http_status CIDR, extended trusted_proxies, and added corresponding linter globals.
UnrealIRCd (IRC server)
apps/unrealircd/config/unrealircd.conf.template
Tightened internal masks to 172.16.0.0/12, added WebIRC/x-forwarded proxy blocks, drpass, spamfilter, connthrottle, Docker-network exemptions, adjusted oper/rpc masks and flood/conn defaults.
Atheme (IRC services)
apps/atheme/config/atheme.conf.template, apps/atheme/AUDIT_REPORT.md
Removed static SRA operator block, expanded server loglevels, added ChanFix example block, and added a new Atheme audit report with prioritized recommendations.
Bridge — rename, defaults & adapters
apps/bridge/src/bridge/.../adapter.py, apps/bridge/src/bridge/.../client.py, apps/bridge/src/bridge/.../component.py, apps/bridge/README.md, apps/bridge/src/bridge/AGENTS.md, apps/bridge/src/bridge/adapters/AGENTS.md
Renamed default IRC/XMPP listener/oper identity and docs from atl-bridgebridge, added BRIDGE_XMPP_COMPONENT_PORT default, and updated code and docs to match (oper names, skip checks, defaults).
Bridge tests
apps/bridge/tests/test_irc_client.py
Tests now patch bridge.adapters.irc.client.cfg to control irc_relaymsg_clean_nicks within the test scope (with-patch), preserving assertions while isolating config.
Bridge behavior & healthcheck
infra/compose/bridge.yaml, apps/bridge/src/bridge/adapters/irc/client.py, apps/bridge/src/bridge/adapters/xmpp/component.py
Healthcheck changed to pgrep-based process check; oper/channel logic and XMPP listener checks updated to use bridge.
Docs & audits
AGENTS.md, docs/AGENTS.md, infra/AGENTS.md, docs/deployment.md, docs/audits/*, .cursor/plans/*, README.md, docs/services/irc/*, apps/atheme/AUDIT_REPORT.md, docs/*
Added multiple audit reports (env-vars, prosody, unrealircd, dev-prod lifecycle), Cursor Cloud instructions, deployment guide, removed staging references, and updated docs/plans to reflect bridge rename and new audit directories.
Infra nginx & entrypoint
infra/nginx/docker-entrypoint.sh
Render Prosody HTTPS nginx config from template with envsubst before starting nginx.
Miscellaneous small updates
apps/bridge/README.md, apps/bridge/src/bridge/adapters/AGENTS.md, docs/services/irc/CONFIG.md, justfile, scripts/prepare-config.sh, docs/audits/*
Documentation tweaks (default nick, port vars), CI/CD trimmed to production-only, .env.dev load gating, small script log/message changes, and numerous audit docs additions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title is vague and does not clearly reflect the actual scope of changes, which include security hardening, environment variable cleanup, and a production deployment guide. Revise the title to more accurately describe the primary changes, such as: 'Harden IRC/XMPP configs, streamline env vars, and add production deployment guide' or 'Security hardening and configuration cleanup for IRC/XMPP stack'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to the changeset, outlining security hardening of IRC/XMPP configurations, environment variable cleanup, dev/prod clarification, and a production deployment guide—all supported by the file summaries.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/development-environment-setup-635d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

sh-checker report

To get the full details, please check in the job output.

shellcheck errors

'shellcheck ' returned error 1 finding the following syntactical issues:

----------

In infra/nginx/docker-entrypoint.sh line 7:
envsubst '${XMPP_DOMAIN} ${CERT_DIR}' < /etc/nginx/templates/prosody-https.conf.template > /etc/nginx/conf.d/prosody-https.conf
         ^--------------------------^ SC2016 (info): Expressions don't expand in single quotes, use double quotes for that.

For more information:
  https://www.shellcheck.net/wiki/SC2016 -- Expressions don't expand in singl...
----------

You can address the above issues in one of three ways:
1. Manually correct the issue in the offending shell script;
2. Disable specific issues by adding the comment:
  # shellcheck disable=NNNN
above the line that contains the issue, where NNNN is the error code;
3. Add '-e NNNN' to the SHELLCHECK_OPTS setting in your .yml action file.



shfmt errors

'shfmt ' returned error 1 finding the following formatting issues:

----------
diff .github/scripts/docker.sh.orig .github/scripts/docker.sh
--- .github/scripts/docker.sh.orig
+++ .github/scripts/docker.sh
@@ -57,11 +57,11 @@
 shift || true
 
 case "$COMMAND" in
-  generate-pr-version)        generate_pr_version "$@" ;;
-  generate-release-version)   generate_release_version "$@" ;;
-  validate-build-config)      validate_build_config "$@" ;;
+  generate-pr-version) generate_pr_version "$@" ;;
+  generate-release-version) generate_release_version "$@" ;;
+  validate-build-config) validate_build_config "$@" ;;
   calculate-source-date-epoch) calculate_source_date_epoch "$@" ;;
-  generate-build-date)        generate_build_date "$@" ;;
+  generate-build-date) generate_build_date "$@" ;;
   *)
     echo "Usage: docker.sh {generate-pr-version|generate-release-version|validate-build-config|calculate-source-date-epoch|generate-build-date} [args...]"
     exit 1
diff apps/atheme/docker-entrypoint.sh.orig apps/atheme/docker-entrypoint.sh
--- apps/atheme/docker-entrypoint.sh.orig
+++ apps/atheme/docker-entrypoint.sh
@@ -7,9 +7,9 @@
 
 # Ensure we have proper permissions
 if [ "$(id -u)" = "0" ]; then
-    echo "ERROR: Atheme should not run as root for security reasons"
-    echo "Please run with a non-root user (UID 1000 recommended)"
-    exit 1
+  echo "ERROR: Atheme should not run as root for security reasons"
+  echo "Please run with a non-root user (UID 1000 recommended)"
+  exit 1
 fi
 
 # Create directories with proper ownership
@@ -17,9 +17,9 @@
 
 # Validate configuration exists
 if [ ! -f "/usr/local/atheme/etc/atheme.conf" ]; then
-    echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
-    echo "Please ensure the configuration is properly mounted"
-    exit 1
+  echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
+  echo "Please ensure the configuration is properly mounted"
+  exit 1
 fi
 
 # Clean up stale PID file
@@ -27,25 +27,25 @@
 
 # Validate database directory is writable
 if [ ! -w "/usr/local/atheme/data" ]; then
-    echo "ERROR: Data directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Data directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Validate logs directory is writable
 if [ ! -w "/usr/local/atheme/logs" ]; then
-    echo "ERROR: Logs directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Logs directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Check if this is first run (no database exists)
 if [ ! -f "/usr/local/atheme/data/services.db" ]; then
-    echo "First run detected - creating initial database..."
-    /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
-    echo "Database created successfully"
+  echo "First run detected - creating initial database..."
+  /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
+  echo "Database created successfully"
 else
-    echo "Existing database found - starting with existing data"
+  echo "Existing database found - starting with existing data"
 fi
 
 # Start Atheme services
diff apps/prosody/docker-entrypoint.sh.orig apps/prosody/docker-entrypoint.sh
--- apps/prosody/docker-entrypoint.sh.orig
+++ apps/prosody/docker-entrypoint.sh
@@ -29,21 +29,21 @@
 # ============================================================================
 
 log_info() {
-    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_warn() {
-    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_debug() {
-    if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
-        echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
-    fi
+  if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
+    echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  fi
 }
 
 # ============================================================================
@@ -51,45 +51,45 @@
 # ============================================================================
 
 validate_environment() {
-    log_info "Validating environment configuration..."
-
-    # Validate required domain
-    if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
-        log_error "PROSODY_DOMAIN is required but not set"
-        exit 1
-    fi
-
-    # Validate domain format
-    if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
-        log_error "Invalid domain format: ${PROSODY_DOMAIN}"
-        exit 1
-    fi
-
-    # Set default admin if not provided
-    if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
-        export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
-        log_info "Using default admin: ${PROSODY_ADMIN_JID}"
-    fi
-
-    # Validate database configuration for SQL storage
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        local required_vars=(
-            "PROSODY_DB_DRIVER"
-            "PROSODY_DB_NAME"
-            "PROSODY_DB_USER"
-            "PROSODY_DB_PASSWORD"
-            "PROSODY_DB_HOST"
-        )
-
-        for var in "${required_vars[@]}"; do
-            if [[ -z "${!var:-}" ]]; then
-                log_error "Database variable ${var} is required for SQL storage"
-                exit 1
-            fi
-        done
-    fi
-
-    log_info "Environment validation complete"
+  log_info "Validating environment configuration..."
+
+  # Validate required domain
+  if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
+    log_error "PROSODY_DOMAIN is required but not set"
+    exit 1
+  fi
+
+  # Validate domain format
+  if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
+    log_error "Invalid domain format: ${PROSODY_DOMAIN}"
+    exit 1
+  fi
+
+  # Set default admin if not provided
+  if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
+    export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
+    log_info "Using default admin: ${PROSODY_ADMIN_JID}"
+  fi
+
+  # Validate database configuration for SQL storage
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    local required_vars=(
+      "PROSODY_DB_DRIVER"
+      "PROSODY_DB_NAME"
+      "PROSODY_DB_USER"
+      "PROSODY_DB_PASSWORD"
+      "PROSODY_DB_HOST"
+    )
+
+    for var in "${required_vars[@]}"; do
+      if [[ -z "${!var:-}" ]]; then
+        log_error "Database variable ${var} is required for SQL storage"
+        exit 1
+      fi
+    done
+  fi
+
+  log_info "Environment validation complete"
 }
 
 # ============================================================================
@@ -97,186 +97,186 @@
 # ============================================================================
 
 setup_directories() {
-    log_info "Setting up directories..."
-
-    local dirs=(
-        "$PROSODY_DATA_DIR"
-        "$PROSODY_LOG_DIR"
-        "$PROSODY_CERT_DIR"
-        "$PROSODY_UPLOAD_DIR"
-        "$(dirname "$PROSODY_PID_FILE")"
-    )
-
-    for dir in "${dirs[@]}"; do
-        if [[ ! -d "$dir" ]]; then
-            log_debug "Creating directory: $dir"
-            mkdir -p "$dir"
-        fi
-
-        # Ensure proper ownership (only if running as root)
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
-        fi
-    done
-
-    # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
-    # Prosody needs write access for SQLite (MAM, PEP, etc.)
-    if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
-        if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
-            log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
-        fi
-    fi
-
-    log_info "Directory setup complete"
+  log_info "Setting up directories..."
+
+  local dirs=(
+    "$PROSODY_DATA_DIR"
+    "$PROSODY_LOG_DIR"
+    "$PROSODY_CERT_DIR"
+    "$PROSODY_UPLOAD_DIR"
+    "$(dirname "$PROSODY_PID_FILE")"
+  )
+
+  for dir in "${dirs[@]}"; do
+    if [[ ! -d "$dir" ]]; then
+      log_debug "Creating directory: $dir"
+      mkdir -p "$dir"
+    fi
+
+    # Ensure proper ownership (only if running as root)
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
+    fi
+  done
+
+  # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
+  # Prosody needs write access for SQLite (MAM, PEP, etc.)
+  if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
+    if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
+      log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
+    fi
+  fi
+
+  log_info "Directory setup complete"
 }
 
 setup_certificates() {
-    log_info "Setting up SSL certificates..."
-
-    # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
-    local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
-    local cert_file="${live_dir}/fullchain.pem"
-    local key_file="${live_dir}/privkey.pem"
-
-    if [[ -f "$cert_file" && -f "$key_file" ]]; then
-        log_info "Certificates found for ${PROSODY_DOMAIN}"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file" 2> /dev/null || true
-        chmod 600 "$key_file" 2> /dev/null || true
-        # HTTPS service automatic discovery (Prosody Certificates doc):
-        # - https/fullchain.pem + https/privkey.pem (directory symlink)
-        # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
-    local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
-    local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
-    if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
-        log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
-        mkdir -p "$live_dir"
-        cp "$legacy_cert" "$cert_file"
-        cp "$legacy_key" "$key_file"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file"
-        chmod 600 "$key_file"
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Generate self-signed certificate for development/testing
-    log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
-    log_warn "This is suitable for development only - use proper certificates in production"
-
+  log_info "Setting up SSL certificates..."
+
+  # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
+  local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
+  local cert_file="${live_dir}/fullchain.pem"
+  local key_file="${live_dir}/privkey.pem"
+
+  if [[ -f "$cert_file" && -f "$key_file" ]]; then
+    log_info "Certificates found for ${PROSODY_DOMAIN}"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    fi
+    chmod 644 "$cert_file" 2> /dev/null || true
+    chmod 600 "$key_file" 2> /dev/null || true
+    # HTTPS service automatic discovery (Prosody Certificates doc):
+    # - https/fullchain.pem + https/privkey.pem (directory symlink)
+    # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
+  local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
+  local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
+  if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
+    log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
     mkdir -p "$live_dir"
-    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-        -keyout "$key_file" \
-        -out "$cert_file" \
-        -subj "/CN=${PROSODY_DOMAIN}" \
-        -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    cp "$legacy_cert" "$cert_file"
+    cp "$legacy_key" "$key_file"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
     fi
     chmod 644 "$cert_file"
     chmod 600 "$key_file"
-
-    # HTTPS service automatic discovery
-    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-
-    log_info "Self-signed certificate generated successfully"
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Generate self-signed certificate for development/testing
+  log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
+  log_warn "This is suitable for development only - use proper certificates in production"
+
+  mkdir -p "$live_dir"
+  openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+    -keyout "$key_file" \
+    -out "$cert_file" \
+    -subj "/CN=${PROSODY_DOMAIN}" \
+    -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+  fi
+  chmod 644 "$cert_file"
+  chmod 600 "$key_file"
+
+  # HTTPS service automatic discovery
+  ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+
+  log_info "Self-signed certificate generated successfully"
 }
 
 wait_for_database() {
-    if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
-        log_debug "Not using SQL storage, skipping database wait"
-        return 0
-    fi
-
-    local host="${PROSODY_DB_HOST}"
-    local port="${PROSODY_DB_PORT:-5432}"
-    local max_attempts=30
-    local attempt=1
-
-    log_info "Waiting for database connection to ${host}:${port}..."
-
-    while [[ $attempt -le $max_attempts ]]; do
-        if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
-            log_info "Database connection established"
-            return 0
-        fi
-
-        log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
-        sleep 2
-        ((attempt++))
-    done
-
-    log_error "Database connection timeout after ${max_attempts} attempts"
-    exit 1
+  if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
+    log_debug "Not using SQL storage, skipping database wait"
+    return 0
+  fi
+
+  local host="${PROSODY_DB_HOST}"
+  local port="${PROSODY_DB_PORT:-5432}"
+  local max_attempts=30
+  local attempt=1
+
+  log_info "Waiting for database connection to ${host}:${port}..."
+
+  while [[ $attempt -le $max_attempts ]]; do
+    if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
+      log_info "Database connection established"
+      return 0
+    fi
+
+    log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
+    sleep 2
+    ((attempt++))
+  done
+
+  log_error "Database connection timeout after ${max_attempts} attempts"
+  exit 1
 }
 
 validate_configuration() {
-    log_info "Validating Prosody configuration..."
-
-    # Check if config file exists
-    if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
-        log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
-        exit 1
-    fi
-
-    # Validate configuration using prosodyctl (allow warnings in development)
-    log_info "Validating Prosody configuration..."
-    if ! prosodyctl check config; then
-        log_error "Prosody configuration validation failed"
-        log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
-        if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
-            exit 1
-        else
-            log_warn "Development mode: continuing despite configuration warnings"
-        fi
-    fi
-
-    log_info "Configuration validation successful"
-}
-
-setup_community_modules() {
-    log_info "Setting up community modules..."
-
-    # Check if community modules source exists
-    local source_dir="/usr/local/lib/prosody/prosody-modules"
-    if [[ ! -d "$source_dir" ]]; then
-        log_warn "Community modules repository not found at $source_dir"
-        log_warn "Modules will need to be installed manually or via prosodyctl"
-        return 0
-    fi
-
-    # Check if modules are available from enabled directory
-    local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
-    if [[ ! -d "$enabled_dir" ]]; then
-        log_warn "Enabled community modules directory not found at $enabled_dir"
-        return 0
+  log_info "Validating Prosody configuration..."
+
+  # Check if config file exists
+  if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
+    log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
+    exit 1
+  fi
+
+  # Validate configuration using prosodyctl (allow warnings in development)
+  log_info "Validating Prosody configuration..."
+  if ! prosodyctl check config; then
+    log_error "Prosody configuration validation failed"
+    log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
+    if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
+      exit 1
     else
-        log_info "Community modules found in $enabled_dir"
-        local module_count
-        module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
-        log_info "Enabled modules: $module_count"
-    fi
-
-    # Ensure proper ownership (only if running as root)
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
-    fi
+      log_warn "Development mode: continuing despite configuration warnings"
+    fi
+  fi
+
+  log_info "Configuration validation successful"
+}
+
+setup_community_modules() {
+  log_info "Setting up community modules..."
+
+  # Check if community modules source exists
+  local source_dir="/usr/local/lib/prosody/prosody-modules"
+  if [[ ! -d "$source_dir" ]]; then
+    log_warn "Community modules repository not found at $source_dir"
+    log_warn "Modules will need to be installed manually or via prosodyctl"
+    return 0
+  fi
+
+  # Check if modules are available from enabled directory
+  local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
+  if [[ ! -d "$enabled_dir" ]]; then
+    log_warn "Enabled community modules directory not found at $enabled_dir"
+    return 0
+  else
+    log_info "Community modules found in $enabled_dir"
+    local module_count
+    module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
+    log_info "Enabled modules: $module_count"
+  fi
+
+  # Ensure proper ownership (only if running as root)
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
+  fi
 }
 
 # ============================================================================
@@ -285,28 +285,28 @@
 
 # shellcheck disable=SC2317,SC2329  # Function is called by signal handlers via trap
 cleanup() {
-    log_info "Received shutdown signal, stopping Prosody..."
-
-    if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
-        # Send SIGTERM for graceful shutdown
-        kill -TERM "$PROSODY_PID" 2> /dev/null || true
-
-        # Wait for graceful shutdown (max 30 seconds)
-        local timeout=30
-        while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
-            sleep 1
-            ((timeout--))
-        done
-
-        # Force kill if still running
-        if kill -0 "$PROSODY_PID" 2> /dev/null; then
-            log_warn "Prosody did not shut down gracefully, forcing termination"
-            kill -KILL "$PROSODY_PID" 2> /dev/null || true
-        fi
-    fi
-
-    log_info "Prosody shutdown complete"
-    exit 0
+  log_info "Received shutdown signal, stopping Prosody..."
+
+  if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
+    # Send SIGTERM for graceful shutdown
+    kill -TERM "$PROSODY_PID" 2> /dev/null || true
+
+    # Wait for graceful shutdown (max 30 seconds)
+    local timeout=30
+    while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
+      sleep 1
+      ((timeout--))
+    done
+
+    # Force kill if still running
+    if kill -0 "$PROSODY_PID" 2> /dev/null; then
+      log_warn "Prosody did not shut down gracefully, forcing termination"
+      kill -KILL "$PROSODY_PID" 2> /dev/null || true
+    fi
+  fi
+
+  log_info "Prosody shutdown complete"
+  exit 0
 }
 
 # Setup signal handlers
@@ -317,57 +317,57 @@
 # ============================================================================
 
 main() {
-    log_info "Starting Professional Prosody XMPP Server..."
-
-    # Display version information
-    local prosody_version
-    prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
-    log_info "Prosody version: $prosody_version"
-
-    # Prefer mounted config if present
-    if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
-        log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
-        cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
-        chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-        chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-    fi
-
-    # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
-    if [[ -d "/etc/prosody/config/conf.d" ]]; then
-        log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
-        mkdir -p "/etc/prosody/conf.d"
-        rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
-        chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
-        find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
-    fi
-
-    # Environment and setup
-    validate_environment
-    setup_directories
-    setup_certificates
-    wait_for_database
-    validate_configuration
-    setup_community_modules
-
-    # Display configuration summary
-    log_info "Configuration summary:"
-    log_info "  Domain: ${PROSODY_DOMAIN}"
-    log_info "  Admins: ${PROSODY_ADMIN_JID}"
-    log_info "  Storage: ${PROSODY_STORAGE:-sql}"
-    log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
-    log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
-
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
-    fi
-
-    # Start Prosody
-    log_info "Starting Prosody XMPP server..."
-
-    # Switch to prosody user and start in foreground
-    exec gosu "$PROSODY_USER" prosody \
-        --config="$PROSODY_CONFIG_FILE" \
-        --foreground
+  log_info "Starting Professional Prosody XMPP Server..."
+
+  # Display version information
+  local prosody_version
+  prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
+  log_info "Prosody version: $prosody_version"
+
+  # Prefer mounted config if present
+  if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
+    log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
+    cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
+    chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+    chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+  fi
+
+  # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
+  if [[ -d "/etc/prosody/config/conf.d" ]]; then
+    log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
+    mkdir -p "/etc/prosody/conf.d"
+    rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
+    chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
+    find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
+  fi
+
+  # Environment and setup
+  validate_environment
+  setup_directories
+  setup_certificates
+  wait_for_database
+  validate_configuration
+  setup_community_modules
+
+  # Display configuration summary
+  log_info "Configuration summary:"
+  log_info "  Domain: ${PROSODY_DOMAIN}"
+  log_info "  Admins: ${PROSODY_ADMIN_JID}"
+  log_info "  Storage: ${PROSODY_STORAGE:-sql}"
+  log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
+  log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
+
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
+  fi
+
+  # Start Prosody
+  log_info "Starting Prosody XMPP server..."
+
+  # Switch to prosody user and start in foreground
+  exec gosu "$PROSODY_USER" prosody \
+    --config="$PROSODY_CONFIG_FILE" \
+    --foreground
 }
 
 # ============================================================================
@@ -376,8 +376,8 @@
 
 # Ensure we're running as root initially (for setup)
 if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root for initial setup"
-    exit 1
+  log_error "This script must be run as root for initial setup"
+  exit 1
 fi
 
 # Execute main function
diff apps/unrealircd/docker-entrypoint.sh.orig apps/unrealircd/docker-entrypoint.sh
--- apps/unrealircd/docker-entrypoint.sh.orig
+++ apps/unrealircd/docker-entrypoint.sh
@@ -21,8 +21,8 @@
 
 # Validate config exists
 if [ ! -f "/home/unrealircd/unrealircd/config/unrealircd.conf" ]; then
-    echo "ERROR: Configuration file not found!"
-    exit 1
+  echo "ERROR: Configuration file not found!"
+  exit 1
 fi
 
 # Ownership and permissions are handled by Containerfile and user switching
@@ -29,21 +29,21 @@
 
 # Handle commands
 case "$1" in
-    start)
-        shift
-        echo "Starting UnrealIRCd in foreground..."
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        else
-            exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        fi
-        ;;
-    *)
-        echo "Running command: $1"
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
-        else
-            exec /home/unrealircd/unrealircd/unrealircd "$@"
-        fi
-        ;;
+  start)
+    shift
+    echo "Starting UnrealIRCd in foreground..."
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    else
+      exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    fi
+    ;;
+  *)
+    echo "Running command: $1"
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
+    else
+      exec /home/unrealircd/unrealircd/unrealircd "$@"
+    fi
+    ;;
 esac
diff apps/unrealircd/scripts/manage-modules.sh.orig apps/unrealircd/scripts/manage-modules.sh
--- apps/unrealircd/scripts/manage-modules.sh.orig
+++ apps/unrealircd/scripts/manage-modules.sh
@@ -29,280 +29,280 @@
 
 # Check if we're running as the correct user (unrealircd, typically UID from PUID)
 check_user() {
-    local expected_uid="${PUID:-1000}"
-    if [ "$(id -u)" != "$expected_uid" ]; then
-        print_error "This script must run as the unrealircd user (UID ${expected_uid})"
-        print_error "Current user: $(id -un) (UID $(id -u))"
-        exit 1
-    fi
+  local expected_uid="${PUID:-1000}"
+  if [ "$(id -u)" != "$expected_uid" ]; then
+    print_error "This script must run as the unrealircd user (UID ${expected_uid})"
+    print_error "Current user: $(id -un) (UID $(id -u))"
+    exit 1
+  fi
 }
 
 # Check if UnrealIRCd is running
 check_unrealircd_running() {
-    if pgrep -f unrealircd >/dev/null; then
-        print_warning "UnrealIRCd is currently running"
-        print_warning "Some operations may require a restart to take effect"
-        return 0
-    else
-        print_status "UnrealIRCd is not currently running"
-        return 1
-    fi
+  if pgrep -f unrealircd > /dev/null; then
+    print_warning "UnrealIRCd is currently running"
+    print_warning "Some operations may require a restart to take effect"
+    return 0
+  else
+    print_status "UnrealIRCd is not currently running"
+    return 1
+  fi
 }
 
 # List available modules
 list_modules() {
-    print_header "Available Contrib Modules"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Fetching latest module list..."
-    if [ -d "$CONTRIB_DIR" ]; then
-        cd "$CONTRIB_DIR" && git pull --quiet origin main 2>/dev/null || true
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Available modules:"
-    echo
-    "$UNREALIRCD_BIN" module list 2>/dev/null || {
-        print_warning "Module manager not available, showing contrib directory contents:"
-        for item in "$CONTRIB_DIR"/*; do
-            if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
-                basename "$item"
-            fi
-        done | sort
-    }
+  print_header "Available Contrib Modules"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Fetching latest module list..."
+  if [ -d "$CONTRIB_DIR" ]; then
+    cd "$CONTRIB_DIR" && git pull --quiet origin main 2> /dev/null || true
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Available modules:"
+  echo
+  "$UNREALIRCD_BIN" module list 2> /dev/null || {
+    print_warning "Module manager not available, showing contrib directory contents:"
+    for item in "$CONTRIB_DIR"/*; do
+      if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
+        basename "$item"
+      fi
+    done | sort
+  }
 }
 
 # Show module information
 show_module_info() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 info <module-name>"
-        return 1
-    fi
-
-    print_header "Module Information: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to get info via module manager first
-    if "$UNREALIRCD_BIN" module info "third/$module_name" 2>/dev/null; then
-        return 0
-    fi
-
-    # Fallback to showing contrib directory info
-    if [ -d "$CONTRIB_DIR/$module_name" ]; then
-        print_status "Module found in contrib directory:"
-        ls -la "$CONTRIB_DIR/$module_name"
-
-        if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README.md"
-        fi
-
-        if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README"
-        fi
-    else
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 info <module-name>"
+    return 1
+  fi
+
+  print_header "Module Information: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to get info via module manager first
+  if "$UNREALIRCD_BIN" module info "third/$module_name" 2> /dev/null; then
+    return 0
+  fi
+
+  # Fallback to showing contrib directory info
+  if [ -d "$CONTRIB_DIR/$module_name" ]; then
+    print_status "Module found in contrib directory:"
+    ls -la "$CONTRIB_DIR/$module_name"
+
+    if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README.md"
+    fi
+
+    if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README"
+    fi
+  else
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
 }
 
 # Install a module
 install_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 install <module-name>"
-        return 1
-    fi
-
-    print_header "Installing Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Check if module is already installed
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        print_warning "Module '$module_name' is already installed"
-        return 0
-    fi
-
-    # Try to install via module manager
-    print_status "Installing via module manager..."
-    if "$UNREALIRCD_BIN" module install "third/$module_name"; then
-        print_success "Module '$module_name' installed successfully"
-
-        # Check if we need to add loadmodule to config
-        if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
-            print_status "Checking if loadmodule line needs to be added..."
-            if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
-                print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-                print_warning "Then REHASH or restart UnrealIRCd"
-            fi
-        fi
-
-        return 0
-    fi
-
-    # Fallback to manual installation
-    print_warning "Module manager failed, attempting manual installation..."
-
-    if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR/$module_name" || exit 1
-
-    if [ -f "Makefile" ]; then
-        print_status "Compiling module..."
-        make clean 2>/dev/null || true
-        make
-
-        if [ -f "$module_name.so" ]; then
-            print_status "Installing module..."
-            cp "$module_name.so" "$MODULES_DIR/third/"
-            print_success "Module '$module_name' installed manually"
-
-            print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-            print_warning "Then REHASH or restart UnrealIRCd"
-        else
-            print_error "Module compilation failed"
-            return 1
-        fi
-    else
-        print_error "No Makefile found for module '$module_name'"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 install <module-name>"
+    return 1
+  fi
+
+  print_header "Installing Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Check if module is already installed
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    print_warning "Module '$module_name' is already installed"
+    return 0
+  fi
+
+  # Try to install via module manager
+  print_status "Installing via module manager..."
+  if "$UNREALIRCD_BIN" module install "third/$module_name"; then
+    print_success "Module '$module_name' installed successfully"
+
+    # Check if we need to add loadmodule to config
+    if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
+      print_status "Checking if loadmodule line needs to be added..."
+      if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
+        print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+        print_warning "Then REHASH or restart UnrealIRCd"
+      fi
+    fi
+
+    return 0
+  fi
+
+  # Fallback to manual installation
+  print_warning "Module manager failed, attempting manual installation..."
+
+  if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR/$module_name" || exit 1
+
+  if [ -f "Makefile" ]; then
+    print_status "Compiling module..."
+    make clean 2> /dev/null || true
+    make
+
+    if [ -f "$module_name.so" ]; then
+      print_status "Installing module..."
+      cp "$module_name.so" "$MODULES_DIR/third/"
+      print_success "Module '$module_name' installed manually"
+
+      print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+      print_warning "Then REHASH or restart UnrealIRCd"
+    else
+      print_error "Module compilation failed"
+      return 1
+    fi
+  else
+    print_error "No Makefile found for module '$module_name'"
+    return 1
+  fi
 }
 
 # Uninstall a module
 uninstall_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 uninstall <module-name>"
-        return 1
-    fi
-
-    print_header "Uninstalling Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to uninstall via module manager first
-    if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2>/dev/null; then
-        print_success "Module '$module_name' uninstalled successfully"
-        return 0
-    fi
-
-    # Fallback to manual removal
-    print_warning "Module manager failed, attempting manual removal..."
-
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        rm -f "$MODULES_DIR/third/$module_name.so"
-        print_success "Module '$module_name' removed manually"
-
-        print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
-        print_warning "Then REHASH or restart UnrealIRCd"
-    else
-        print_error "Module '$module_name' not found in modules directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 uninstall <module-name>"
+    return 1
+  fi
+
+  print_header "Uninstalling Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to uninstall via module manager first
+  if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2> /dev/null; then
+    print_success "Module '$module_name' uninstalled successfully"
+    return 0
+  fi
+
+  # Fallback to manual removal
+  print_warning "Module manager failed, attempting manual removal..."
+
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    rm -f "$MODULES_DIR/third/$module_name.so"
+    print_success "Module '$module_name' removed manually"
+
+    print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
+    print_warning "Then REHASH or restart UnrealIRCd"
+  else
+    print_error "Module '$module_name' not found in modules directory"
+    return 1
+  fi
 }
 
 # Upgrade modules
 upgrade_modules() {
-    local module_name="$1"
-
-    print_header "Upgrading Modules"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    if [ -n "$module_name" ]; then
-        print_status "Upgrading specific module: $module_name"
-        if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2>/dev/null; then
-            print_success "Module '$module_name' upgraded successfully"
-        else
-            print_error "Failed to upgrade module '$module_name'"
-            return 1
-        fi
-    else
-        print_status "Upgrading all modules..."
-        if "$UNREALIRCD_BIN" module upgrade 2>/dev/null; then
-            print_success "All modules upgraded successfully"
-        else
-            print_error "Failed to upgrade modules"
-            return 1
-        fi
-    fi
-
-    print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
+  local module_name="$1"
+
+  print_header "Upgrading Modules"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  if [ -n "$module_name" ]; then
+    print_status "Upgrading specific module: $module_name"
+    if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2> /dev/null; then
+      print_success "Module '$module_name' upgraded successfully"
+    else
+      print_error "Failed to upgrade module '$module_name'"
+      return 1
+    fi
+  else
+    print_status "Upgrading all modules..."
+    if "$UNREALIRCD_BIN" module upgrade 2> /dev/null; then
+      print_success "All modules upgraded successfully"
+    else
+      print_error "Failed to upgrade modules"
+      return 1
+    fi
+  fi
+
+  print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
 }
 
 # Update contrib repository
 update_contrib() {
-    print_header "Updating Contrib Repository"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR" || exit 1
-
-    print_status "Pulling latest changes from unrealircd-contrib..."
-    if git pull --quiet origin main; then
-        print_success "Contrib repository updated successfully"
-        print_status "New modules may now be available"
-    else
-        print_error "Failed to update contrib repository"
-        return 1
-    fi
+  print_header "Updating Contrib Repository"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR" || exit 1
+
+  print_status "Pulling latest changes from unrealircd-contrib..."
+  if git pull --quiet origin main; then
+    print_success "Contrib repository updated successfully"
+    print_status "New modules may now be available"
+  else
+    print_error "Failed to update contrib repository"
+    return 1
+  fi
 }
 
 # Show installed modules
 show_installed() {
-    print_header "Installed Modules"
-
-    if [ ! -d "$MODULES_DIR/third" ]; then
-        print_status "No third-party modules installed"
-        return 0
-    fi
-
-    cd "$MODULES_DIR/third" || exit 1
-
-    local count=0
-    for module in *.so; do
-        if [ -f "$module" ]; then
-            echo "  - third/${module%.so}"
-            ((count++))
-        fi
-    done
-
-    if [ $count -eq 0 ]; then
-        print_status "No third-party modules installed"
-    else
-        print_status "Total installed modules: $count"
-    fi
+  print_header "Installed Modules"
+
+  if [ ! -d "$MODULES_DIR/third" ]; then
+    print_status "No third-party modules installed"
+    return 0
+  fi
+
+  cd "$MODULES_DIR/third" || exit 1
+
+  local count=0
+  for module in *.so; do
+    if [ -f "$module" ]; then
+      echo "  - third/${module%.so}"
+      ((count++))
+    fi
+  done
+
+  if [ $count -eq 0 ]; then
+    print_status "No third-party modules installed"
+  else
+    print_status "Total installed modules: $count"
+  fi
 }
 
 # Show usage
 show_usage() {
-    cat <<EOF
+  cat << EOF
 UnrealIRCd Contrib Modules Management Script
 
 Usage: $0 <command> [options]
@@ -344,40 +344,40 @@
 
 # Main execution
 main() {
-    check_user
-
-    case "${1:-help}" in
+  check_user
+
+  case "${1:-help}" in
     list)
-        list_modules
-        ;;
+      list_modules
+      ;;
     info)
-        show_module_info "$2"
-        ;;
+      show_module_info "$2"
+      ;;
     install)
-        install_module "$2"
-        ;;
+      install_module "$2"
+      ;;
     uninstall)
-        uninstall_module "$2"
-        ;;
+      uninstall_module "$2"
+      ;;
     upgrade)
-        upgrade_modules "$2"
-        ;;
+      upgrade_modules "$2"
+      ;;
     update)
-        update_contrib
-        ;;
+      update_contrib
+      ;;
     installed)
-        show_installed
-        ;;
+      show_installed
+      ;;
     help | --help | -h)
-        show_usage
-        ;;
+      show_usage
+      ;;
     *)
-        print_error "Unknown command: $1"
-        echo
-        show_usage
-        exit 1
-        ;;
-    esac
+      print_error "Unknown command: $1"
+      echo
+      show_usage
+      exit 1
+      ;;
+  esac
 }
 
 # Run main function with all arguments
diff scripts/cert-manager/run.sh.orig scripts/cert-manager/run.sh
--- scripts/cert-manager/run.sh.orig
+++ scripts/cert-manager/run.sh
@@ -9,26 +9,26 @@
 
 # Validate domain format (alphanumeric, hyphens, dots only - prevent injection)
 case "$ROOT_DOMAIN" in
-    *[!a-zA-Z0-9.-]*)
-        echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Domain cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9.-]*)
+    echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Domain cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Validate email format (basic check - no spaces or shell metacharacters)
 case "$EMAIL" in
-    *[!a-zA-Z0-9@._+-]*)
-        echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Email cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9@._+-]*)
+    echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Email cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Lego outputs: certificates/_.<domain>.crt and _.<domain>.key for wildcards
@@ -38,9 +38,9 @@
 
 # Ensure we have credentials
 if [ -z "$CLOUDFLARE_DNS_API_TOKEN" ]; then
-    echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
-    echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
-    exec sleep infinity
+  echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
+  echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
+  exec sleep infinity
 fi
 
 # Initial issuance
@@ -49,8 +49,8 @@
 
 # Renewal loop
 while true; do
-    echo "Sleeping for 24 hours..."
-    sleep 86400
-    echo "Checking for renewal..."
-    lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
+  echo "Sleeping for 24 hours..."
+  sleep 86400
+  echo "Checking for renewal..."
+  lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
 done
diff scripts/gencloak-update-env.sh.orig scripts/gencloak-update-env.sh
--- scripts/gencloak-update-env.sh.orig
+++ scripts/gencloak-update-env.sh
@@ -12,23 +12,23 @@
 
 cd "$PROJECT_ROOT"
 
-output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2>/dev/null)
+output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2> /dev/null)
 echo "$output"
 
 mapfile -t keys < <(echo "$output" | grep -oE '"[^"]{50,}"' | tr -d '"')
 if [ ${#keys[@]} -ne 3 ]; then
-    echo "Failed to parse 3 cloak keys from gencloak output"
-    exit 1
+  echo "Failed to parse 3 cloak keys from gencloak output"
+  exit 1
 fi
 
 [ -f "$ENV_FILE" ] || cp .env.example "$ENV_FILE"
 
 if grep -q '^IRC_CLOAK_KEY_1=' "$ENV_FILE"; then
-    sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
 else
-    sed -i "/^IRC_CLOAK_PREFIX=/a\\
+  sed -i "/^IRC_CLOAK_PREFIX=/a\\
 # Cloak keys - keep secret; must be identical on all servers. Generate with: just irc gencloak\\
 IRC_CLOAK_KEY_1=${keys[0]}\\
 IRC_CLOAK_KEY_2=${keys[1]}\\
diff scripts/init.sh.orig scripts/init.sh
--- scripts/init.sh.orig
+++ scripts/init.sh
@@ -11,16 +11,16 @@
 
 # Load environment variables: .env (base) then .env.dev (overrides for just dev)
 if [ -f "$PROJECT_ROOT/.env" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env"
+  set +a
 fi
 if [ -f "$PROJECT_ROOT/.env.dev" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env.dev"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env.dev"
+  set +a
 fi
 
 # Ensure Atheme JSON-RPC port has a default (for existing .env without it)
@@ -35,386 +35,386 @@
 
 # Helper functions
 log_info() {
-    echo -e "${BLUE}[INFO]${NC} $1"
+  echo -e "${BLUE}[INFO]${NC} $1"
 }
 
 log_success() {
-    echo -e "${GREEN}[SUCCESS]${NC} $1"
+  echo -e "${GREEN}[SUCCESS]${NC} $1"
 }
 
 log_warning() {
-    echo -e "${YELLOW}[WARNING]${NC} $1"
+  echo -e "${YELLOW}[WARNING]${NC} $1"
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $1"
+  echo -e "${RED}[ERROR]${NC} $1"
 }
 
 # Function to create directory structure
 create_directories() {
-    log_info "Creating required directory structure..."
-
-    # Data directories (must match compose volume mounts)
-    local data_dirs=(
-        "$PROJECT_ROOT/data/irc/data"
-        "$PROJECT_ROOT/data/irc/logs"
-        "$PROJECT_ROOT/data/irc/webpanel-data"
-        "$PROJECT_ROOT/data/atheme/data"
-        "$PROJECT_ROOT/data/atheme/logs"
-        "$PROJECT_ROOT/data/xmpp/data"
-        "$PROJECT_ROOT/data/xmpp/logs"
-        "$PROJECT_ROOT/data/xmpp/uploads"
-        "$PROJECT_ROOT/data/thelounge"
-        "$PROJECT_ROOT/data/certs"
-    )
-
-    # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
-    local ssl_dirs=(
-        "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    )
-
-    # Create all directories
-    for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
-        if [ ! -d "$dir" ]; then
-            mkdir -p "$dir"
-            log_info "Created directory: $dir"
-    else
-            log_info "Directory already exists: $dir"
+  log_info "Creating required directory structure..."
+
+  # Data directories (must match compose volume mounts)
+  local data_dirs=(
+    "$PROJECT_ROOT/data/irc/data"
+    "$PROJECT_ROOT/data/irc/logs"
+    "$PROJECT_ROOT/data/irc/webpanel-data"
+    "$PROJECT_ROOT/data/atheme/data"
+    "$PROJECT_ROOT/data/atheme/logs"
+    "$PROJECT_ROOT/data/xmpp/data"
+    "$PROJECT_ROOT/data/xmpp/logs"
+    "$PROJECT_ROOT/data/xmpp/uploads"
+    "$PROJECT_ROOT/data/thelounge"
+    "$PROJECT_ROOT/data/certs"
+  )
+
+  # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
+  local ssl_dirs=(
+    "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  )
+
+  # Create all directories
+  for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
+    if [ ! -d "$dir" ]; then
+      mkdir -p "$dir"
+      log_info "Created directory: $dir"
+    else
+      log_info "Directory already exists: $dir"
     fi
   done
 
-    log_success "Directory structure created successfully"
+  log_success "Directory structure created successfully"
 }
 
 # Function to set proper permissions
 set_permissions() {
-    log_info "Setting proper permissions..."
-
-    # Get current user ID and group ID
-    local current_uid
-    local current_gid
-    # Use actual user ID instead of hardcoded values
-    current_uid=$(id -u)
-    current_gid=$(id -g)
-
-    # Use same UID for all services to avoid permission issues
-    local atheme_uid=$current_uid
-    local atheme_gid=$current_gid
-
-    log_info "Current user: $current_uid:$current_gid"
-
-    # Set ownership for data directories (if they exist)
-    if [ -d "$PROJECT_ROOT/data" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
-        # Ensure directories are writable by owner (critical for socket creation)
-        find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
-        log_info "Set ownership for data directory"
-  fi
-
-    # Set ownership for IRC data directory
-    if [ -d "$PROJECT_ROOT/data/irc" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
-        chmod -R 755 "$PROJECT_ROOT/data/irc"
-        log_info "Set permissions for IRC data directory"
-  fi
-
-    # Set ownership for Atheme data directory with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
-        chmod 755 "$PROJECT_ROOT/data/atheme"
-        log_info "Set permissions for Atheme data directory"
-  fi
-
-    # Set ownership for UnrealIRCd logs
-    if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
-        chmod 755 "$PROJECT_ROOT/data/irc/logs"
-        log_info "Set ownership for UnrealIRCd logs"
-    fi
-
-    # Set ownership for Atheme logs with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
-        chmod 755 "$PROJECT_ROOT/data/atheme/logs"
-        log_info "Set permissions for Atheme logs directory"
-    fi
-
-    # Set permissions for SSL certificates
-    if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
-        sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    fi
-    sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
-    chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
-    log_info "Set permissions for SSL directory"
-
-    # Make sure data directories are writable
-    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
-    find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
-
-    log_success "Permissions set successfully"
+  log_info "Setting proper permissions..."
+
+  # Get current user ID and group ID
+  local current_uid
+  local current_gid
+  # Use actual user ID instead of hardcoded values
+  current_uid=$(id -u)
+  current_gid=$(id -g)
+
+  # Use same UID for all services to avoid permission issues
+  local atheme_uid=$current_uid
+  local atheme_gid=$current_gid
+
+  log_info "Current user: $current_uid:$current_gid"
+
+  # Set ownership for data directories (if they exist)
+  if [ -d "$PROJECT_ROOT/data" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
+    # Ensure directories are writable by owner (critical for socket creation)
+    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
+    log_info "Set ownership for data directory"
+  fi
+
+  # Set ownership for IRC data directory
+  if [ -d "$PROJECT_ROOT/data/irc" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
+    chmod -R 755 "$PROJECT_ROOT/data/irc"
+    log_info "Set permissions for IRC data directory"
+  fi
+
+  # Set ownership for Atheme data directory with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
+    chmod 755 "$PROJECT_ROOT/data/atheme"
+    log_info "Set permissions for Atheme data directory"
+  fi
+
+  # Set ownership for UnrealIRCd logs
+  if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
+    chmod 755 "$PROJECT_ROOT/data/irc/logs"
+    log_info "Set ownership for UnrealIRCd logs"
+  fi
+
+  # Set ownership for Atheme logs with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
+    chmod 755 "$PROJECT_ROOT/data/atheme/logs"
+    log_info "Set permissions for Atheme logs directory"
+  fi
+
+  # Set permissions for SSL certificates
+  if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
+    sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  fi
+  sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
+  chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
+  log_info "Set permissions for SSL directory"
+
+  # Make sure data directories are writable
+  find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
+  find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
+
+  log_success "Permissions set successfully"
 }
 
 # Function to set up CA certificate bundle
 setup_ca_bundle() {
-    log_info "Setting up CA certificate bundle..."
-
-    local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
-    local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
-    local ca_bundle_file="curl-ca-bundle.crt"
-
-    # Ensure runtime directory exists
-    if [ ! -d "$ca_runtime_dir" ]; then
-        mkdir -p "$ca_runtime_dir"
-        log_info "Created TLS runtime directory: $ca_runtime_dir"
-  fi
-
-    # Ensure template directory exists
-    if [ ! -d "$ca_template_dir" ]; then
-        mkdir -p "$ca_template_dir"
-        log_info "Created TLS template directory: $ca_template_dir"
-  fi
-
-    # Check if system CA bundle exists
-    local system_ca_bundle=""
-    if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
-        system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
-  elif   [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
-        system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
-  fi
-
-    if [ -n "$system_ca_bundle" ]; then
-        # Create template if it doesn't exist
-        if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle template"
-      else
-                log_warning "Could not create CA certificate bundle template"
-                return 1
-      fi
-    fi
-
-        # Copy to runtime directory if it doesn't exist
-        if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle in runtime directory"
-      else
-                log_warning "Could not create CA certificate bundle in runtime directory"
-                return 1
-      fi
-    else
-            log_info "CA certificate bundle already exists in runtime directory"
-    fi
-  else
-        log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
-        return 1
-  fi
-
-    # Remove obsolete cert files from config/tls (server certs now live in data/certs)
-    rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2>/dev/null || true
-    rm -rf "$ca_runtime_dir/live" 2>/dev/null || true
-
-    log_success "CA certificate bundle setup completed"
+  log_info "Setting up CA certificate bundle..."
+
+  local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
+  local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
+  local ca_bundle_file="curl-ca-bundle.crt"
+
+  # Ensure runtime directory exists
+  if [ ! -d "$ca_runtime_dir" ]; then
+    mkdir -p "$ca_runtime_dir"
+    log_info "Created TLS runtime directory: $ca_runtime_dir"
+  fi
+
+  # Ensure template directory exists
+  if [ ! -d "$ca_template_dir" ]; then
+    mkdir -p "$ca_template_dir"
+    log_info "Created TLS template directory: $ca_template_dir"
+  fi
+
+  # Check if system CA bundle exists
+  local system_ca_bundle=""
+  if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
+    system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
+  elif [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+    system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
+  fi
+
+  if [ -n "$system_ca_bundle" ]; then
+    # Create template if it doesn't exist
+    if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle template"
+      else
+        log_warning "Could not create CA certificate bundle template"
+        return 1
+      fi
+    fi
+
+    # Copy to runtime directory if it doesn't exist
+    if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle in runtime directory"
+      else
+        log_warning "Could not create CA certificate bundle in runtime directory"
+        return 1
+      fi
+    else
+      log_info "CA certificate bundle already exists in runtime directory"
+    fi
+  else
+    log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
+    return 1
+  fi
+
+  # Remove obsolete cert files from config/tls (server certs now live in data/certs)
+  rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2> /dev/null || true
+  rm -rf "$ca_runtime_dir/live" 2> /dev/null || true
+
+  log_success "CA certificate bundle setup completed"
 }
 
 # Function to generate self-signed certificates for dev mode
 generate_cert() {
-    local domain="$1"
-    local base_dir="$2"
-    local live_dir="$base_dir/live/$domain"
-
-    # Ensure directory exists
-    mkdir -p "$live_dir"
-
-    # Generate self-signed cert if it doesn't exist
-    if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
-        log_info "Generating self-signed certificate for $domain..."
-        # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
-        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-            -keyout "$live_dir/privkey.pem" \
-            -out "$live_dir/fullchain.pem" \
-            -subj "/CN=$domain" \
-            -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-        log_success "Generated self-signed certificate for $domain"
-    else
-        log_info "Self-signed certificate already exists for $domain"
-    fi
-
-    # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
-    chmod 644 "$live_dir/privkey.pem" 2>/dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2>/dev/null || true
+  local domain="$1"
+  local base_dir="$2"
+  local live_dir="$base_dir/live/$domain"
+
+  # Ensure directory exists
+  mkdir -p "$live_dir"
+
+  # Generate self-signed cert if it doesn't exist
+  if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
+    log_info "Generating self-signed certificate for $domain..."
+    # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
+    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+      -keyout "$live_dir/privkey.pem" \
+      -out "$live_dir/fullchain.pem" \
+      -subj "/CN=$domain" \
+      -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+    log_success "Generated self-signed certificate for $domain"
+  else
+    log_info "Self-signed certificate already exists for $domain"
+  fi
+
+  # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
+  chmod 644 "$live_dir/privkey.pem" 2> /dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2> /dev/null || true
 }
 
 generate_dev_certs() {
-    log_info "Setting up self-signed certificates for dev mode..."
-
-    # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
-    local shared_cert_dir="$PROJECT_ROOT/data/certs"
-    generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
-    generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
-
-    log_success "Dev certificate setup completed"
+  log_info "Setting up self-signed certificates for dev mode..."
+
+  # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
+  local shared_cert_dir="$PROJECT_ROOT/data/certs"
+  generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
+  generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
+
+  log_success "Dev certificate setup completed"
 }
 
 # Function to prepare configuration files from templates
 prepare_config_files() {
-    log_info "Preparing configuration files from templates..."
-
-    # Load environment variables from .env if it exists
-    if [ -f "$PROJECT_ROOT/.env" ]; then
-        log_info "Loading environment variables from .env"
-        set -a
-        # shellcheck disable=SC1091
-        source "$PROJECT_ROOT/.env"
-        set +a
-        log_info "Environment variables loaded"
-    fi
-
-    # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
-    export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
-    export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
-
-    if [ ! -f "$PROJECT_ROOT/.env" ]; then
-        log_warning ".env file not found. Configuration will use defaults."
-        return 1
-  fi
-
-    # Check if envsubst is available
-    if ! command -v envsubst > /dev/null 2>&1; then
-        log_error "envsubst command not found. Please install gettext package."
-        return 1
-  fi
-
-    # Prepare UnrealIRCd configuration
-    local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
-    local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
-
-    if [ -f "$unreal_template" ]; then
-        log_info "Creating UnrealIRCd configuration from template..."
-        if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
-            log_success "UnrealIRCd configuration created"
-    else
-            log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
-    fi
-  elif   [ -f "$unreal_config" ]; then
-        log_info "UnrealIRCd configuration already exists"
-  else
-        log_warning "No UnrealIRCd configuration template found"
-  fi
-
-    # Prepare Atheme configuration
-    local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
-    local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
-
-    if [ -f "$atheme_template" ]; then
-        log_info "Creating Atheme configuration from template..."
-        envsubst < "$atheme_template" > "$atheme_config"
-        log_success "Atheme configuration created"
-  elif   [ -f "$atheme_config" ]; then
-        log_info "Atheme configuration already exists"
-  else
-        log_warning "No Atheme configuration template found"
-  fi
-
-    # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
-    if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
-        log_info "Running prepare-config.sh (bridge config, etc.)..."
-        # shellcheck source=prepare-config.sh
-        "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
-    fi
-
-    # Show substituted values for verification
-    log_info "Configuration values:"
-    echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
-    echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
-    echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
-    echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
-    echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
+  log_info "Preparing configuration files from templates..."
+
+  # Load environment variables from .env if it exists
+  if [ -f "$PROJECT_ROOT/.env" ]; then
+    log_info "Loading environment variables from .env"
+    set -a
+    # shellcheck disable=SC1091
+    source "$PROJECT_ROOT/.env"
+    set +a
+    log_info "Environment variables loaded"
+  fi
+
+  # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
+  export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
+  export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
+
+  if [ ! -f "$PROJECT_ROOT/.env" ]; then
+    log_warning ".env file not found. Configuration will use defaults."
+    return 1
+  fi
+
+  # Check if envsubst is available
+  if ! command -v envsubst > /dev/null 2>&1; then
+    log_error "envsubst command not found. Please install gettext package."
+    return 1
+  fi
+
+  # Prepare UnrealIRCd configuration
+  local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
+  local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
+
+  if [ -f "$unreal_template" ]; then
+    log_info "Creating UnrealIRCd configuration from template..."
+    if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
+      log_success "UnrealIRCd configuration created"
+    else
+      log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
+    fi
+  elif [ -f "$unreal_config" ]; then
+    log_info "UnrealIRCd configuration already exists"
+  else
+    log_warning "No UnrealIRCd configuration template found"
+  fi
+
+  # Prepare Atheme configuration
+  local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
+  local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
+
+  if [ -f "$atheme_template" ]; then
+    log_info "Creating Atheme configuration from template..."
+    envsubst < "$atheme_template" > "$atheme_config"
+    log_success "Atheme configuration created"
+  elif [ -f "$atheme_config" ]; then
+    log_info "Atheme configuration already exists"
+  else
+    log_warning "No Atheme configuration template found"
+  fi
+
+  # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
+  if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
+    log_info "Running prepare-config.sh (bridge config, etc.)..."
+    # shellcheck source=prepare-config.sh
+    "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
+  fi
+
+  # Show substituted values for verification
+  log_info "Configuration values:"
+  echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
+  echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
+  echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
+  echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
+  echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
 }
 
 # Function to create .env template if it doesn't exist
 create_env_template() {
-    local env_file="$PROJECT_ROOT/.env"
-    local env_example="$PROJECT_ROOT/.env.example"
-
-    if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
-        cp "$env_example" "$env_file"
-        log_info "Created .env file from template"
-        log_warning "Please edit .env file with your configuration before starting services"
-  elif   [ -f "$env_file" ]; then
-        log_info ".env file already exists"
-  else
-        log_warning "No .env template found. You may need to create environment variables manually"
+  local env_file="$PROJECT_ROOT/.env"
+  local env_example="$PROJECT_ROOT/.env.example"
+
+  if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
+    cp "$env_example" "$env_file"
+    log_info "Created .env file from template"
+    log_warning "Please edit .env file with your configuration before starting services"
+  elif [ -f "$env_file" ]; then
+    log_info ".env file already exists"
+  else
+    log_warning "No .env template found. You may need to create environment variables manually"
   fi
 }
 
 # Function to check Docker availability
 check_docker() {
-    log_info "Checking Docker availability..."
-
-    if ! command -v docker > /dev/null 2>&1; then
-        log_error "Docker is not installed or not in PATH"
-        exit 1
-  fi
-
-    if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
-        log_error "Docker Compose is not available"
-        exit 1
-  fi
-
-    log_success "Docker is available"
+  log_info "Checking Docker availability..."
+
+  if ! command -v docker > /dev/null 2>&1; then
+    log_error "Docker is not installed or not in PATH"
+    exit 1
+  fi
+
+  if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
+    log_error "Docker Compose is not available"
+    exit 1
+  fi
+
+  log_success "Docker is available"
 }
 
 # Function to show next steps
 show_next_steps() {
-    echo ""
-    log_info "Next steps:"
-    echo "  1. Edit .env file with your configuration (optional)"
-    echo "  3. Or run: docker compose up -d"
-    echo ""
-    log_info "Data will be stored in:"
-    echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
+  echo ""
+  log_info "Next steps:"
+  echo "  1. Edit .env file with your configuration (optional)"
+  echo "  3. Or run: docker compose up -d"
+  echo ""
+  log_info "Data will be stored in:"
+  echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
 }
 
 # Main function
 main() {
-    log_info "ATL Chat Infrastructure Initialization"
-    log_info "======================================"
-
-    # Check if we're running as root (for permission info)
-    if [ "$(id -u)" = "0" ]; then
-        log_warning "Running as root - this is fine for initial setup"
-  fi
-
-    # Check Docker availability
-    check_docker
-
-    # Create directory structure
-    create_directories
-
-    # Set permissions
-    set_permissions
-
-    # Set up CA certificate bundle
-    setup_ca_bundle
-
-    # Generate dev certs
-    generate_dev_certs
-
-    # Create .env if needed
-    create_env_template
-
-    # Prepare configuration files from templates
-    prepare_config_files
-
-    # Show next steps
-    show_next_steps
-
-    log_success "Initialization completed successfully!"
+  log_info "ATL Chat Infrastructure Initialization"
+  log_info "======================================"
+
+  # Check if we're running as root (for permission info)
+  if [ "$(id -u)" = "0" ]; then
+    log_warning "Running as root - this is fine for initial setup"
+  fi
+
+  # Check Docker availability
+  check_docker
+
+  # Create directory structure
+  create_directories
+
+  # Set permissions
+  set_permissions
+
+  # Set up CA certificate bundle
+  setup_ca_bundle
+
+  # Generate dev certs
+  generate_dev_certs
+
+  # Create .env if needed
+  create_env_template
+
+  # Prepare configuration files from templates
+  prepare_config_files
+
+  # Show next steps
+  show_next_steps
+
+  log_success "Initialization completed successfully!"
 }
 
 # Run main function
 if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
-    main "$@"
+  main "$@"
 fi
diff scripts/prepare-config.sh.orig scripts/prepare-config.sh
--- scripts/prepare-config.sh.orig
+++ scripts/prepare-config.sh
@@ -180,7 +180,7 @@
     log_info "Preparing bridge configuration from template..."
     local temp_file="/tmp/bridge-config.yaml.tmp"
     envsubst < "$bridge_template" > "$temp_file"
-    if cp "$temp_file" "$bridge_config" 2>/dev/null || sudo cp "$temp_file" "$bridge_config" 2>/dev/null; then
+    if cp "$temp_file" "$bridge_config" 2> /dev/null || sudo cp "$temp_file" "$bridge_config" 2> /dev/null; then
       log_success "Bridge configuration prepared"
     else
       log_warning "Could not write bridge config to $bridge_config"
@@ -189,7 +189,7 @@
   elif [ ! -f "$bridge_config" ]; then
     log_warning "Bridge config not found. Copy apps/bridge/config.example.yaml to apps/bridge/config.yaml and customize."
     if [ -f "$PROJECT_ROOT/apps/bridge/config.example.yaml" ]; then
-      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2>/dev/null || true
+      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2> /dev/null || true
       log_info "Copied config.example.yaml to apps/bridge/config.yaml - edit with your Discord channel ID"
     fi
   fi
@@ -202,7 +202,7 @@
     mkdir -p "$(dirname "$lounge_config")"
     local temp_file="/tmp/thelounge-config.js.tmp"
     envsubst < "$lounge_template" > "$temp_file"
-    if cp "$temp_file" "$lounge_config" 2>/dev/null || sudo cp "$temp_file" "$lounge_config" 2>/dev/null; then
+    if cp "$temp_file" "$lounge_config" 2> /dev/null || sudo cp "$temp_file" "$lounge_config" 2> /dev/null; then
       log_success "The Lounge configuration prepared"
     else
       log_warning "Could not write The Lounge config to $lounge_config"
----------

You can reformat the above files to meet shfmt's requirements by typing:

  shfmt  -w filename


Copy link

@amazon-q-developer amazon-q-developer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR significantly improves the development environment setup with comprehensive security hardening for IRC/XMPP services. However, critical security vulnerabilities must be addressed before merge.

Security Issues Requiring Fix:

  1. Hardcoded credentials in .env.example must not be deployed unchanged
  2. OAuth2 registration key needs secure default or required configuration
  3. Temporary file creation vulnerabilities enable privilege escalation attacks

The configuration improvements (TLS hardening, proper network isolation, OAuth2 integration) are well-implemented. Once the security issues are resolved, this will be ready to merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment on lines 48 to 54
IRC_CLOAK_KEY_1=daa0ad2a69ba7683a2cdb02499f2e98b0729423bb7578d1f1dfbcdfe015f1f8b554b13203289c83D
IRC_CLOAK_KEY_2=899874eda706ee805bd34792bfd7bd62711f1938dea920c8bdf8396fe136ab6a83785a3ce54eB298
IRC_CLOAK_KEY_3=d8936d8fff38eace5c379c94578abfa802088bd241329c64506513fe8e4de3e2304f7dd00355A8d6
IRC_OPER_PASSWORD='$argon2id$v=19$m=6144,t=2,p=2$WXOLpTE+DPDr8q6OBVTx3w$bqXpBsaAK6lkXfR/IPn+TcE0VJEKjUFD7xordE6pFSo'
IRC_DRPASS=change_me_drpass
IRC_SERVICES_PASSWORD=change_me_secure_services_pass
ATL_WEBIRC_PASSWORD=change_me_webirc_password

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Production environment file contains hardcoded secrets and example credentials that must be changed before deployment. Multiple placeholder passwords use predictable patterns like "change_me_*" which creates security risk if deployed without modification.1

Footnotes

  1. CWE-798: Use of Hard-coded Credentials - https://cwe.mitre.org/data/definitions/798.html

oauth2_refresh_token_ttl = 2592000 -- 30 days
oauth2_require_code_challenge = false -- Portal uses password grant, not PKCE
-- Dynamic client registration (enables Portal to register as OAuth2 client)
oauth2_registration_key = Lua.os.getenv("PROSODY_OAUTH2_REGISTRATION_KEY") or "dev-oauth2-registration-key"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: OAuth2 registration key uses a weak default value "dev-oauth2-registration-key" that allows any client to register. This enables unauthorized access to the admin API if deployed to production without changing this value.1

Suggested change
oauth2_registration_key = Lua.os.getenv("PROSODY_OAUTH2_REGISTRATION_KEY") or "dev-oauth2-registration-key"
oauth2_registration_key = Lua.os.getenv("PROSODY_OAUTH2_REGISTRATION_KEY") or error("PROSODY_OAUTH2_REGISTRATION_KEY must be set")

Footnotes

  1. CWE-798: Use of Hard-coded Credentials - https://cwe.mitre.org/data/definitions/798.html

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the development environment setup and hardens the IRC/XMPP stack configurations based on detailed security audits. It streamlines environment variable management, ensuring clearer separation between development and production settings. A new, comprehensive production deployment guide is also included, outlining the necessary steps for a robust and secure deployment.

Highlights

  • Configuration Hardening: Implemented several security and best practice recommendations for UnrealIRCd, Atheme, and Prosody configurations, addressing findings from comprehensive audits.
  • Environment Variable Streamlining: Refactored environment variable usage across the stack, clarifying dev/prod setups, removing orphaned variables, and ensuring consistent naming.
  • Production Deployment Guide: Introduced a detailed guide for deploying the stack in a production environment, including architecture, prerequisites, DNS, Nginx Proxy Manager setup, and security checklist.
  • Development Environment Improvements: Added specific instructions for setting up the development environment on Cursor Cloud and refined the justfile and prepare-config.sh scripts for better dev/prod separation.
  • Audit Reports: Included comprehensive audit reports for Atheme, Prosody, UnrealIRCd, environment variables, and the dev/prod lifecycle, detailing findings and recommended fixes.
Changelog
  • .env.dev.example
    • Added new Prosody TLS relaxation variables for development environments.
  • .env.example
    • Reorganized and clarified sections with new comments for better readability.
    • Added new security-related variables for IRC and XMPP services.
    • Removed several unused or orphaned environment variables.
    • Updated default values for various service configurations.
  • AGENTS.md
    • Added a new section detailing Cursor Cloud specific instructions for system dependencies, starting the dev environment, service ports, running tests, linting, and common gotchas.
  • apps/atheme/AUDIT_REPORT.md
    • Added a new comprehensive audit report for the Atheme IRC Services configuration, detailing findings, warnings, and suggestions.
  • apps/atheme/config/atheme.conf.template
    • Updated the comment for saslserv/scram to reflect its loading status.
    • Adjusted the default loglevel to be less verbose for production, including admin, error, info, network, wallops, and register.
    • Commented out the chanfix configuration block, clarifying that the module is not loaded by default.
    • Removed the example operator "jilles" block and added a comment explaining runtime operator management.
  • apps/bridge/tests/test_irc_client.py
    • Wrapped the test_sends_relaymsg_when_capability_negotiated test in a patch context to control cfg.irc_relaymsg_clean_nicks.
  • apps/prosody/config/prosody.cfg.lua
    • Commented out the legacyauth module, recommending SASL for authentication.
    • Added the http_oauth2 module to modules_enabled.
    • Enabled the invites module, noting its requirement for mod_http_admin_api and mod_http_oauth2.
    • Uncommented dont_archive_namespaces to exclude chat states and Jingle messages from archiving.
    • Added the Tailscale IP range (100.64.0.0/10) to trusted_proxies for better network handling.
    • Restricted http_status_allow_cidr to the Docker network and localhost for security.
    • Uncommented the global ssl block to enable explicit TLS configuration.
    • Added detailed OAuth2 configuration for mod_http_oauth2, including grant types, token TTLs, and dynamic client registration.
    • Updated the PROSODY_FEED_URL to a redacted placeholder.
  • apps/prosody/modules.list
    • Added mod_http_oauth2 to the list of Prosody modules.
  • apps/unrealircd/config/unrealircd.conf.template
    • Updated the ip mask in the except block to include the broader Docker network range (172.16.0.0/12).
    • Replaced the single webirc block with new proxy blocks: npm-webirc for standard WEBIRC and npm-x-forwarded for trusting X-Forwarded-For headers from NPM/Tailscale.
    • Restricted the link block masks for services to specific Docker and localhost IP ranges (172.16.0.0/12, 127.0.0.0/8).
    • Added an except ban block to exempt the internal Docker network from various bans and blacklists.
    • Added a drpass block to password-protect /DIE and /RESTART commands.
    • Restricted the oper admin mask to the Docker network (*@172.16.0.0/12) and added require-modes "z" to enforce TLS for opers.
    • Added gline-address and snomask-on-oper to the set block.
    • Introduced allow-user-stats to restrict stats for regular users.
    • Added spamfilter and connthrottle configuration blocks for enhanced security.
    • Expanded restrict-commands to include private-message and private-notice restrictions.
    • Tightened connect-flood settings from 20:10 to 5:60 for production-level security.
  • docs/audits/dev-prod-lifecycle-audit.md
    • Added a new comprehensive audit report for the development vs. production lifecycle, highlighting issues and suggesting improvements.
  • docs/audits/env-var-audit.md
    • Added a new comprehensive audit report for environment variables, categorizing findings and recommending actions.
  • docs/audits/prosody-config-audit.md
    • Added a new comprehensive audit report for the Prosody XMPP configuration, detailing critical issues, warnings, and suggestions.
  • docs/audits/unrealircd-config-audit.md
    • Added a new comprehensive audit report for the UnrealIRCd configuration, detailing critical issues, warnings, and suggestions.
  • docs/deployment.md
    • Added a new production deployment guide, covering architecture, prerequisites, DNS records, Nginx Proxy Manager configuration, verification, certificate renewal, security checklist, and troubleshooting.
  • justfile
    • Modified the staging and prod commands to explicitly run scripts/init.sh before starting Docker Compose services.
    • Updated staging and prod commands to use --env-file .env for explicit environment variable loading.
    • Adjusted down-staging and down-prod commands to use docker compose down without profiles for consistency.
  • scripts/prepare-config.sh
    • Modified the script to conditionally load .env.dev overrides only when ATL_ENVIRONMENT is set to dev, preventing development-specific settings from leaking into production configurations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request delivers a significant hardening and refactoring of the IRC/XMPP stack. The changes, well-documented through included audit reports, address critical security vulnerabilities in UnrealIRCd and Atheme, streamline environment variables for clarity and security, and fix the production deployment process. The addition of a detailed production deployment guide is a major improvement. Overall, these changes substantially enhance the project's security, maintainability, and operational readiness.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 16 files

Confidence score: 3/5

  • AGENTS.md advises chmod 666 on the Docker socket, which is a concrete security footgun; prefer docker group membership to avoid root-level access for any local user.
  • justfile removes the staging compose profile when bringing up staging, so staging-only services/configs may not start as expected.
  • Score reflects a few user-impacting risks (security guidance and staging/prosody behavior) rather than code-level breakage across the whole PR.
  • Pay close attention to AGENTS.md, justfile, apps/prosody/config/prosody.cfg.lua - security guidance and environment/profile behavior.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="AGENTS.md">

<violation number="1" location="AGENTS.md:92">
P1: Avoid instructing users to make the Docker socket world-writable. `chmod 666` allows any local user to gain root-level control via Docker. Prefer adding the user to the `docker` group and keeping the socket at 660.</violation>
</file>

<file name="apps/prosody/config/prosody.cfg.lua">

<violation number="1" location="apps/prosody/config/prosody.cfg.lua:448">
P2: CIDR `172.16.0.0/12` does not include localhost (`127.0.0.1`), contradicting the comment and potentially breaking any in-container health checks against the status endpoint. If localhost access is intended, add it explicitly.</violation>
</file>

<file name="justfile">

<violation number="1" location="justfile:43">
P2: Staging now starts without the `staging` compose profile, so staging-only services/config won’t be enabled. Keep the staging profile when bringing up the stack.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines 235 to +236

# Logging & Monitoring
# --- Logging ---
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Changing XMPP_DOMAIN in .env without updating the hardcoded PROSODY_SSL_KEY and PROSODY_SSL_CERT paths will cause Prosody to fail on startup.
Severity: MEDIUM

Suggested Fix

Remove the explicit PROSODY_SSL_KEY and PROSODY_SSL_CERT variables from .env.example to allow Prosody's configuration to automatically derive the correct certificate paths from the PROSODY_DOMAIN. This removes the tight coupling and prevents configuration mismatches.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: .env.example#L235-L236

Potential issue: The `.env.example` file sets explicit paths for `PROSODY_SSL_KEY` and
`PROSODY_SSL_CERT` that are hardcoded to the `atl.chat` domain. However, the entrypoint
script generates certificates based on the `XMPP_DOMAIN` variable. If a user changes
`XMPP_DOMAIN` to a different value without also updating the `PROSODY_SSL_KEY` and
`PROSODY_SSL_CERT` paths, a mismatch will occur. This will cause Prosody to fail to
start because it cannot find the certificate files at the configured, now incorrect,
path.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (5)
apps/atheme/config/atheme.conf.template (1)

258-258: Avoid hardcoded line references in inline comments.

“(line 174)” will drift on future edits. Prefer a stable reference without line numbers.

♻️ Suggested tweak
-/* saslserv/scram already loaded above in the SASL section (line 174) */
+/* saslserv/scram already loaded above in the SASL section */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/atheme/config/atheme.conf.template` at line 258, Update the inline
comment mentioning "saslserv/scram" to remove the hardcoded "(line 174)" and
replace it with a stable reference such as "see the SASL section above" or "as
noted in the SASL section" so future edits won't break the reference; locate the
comment that currently reads "saslserv/scram already loaded above in the SASL
section (line 174)" and edit it to reference the "SASL section" by name only (or
add a nearby header/anchor comment "SASL section" if one doesn't exist) to
provide a durable pointer.
docs/deployment.md (1)

114-116: Avoid curl | bash in production runbooks.

Line 115 executes remote code directly. Prefer package-managed install or checksum/signature-verified download in production docs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/deployment.md` around lines 114 - 116, The runbook currently pipes the
remote install script directly into bash with the `curl --proto '=https'
--tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin`
pattern; change the guidance to avoid `curl | bash` by recommending
package-managed installation if available (e.g., distro package or Homebrew), or
instructing to download the installer to a temporary file, verify its checksum
or signature against an authoritative source, and only then execute it (or
extract/install manually) for the `just` installer; update the docs to include
the exact alternative steps (download URL, checksum/signature verification step,
and safe execution instructions) in place of the current piped command.
docs/audits/prosody-config-audit.md (1)

206-214: Update summary table to reflect fixes applied in this PR.

Several critical/warning items have been addressed by the configuration changes in this same PR:

  • SSL block is now uncommented
  • legacyauth is disabled
  • http_status_allow_cidr is restricted

Consider updating the summary counts or adding a "Status: Fixed in this PR" note.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/audits/prosody-config-audit.md` around lines 206 - 214, Update the
summary table to reflect the fixes applied in this PR: decrease the 🔴 CRITICAL
count by 1 to remove the "global ssl block commented out" item (since the ssl
block is now uncommented), decrease the 🟡 WARNING count by 2 to remove
"legacyauth enabled" and the open `http_status_allow_cidr` item (since
`legacyauth` is now disabled and `http_status_allow_cidr` is restricted), and
optionally add a short "Status: Fixed in this PR" note next to those specific
rows (reference `legacyauth`, `http_status_allow_cidr`, and the global ssl
block) so readers can see these items were remediated rather than still
outstanding.
apps/unrealircd/config/unrealircd.conf.template (1)

778-782: Consider using separate passwords for restart and die.

Using the same IRC_DRPASS for both /DIE and /RESTART is acceptable but reduces defense-in-depth. If the password is compromised, both critical commands become accessible.

💡 Optional: Use separate environment variables
 drpass {
-	restart "${IRC_DRPASS}";
-	die "${IRC_DRPASS}";
+	restart "${IRC_DRPASS_RESTART}";
+	die "${IRC_DRPASS_DIE}";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/unrealircd/config/unrealircd.conf.template` around lines 778 - 782, The
drpass block currently uses a single env var IRC_DRPASS for both restart and
die; change it to use separate environment variables (e.g., IRC_RESTART_PASS for
restart and IRC_DIE_PASS for die) so each command has its own credential,
updating the drpass { restart ...; die ...; } entries accordingly and keeping
IRC_DRPASS only as an optional fallback if needed; ensure the template
references the new env names wherever drpass is configured.
docs/audits/unrealircd-config-audit.md (1)

642-666: Update summary to reflect fixes applied in this PR.

The summary shows 3 critical issues, but the actual config changes in this PR address all three:

  1. drpass block added (lines 778-782)
  2. WEBIRC password uses env var (line 375)
  3. Admin oper mask restricted (line 787)

Additionally, several warnings are addressed (connthrottle, spamfilter, oper-only-stats via allow-user-stats). Consider updating the summary or adding notes about which fixes were applied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/audits/unrealircd-config-audit.md` around lines 642 - 666, Update the
"Summary" section under "By Severity" and "Priority Fixes" to reflect that the
three critical issues have been fixed in this PR: remove or decrement the "🔴
CRITICAL" count and delete or mark resolved the items "Missing `drpass` block",
"admin oper `mask *@*`", and "hardcoded WEBIRC password"; instead list them as
fixed and reference the changes that addressed them (the added drpass block,
WEBIRC password switched to env var, and tightened admin oper mask). Also note
the additional warnings that were addressed (add mentions that
`set::connthrottle`, `set::spamfilter`, and `set::oper-only-stats` were added or
that `allow-user-stats` was applied) and update the "Priority Fixes" list to
remove or demote items already implemented. Ensure the table counts and the
"Priority Fixes" bullets match the current state of the config changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.example:
- Around line 4-5: The top-level claim "No orphans" is inconsistent with the
Portal section; update the .env example wording so it no longer asserts every
variable is consumed in-repo unconditionally—either change the line containing
"No orphans" to a qualified statement like "Most variables are consumed by at
least one compose file, config template, script, or application; exceptions
(e.g., Portal vars) are documented below" or add a parenthetical noting that
Portal variables are external and not consumed by this monorepo (refer to the
"Portal" section). Ensure the new text clearly links to or references the Portal
section so readers understand the exception.

In `@AGENTS.md`:
- Line 92: Replace the unsafe `sudo chmod 666 /var/run/docker.sock` instruction
with a safer alternative: instruct users to add themselves to the `docker` group
(e.g., `usermod -aG docker $USER` and re-login) or, if temporary access is
needed, use `sudo` for Docker commands or change socket ownership to root:docker
with `sudo chown root:docker /var/run/docker.sock && sudo chmod 660
/var/run/docker.sock`; update the AGENTS.md line that currently references `sudo
chmod 666 /var/run/docker.sock` to one of these safer options and include a note
that adding users to the docker group requires a new login/session.

In `@apps/atheme/AUDIT_REPORT.md`:
- Around line 3-7: Update the audit document header to pin the exact audited
revision by adding the commit SHA (audited_commit: <sha>) and a remediation
snapshot identifier, and annotate each finding entry in AUDIT_REPORT.md with an
explicit remediation status (pre-remediation or post-remediation) and a
reference to the PR/commit that fixed it; specifically update the header fields
around "Date", "Local config", and "Upstream reference" to include the audited
commit and add a per-finding status column or inline tag for the findings that
were fixed in this PR (the entries noted as already changed) so readers can
clearly see pre/post remediation state and where fixes live.

In `@apps/atheme/config/atheme.conf.template`:
- Around line 791-793: The repo lacks a bootstrap for the first SRA which causes
operator lockout; add an automated seed or clear docs: implement one of the
following fixes—(A) in docker-entrypoint.sh detect if no SRA exists and run the
SOPER ADD command against the running IRCd (e.g., piping "SOPER ADD
$ATHEME_SRA_ACCOUNT sra" via nc to localhost:6667) after startup, or (B) add a
templated include file (e.g., sras.conf) that substitutes an ATHEME_SRA_ACCOUNT
env var and is included by atheme.conf.template, or (C) add a clear README
section with explicit manual first-boot steps to connect and run "/msg OperServ
SOPER ADD <account> sra"; reference SOPER ADD, ATHEME_SRA_ACCOUNT,
docker-entrypoint.sh, and sras.conf to locate where to implement the change.

In `@apps/prosody/config/prosody.cfg.lua`:
- Around line 916-918: The fallback feed URL in the feeds table (the feed entry
using Lua.os.getenv("PROSODY_FEED_URL") or "...") is incorrect; replace the
literal "https://[REDACTED].org/feed" with "https://allthingslinux.org/feed" so
the feed key uses the correct fallback when PROSODY_FEED_URL is not set.
- Around line 572-584: The config currently allows the insecure Resource Owner
Password Grant; remove "password" from the allowed_oauth2_grant_types table
(leaving "authorization_code" and "device_code"), enable PKCE by setting
oauth2_require_code_challenge = true so authorization_code uses PKCE, and ensure
oauth2_registration_key is not falling back to the hardcoded
"dev-oauth2-registration-key" in production (use
Lua.os.getenv("PROSODY_OAUTH2_REGISTRATION_KEY") only and fail or warn when
unset). Reference the symbols allowed_oauth2_grant_types,
oauth2_require_code_challenge, and oauth2_registration_key when making these
changes.

In `@docs/audits/env-var-audit.md`:
- Line 3: The audit file’s generated header ("Generated: 2026-02-26 | Scope:
Full 12-factor env var cleanup") and the recommendations block that calls out
env var issues must be clarified as either a pre-remediation snapshot or be
regenerated after the PR’s environment template changes; update that header and
the recommendations text to explicitly state “pre-remediation snapshot” (or
re-run generation so it reflects current state) and annotate which specific
findings are resolved by the PR’s updated environment template so operators
won’t be confused about which items remain to address.

In `@justfile`:
- Around line 57-65: The two just targets down-staging and down-prod run an
unscoped docker compose down and can tear down all services; update each target
(down-staging and down-prod) to pass an explicit project name (e.g., docker
compose -p staging down and docker compose -p prod down or use --project-name)
so the teardown is scoped to the intended environment, ensuring you reference
the same project name that your corresponding up/start targets use.
- Around line 41-50: The staging and prod targets in the Justfile must set
ATL_ENVIRONMENT explicitly to avoid using a stray value from .env; update the
staging target (symbol: staging) to set ATL_ENVIRONMENT=staging and the prod
target (symbol: prod) to set ATL_ENVIRONMENT=production before calling
./scripts/init.sh and docker compose so both the init script and Docker Compose
use the intended environment (e.g., export or prefix the two commands in each
target).

In `@scripts/prepare-config.sh`:
- Around line 59-60: The if-condition that decides to load .env.dev currently
uses a default value (${ATL_ENVIRONMENT:-dev}) which causes unset
ATL_ENVIRONMENT to behave as "dev"; update the conditional in
scripts/prepare-config.sh so it only loads .env.dev when ATL_ENVIRONMENT is
explicitly set to "dev" (e.g. use a check that does not default, like testing
"${ATL_ENVIRONMENT:-}" = "dev" or ensuring ATL_ENVIRONMENT is non-empty and
equals "dev"), keeping the existing file existence check for
$PROJECT_ROOT/.env.dev and the log_info call intact.

---

Nitpick comments:
In `@apps/atheme/config/atheme.conf.template`:
- Line 258: Update the inline comment mentioning "saslserv/scram" to remove the
hardcoded "(line 174)" and replace it with a stable reference such as "see the
SASL section above" or "as noted in the SASL section" so future edits won't
break the reference; locate the comment that currently reads "saslserv/scram
already loaded above in the SASL section (line 174)" and edit it to reference
the "SASL section" by name only (or add a nearby header/anchor comment "SASL
section" if one doesn't exist) to provide a durable pointer.

In `@apps/unrealircd/config/unrealircd.conf.template`:
- Around line 778-782: The drpass block currently uses a single env var
IRC_DRPASS for both restart and die; change it to use separate environment
variables (e.g., IRC_RESTART_PASS for restart and IRC_DIE_PASS for die) so each
command has its own credential, updating the drpass { restart ...; die ...; }
entries accordingly and keeping IRC_DRPASS only as an optional fallback if
needed; ensure the template references the new env names wherever drpass is
configured.

In `@docs/audits/prosody-config-audit.md`:
- Around line 206-214: Update the summary table to reflect the fixes applied in
this PR: decrease the 🔴 CRITICAL count by 1 to remove the "global ssl block
commented out" item (since the ssl block is now uncommented), decrease the 🟡
WARNING count by 2 to remove "legacyauth enabled" and the open
`http_status_allow_cidr` item (since `legacyauth` is now disabled and
`http_status_allow_cidr` is restricted), and optionally add a short "Status:
Fixed in this PR" note next to those specific rows (reference `legacyauth`,
`http_status_allow_cidr`, and the global ssl block) so readers can see these
items were remediated rather than still outstanding.

In `@docs/audits/unrealircd-config-audit.md`:
- Around line 642-666: Update the "Summary" section under "By Severity" and
"Priority Fixes" to reflect that the three critical issues have been fixed in
this PR: remove or decrement the "🔴 CRITICAL" count and delete or mark resolved
the items "Missing `drpass` block", "admin oper `mask *@*`", and "hardcoded
WEBIRC password"; instead list them as fixed and reference the changes that
addressed them (the added drpass block, WEBIRC password switched to env var, and
tightened admin oper mask). Also note the additional warnings that were
addressed (add mentions that `set::connthrottle`, `set::spamfilter`, and
`set::oper-only-stats` were added or that `allow-user-stats` was applied) and
update the "Priority Fixes" list to remove or demote items already implemented.
Ensure the table counts and the "Priority Fixes" bullets match the current state
of the config changes.

In `@docs/deployment.md`:
- Around line 114-116: The runbook currently pipes the remote install script
directly into bash with the `curl --proto '=https' --tlsv1.2 -sSf
https://just.systems/install.sh | bash -s -- --to /usr/local/bin` pattern;
change the guidance to avoid `curl | bash` by recommending package-managed
installation if available (e.g., distro package or Homebrew), or instructing to
download the installer to a temporary file, verify its checksum or signature
against an authoritative source, and only then execute it (or extract/install
manually) for the `just` installer; update the docs to include the exact
alternative steps (download URL, checksum/signature verification step, and safe
execution instructions) in place of the current piped command.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21d86fb and 0239b22.

📒 Files selected for processing (16)
  • .env.dev.example
  • .env.example
  • AGENTS.md
  • apps/atheme/AUDIT_REPORT.md
  • apps/atheme/config/atheme.conf.template
  • apps/bridge/tests/test_irc_client.py
  • apps/prosody/config/prosody.cfg.lua
  • apps/prosody/modules.list
  • apps/unrealircd/config/unrealircd.conf.template
  • docs/audits/dev-prod-lifecycle-audit.md
  • docs/audits/env-var-audit.md
  • docs/audits/prosody-config-audit.md
  • docs/audits/unrealircd-config-audit.md
  • docs/deployment.md
  • justfile
  • scripts/prepare-config.sh
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Seer Code Review
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
🧬 Code graph analysis (2)
scripts/prepare-config.sh (1)
scripts/init.sh (1)
  • log_info (37-39)
apps/bridge/tests/test_irc_client.py (4)
apps/bridge/src/bridge/config/schema.py (1)
  • irc_relaymsg_clean_nicks (159-163)
apps/bridge/tests/test_discord_adapter.py (1)
  • router (27-39)
apps/bridge/src/bridge/gateway/router.py (1)
  • get_mapping_for_discord (97-102)
apps/bridge/src/bridge/adapters/irc/client.py (1)
  • _send_message (552-607)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 12-12: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 16-16: [UnorderedKey] The PGID key should go before the PUID key

(UnorderedKey)


[warning] 21-21: [UnorderedKey] The ATL_CHAT_IP key should go before the ATL_GATEWAY_IP key

(UnorderedKey)


[warning] 25-25: [UnorderedKey] The CLOUDFLARE_DNS_API_TOKEN key should go before the LETSENCRYPT_EMAIL key

(UnorderedKey)


[warning] 32-32: [UnorderedKey] The ATHEME_VERSION key should go before the UNREALIRCD_VERSION key

(UnorderedKey)


[warning] 37-37: [UnorderedKey] The IRC_NETWORK_NAME key should go before the IRC_ROOT_DOMAIN key

(UnorderedKey)


[warning] 38-38: [UnorderedKey] The IRC_CLOAK_PREFIX key should go before the IRC_DOMAIN key

(UnorderedKey)


[warning] 42-42: [UnorderedKey] The IRC_SERVER_PORT key should go before the IRC_TLS_PORT key

(UnorderedKey)


[warning] 43-43: [UnorderedKey] The IRC_RPC_PORT key should go before the IRC_SERVER_PORT key

(UnorderedKey)


[warning] 52-52: [UnorderedKey] The IRC_DRPASS key should go before the IRC_OPER_PASSWORD key

(UnorderedKey)


[warning] 54-54: [UnorderedKey] The ATL_WEBIRC_PASSWORD key should go before the IRC_CLOAK_KEY_1 key

(UnorderedKey)


[warning] 58-58: [UnorderedKey] The IRC_ADMIN_EMAIL key should go before the IRC_ADMIN_NAME key

(UnorderedKey)


[warning] 71-71: [UnorderedKey] The ATHEME_SERVER_NAME key should go before the IRC_SERVICES_SERVER key

(UnorderedKey)


[warning] 72-72: [UnorderedKey] The ATHEME_SERVER_DESC key should go before the ATHEME_SERVER_NAME key

(UnorderedKey)


[warning] 73-73: [UnorderedKey] The ATHEME_UPLINK_HOST key should go before the IRC_SERVICES_SERVER key

(UnorderedKey)


[warning] 74-74: [UnorderedKey] The ATHEME_UPLINK_PORT key should go before the IRC_SERVICES_SERVER key

(UnorderedKey)


[warning] 75-75: [UnorderedKey] The ATHEME_NUMERIC key should go before the ATHEME_SERVER_DESC key

(UnorderedKey)


[warning] 76-76: [UnorderedKey] The ATHEME_RECONTIME key should go before the ATHEME_SERVER_DESC key

(UnorderedKey)


[warning] 77-77: [UnorderedKey] The ATHEME_HTTPD_PORT key should go before the ATHEME_NUMERIC key

(UnorderedKey)


[warning] 173-173: [UnorderedKey] The WEBPANEL_RPC_PASSWORD key should go before the WEBPANEL_RPC_USER key

(UnorderedKey)


[warning] 178-178: [UnorderedKey] The THELOUNGE_DELETE_UPLOADS_AFTER_MINUTES key should go before the THELOUNGE_PORT key

(UnorderedKey)


[warning] 184-184: [UnorderedKey] The PROSODY_ADMIN_EMAIL key should go before the XMPP_DOMAIN key

(UnorderedKey)


[warning] 193-193: [UnorderedKey] The PROSODY_DB_NAME key should go before the PROSODY_DB_PORT key

(UnorderedKey)


[warning] 195-195: [UnorderedKey] The PROSODY_DB_PASSWORD key should go before the PROSODY_DB_PORT key

(UnorderedKey)


[warning] 201-201: [UnorderedKey] The PROSODY_HTTP_PORT key should go before the PROSODY_S2S_PORT key

(UnorderedKey)


[warning] 202-202: [UnorderedKey] The PROSODY_HTTPS_PORT key should go before the PROSODY_HTTP_PORT key

(UnorderedKey)


[warning] 203-203: [UnorderedKey] The PROSODY_C2S_DIRECT_TLS_PORT key should go before the PROSODY_C2S_PORT key

(UnorderedKey)


[warning] 204-204: [UnorderedKey] The PROSODY_S2S_DIRECT_TLS_PORT key should go before the PROSODY_S2S_PORT key

(UnorderedKey)


[warning] 205-205: [UnorderedKey] The PROSODY_PROXY65_PORT key should go before the PROSODY_S2S_DIRECT_TLS_PORT key

(UnorderedKey)


[warning] 209-209: [UnorderedKey] The TURNS_PORT key should go before the TURN_PORT key

(UnorderedKey)


[warning] 211-211: [UnorderedKey] The TURN_EXTERNAL_HOST key should go before the TURN_PORT key

(UnorderedKey)


[warning] 218-218: [UnorderedKey] The PROSODY_ALLOW_UNENCRYPTED_PLAIN_AUTH key should go before the PROSODY_C2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 219-219: [UnorderedKey] The PROSODY_MAX_CONNECTIONS_PER_IP key should go before the PROSODY_S2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 220-220: [UnorderedKey] The PROSODY_REGISTRATION_THROTTLE_MAX key should go before the PROSODY_S2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 221-221: [UnorderedKey] The PROSODY_REGISTRATION_THROTTLE_PERIOD key should go before the PROSODY_S2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 222-222: [UnorderedKey] The PROSODY_BLOCK_REGISTRATIONS_REQUIRE key should go before the PROSODY_C2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 229-229: [UnorderedKey] The PROSODY_PROXY_ADDRESS key should go before the PROSODY_UPLOAD_EXTERNAL_URL key

(UnorderedKey)


[warning] 234-234: [UnorderedKey] The PROSODY_SSL_CERT key should go before the PROSODY_SSL_KEY key

(UnorderedKey)


[warning] 240-240: [UnorderedKey] The PROSODY_OPENMETRICS_IP key should go before the PROSODY_STATISTICS key

(UnorderedKey)


[warning] 241-241: [UnorderedKey] The PROSODY_OPENMETRICS_CIDR key should go before the PROSODY_OPENMETRICS_IP key

(UnorderedKey)


[warning] 246-246: [UnorderedKey] The PROSODY_ARCHIVE_COMPRESSION key should go before the PROSODY_ARCHIVE_EXPIRES_AFTER key

(UnorderedKey)


[warning] 248-248: [UnorderedKey] The PROSODY_ARCHIVE_MAX_QUERY_RESULTS key should go before the PROSODY_ARCHIVE_POLICY key

(UnorderedKey)


[warning] 282-282: [UnorderedKey] The PROSODY_PUSH_MAX_DEVICES key should go before the PROSODY_PUSH_MAX_ERRORS key

(UnorderedKey)


[warning] 289-289: [UnorderedKey] The PROSODY_ACCOUNT_GRACE_PERIOD key should go before the PROSODY_ACCOUNT_INACTIVE_PERIOD key

(UnorderedKey)


[warning] 294-294: [UnorderedKey] The PROSODY_SERVER_DESCRIPTION key should go before the PROSODY_SERVER_NAME key

(UnorderedKey)


[warning] 298-298: [UnorderedKey] The LUA_GC_PAUSE key should go before the LUA_GC_STEP_SIZE key

(UnorderedKey)


[warning] 299-299: [UnorderedKey] The LUA_GC_SPEED key should go before the LUA_GC_STEP_SIZE key

(UnorderedKey)


[warning] 309-309: [UnorderedKey] The BRIDGE_DISCORD_CHANNEL_ID key should go before the BRIDGE_DISCORD_TOKEN key

(UnorderedKey)


[warning] 317-317: [UnorderedKey] The BRIDGE_XMPP_COMPONENT_PORT key should go before the BRIDGE_XMPP_COMPONENT_SECRET key

(UnorderedKey)


[warning] 339-339: [UnorderedKey] The IRC_UNREAL_RPC_PASSWORD key should go before the IRC_UNREAL_RPC_USER key

(UnorderedKey)

.env.dev.example

[warning] 69-69: [UnorderedKey] The PROSODY_ALLOW_UNENCRYPTED_PLAIN_AUTH key should go before the PROSODY_C2S_REQUIRE_ENCRYPTION key

(UnorderedKey)

🪛 LanguageTool
docs/audits/unrealircd-config-audit.md

[style] ~79-~79: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nnel "#support"— reasonable choice. - 🟢 **OK** —cloak-keys` — properly source...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~80-~80: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...y sourced from environment variables. - 🟢 OKhiddenhost-prefix — configur...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~81-~81: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nfigurable via ${IRC_CLOAK_PREFIX}. - 🟢 OKcloak-method ip — good choic...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~87-~87: Ensure spelling is correct
Context: ... modes-on-oper "+xws" — good. Ensures opers get cloaking + wallops + server notices...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~92-~92: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...message-time 10s— matches upstream. - 🟢 **OK** —oper-auto-join "#mod-chat"` —...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~93-~93: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...od-chat"— appropriate for team use. - 🟢 **OK** —hide-ulinesandshow-connec...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~192-~192: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...uration with explicit cert/key paths. - 🟢 OK — Port 6901 plaintext for server...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~243-~243: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...M maxsize — matches upstream example. - 🟢 OKJSON log: ircd.json.log ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~244-~244: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...rovides machine-readable audit trail. - 🟢 OKSource filters: Identical ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~245-~245: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ick.*) — consistent and appropriate. - 🟢 **OK** — Log path templated via ${IRC_...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~298-~298: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...kup URLs for users to check their IP. - 🟢 OK — Action gline is appropriate ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~359-~359: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...s (just channel override + relaymsg). - 🟢 OK — Password sourced from environm...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~369-~369: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...th 10m zline — reasonable protection. - 🟢 OKTarget flood protection: C...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~370-~370: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...rivate messages, notices, and tagmsg. - 🟢 OK — **Known vs unknown user differ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~519-~519: Ensure spelling is correct
Context: ...oling Fix: Review and update the badwords list to match your community standards,...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~539-~539: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...separator nofor clean bridge nicks. - 🟢 **OK** —metadata` limits are reasonab...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~621-~621: Use a hyphen to join words.
Context: ...ilarly, *admin* blocks "administrator" type nicks but also catches legitimate n...

(QB_NEW_EN_HYPHEN)

AGENTS.md

[grammar] ~87-~87: Use a hyphen to join words.
Context: ... README.md ## Cursor Cloud specific instructions ### System depend...

(QB_NEW_EN_HYPHEN)

apps/atheme/AUDIT_REPORT.md

[style] ~557-~557: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...alysis difficult and the file will grow very quickly. Combined with `serverinfo::loglevel = ...

(EN_WEAK_ADJECTIVE)

docs/audits/prosody-config-audit.md

[style] ~25-~25: To form a complete sentence, be sure to include a subject.
Context: ...docs state: - c2s_require_encryption should be true (default in Prosody). "Almo...

(MISSING_IT_THERE)


[style] ~26-~26: The double modal “requires encrypted” is nonstandard (only accepted in certain dialects). Consider “to be encrypted”.
Context: ... Prosody). "By default Prosody requires encrypted server-to-server connections." - `s2s_s...

(NEEDS_FIXED)

docs/audits/dev-prod-lifecycle-audit.md

[style] ~71-~71: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...just prodandjust stagingstart the exact same services asdocker compose up -d` (eve...

(EN_WORDINESS_PREMIUM_EXACT_SAME)

🔇 Additional comments (24)
apps/atheme/config/atheme.conf.template (2)

356-356: Good production loglevel hardening.

Line 356 is a solid improvement over debug-all logging for default deployments.


451-464: ChanFix block is now safely non-active and self-documented.

This removes the prior module/block mismatch risk and keeps enablement instructions clear.

.env.dev.example (1)

65-69: Dev-only Prosody overrides are scoped appropriately.

This block is clear and aligned with localhost/self-signed development behavior.

apps/bridge/tests/test_irc_client.py (1)

448-467: Nice test hardening for RELAYMSG config behavior.

Scoping cfg patching in this block makes the test deterministic and avoids relying on ambient global config.

.env.example (1)

215-218: Prosody auth/encryption defaults are correctly hardened.

These defaults set a safer production baseline and align with the security-hardening objective.

apps/unrealircd/config/unrealircd.conf.template (4)

369-390: LGTM! Well-documented proxy blocks for reverse proxy integration.

The new proxy blocks properly handle WEBIRC and X-Forwarded-For headers from Nginx Proxy Manager and Tailscale. The comments clearly explain the purpose and limitations (TCP streams don't support HAProxy PROXY protocol).


787-793: Good security hardening: oper mask restricted and TLS required.

Restricting admin oper to Docker network (172.16.0.0/12) and requiring TLS via require-modes "z" addresses the critical findings from the audit. This significantly reduces the attack surface for oper password brute-forcing.


893-915: Excellent addition of allow-user-stats, spamfilter, and connthrottle configuration.

These blocks address the audit warnings about missing configurations:

  • allow-user-stats "kMp" restricts sensitive stats to opers
  • spamfilter provides global defaults for spam handling
  • connthrottle protects against connection floods with proper exemptions for identified users

971-972: Connection flood limit tightened appropriately for production.

Changing connect-flood from 20:10 to 5:60 (5 connections per 60 seconds per IP) is a significant security improvement that aligns with production best practices mentioned in the audit.

apps/prosody/modules.list (1)

37-37: LGTM! OAuth2 module added to support Portal integration.

This addition is consistent with the http_oauth2 module enablement in prosody.cfg.lua and the new OAuth2 configuration block.

docs/audits/prosody-config-audit.md (3)

32-47: Audit finding appears addressed by changes in this PR.

The audit flags the global ssl block as commented out (lines 531-536), but the actual prosody.cfg.lua in this PR shows the ssl block is now uncommented at lines 533-538. Consider updating this finding to reflect the fix.


49-59: Audit finding addressed: legacyauth now disabled.

The actual prosody.cfg.lua shows legacyauth is now commented out at line 17 with a note to use SASL instead. This matches the recommended fix. Consider updating the audit to mark this as resolved.


61-75: Audit finding addressed: http_status_allow_cidr now restricted.

The actual prosody.cfg.lua shows http_status_allow_cidr is now set to "172.16.0.0/12" at line 448, matching the recommended fix exactly. Consider updating the audit to mark this as resolved.

docs/audits/dev-prod-lifecycle-audit.md (1)

1-283: Comprehensive and well-structured dev/prod lifecycle audit.

This audit provides excellent documentation of the deployment flow gaps between dev and prod environments. The findings are clearly categorized by severity with actionable fix recommendations. The priority order at the end provides a clear remediation roadmap.

docs/audits/unrealircd-config-audit.md (4)

306-338: Audit finding addressed: admin oper mask now restricted.

The actual unrealircd.conf.template shows the admin oper mask is now *@172.16.0.0/12 (line 787) with require-modes "z" (line 792), addressing both the mask restriction and TLS requirement recommendations. Consider updating this finding to reflect the fix.


407-432: Audit finding addressed: drpass block now present.

The actual unrealircd.conf.template now includes a drpass block at lines 778-782 using ${IRC_DRPASS}. Consider updating this finding to reflect the fix.


556-575: Audit finding addressed: WEBIRC password now uses environment variable.

The actual unrealircd.conf.template shows the npm-webirc proxy block at lines 372-376 now uses password "${ATL_WEBIRC_PASSWORD}"; instead of a hardcoded placeholder. Consider updating this finding to reflect the fix.


577-591: Audit finding addressed: RPC user match now restricted.

The actual unrealircd.conf.template shows rpc-user match at line 815 is now { ip 172.16.0.0/12; ip 127.0.0.0/8; } instead of { ip *; }. Consider updating this finding to reflect the fix.

apps/prosody/config/prosody.cfg.lua (6)

17-17: LGTM! Legacy authentication properly disabled.

Commenting out legacyauth and using SCRAM-SHA-256 SASL authentication is the correct security choice for a modern XMPP deployment.


67-67: LGTM! OAuth2 and invites modules properly enabled.

The http_oauth2 module enables Bearer token generation for Portal integration, and the invites module is correctly noted as a dependency for both mod_http_admin_api and mod_http_oauth2.

Also applies to: 77-77


200-204: Good addition: Archive namespace exclusions reduce storage overhead.

Excluding chat state notifications (typing indicators) and Jingle call signaling from the message archive is a sensible optimization that reduces database growth without losing important message content.


533-538: LGTM! Global SSL block enabled with strong TLS settings.

TLS 1.2+ with strong ECDHE ciphers and proper options (cipher_server_preference, single_dh_use, single_ecdh_use) addresses the audit's critical finding about the commented-out SSL block.


447-448: LGTM! HTTP status endpoint properly restricted.

Restricting http_status_allow_cidr to "172.16.0.0/12" (Docker network) addresses the audit finding about the world-open status endpoint.


388-389: LGTM! Trusted proxies expanded for Tailscale support.

Adding 100.64.0.0/10 (Tailscale CGNAT range) to trusted_proxies enables proper X-Forwarded-For handling for Tailscale-proxied connections.

Comment on lines 3 to 7
**Date:** 2026-02-26
**Local config:** `apps/atheme/config/atheme.conf.template`
**Upstream reference:** Atheme `master` branch — `dist/atheme.conf.example`
**Upstream modules:** `modules/` directory tree

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pin the audit to an exact revision and remediation status.

This report reads as current-state, but several findings are already fixed in this PR (for example: Line 32–40, Line 257–280, Line 327–349). Without an explicit audited commit/snapshot and “pre/post remediation” status, the doc is misleading for operations and security triage.

📝 Proposed doc hardening
-**Date:** 2026-02-26
-**Local config:** `apps/atheme/config/atheme.conf.template`
+**Date:** 2026-02-26
+**Audit target commit:** `<commit_sha_audited>`
+**Local config:** `apps/atheme/config/atheme.conf.template`
+**Report scope:** Pre-remediation findings
+**Remediation status:** Some findings in this report are already addressed in PR `#6`; treat this document as an audit snapshot, not live state.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Date:** 2026-02-26
**Local config:** `apps/atheme/config/atheme.conf.template`
**Upstream reference:** Atheme `master` branch — `dist/atheme.conf.example`
**Upstream modules:** `modules/` directory tree
**Date:** 2026-02-26
**Audit target commit:** `<commit_sha_audited>`
**Local config:** `apps/atheme/config/atheme.conf.template`
**Report scope:** Pre-remediation findings
**Remediation status:** Some findings in this report are already addressed in PR `#6`; treat this document as an audit snapshot, not live state.
**Upstream reference:** Atheme `master` branch — `dist/atheme.conf.example`
**Upstream modules:** `modules/` directory tree
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/atheme/AUDIT_REPORT.md` around lines 3 - 7, Update the audit document
header to pin the exact audited revision by adding the commit SHA
(audited_commit: <sha>) and a remediation snapshot identifier, and annotate each
finding entry in AUDIT_REPORT.md with an explicit remediation status
(pre-remediation or post-remediation) and a reference to the PR/commit that
fixed it; specifically update the header fields around "Date", "Local config",
and "Upstream reference" to include the audited commit and add a per-finding
status column or inline tag for the findings that were fixed in this PR (the
entries noted as already changed) so readers can clearly see pre/post
remediation state and where fixes live.

@@ -0,0 +1,513 @@
# Environment Variable Audit — atl.chat Monorepo

> Generated: 2026-02-26 | Scope: Full 12-factor env var cleanup
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify this as a pre-remediation snapshot or regenerate after applied fixes.

The recommendations in Lines 499-507 read as current-state issues, but parts of them are already addressed by this PR’s updated environment template, which can confuse operators.

Also applies to: 499-507

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/audits/env-var-audit.md` at line 3, The audit file’s generated header
("Generated: 2026-02-26 | Scope: Full 12-factor env var cleanup") and the
recommendations block that calls out env var issues must be clarified as either
a pre-remediation snapshot or be regenerated after the PR’s environment template
changes; update that header and the recommendations text to explicitly state
“pre-remediation snapshot” (or re-run generation so it reflects current state)
and annotate which specific findings are resolved by the PR’s updated
environment template so operators won’t be confused about which items remain to
address.

justfile Outdated
Comment on lines 41 to 50
@echo "Initializing Staging Environment..."
./scripts/init.sh
docker compose --env-file .env up -d

# Spin up the production stack (default profile)
# Spin up the production stack
[group('Orchestration')]
prod:
docker compose --profile prod up -d
@echo "Initializing Production Environment..."
./scripts/init.sh
docker compose --env-file .env up -d
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Set ATL_ENVIRONMENT explicitly in staging and prod targets.

These targets currently depend on whatever is in .env; if it is left as dev, initialization can apply dev behavior.

✅ Target-scoped environment fix
 staging:
     `@echo` "Initializing Staging Environment..."
-    ./scripts/init.sh
-    docker compose --env-file .env up -d
+    ATL_ENVIRONMENT=staging ./scripts/init.sh
+    ATL_ENVIRONMENT=staging docker compose --env-file .env up -d

 prod:
     `@echo` "Initializing Production Environment..."
-    ./scripts/init.sh
-    docker compose --env-file .env up -d
+    ATL_ENVIRONMENT=prod ./scripts/init.sh
+    ATL_ENVIRONMENT=prod docker compose --env-file .env up -d
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@echo "Initializing Staging Environment..."
./scripts/init.sh
docker compose --env-file .env up -d
# Spin up the production stack (default profile)
# Spin up the production stack
[group('Orchestration')]
prod:
docker compose --profile prod up -d
@echo "Initializing Production Environment..."
./scripts/init.sh
docker compose --env-file .env up -d
`@echo` "Initializing Staging Environment..."
ATL_ENVIRONMENT=staging ./scripts/init.sh
ATL_ENVIRONMENT=staging docker compose --env-file .env up -d
# Spin up the production stack
[group('Orchestration')]
prod:
`@echo` "Initializing Production Environment..."
ATL_ENVIRONMENT=prod ./scripts/init.sh
ATL_ENVIRONMENT=prod docker compose --env-file .env up -d
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@justfile` around lines 41 - 50, The staging and prod targets in the Justfile
must set ATL_ENVIRONMENT explicitly to avoid using a stray value from .env;
update the staging target (symbol: staging) to set ATL_ENVIRONMENT=staging and
the prod target (symbol: prod) to set ATL_ENVIRONMENT=production before calling
./scripts/init.sh and docker compose so both the init script and Docker Compose
use the intended environment (e.g., export or prefix the two commands in each
target).

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 27, 2026

Deploying atl-chat with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4523ed4
Status:🚫  Build failed.

View logs

@github-actions
Copy link

sh-checker report

To get the full details, please check in the job output.

shellcheck errors

'shellcheck ' returned error 1 finding the following syntactical issues:

----------

In infra/nginx/docker-entrypoint.sh line 7:
envsubst '${XMPP_DOMAIN} ${CERT_DIR}' < /etc/nginx/templates/prosody-https.conf.template > /etc/nginx/conf.d/prosody-https.conf
         ^--------------------------^ SC2016 (info): Expressions don't expand in single quotes, use double quotes for that.

For more information:
  https://www.shellcheck.net/wiki/SC2016 -- Expressions don't expand in singl...
----------

You can address the above issues in one of three ways:
1. Manually correct the issue in the offending shell script;
2. Disable specific issues by adding the comment:
  # shellcheck disable=NNNN
above the line that contains the issue, where NNNN is the error code;
3. Add '-e NNNN' to the SHELLCHECK_OPTS setting in your .yml action file.



shfmt errors

'shfmt ' returned error 1 finding the following formatting issues:

----------
diff .github/scripts/docker.sh.orig .github/scripts/docker.sh
--- .github/scripts/docker.sh.orig
+++ .github/scripts/docker.sh
@@ -57,11 +57,11 @@
 shift || true
 
 case "$COMMAND" in
-  generate-pr-version)        generate_pr_version "$@" ;;
-  generate-release-version)   generate_release_version "$@" ;;
-  validate-build-config)      validate_build_config "$@" ;;
+  generate-pr-version) generate_pr_version "$@" ;;
+  generate-release-version) generate_release_version "$@" ;;
+  validate-build-config) validate_build_config "$@" ;;
   calculate-source-date-epoch) calculate_source_date_epoch "$@" ;;
-  generate-build-date)        generate_build_date "$@" ;;
+  generate-build-date) generate_build_date "$@" ;;
   *)
     echo "Usage: docker.sh {generate-pr-version|generate-release-version|validate-build-config|calculate-source-date-epoch|generate-build-date} [args...]"
     exit 1
diff apps/atheme/docker-entrypoint.sh.orig apps/atheme/docker-entrypoint.sh
--- apps/atheme/docker-entrypoint.sh.orig
+++ apps/atheme/docker-entrypoint.sh
@@ -7,9 +7,9 @@
 
 # Ensure we have proper permissions
 if [ "$(id -u)" = "0" ]; then
-    echo "ERROR: Atheme should not run as root for security reasons"
-    echo "Please run with a non-root user (UID 1000 recommended)"
-    exit 1
+  echo "ERROR: Atheme should not run as root for security reasons"
+  echo "Please run with a non-root user (UID 1000 recommended)"
+  exit 1
 fi
 
 # Create directories with proper ownership
@@ -17,9 +17,9 @@
 
 # Validate configuration exists
 if [ ! -f "/usr/local/atheme/etc/atheme.conf" ]; then
-    echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
-    echo "Please ensure the configuration is properly mounted"
-    exit 1
+  echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
+  echo "Please ensure the configuration is properly mounted"
+  exit 1
 fi
 
 # Clean up stale PID file
@@ -27,25 +27,25 @@
 
 # Validate database directory is writable
 if [ ! -w "/usr/local/atheme/data" ]; then
-    echo "ERROR: Data directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Data directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Validate logs directory is writable
 if [ ! -w "/usr/local/atheme/logs" ]; then
-    echo "ERROR: Logs directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Logs directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Check if this is first run (no database exists)
 if [ ! -f "/usr/local/atheme/data/services.db" ]; then
-    echo "First run detected - creating initial database..."
-    /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
-    echo "Database created successfully"
+  echo "First run detected - creating initial database..."
+  /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
+  echo "Database created successfully"
 else
-    echo "Existing database found - starting with existing data"
+  echo "Existing database found - starting with existing data"
 fi
 
 # Start Atheme services
diff apps/prosody/docker-entrypoint.sh.orig apps/prosody/docker-entrypoint.sh
--- apps/prosody/docker-entrypoint.sh.orig
+++ apps/prosody/docker-entrypoint.sh
@@ -29,21 +29,21 @@
 # ============================================================================
 
 log_info() {
-    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_warn() {
-    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_debug() {
-    if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
-        echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
-    fi
+  if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
+    echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  fi
 }
 
 # ============================================================================
@@ -51,45 +51,45 @@
 # ============================================================================
 
 validate_environment() {
-    log_info "Validating environment configuration..."
-
-    # Validate required domain
-    if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
-        log_error "PROSODY_DOMAIN is required but not set"
-        exit 1
-    fi
-
-    # Validate domain format
-    if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
-        log_error "Invalid domain format: ${PROSODY_DOMAIN}"
-        exit 1
-    fi
-
-    # Set default admin if not provided
-    if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
-        export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
-        log_info "Using default admin: ${PROSODY_ADMIN_JID}"
-    fi
-
-    # Validate database configuration for SQL storage
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        local required_vars=(
-            "PROSODY_DB_DRIVER"
-            "PROSODY_DB_NAME"
-            "PROSODY_DB_USER"
-            "PROSODY_DB_PASSWORD"
-            "PROSODY_DB_HOST"
-        )
-
-        for var in "${required_vars[@]}"; do
-            if [[ -z "${!var:-}" ]]; then
-                log_error "Database variable ${var} is required for SQL storage"
-                exit 1
-            fi
-        done
-    fi
-
-    log_info "Environment validation complete"
+  log_info "Validating environment configuration..."
+
+  # Validate required domain
+  if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
+    log_error "PROSODY_DOMAIN is required but not set"
+    exit 1
+  fi
+
+  # Validate domain format
+  if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
+    log_error "Invalid domain format: ${PROSODY_DOMAIN}"
+    exit 1
+  fi
+
+  # Set default admin if not provided
+  if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
+    export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
+    log_info "Using default admin: ${PROSODY_ADMIN_JID}"
+  fi
+
+  # Validate database configuration for SQL storage
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    local required_vars=(
+      "PROSODY_DB_DRIVER"
+      "PROSODY_DB_NAME"
+      "PROSODY_DB_USER"
+      "PROSODY_DB_PASSWORD"
+      "PROSODY_DB_HOST"
+    )
+
+    for var in "${required_vars[@]}"; do
+      if [[ -z "${!var:-}" ]]; then
+        log_error "Database variable ${var} is required for SQL storage"
+        exit 1
+      fi
+    done
+  fi
+
+  log_info "Environment validation complete"
 }
 
 # ============================================================================
@@ -97,186 +97,186 @@
 # ============================================================================
 
 setup_directories() {
-    log_info "Setting up directories..."
-
-    local dirs=(
-        "$PROSODY_DATA_DIR"
-        "$PROSODY_LOG_DIR"
-        "$PROSODY_CERT_DIR"
-        "$PROSODY_UPLOAD_DIR"
-        "$(dirname "$PROSODY_PID_FILE")"
-    )
-
-    for dir in "${dirs[@]}"; do
-        if [[ ! -d "$dir" ]]; then
-            log_debug "Creating directory: $dir"
-            mkdir -p "$dir"
-        fi
-
-        # Ensure proper ownership (only if running as root)
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
-        fi
-    done
-
-    # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
-    # Prosody needs write access for SQLite (MAM, PEP, etc.)
-    if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
-        if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
-            log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
-        fi
-    fi
-
-    log_info "Directory setup complete"
+  log_info "Setting up directories..."
+
+  local dirs=(
+    "$PROSODY_DATA_DIR"
+    "$PROSODY_LOG_DIR"
+    "$PROSODY_CERT_DIR"
+    "$PROSODY_UPLOAD_DIR"
+    "$(dirname "$PROSODY_PID_FILE")"
+  )
+
+  for dir in "${dirs[@]}"; do
+    if [[ ! -d "$dir" ]]; then
+      log_debug "Creating directory: $dir"
+      mkdir -p "$dir"
+    fi
+
+    # Ensure proper ownership (only if running as root)
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
+    fi
+  done
+
+  # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
+  # Prosody needs write access for SQLite (MAM, PEP, etc.)
+  if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
+    if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
+      log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
+    fi
+  fi
+
+  log_info "Directory setup complete"
 }
 
 setup_certificates() {
-    log_info "Setting up SSL certificates..."
-
-    # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
-    local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
-    local cert_file="${live_dir}/fullchain.pem"
-    local key_file="${live_dir}/privkey.pem"
-
-    if [[ -f "$cert_file" && -f "$key_file" ]]; then
-        log_info "Certificates found for ${PROSODY_DOMAIN}"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file" 2> /dev/null || true
-        chmod 600 "$key_file" 2> /dev/null || true
-        # HTTPS service automatic discovery (Prosody Certificates doc):
-        # - https/fullchain.pem + https/privkey.pem (directory symlink)
-        # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
-    local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
-    local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
-    if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
-        log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
-        mkdir -p "$live_dir"
-        cp "$legacy_cert" "$cert_file"
-        cp "$legacy_key" "$key_file"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file"
-        chmod 600 "$key_file"
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Generate self-signed certificate for development/testing
-    log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
-    log_warn "This is suitable for development only - use proper certificates in production"
-
+  log_info "Setting up SSL certificates..."
+
+  # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
+  local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
+  local cert_file="${live_dir}/fullchain.pem"
+  local key_file="${live_dir}/privkey.pem"
+
+  if [[ -f "$cert_file" && -f "$key_file" ]]; then
+    log_info "Certificates found for ${PROSODY_DOMAIN}"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    fi
+    chmod 644 "$cert_file" 2> /dev/null || true
+    chmod 600 "$key_file" 2> /dev/null || true
+    # HTTPS service automatic discovery (Prosody Certificates doc):
+    # - https/fullchain.pem + https/privkey.pem (directory symlink)
+    # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
+  local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
+  local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
+  if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
+    log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
     mkdir -p "$live_dir"
-    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-        -keyout "$key_file" \
-        -out "$cert_file" \
-        -subj "/CN=${PROSODY_DOMAIN}" \
-        -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    cp "$legacy_cert" "$cert_file"
+    cp "$legacy_key" "$key_file"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
     fi
     chmod 644 "$cert_file"
     chmod 600 "$key_file"
-
-    # HTTPS service automatic discovery
-    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-
-    log_info "Self-signed certificate generated successfully"
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Generate self-signed certificate for development/testing
+  log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
+  log_warn "This is suitable for development only - use proper certificates in production"
+
+  mkdir -p "$live_dir"
+  openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+    -keyout "$key_file" \
+    -out "$cert_file" \
+    -subj "/CN=${PROSODY_DOMAIN}" \
+    -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+  fi
+  chmod 644 "$cert_file"
+  chmod 600 "$key_file"
+
+  # HTTPS service automatic discovery
+  ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+
+  log_info "Self-signed certificate generated successfully"
 }
 
 wait_for_database() {
-    if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
-        log_debug "Not using SQL storage, skipping database wait"
-        return 0
-    fi
-
-    local host="${PROSODY_DB_HOST}"
-    local port="${PROSODY_DB_PORT:-5432}"
-    local max_attempts=30
-    local attempt=1
-
-    log_info "Waiting for database connection to ${host}:${port}..."
-
-    while [[ $attempt -le $max_attempts ]]; do
-        if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
-            log_info "Database connection established"
-            return 0
-        fi
-
-        log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
-        sleep 2
-        ((attempt++))
-    done
-
-    log_error "Database connection timeout after ${max_attempts} attempts"
-    exit 1
+  if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
+    log_debug "Not using SQL storage, skipping database wait"
+    return 0
+  fi
+
+  local host="${PROSODY_DB_HOST}"
+  local port="${PROSODY_DB_PORT:-5432}"
+  local max_attempts=30
+  local attempt=1
+
+  log_info "Waiting for database connection to ${host}:${port}..."
+
+  while [[ $attempt -le $max_attempts ]]; do
+    if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
+      log_info "Database connection established"
+      return 0
+    fi
+
+    log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
+    sleep 2
+    ((attempt++))
+  done
+
+  log_error "Database connection timeout after ${max_attempts} attempts"
+  exit 1
 }
 
 validate_configuration() {
-    log_info "Validating Prosody configuration..."
-
-    # Check if config file exists
-    if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
-        log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
-        exit 1
-    fi
-
-    # Validate configuration using prosodyctl (allow warnings in development)
-    log_info "Validating Prosody configuration..."
-    if ! prosodyctl check config; then
-        log_error "Prosody configuration validation failed"
-        log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
-        if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
-            exit 1
-        else
-            log_warn "Development mode: continuing despite configuration warnings"
-        fi
-    fi
-
-    log_info "Configuration validation successful"
-}
-
-setup_community_modules() {
-    log_info "Setting up community modules..."
-
-    # Check if community modules source exists
-    local source_dir="/usr/local/lib/prosody/prosody-modules"
-    if [[ ! -d "$source_dir" ]]; then
-        log_warn "Community modules repository not found at $source_dir"
-        log_warn "Modules will need to be installed manually or via prosodyctl"
-        return 0
-    fi
-
-    # Check if modules are available from enabled directory
-    local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
-    if [[ ! -d "$enabled_dir" ]]; then
-        log_warn "Enabled community modules directory not found at $enabled_dir"
-        return 0
+  log_info "Validating Prosody configuration..."
+
+  # Check if config file exists
+  if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
+    log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
+    exit 1
+  fi
+
+  # Validate configuration using prosodyctl (allow warnings in development)
+  log_info "Validating Prosody configuration..."
+  if ! prosodyctl check config; then
+    log_error "Prosody configuration validation failed"
+    log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
+    if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
+      exit 1
     else
-        log_info "Community modules found in $enabled_dir"
-        local module_count
-        module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
-        log_info "Enabled modules: $module_count"
-    fi
-
-    # Ensure proper ownership (only if running as root)
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
-    fi
+      log_warn "Development mode: continuing despite configuration warnings"
+    fi
+  fi
+
+  log_info "Configuration validation successful"
+}
+
+setup_community_modules() {
+  log_info "Setting up community modules..."
+
+  # Check if community modules source exists
+  local source_dir="/usr/local/lib/prosody/prosody-modules"
+  if [[ ! -d "$source_dir" ]]; then
+    log_warn "Community modules repository not found at $source_dir"
+    log_warn "Modules will need to be installed manually or via prosodyctl"
+    return 0
+  fi
+
+  # Check if modules are available from enabled directory
+  local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
+  if [[ ! -d "$enabled_dir" ]]; then
+    log_warn "Enabled community modules directory not found at $enabled_dir"
+    return 0
+  else
+    log_info "Community modules found in $enabled_dir"
+    local module_count
+    module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
+    log_info "Enabled modules: $module_count"
+  fi
+
+  # Ensure proper ownership (only if running as root)
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
+  fi
 }
 
 # ============================================================================
@@ -285,28 +285,28 @@
 
 # shellcheck disable=SC2317,SC2329  # Function is called by signal handlers via trap
 cleanup() {
-    log_info "Received shutdown signal, stopping Prosody..."
-
-    if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
-        # Send SIGTERM for graceful shutdown
-        kill -TERM "$PROSODY_PID" 2> /dev/null || true
-
-        # Wait for graceful shutdown (max 30 seconds)
-        local timeout=30
-        while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
-            sleep 1
-            ((timeout--))
-        done
-
-        # Force kill if still running
-        if kill -0 "$PROSODY_PID" 2> /dev/null; then
-            log_warn "Prosody did not shut down gracefully, forcing termination"
-            kill -KILL "$PROSODY_PID" 2> /dev/null || true
-        fi
-    fi
-
-    log_info "Prosody shutdown complete"
-    exit 0
+  log_info "Received shutdown signal, stopping Prosody..."
+
+  if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
+    # Send SIGTERM for graceful shutdown
+    kill -TERM "$PROSODY_PID" 2> /dev/null || true
+
+    # Wait for graceful shutdown (max 30 seconds)
+    local timeout=30
+    while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
+      sleep 1
+      ((timeout--))
+    done
+
+    # Force kill if still running
+    if kill -0 "$PROSODY_PID" 2> /dev/null; then
+      log_warn "Prosody did not shut down gracefully, forcing termination"
+      kill -KILL "$PROSODY_PID" 2> /dev/null || true
+    fi
+  fi
+
+  log_info "Prosody shutdown complete"
+  exit 0
 }
 
 # Setup signal handlers
@@ -317,57 +317,57 @@
 # ============================================================================
 
 main() {
-    log_info "Starting Professional Prosody XMPP Server..."
-
-    # Display version information
-    local prosody_version
-    prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
-    log_info "Prosody version: $prosody_version"
-
-    # Prefer mounted config if present
-    if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
-        log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
-        cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
-        chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-        chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-    fi
-
-    # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
-    if [[ -d "/etc/prosody/config/conf.d" ]]; then
-        log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
-        mkdir -p "/etc/prosody/conf.d"
-        rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
-        chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
-        find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
-    fi
-
-    # Environment and setup
-    validate_environment
-    setup_directories
-    setup_certificates
-    wait_for_database
-    validate_configuration
-    setup_community_modules
-
-    # Display configuration summary
-    log_info "Configuration summary:"
-    log_info "  Domain: ${PROSODY_DOMAIN}"
-    log_info "  Admins: ${PROSODY_ADMIN_JID}"
-    log_info "  Storage: ${PROSODY_STORAGE:-sql}"
-    log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
-    log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
-
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
-    fi
-
-    # Start Prosody
-    log_info "Starting Prosody XMPP server..."
-
-    # Switch to prosody user and start in foreground
-    exec gosu "$PROSODY_USER" prosody \
-        --config="$PROSODY_CONFIG_FILE" \
-        --foreground
+  log_info "Starting Professional Prosody XMPP Server..."
+
+  # Display version information
+  local prosody_version
+  prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
+  log_info "Prosody version: $prosody_version"
+
+  # Prefer mounted config if present
+  if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
+    log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
+    cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
+    chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+    chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+  fi
+
+  # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
+  if [[ -d "/etc/prosody/config/conf.d" ]]; then
+    log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
+    mkdir -p "/etc/prosody/conf.d"
+    rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
+    chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
+    find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
+  fi
+
+  # Environment and setup
+  validate_environment
+  setup_directories
+  setup_certificates
+  wait_for_database
+  validate_configuration
+  setup_community_modules
+
+  # Display configuration summary
+  log_info "Configuration summary:"
+  log_info "  Domain: ${PROSODY_DOMAIN}"
+  log_info "  Admins: ${PROSODY_ADMIN_JID}"
+  log_info "  Storage: ${PROSODY_STORAGE:-sql}"
+  log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
+  log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
+
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
+  fi
+
+  # Start Prosody
+  log_info "Starting Prosody XMPP server..."
+
+  # Switch to prosody user and start in foreground
+  exec gosu "$PROSODY_USER" prosody \
+    --config="$PROSODY_CONFIG_FILE" \
+    --foreground
 }
 
 # ============================================================================
@@ -376,8 +376,8 @@
 
 # Ensure we're running as root initially (for setup)
 if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root for initial setup"
-    exit 1
+  log_error "This script must be run as root for initial setup"
+  exit 1
 fi
 
 # Execute main function
diff apps/unrealircd/docker-entrypoint.sh.orig apps/unrealircd/docker-entrypoint.sh
--- apps/unrealircd/docker-entrypoint.sh.orig
+++ apps/unrealircd/docker-entrypoint.sh
@@ -21,8 +21,8 @@
 
 # Validate config exists
 if [ ! -f "/home/unrealircd/unrealircd/config/unrealircd.conf" ]; then
-    echo "ERROR: Configuration file not found!"
-    exit 1
+  echo "ERROR: Configuration file not found!"
+  exit 1
 fi
 
 # Ownership and permissions are handled by Containerfile and user switching
@@ -29,21 +29,21 @@
 
 # Handle commands
 case "$1" in
-    start)
-        shift
-        echo "Starting UnrealIRCd in foreground..."
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        else
-            exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        fi
-        ;;
-    *)
-        echo "Running command: $1"
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
-        else
-            exec /home/unrealircd/unrealircd/unrealircd "$@"
-        fi
-        ;;
+  start)
+    shift
+    echo "Starting UnrealIRCd in foreground..."
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    else
+      exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    fi
+    ;;
+  *)
+    echo "Running command: $1"
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
+    else
+      exec /home/unrealircd/unrealircd/unrealircd "$@"
+    fi
+    ;;
 esac
diff apps/unrealircd/scripts/manage-modules.sh.orig apps/unrealircd/scripts/manage-modules.sh
--- apps/unrealircd/scripts/manage-modules.sh.orig
+++ apps/unrealircd/scripts/manage-modules.sh
@@ -29,280 +29,280 @@
 
 # Check if we're running as the correct user (unrealircd, typically UID from PUID)
 check_user() {
-    local expected_uid="${PUID:-1000}"
-    if [ "$(id -u)" != "$expected_uid" ]; then
-        print_error "This script must run as the unrealircd user (UID ${expected_uid})"
-        print_error "Current user: $(id -un) (UID $(id -u))"
-        exit 1
-    fi
+  local expected_uid="${PUID:-1000}"
+  if [ "$(id -u)" != "$expected_uid" ]; then
+    print_error "This script must run as the unrealircd user (UID ${expected_uid})"
+    print_error "Current user: $(id -un) (UID $(id -u))"
+    exit 1
+  fi
 }
 
 # Check if UnrealIRCd is running
 check_unrealircd_running() {
-    if pgrep -f unrealircd >/dev/null; then
-        print_warning "UnrealIRCd is currently running"
-        print_warning "Some operations may require a restart to take effect"
-        return 0
-    else
-        print_status "UnrealIRCd is not currently running"
-        return 1
-    fi
+  if pgrep -f unrealircd > /dev/null; then
+    print_warning "UnrealIRCd is currently running"
+    print_warning "Some operations may require a restart to take effect"
+    return 0
+  else
+    print_status "UnrealIRCd is not currently running"
+    return 1
+  fi
 }
 
 # List available modules
 list_modules() {
-    print_header "Available Contrib Modules"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Fetching latest module list..."
-    if [ -d "$CONTRIB_DIR" ]; then
-        cd "$CONTRIB_DIR" && git pull --quiet origin main 2>/dev/null || true
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Available modules:"
-    echo
-    "$UNREALIRCD_BIN" module list 2>/dev/null || {
-        print_warning "Module manager not available, showing contrib directory contents:"
-        for item in "$CONTRIB_DIR"/*; do
-            if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
-                basename "$item"
-            fi
-        done | sort
-    }
+  print_header "Available Contrib Modules"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Fetching latest module list..."
+  if [ -d "$CONTRIB_DIR" ]; then
+    cd "$CONTRIB_DIR" && git pull --quiet origin main 2> /dev/null || true
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Available modules:"
+  echo
+  "$UNREALIRCD_BIN" module list 2> /dev/null || {
+    print_warning "Module manager not available, showing contrib directory contents:"
+    for item in "$CONTRIB_DIR"/*; do
+      if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
+        basename "$item"
+      fi
+    done | sort
+  }
 }
 
 # Show module information
 show_module_info() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 info <module-name>"
-        return 1
-    fi
-
-    print_header "Module Information: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to get info via module manager first
-    if "$UNREALIRCD_BIN" module info "third/$module_name" 2>/dev/null; then
-        return 0
-    fi
-
-    # Fallback to showing contrib directory info
-    if [ -d "$CONTRIB_DIR/$module_name" ]; then
-        print_status "Module found in contrib directory:"
-        ls -la "$CONTRIB_DIR/$module_name"
-
-        if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README.md"
-        fi
-
-        if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README"
-        fi
-    else
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 info <module-name>"
+    return 1
+  fi
+
+  print_header "Module Information: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to get info via module manager first
+  if "$UNREALIRCD_BIN" module info "third/$module_name" 2> /dev/null; then
+    return 0
+  fi
+
+  # Fallback to showing contrib directory info
+  if [ -d "$CONTRIB_DIR/$module_name" ]; then
+    print_status "Module found in contrib directory:"
+    ls -la "$CONTRIB_DIR/$module_name"
+
+    if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README.md"
+    fi
+
+    if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README"
+    fi
+  else
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
 }
 
 # Install a module
 install_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 install <module-name>"
-        return 1
-    fi
-
-    print_header "Installing Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Check if module is already installed
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        print_warning "Module '$module_name' is already installed"
-        return 0
-    fi
-
-    # Try to install via module manager
-    print_status "Installing via module manager..."
-    if "$UNREALIRCD_BIN" module install "third/$module_name"; then
-        print_success "Module '$module_name' installed successfully"
-
-        # Check if we need to add loadmodule to config
-        if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
-            print_status "Checking if loadmodule line needs to be added..."
-            if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
-                print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-                print_warning "Then REHASH or restart UnrealIRCd"
-            fi
-        fi
-
-        return 0
-    fi
-
-    # Fallback to manual installation
-    print_warning "Module manager failed, attempting manual installation..."
-
-    if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR/$module_name" || exit 1
-
-    if [ -f "Makefile" ]; then
-        print_status "Compiling module..."
-        make clean 2>/dev/null || true
-        make
-
-        if [ -f "$module_name.so" ]; then
-            print_status "Installing module..."
-            cp "$module_name.so" "$MODULES_DIR/third/"
-            print_success "Module '$module_name' installed manually"
-
-            print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-            print_warning "Then REHASH or restart UnrealIRCd"
-        else
-            print_error "Module compilation failed"
-            return 1
-        fi
-    else
-        print_error "No Makefile found for module '$module_name'"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 install <module-name>"
+    return 1
+  fi
+
+  print_header "Installing Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Check if module is already installed
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    print_warning "Module '$module_name' is already installed"
+    return 0
+  fi
+
+  # Try to install via module manager
+  print_status "Installing via module manager..."
+  if "$UNREALIRCD_BIN" module install "third/$module_name"; then
+    print_success "Module '$module_name' installed successfully"
+
+    # Check if we need to add loadmodule to config
+    if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
+      print_status "Checking if loadmodule line needs to be added..."
+      if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
+        print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+        print_warning "Then REHASH or restart UnrealIRCd"
+      fi
+    fi
+
+    return 0
+  fi
+
+  # Fallback to manual installation
+  print_warning "Module manager failed, attempting manual installation..."
+
+  if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR/$module_name" || exit 1
+
+  if [ -f "Makefile" ]; then
+    print_status "Compiling module..."
+    make clean 2> /dev/null || true
+    make
+
+    if [ -f "$module_name.so" ]; then
+      print_status "Installing module..."
+      cp "$module_name.so" "$MODULES_DIR/third/"
+      print_success "Module '$module_name' installed manually"
+
+      print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+      print_warning "Then REHASH or restart UnrealIRCd"
+    else
+      print_error "Module compilation failed"
+      return 1
+    fi
+  else
+    print_error "No Makefile found for module '$module_name'"
+    return 1
+  fi
 }
 
 # Uninstall a module
 uninstall_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 uninstall <module-name>"
-        return 1
-    fi
-
-    print_header "Uninstalling Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to uninstall via module manager first
-    if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2>/dev/null; then
-        print_success "Module '$module_name' uninstalled successfully"
-        return 0
-    fi
-
-    # Fallback to manual removal
-    print_warning "Module manager failed, attempting manual removal..."
-
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        rm -f "$MODULES_DIR/third/$module_name.so"
-        print_success "Module '$module_name' removed manually"
-
-        print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
-        print_warning "Then REHASH or restart UnrealIRCd"
-    else
-        print_error "Module '$module_name' not found in modules directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 uninstall <module-name>"
+    return 1
+  fi
+
+  print_header "Uninstalling Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to uninstall via module manager first
+  if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2> /dev/null; then
+    print_success "Module '$module_name' uninstalled successfully"
+    return 0
+  fi
+
+  # Fallback to manual removal
+  print_warning "Module manager failed, attempting manual removal..."
+
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    rm -f "$MODULES_DIR/third/$module_name.so"
+    print_success "Module '$module_name' removed manually"
+
+    print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
+    print_warning "Then REHASH or restart UnrealIRCd"
+  else
+    print_error "Module '$module_name' not found in modules directory"
+    return 1
+  fi
 }
 
 # Upgrade modules
 upgrade_modules() {
-    local module_name="$1"
-
-    print_header "Upgrading Modules"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    if [ -n "$module_name" ]; then
-        print_status "Upgrading specific module: $module_name"
-        if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2>/dev/null; then
-            print_success "Module '$module_name' upgraded successfully"
-        else
-            print_error "Failed to upgrade module '$module_name'"
-            return 1
-        fi
-    else
-        print_status "Upgrading all modules..."
-        if "$UNREALIRCD_BIN" module upgrade 2>/dev/null; then
-            print_success "All modules upgraded successfully"
-        else
-            print_error "Failed to upgrade modules"
-            return 1
-        fi
-    fi
-
-    print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
+  local module_name="$1"
+
+  print_header "Upgrading Modules"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  if [ -n "$module_name" ]; then
+    print_status "Upgrading specific module: $module_name"
+    if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2> /dev/null; then
+      print_success "Module '$module_name' upgraded successfully"
+    else
+      print_error "Failed to upgrade module '$module_name'"
+      return 1
+    fi
+  else
+    print_status "Upgrading all modules..."
+    if "$UNREALIRCD_BIN" module upgrade 2> /dev/null; then
+      print_success "All modules upgraded successfully"
+    else
+      print_error "Failed to upgrade modules"
+      return 1
+    fi
+  fi
+
+  print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
 }
 
 # Update contrib repository
 update_contrib() {
-    print_header "Updating Contrib Repository"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR" || exit 1
-
-    print_status "Pulling latest changes from unrealircd-contrib..."
-    if git pull --quiet origin main; then
-        print_success "Contrib repository updated successfully"
-        print_status "New modules may now be available"
-    else
-        print_error "Failed to update contrib repository"
-        return 1
-    fi
+  print_header "Updating Contrib Repository"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR" || exit 1
+
+  print_status "Pulling latest changes from unrealircd-contrib..."
+  if git pull --quiet origin main; then
+    print_success "Contrib repository updated successfully"
+    print_status "New modules may now be available"
+  else
+    print_error "Failed to update contrib repository"
+    return 1
+  fi
 }
 
 # Show installed modules
 show_installed() {
-    print_header "Installed Modules"
-
-    if [ ! -d "$MODULES_DIR/third" ]; then
-        print_status "No third-party modules installed"
-        return 0
-    fi
-
-    cd "$MODULES_DIR/third" || exit 1
-
-    local count=0
-    for module in *.so; do
-        if [ -f "$module" ]; then
-            echo "  - third/${module%.so}"
-            ((count++))
-        fi
-    done
-
-    if [ $count -eq 0 ]; then
-        print_status "No third-party modules installed"
-    else
-        print_status "Total installed modules: $count"
-    fi
+  print_header "Installed Modules"
+
+  if [ ! -d "$MODULES_DIR/third" ]; then
+    print_status "No third-party modules installed"
+    return 0
+  fi
+
+  cd "$MODULES_DIR/third" || exit 1
+
+  local count=0
+  for module in *.so; do
+    if [ -f "$module" ]; then
+      echo "  - third/${module%.so}"
+      ((count++))
+    fi
+  done
+
+  if [ $count -eq 0 ]; then
+    print_status "No third-party modules installed"
+  else
+    print_status "Total installed modules: $count"
+  fi
 }
 
 # Show usage
 show_usage() {
-    cat <<EOF
+  cat << EOF
 UnrealIRCd Contrib Modules Management Script
 
 Usage: $0 <command> [options]
@@ -344,40 +344,40 @@
 
 # Main execution
 main() {
-    check_user
-
-    case "${1:-help}" in
+  check_user
+
+  case "${1:-help}" in
     list)
-        list_modules
-        ;;
+      list_modules
+      ;;
     info)
-        show_module_info "$2"
-        ;;
+      show_module_info "$2"
+      ;;
     install)
-        install_module "$2"
-        ;;
+      install_module "$2"
+      ;;
     uninstall)
-        uninstall_module "$2"
-        ;;
+      uninstall_module "$2"
+      ;;
     upgrade)
-        upgrade_modules "$2"
-        ;;
+      upgrade_modules "$2"
+      ;;
     update)
-        update_contrib
-        ;;
+      update_contrib
+      ;;
     installed)
-        show_installed
-        ;;
+      show_installed
+      ;;
     help | --help | -h)
-        show_usage
-        ;;
+      show_usage
+      ;;
     *)
-        print_error "Unknown command: $1"
-        echo
-        show_usage
-        exit 1
-        ;;
-    esac
+      print_error "Unknown command: $1"
+      echo
+      show_usage
+      exit 1
+      ;;
+  esac
 }
 
 # Run main function with all arguments
diff scripts/cert-manager/run.sh.orig scripts/cert-manager/run.sh
--- scripts/cert-manager/run.sh.orig
+++ scripts/cert-manager/run.sh
@@ -9,26 +9,26 @@
 
 # Validate domain format (alphanumeric, hyphens, dots only - prevent injection)
 case "$ROOT_DOMAIN" in
-    *[!a-zA-Z0-9.-]*)
-        echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Domain cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9.-]*)
+    echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Domain cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Validate email format (basic check - no spaces or shell metacharacters)
 case "$EMAIL" in
-    *[!a-zA-Z0-9@._+-]*)
-        echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Email cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9@._+-]*)
+    echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Email cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Lego outputs: certificates/_.<domain>.crt and _.<domain>.key for wildcards
@@ -38,9 +38,9 @@
 
 # Ensure we have credentials
 if [ -z "$CLOUDFLARE_DNS_API_TOKEN" ]; then
-    echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
-    echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
-    exec sleep infinity
+  echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
+  echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
+  exec sleep infinity
 fi
 
 # Initial issuance
@@ -49,8 +49,8 @@
 
 # Renewal loop
 while true; do
-    echo "Sleeping for 24 hours..."
-    sleep 86400
-    echo "Checking for renewal..."
-    lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
+  echo "Sleeping for 24 hours..."
+  sleep 86400
+  echo "Checking for renewal..."
+  lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
 done
diff scripts/gencloak-update-env.sh.orig scripts/gencloak-update-env.sh
--- scripts/gencloak-update-env.sh.orig
+++ scripts/gencloak-update-env.sh
@@ -12,23 +12,23 @@
 
 cd "$PROJECT_ROOT"
 
-output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2>/dev/null)
+output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2> /dev/null)
 echo "$output"
 
 mapfile -t keys < <(echo "$output" | grep -oE '"[^"]{50,}"' | tr -d '"')
 if [ ${#keys[@]} -ne 3 ]; then
-    echo "Failed to parse 3 cloak keys from gencloak output"
-    exit 1
+  echo "Failed to parse 3 cloak keys from gencloak output"
+  exit 1
 fi
 
 [ -f "$ENV_FILE" ] || cp .env.example "$ENV_FILE"
 
 if grep -q '^IRC_CLOAK_KEY_1=' "$ENV_FILE"; then
-    sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
 else
-    sed -i "/^IRC_CLOAK_PREFIX=/a\\
+  sed -i "/^IRC_CLOAK_PREFIX=/a\\
 # Cloak keys - keep secret; must be identical on all servers. Generate with: just irc gencloak\\
 IRC_CLOAK_KEY_1=${keys[0]}\\
 IRC_CLOAK_KEY_2=${keys[1]}\\
diff scripts/init.sh.orig scripts/init.sh
--- scripts/init.sh.orig
+++ scripts/init.sh
@@ -11,16 +11,16 @@
 
 # Load environment variables: .env (base) then .env.dev (overrides for just dev)
 if [ -f "$PROJECT_ROOT/.env" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env"
+  set +a
 fi
 if [ -f "$PROJECT_ROOT/.env.dev" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env.dev"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env.dev"
+  set +a
 fi
 
 # Ensure Atheme JSON-RPC port has a default (for existing .env without it)
@@ -35,386 +35,386 @@
 
 # Helper functions
 log_info() {
-    echo -e "${BLUE}[INFO]${NC} $1"
+  echo -e "${BLUE}[INFO]${NC} $1"
 }
 
 log_success() {
-    echo -e "${GREEN}[SUCCESS]${NC} $1"
+  echo -e "${GREEN}[SUCCESS]${NC} $1"
 }
 
 log_warning() {
-    echo -e "${YELLOW}[WARNING]${NC} $1"
+  echo -e "${YELLOW}[WARNING]${NC} $1"
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $1"
+  echo -e "${RED}[ERROR]${NC} $1"
 }
 
 # Function to create directory structure
 create_directories() {
-    log_info "Creating required directory structure..."
-
-    # Data directories (must match compose volume mounts)
-    local data_dirs=(
-        "$PROJECT_ROOT/data/irc/data"
-        "$PROJECT_ROOT/data/irc/logs"
-        "$PROJECT_ROOT/data/irc/webpanel-data"
-        "$PROJECT_ROOT/data/atheme/data"
-        "$PROJECT_ROOT/data/atheme/logs"
-        "$PROJECT_ROOT/data/xmpp/data"
-        "$PROJECT_ROOT/data/xmpp/logs"
-        "$PROJECT_ROOT/data/xmpp/uploads"
-        "$PROJECT_ROOT/data/thelounge"
-        "$PROJECT_ROOT/data/certs"
-    )
-
-    # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
-    local ssl_dirs=(
-        "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    )
-
-    # Create all directories
-    for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
-        if [ ! -d "$dir" ]; then
-            mkdir -p "$dir"
-            log_info "Created directory: $dir"
-    else
-            log_info "Directory already exists: $dir"
+  log_info "Creating required directory structure..."
+
+  # Data directories (must match compose volume mounts)
+  local data_dirs=(
+    "$PROJECT_ROOT/data/irc/data"
+    "$PROJECT_ROOT/data/irc/logs"
+    "$PROJECT_ROOT/data/irc/webpanel-data"
+    "$PROJECT_ROOT/data/atheme/data"
+    "$PROJECT_ROOT/data/atheme/logs"
+    "$PROJECT_ROOT/data/xmpp/data"
+    "$PROJECT_ROOT/data/xmpp/logs"
+    "$PROJECT_ROOT/data/xmpp/uploads"
+    "$PROJECT_ROOT/data/thelounge"
+    "$PROJECT_ROOT/data/certs"
+  )
+
+  # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
+  local ssl_dirs=(
+    "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  )
+
+  # Create all directories
+  for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
+    if [ ! -d "$dir" ]; then
+      mkdir -p "$dir"
+      log_info "Created directory: $dir"
+    else
+      log_info "Directory already exists: $dir"
     fi
   done
 
-    log_success "Directory structure created successfully"
+  log_success "Directory structure created successfully"
 }
 
 # Function to set proper permissions
 set_permissions() {
-    log_info "Setting proper permissions..."
-
-    # Get current user ID and group ID
-    local current_uid
-    local current_gid
-    # Use actual user ID instead of hardcoded values
-    current_uid=$(id -u)
-    current_gid=$(id -g)
-
-    # Use same UID for all services to avoid permission issues
-    local atheme_uid=$current_uid
-    local atheme_gid=$current_gid
-
-    log_info "Current user: $current_uid:$current_gid"
-
-    # Set ownership for data directories (if they exist)
-    if [ -d "$PROJECT_ROOT/data" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
-        # Ensure directories are writable by owner (critical for socket creation)
-        find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
-        log_info "Set ownership for data directory"
-  fi
-
-    # Set ownership for IRC data directory
-    if [ -d "$PROJECT_ROOT/data/irc" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
-        chmod -R 755 "$PROJECT_ROOT/data/irc"
-        log_info "Set permissions for IRC data directory"
-  fi
-
-    # Set ownership for Atheme data directory with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
-        chmod 755 "$PROJECT_ROOT/data/atheme"
-        log_info "Set permissions for Atheme data directory"
-  fi
-
-    # Set ownership for UnrealIRCd logs
-    if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
-        chmod 755 "$PROJECT_ROOT/data/irc/logs"
-        log_info "Set ownership for UnrealIRCd logs"
-    fi
-
-    # Set ownership for Atheme logs with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
-        chmod 755 "$PROJECT_ROOT/data/atheme/logs"
-        log_info "Set permissions for Atheme logs directory"
-    fi
-
-    # Set permissions for SSL certificates
-    if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
-        sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    fi
-    sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
-    chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
-    log_info "Set permissions for SSL directory"
-
-    # Make sure data directories are writable
-    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
-    find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
-
-    log_success "Permissions set successfully"
+  log_info "Setting proper permissions..."
+
+  # Get current user ID and group ID
+  local current_uid
+  local current_gid
+  # Use actual user ID instead of hardcoded values
+  current_uid=$(id -u)
+  current_gid=$(id -g)
+
+  # Use same UID for all services to avoid permission issues
+  local atheme_uid=$current_uid
+  local atheme_gid=$current_gid
+
+  log_info "Current user: $current_uid:$current_gid"
+
+  # Set ownership for data directories (if they exist)
+  if [ -d "$PROJECT_ROOT/data" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
+    # Ensure directories are writable by owner (critical for socket creation)
+    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
+    log_info "Set ownership for data directory"
+  fi
+
+  # Set ownership for IRC data directory
+  if [ -d "$PROJECT_ROOT/data/irc" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
+    chmod -R 755 "$PROJECT_ROOT/data/irc"
+    log_info "Set permissions for IRC data directory"
+  fi
+
+  # Set ownership for Atheme data directory with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
+    chmod 755 "$PROJECT_ROOT/data/atheme"
+    log_info "Set permissions for Atheme data directory"
+  fi
+
+  # Set ownership for UnrealIRCd logs
+  if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
+    chmod 755 "$PROJECT_ROOT/data/irc/logs"
+    log_info "Set ownership for UnrealIRCd logs"
+  fi
+
+  # Set ownership for Atheme logs with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
+    chmod 755 "$PROJECT_ROOT/data/atheme/logs"
+    log_info "Set permissions for Atheme logs directory"
+  fi
+
+  # Set permissions for SSL certificates
+  if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
+    sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  fi
+  sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
+  chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
+  log_info "Set permissions for SSL directory"
+
+  # Make sure data directories are writable
+  find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
+  find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
+
+  log_success "Permissions set successfully"
 }
 
 # Function to set up CA certificate bundle
 setup_ca_bundle() {
-    log_info "Setting up CA certificate bundle..."
-
-    local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
-    local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
-    local ca_bundle_file="curl-ca-bundle.crt"
-
-    # Ensure runtime directory exists
-    if [ ! -d "$ca_runtime_dir" ]; then
-        mkdir -p "$ca_runtime_dir"
-        log_info "Created TLS runtime directory: $ca_runtime_dir"
-  fi
-
-    # Ensure template directory exists
-    if [ ! -d "$ca_template_dir" ]; then
-        mkdir -p "$ca_template_dir"
-        log_info "Created TLS template directory: $ca_template_dir"
-  fi
-
-    # Check if system CA bundle exists
-    local system_ca_bundle=""
-    if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
-        system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
-  elif   [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
-        system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
-  fi
-
-    if [ -n "$system_ca_bundle" ]; then
-        # Create template if it doesn't exist
-        if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle template"
-      else
-                log_warning "Could not create CA certificate bundle template"
-                return 1
-      fi
-    fi
-
-        # Copy to runtime directory if it doesn't exist
-        if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle in runtime directory"
-      else
-                log_warning "Could not create CA certificate bundle in runtime directory"
-                return 1
-      fi
-    else
-            log_info "CA certificate bundle already exists in runtime directory"
-    fi
-  else
-        log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
-        return 1
-  fi
-
-    # Remove obsolete cert files from config/tls (server certs now live in data/certs)
-    rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2>/dev/null || true
-    rm -rf "$ca_runtime_dir/live" 2>/dev/null || true
-
-    log_success "CA certificate bundle setup completed"
+  log_info "Setting up CA certificate bundle..."
+
+  local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
+  local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
+  local ca_bundle_file="curl-ca-bundle.crt"
+
+  # Ensure runtime directory exists
+  if [ ! -d "$ca_runtime_dir" ]; then
+    mkdir -p "$ca_runtime_dir"
+    log_info "Created TLS runtime directory: $ca_runtime_dir"
+  fi
+
+  # Ensure template directory exists
+  if [ ! -d "$ca_template_dir" ]; then
+    mkdir -p "$ca_template_dir"
+    log_info "Created TLS template directory: $ca_template_dir"
+  fi
+
+  # Check if system CA bundle exists
+  local system_ca_bundle=""
+  if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
+    system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
+  elif [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+    system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
+  fi
+
+  if [ -n "$system_ca_bundle" ]; then
+    # Create template if it doesn't exist
+    if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle template"
+      else
+        log_warning "Could not create CA certificate bundle template"
+        return 1
+      fi
+    fi
+
+    # Copy to runtime directory if it doesn't exist
+    if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle in runtime directory"
+      else
+        log_warning "Could not create CA certificate bundle in runtime directory"
+        return 1
+      fi
+    else
+      log_info "CA certificate bundle already exists in runtime directory"
+    fi
+  else
+    log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
+    return 1
+  fi
+
+  # Remove obsolete cert files from config/tls (server certs now live in data/certs)
+  rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2> /dev/null || true
+  rm -rf "$ca_runtime_dir/live" 2> /dev/null || true
+
+  log_success "CA certificate bundle setup completed"
 }
 
 # Function to generate self-signed certificates for dev mode
 generate_cert() {
-    local domain="$1"
-    local base_dir="$2"
-    local live_dir="$base_dir/live/$domain"
-
-    # Ensure directory exists
-    mkdir -p "$live_dir"
-
-    # Generate self-signed cert if it doesn't exist
-    if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
-        log_info "Generating self-signed certificate for $domain..."
-        # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
-        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-            -keyout "$live_dir/privkey.pem" \
-            -out "$live_dir/fullchain.pem" \
-            -subj "/CN=$domain" \
-            -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-        log_success "Generated self-signed certificate for $domain"
-    else
-        log_info "Self-signed certificate already exists for $domain"
-    fi
-
-    # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
-    chmod 644 "$live_dir/privkey.pem" 2>/dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2>/dev/null || true
+  local domain="$1"
+  local base_dir="$2"
+  local live_dir="$base_dir/live/$domain"
+
+  # Ensure directory exists
+  mkdir -p "$live_dir"
+
+  # Generate self-signed cert if it doesn't exist
+  if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
+    log_info "Generating self-signed certificate for $domain..."
+    # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
+    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+      -keyout "$live_dir/privkey.pem" \
+      -out "$live_dir/fullchain.pem" \
+      -subj "/CN=$domain" \
+      -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+    log_success "Generated self-signed certificate for $domain"
+  else
+    log_info "Self-signed certificate already exists for $domain"
+  fi
+
+  # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
+  chmod 644 "$live_dir/privkey.pem" 2> /dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2> /dev/null || true
 }
 
 generate_dev_certs() {
-    log_info "Setting up self-signed certificates for dev mode..."
-
-    # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
-    local shared_cert_dir="$PROJECT_ROOT/data/certs"
-    generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
-    generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
-
-    log_success "Dev certificate setup completed"
+  log_info "Setting up self-signed certificates for dev mode..."
+
+  # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
+  local shared_cert_dir="$PROJECT_ROOT/data/certs"
+  generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
+  generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
+
+  log_success "Dev certificate setup completed"
 }
 
 # Function to prepare configuration files from templates
 prepare_config_files() {
-    log_info "Preparing configuration files from templates..."
-
-    # Load environment variables from .env if it exists
-    if [ -f "$PROJECT_ROOT/.env" ]; then
-        log_info "Loading environment variables from .env"
-        set -a
-        # shellcheck disable=SC1091
-        source "$PROJECT_ROOT/.env"
-        set +a
-        log_info "Environment variables loaded"
-    fi
-
-    # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
-    export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
-    export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
-
-    if [ ! -f "$PROJECT_ROOT/.env" ]; then
-        log_warning ".env file not found. Configuration will use defaults."
-        return 1
-  fi
-
-    # Check if envsubst is available
-    if ! command -v envsubst > /dev/null 2>&1; then
-        log_error "envsubst command not found. Please install gettext package."
-        return 1
-  fi
-
-    # Prepare UnrealIRCd configuration
-    local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
-    local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
-
-    if [ -f "$unreal_template" ]; then
-        log_info "Creating UnrealIRCd configuration from template..."
-        if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
-            log_success "UnrealIRCd configuration created"
-    else
-            log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
-    fi
-  elif   [ -f "$unreal_config" ]; then
-        log_info "UnrealIRCd configuration already exists"
-  else
-        log_warning "No UnrealIRCd configuration template found"
-  fi
-
-    # Prepare Atheme configuration
-    local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
-    local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
-
-    if [ -f "$atheme_template" ]; then
-        log_info "Creating Atheme configuration from template..."
-        envsubst < "$atheme_template" > "$atheme_config"
-        log_success "Atheme configuration created"
-  elif   [ -f "$atheme_config" ]; then
-        log_info "Atheme configuration already exists"
-  else
-        log_warning "No Atheme configuration template found"
-  fi
-
-    # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
-    if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
-        log_info "Running prepare-config.sh (bridge config, etc.)..."
-        # shellcheck source=prepare-config.sh
-        "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
-    fi
-
-    # Show substituted values for verification
-    log_info "Configuration values:"
-    echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
-    echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
-    echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
-    echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
-    echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
+  log_info "Preparing configuration files from templates..."
+
+  # Load environment variables from .env if it exists
+  if [ -f "$PROJECT_ROOT/.env" ]; then
+    log_info "Loading environment variables from .env"
+    set -a
+    # shellcheck disable=SC1091
+    source "$PROJECT_ROOT/.env"
+    set +a
+    log_info "Environment variables loaded"
+  fi
+
+  # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
+  export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
+  export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
+
+  if [ ! -f "$PROJECT_ROOT/.env" ]; then
+    log_warning ".env file not found. Configuration will use defaults."
+    return 1
+  fi
+
+  # Check if envsubst is available
+  if ! command -v envsubst > /dev/null 2>&1; then
+    log_error "envsubst command not found. Please install gettext package."
+    return 1
+  fi
+
+  # Prepare UnrealIRCd configuration
+  local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
+  local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
+
+  if [ -f "$unreal_template" ]; then
+    log_info "Creating UnrealIRCd configuration from template..."
+    if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
+      log_success "UnrealIRCd configuration created"
+    else
+      log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
+    fi
+  elif [ -f "$unreal_config" ]; then
+    log_info "UnrealIRCd configuration already exists"
+  else
+    log_warning "No UnrealIRCd configuration template found"
+  fi
+
+  # Prepare Atheme configuration
+  local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
+  local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
+
+  if [ -f "$atheme_template" ]; then
+    log_info "Creating Atheme configuration from template..."
+    envsubst < "$atheme_template" > "$atheme_config"
+    log_success "Atheme configuration created"
+  elif [ -f "$atheme_config" ]; then
+    log_info "Atheme configuration already exists"
+  else
+    log_warning "No Atheme configuration template found"
+  fi
+
+  # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
+  if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
+    log_info "Running prepare-config.sh (bridge config, etc.)..."
+    # shellcheck source=prepare-config.sh
+    "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
+  fi
+
+  # Show substituted values for verification
+  log_info "Configuration values:"
+  echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
+  echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
+  echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
+  echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
+  echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
 }
 
 # Function to create .env template if it doesn't exist
 create_env_template() {
-    local env_file="$PROJECT_ROOT/.env"
-    local env_example="$PROJECT_ROOT/.env.example"
-
-    if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
-        cp "$env_example" "$env_file"
-        log_info "Created .env file from template"
-        log_warning "Please edit .env file with your configuration before starting services"
-  elif   [ -f "$env_file" ]; then
-        log_info ".env file already exists"
-  else
-        log_warning "No .env template found. You may need to create environment variables manually"
+  local env_file="$PROJECT_ROOT/.env"
+  local env_example="$PROJECT_ROOT/.env.example"
+
+  if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
+    cp "$env_example" "$env_file"
+    log_info "Created .env file from template"
+    log_warning "Please edit .env file with your configuration before starting services"
+  elif [ -f "$env_file" ]; then
+    log_info ".env file already exists"
+  else
+    log_warning "No .env template found. You may need to create environment variables manually"
   fi
 }
 
 # Function to check Docker availability
 check_docker() {
-    log_info "Checking Docker availability..."
-
-    if ! command -v docker > /dev/null 2>&1; then
-        log_error "Docker is not installed or not in PATH"
-        exit 1
-  fi
-
-    if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
-        log_error "Docker Compose is not available"
-        exit 1
-  fi
-
-    log_success "Docker is available"
+  log_info "Checking Docker availability..."
+
+  if ! command -v docker > /dev/null 2>&1; then
+    log_error "Docker is not installed or not in PATH"
+    exit 1
+  fi
+
+  if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
+    log_error "Docker Compose is not available"
+    exit 1
+  fi
+
+  log_success "Docker is available"
 }
 
 # Function to show next steps
 show_next_steps() {
-    echo ""
-    log_info "Next steps:"
-    echo "  1. Edit .env file with your configuration (optional)"
-    echo "  3. Or run: docker compose up -d"
-    echo ""
-    log_info "Data will be stored in:"
-    echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
+  echo ""
+  log_info "Next steps:"
+  echo "  1. Edit .env file with your configuration (optional)"
+  echo "  3. Or run: docker compose up -d"
+  echo ""
+  log_info "Data will be stored in:"
+  echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
 }
 
 # Main function
 main() {
-    log_info "ATL Chat Infrastructure Initialization"
-    log_info "======================================"
-
-    # Check if we're running as root (for permission info)
-    if [ "$(id -u)" = "0" ]; then
-        log_warning "Running as root - this is fine for initial setup"
-  fi
-
-    # Check Docker availability
-    check_docker
-
-    # Create directory structure
-    create_directories
-
-    # Set permissions
-    set_permissions
-
-    # Set up CA certificate bundle
-    setup_ca_bundle
-
-    # Generate dev certs
-    generate_dev_certs
-
-    # Create .env if needed
-    create_env_template
-
-    # Prepare configuration files from templates
-    prepare_config_files
-
-    # Show next steps
-    show_next_steps
-
-    log_success "Initialization completed successfully!"
+  log_info "ATL Chat Infrastructure Initialization"
+  log_info "======================================"
+
+  # Check if we're running as root (for permission info)
+  if [ "$(id -u)" = "0" ]; then
+    log_warning "Running as root - this is fine for initial setup"
+  fi
+
+  # Check Docker availability
+  check_docker
+
+  # Create directory structure
+  create_directories
+
+  # Set permissions
+  set_permissions
+
+  # Set up CA certificate bundle
+  setup_ca_bundle
+
+  # Generate dev certs
+  generate_dev_certs
+
+  # Create .env if needed
+  create_env_template
+
+  # Prepare configuration files from templates
+  prepare_config_files
+
+  # Show next steps
+  show_next_steps
+
+  log_success "Initialization completed successfully!"
 }
 
 # Run main function
 if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
-    main "$@"
+  main "$@"
 fi
diff scripts/prepare-config.sh.orig scripts/prepare-config.sh
--- scripts/prepare-config.sh.orig
+++ scripts/prepare-config.sh
@@ -180,7 +180,7 @@
     log_info "Preparing bridge configuration from template..."
     local temp_file="/tmp/bridge-config.yaml.tmp"
     envsubst < "$bridge_template" > "$temp_file"
-    if cp "$temp_file" "$bridge_config" 2>/dev/null || sudo cp "$temp_file" "$bridge_config" 2>/dev/null; then
+    if cp "$temp_file" "$bridge_config" 2> /dev/null || sudo cp "$temp_file" "$bridge_config" 2> /dev/null; then
       log_success "Bridge configuration prepared"
     else
       log_warning "Could not write bridge config to $bridge_config"
@@ -189,7 +189,7 @@
   elif [ ! -f "$bridge_config" ]; then
     log_warning "Bridge config not found. Copy apps/bridge/config.example.yaml to apps/bridge/config.yaml and customize."
     if [ -f "$PROJECT_ROOT/apps/bridge/config.example.yaml" ]; then
-      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2>/dev/null || true
+      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2> /dev/null || true
       log_info "Copied config.example.yaml to apps/bridge/config.yaml - edit with your Discord channel ID"
     fi
   fi
@@ -202,7 +202,7 @@
     mkdir -p "$(dirname "$lounge_config")"
     local temp_file="/tmp/thelounge-config.js.tmp"
     envsubst < "$lounge_template" > "$temp_file"
-    if cp "$temp_file" "$lounge_config" 2>/dev/null || sudo cp "$temp_file" "$lounge_config" 2>/dev/null; then
+    if cp "$temp_file" "$lounge_config" 2> /dev/null || sudo cp "$temp_file" "$lounge_config" 2> /dev/null; then
       log_success "The Lounge configuration prepared"
     else
       log_warning "Could not write The Lounge config to $lounge_config"
----------

You can reformat the above files to meet shfmt's requirements by typing:

  shfmt  -w filename


@github-actions
Copy link

sh-checker report

To get the full details, please check in the job output.

shellcheck errors

'shellcheck ' returned error 1 finding the following syntactical issues:

----------

In infra/nginx/docker-entrypoint.sh line 7:
envsubst '${XMPP_DOMAIN} ${CERT_DIR}' < /etc/nginx/templates/prosody-https.conf.template > /etc/nginx/conf.d/prosody-https.conf
         ^--------------------------^ SC2016 (info): Expressions don't expand in single quotes, use double quotes for that.

For more information:
  https://www.shellcheck.net/wiki/SC2016 -- Expressions don't expand in singl...
----------

You can address the above issues in one of three ways:
1. Manually correct the issue in the offending shell script;
2. Disable specific issues by adding the comment:
  # shellcheck disable=NNNN
above the line that contains the issue, where NNNN is the error code;
3. Add '-e NNNN' to the SHELLCHECK_OPTS setting in your .yml action file.



shfmt errors

'shfmt ' returned error 1 finding the following formatting issues:

----------
diff .github/scripts/docker.sh.orig .github/scripts/docker.sh
--- .github/scripts/docker.sh.orig
+++ .github/scripts/docker.sh
@@ -57,11 +57,11 @@
 shift || true
 
 case "$COMMAND" in
-  generate-pr-version)        generate_pr_version "$@" ;;
-  generate-release-version)   generate_release_version "$@" ;;
-  validate-build-config)      validate_build_config "$@" ;;
+  generate-pr-version) generate_pr_version "$@" ;;
+  generate-release-version) generate_release_version "$@" ;;
+  validate-build-config) validate_build_config "$@" ;;
   calculate-source-date-epoch) calculate_source_date_epoch "$@" ;;
-  generate-build-date)        generate_build_date "$@" ;;
+  generate-build-date) generate_build_date "$@" ;;
   *)
     echo "Usage: docker.sh {generate-pr-version|generate-release-version|validate-build-config|calculate-source-date-epoch|generate-build-date} [args...]"
     exit 1
diff apps/atheme/docker-entrypoint.sh.orig apps/atheme/docker-entrypoint.sh
--- apps/atheme/docker-entrypoint.sh.orig
+++ apps/atheme/docker-entrypoint.sh
@@ -7,9 +7,9 @@
 
 # Ensure we have proper permissions
 if [ "$(id -u)" = "0" ]; then
-    echo "ERROR: Atheme should not run as root for security reasons"
-    echo "Please run with a non-root user (UID 1000 recommended)"
-    exit 1
+  echo "ERROR: Atheme should not run as root for security reasons"
+  echo "Please run with a non-root user (UID 1000 recommended)"
+  exit 1
 fi
 
 # Create directories with proper ownership
@@ -17,9 +17,9 @@
 
 # Validate configuration exists
 if [ ! -f "/usr/local/atheme/etc/atheme.conf" ]; then
-    echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
-    echo "Please ensure the configuration is properly mounted"
-    exit 1
+  echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
+  echo "Please ensure the configuration is properly mounted"
+  exit 1
 fi
 
 # Clean up stale PID file
@@ -27,25 +27,25 @@
 
 # Validate database directory is writable
 if [ ! -w "/usr/local/atheme/data" ]; then
-    echo "ERROR: Data directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Data directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Validate logs directory is writable
 if [ ! -w "/usr/local/atheme/logs" ]; then
-    echo "ERROR: Logs directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Logs directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Check if this is first run (no database exists)
 if [ ! -f "/usr/local/atheme/data/services.db" ]; then
-    echo "First run detected - creating initial database..."
-    /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
-    echo "Database created successfully"
+  echo "First run detected - creating initial database..."
+  /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
+  echo "Database created successfully"
 else
-    echo "Existing database found - starting with existing data"
+  echo "Existing database found - starting with existing data"
 fi
 
 # Start Atheme services
diff apps/prosody/docker-entrypoint.sh.orig apps/prosody/docker-entrypoint.sh
--- apps/prosody/docker-entrypoint.sh.orig
+++ apps/prosody/docker-entrypoint.sh
@@ -29,21 +29,21 @@
 # ============================================================================
 
 log_info() {
-    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_warn() {
-    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_debug() {
-    if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
-        echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
-    fi
+  if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
+    echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  fi
 }
 
 # ============================================================================
@@ -51,45 +51,45 @@
 # ============================================================================
 
 validate_environment() {
-    log_info "Validating environment configuration..."
-
-    # Validate required domain
-    if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
-        log_error "PROSODY_DOMAIN is required but not set"
-        exit 1
-    fi
-
-    # Validate domain format
-    if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
-        log_error "Invalid domain format: ${PROSODY_DOMAIN}"
-        exit 1
-    fi
-
-    # Set default admin if not provided
-    if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
-        export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
-        log_info "Using default admin: ${PROSODY_ADMIN_JID}"
-    fi
-
-    # Validate database configuration for SQL storage
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        local required_vars=(
-            "PROSODY_DB_DRIVER"
-            "PROSODY_DB_NAME"
-            "PROSODY_DB_USER"
-            "PROSODY_DB_PASSWORD"
-            "PROSODY_DB_HOST"
-        )
-
-        for var in "${required_vars[@]}"; do
-            if [[ -z "${!var:-}" ]]; then
-                log_error "Database variable ${var} is required for SQL storage"
-                exit 1
-            fi
-        done
-    fi
-
-    log_info "Environment validation complete"
+  log_info "Validating environment configuration..."
+
+  # Validate required domain
+  if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
+    log_error "PROSODY_DOMAIN is required but not set"
+    exit 1
+  fi
+
+  # Validate domain format
+  if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
+    log_error "Invalid domain format: ${PROSODY_DOMAIN}"
+    exit 1
+  fi
+
+  # Set default admin if not provided
+  if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
+    export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
+    log_info "Using default admin: ${PROSODY_ADMIN_JID}"
+  fi
+
+  # Validate database configuration for SQL storage
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    local required_vars=(
+      "PROSODY_DB_DRIVER"
+      "PROSODY_DB_NAME"
+      "PROSODY_DB_USER"
+      "PROSODY_DB_PASSWORD"
+      "PROSODY_DB_HOST"
+    )
+
+    for var in "${required_vars[@]}"; do
+      if [[ -z "${!var:-}" ]]; then
+        log_error "Database variable ${var} is required for SQL storage"
+        exit 1
+      fi
+    done
+  fi
+
+  log_info "Environment validation complete"
 }
 
 # ============================================================================
@@ -97,186 +97,186 @@
 # ============================================================================
 
 setup_directories() {
-    log_info "Setting up directories..."
-
-    local dirs=(
-        "$PROSODY_DATA_DIR"
-        "$PROSODY_LOG_DIR"
-        "$PROSODY_CERT_DIR"
-        "$PROSODY_UPLOAD_DIR"
-        "$(dirname "$PROSODY_PID_FILE")"
-    )
-
-    for dir in "${dirs[@]}"; do
-        if [[ ! -d "$dir" ]]; then
-            log_debug "Creating directory: $dir"
-            mkdir -p "$dir"
-        fi
-
-        # Ensure proper ownership (only if running as root)
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
-        fi
-    done
-
-    # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
-    # Prosody needs write access for SQLite (MAM, PEP, etc.)
-    if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
-        if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
-            log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
-        fi
-    fi
-
-    log_info "Directory setup complete"
+  log_info "Setting up directories..."
+
+  local dirs=(
+    "$PROSODY_DATA_DIR"
+    "$PROSODY_LOG_DIR"
+    "$PROSODY_CERT_DIR"
+    "$PROSODY_UPLOAD_DIR"
+    "$(dirname "$PROSODY_PID_FILE")"
+  )
+
+  for dir in "${dirs[@]}"; do
+    if [[ ! -d "$dir" ]]; then
+      log_debug "Creating directory: $dir"
+      mkdir -p "$dir"
+    fi
+
+    # Ensure proper ownership (only if running as root)
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
+    fi
+  done
+
+  # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
+  # Prosody needs write access for SQLite (MAM, PEP, etc.)
+  if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
+    if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
+      log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
+    fi
+  fi
+
+  log_info "Directory setup complete"
 }
 
 setup_certificates() {
-    log_info "Setting up SSL certificates..."
-
-    # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
-    local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
-    local cert_file="${live_dir}/fullchain.pem"
-    local key_file="${live_dir}/privkey.pem"
-
-    if [[ -f "$cert_file" && -f "$key_file" ]]; then
-        log_info "Certificates found for ${PROSODY_DOMAIN}"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file" 2> /dev/null || true
-        chmod 600 "$key_file" 2> /dev/null || true
-        # HTTPS service automatic discovery (Prosody Certificates doc):
-        # - https/fullchain.pem + https/privkey.pem (directory symlink)
-        # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
-    local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
-    local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
-    if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
-        log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
-        mkdir -p "$live_dir"
-        cp "$legacy_cert" "$cert_file"
-        cp "$legacy_key" "$key_file"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file"
-        chmod 600 "$key_file"
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Generate self-signed certificate for development/testing
-    log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
-    log_warn "This is suitable for development only - use proper certificates in production"
-
+  log_info "Setting up SSL certificates..."
+
+  # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
+  local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
+  local cert_file="${live_dir}/fullchain.pem"
+  local key_file="${live_dir}/privkey.pem"
+
+  if [[ -f "$cert_file" && -f "$key_file" ]]; then
+    log_info "Certificates found for ${PROSODY_DOMAIN}"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    fi
+    chmod 644 "$cert_file" 2> /dev/null || true
+    chmod 600 "$key_file" 2> /dev/null || true
+    # HTTPS service automatic discovery (Prosody Certificates doc):
+    # - https/fullchain.pem + https/privkey.pem (directory symlink)
+    # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
+  local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
+  local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
+  if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
+    log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
     mkdir -p "$live_dir"
-    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-        -keyout "$key_file" \
-        -out "$cert_file" \
-        -subj "/CN=${PROSODY_DOMAIN}" \
-        -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    cp "$legacy_cert" "$cert_file"
+    cp "$legacy_key" "$key_file"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
     fi
     chmod 644 "$cert_file"
     chmod 600 "$key_file"
-
-    # HTTPS service automatic discovery
-    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-
-    log_info "Self-signed certificate generated successfully"
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Generate self-signed certificate for development/testing
+  log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
+  log_warn "This is suitable for development only - use proper certificates in production"
+
+  mkdir -p "$live_dir"
+  openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+    -keyout "$key_file" \
+    -out "$cert_file" \
+    -subj "/CN=${PROSODY_DOMAIN}" \
+    -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+  fi
+  chmod 644 "$cert_file"
+  chmod 600 "$key_file"
+
+  # HTTPS service automatic discovery
+  ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+
+  log_info "Self-signed certificate generated successfully"
 }
 
 wait_for_database() {
-    if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
-        log_debug "Not using SQL storage, skipping database wait"
-        return 0
-    fi
-
-    local host="${PROSODY_DB_HOST}"
-    local port="${PROSODY_DB_PORT:-5432}"
-    local max_attempts=30
-    local attempt=1
-
-    log_info "Waiting for database connection to ${host}:${port}..."
-
-    while [[ $attempt -le $max_attempts ]]; do
-        if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
-            log_info "Database connection established"
-            return 0
-        fi
-
-        log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
-        sleep 2
-        ((attempt++))
-    done
-
-    log_error "Database connection timeout after ${max_attempts} attempts"
-    exit 1
+  if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
+    log_debug "Not using SQL storage, skipping database wait"
+    return 0
+  fi
+
+  local host="${PROSODY_DB_HOST}"
+  local port="${PROSODY_DB_PORT:-5432}"
+  local max_attempts=30
+  local attempt=1
+
+  log_info "Waiting for database connection to ${host}:${port}..."
+
+  while [[ $attempt -le $max_attempts ]]; do
+    if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
+      log_info "Database connection established"
+      return 0
+    fi
+
+    log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
+    sleep 2
+    ((attempt++))
+  done
+
+  log_error "Database connection timeout after ${max_attempts} attempts"
+  exit 1
 }
 
 validate_configuration() {
-    log_info "Validating Prosody configuration..."
-
-    # Check if config file exists
-    if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
-        log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
-        exit 1
-    fi
-
-    # Validate configuration using prosodyctl (allow warnings in development)
-    log_info "Validating Prosody configuration..."
-    if ! prosodyctl check config; then
-        log_error "Prosody configuration validation failed"
-        log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
-        if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
-            exit 1
-        else
-            log_warn "Development mode: continuing despite configuration warnings"
-        fi
-    fi
-
-    log_info "Configuration validation successful"
-}
-
-setup_community_modules() {
-    log_info "Setting up community modules..."
-
-    # Check if community modules source exists
-    local source_dir="/usr/local/lib/prosody/prosody-modules"
-    if [[ ! -d "$source_dir" ]]; then
-        log_warn "Community modules repository not found at $source_dir"
-        log_warn "Modules will need to be installed manually or via prosodyctl"
-        return 0
-    fi
-
-    # Check if modules are available from enabled directory
-    local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
-    if [[ ! -d "$enabled_dir" ]]; then
-        log_warn "Enabled community modules directory not found at $enabled_dir"
-        return 0
+  log_info "Validating Prosody configuration..."
+
+  # Check if config file exists
+  if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
+    log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
+    exit 1
+  fi
+
+  # Validate configuration using prosodyctl (allow warnings in development)
+  log_info "Validating Prosody configuration..."
+  if ! prosodyctl check config; then
+    log_error "Prosody configuration validation failed"
+    log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
+    if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
+      exit 1
     else
-        log_info "Community modules found in $enabled_dir"
-        local module_count
-        module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
-        log_info "Enabled modules: $module_count"
-    fi
-
-    # Ensure proper ownership (only if running as root)
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
-    fi
+      log_warn "Development mode: continuing despite configuration warnings"
+    fi
+  fi
+
+  log_info "Configuration validation successful"
+}
+
+setup_community_modules() {
+  log_info "Setting up community modules..."
+
+  # Check if community modules source exists
+  local source_dir="/usr/local/lib/prosody/prosody-modules"
+  if [[ ! -d "$source_dir" ]]; then
+    log_warn "Community modules repository not found at $source_dir"
+    log_warn "Modules will need to be installed manually or via prosodyctl"
+    return 0
+  fi
+
+  # Check if modules are available from enabled directory
+  local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
+  if [[ ! -d "$enabled_dir" ]]; then
+    log_warn "Enabled community modules directory not found at $enabled_dir"
+    return 0
+  else
+    log_info "Community modules found in $enabled_dir"
+    local module_count
+    module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
+    log_info "Enabled modules: $module_count"
+  fi
+
+  # Ensure proper ownership (only if running as root)
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
+  fi
 }
 
 # ============================================================================
@@ -285,28 +285,28 @@
 
 # shellcheck disable=SC2317,SC2329  # Function is called by signal handlers via trap
 cleanup() {
-    log_info "Received shutdown signal, stopping Prosody..."
-
-    if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
-        # Send SIGTERM for graceful shutdown
-        kill -TERM "$PROSODY_PID" 2> /dev/null || true
-
-        # Wait for graceful shutdown (max 30 seconds)
-        local timeout=30
-        while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
-            sleep 1
-            ((timeout--))
-        done
-
-        # Force kill if still running
-        if kill -0 "$PROSODY_PID" 2> /dev/null; then
-            log_warn "Prosody did not shut down gracefully, forcing termination"
-            kill -KILL "$PROSODY_PID" 2> /dev/null || true
-        fi
-    fi
-
-    log_info "Prosody shutdown complete"
-    exit 0
+  log_info "Received shutdown signal, stopping Prosody..."
+
+  if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
+    # Send SIGTERM for graceful shutdown
+    kill -TERM "$PROSODY_PID" 2> /dev/null || true
+
+    # Wait for graceful shutdown (max 30 seconds)
+    local timeout=30
+    while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
+      sleep 1
+      ((timeout--))
+    done
+
+    # Force kill if still running
+    if kill -0 "$PROSODY_PID" 2> /dev/null; then
+      log_warn "Prosody did not shut down gracefully, forcing termination"
+      kill -KILL "$PROSODY_PID" 2> /dev/null || true
+    fi
+  fi
+
+  log_info "Prosody shutdown complete"
+  exit 0
 }
 
 # Setup signal handlers
@@ -317,57 +317,57 @@
 # ============================================================================
 
 main() {
-    log_info "Starting Professional Prosody XMPP Server..."
-
-    # Display version information
-    local prosody_version
-    prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
-    log_info "Prosody version: $prosody_version"
-
-    # Prefer mounted config if present
-    if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
-        log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
-        cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
-        chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-        chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-    fi
-
-    # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
-    if [[ -d "/etc/prosody/config/conf.d" ]]; then
-        log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
-        mkdir -p "/etc/prosody/conf.d"
-        rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
-        chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
-        find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
-    fi
-
-    # Environment and setup
-    validate_environment
-    setup_directories
-    setup_certificates
-    wait_for_database
-    validate_configuration
-    setup_community_modules
-
-    # Display configuration summary
-    log_info "Configuration summary:"
-    log_info "  Domain: ${PROSODY_DOMAIN}"
-    log_info "  Admins: ${PROSODY_ADMIN_JID}"
-    log_info "  Storage: ${PROSODY_STORAGE:-sql}"
-    log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
-    log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
-
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
-    fi
-
-    # Start Prosody
-    log_info "Starting Prosody XMPP server..."
-
-    # Switch to prosody user and start in foreground
-    exec gosu "$PROSODY_USER" prosody \
-        --config="$PROSODY_CONFIG_FILE" \
-        --foreground
+  log_info "Starting Professional Prosody XMPP Server..."
+
+  # Display version information
+  local prosody_version
+  prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
+  log_info "Prosody version: $prosody_version"
+
+  # Prefer mounted config if present
+  if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
+    log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
+    cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
+    chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+    chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+  fi
+
+  # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
+  if [[ -d "/etc/prosody/config/conf.d" ]]; then
+    log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
+    mkdir -p "/etc/prosody/conf.d"
+    rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
+    chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
+    find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
+  fi
+
+  # Environment and setup
+  validate_environment
+  setup_directories
+  setup_certificates
+  wait_for_database
+  validate_configuration
+  setup_community_modules
+
+  # Display configuration summary
+  log_info "Configuration summary:"
+  log_info "  Domain: ${PROSODY_DOMAIN}"
+  log_info "  Admins: ${PROSODY_ADMIN_JID}"
+  log_info "  Storage: ${PROSODY_STORAGE:-sql}"
+  log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
+  log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
+
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
+  fi
+
+  # Start Prosody
+  log_info "Starting Prosody XMPP server..."
+
+  # Switch to prosody user and start in foreground
+  exec gosu "$PROSODY_USER" prosody \
+    --config="$PROSODY_CONFIG_FILE" \
+    --foreground
 }
 
 # ============================================================================
@@ -376,8 +376,8 @@
 
 # Ensure we're running as root initially (for setup)
 if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root for initial setup"
-    exit 1
+  log_error "This script must be run as root for initial setup"
+  exit 1
 fi
 
 # Execute main function
diff apps/unrealircd/docker-entrypoint.sh.orig apps/unrealircd/docker-entrypoint.sh
--- apps/unrealircd/docker-entrypoint.sh.orig
+++ apps/unrealircd/docker-entrypoint.sh
@@ -21,8 +21,8 @@
 
 # Validate config exists
 if [ ! -f "/home/unrealircd/unrealircd/config/unrealircd.conf" ]; then
-    echo "ERROR: Configuration file not found!"
-    exit 1
+  echo "ERROR: Configuration file not found!"
+  exit 1
 fi
 
 # Ownership and permissions are handled by Containerfile and user switching
@@ -29,21 +29,21 @@
 
 # Handle commands
 case "$1" in
-    start)
-        shift
-        echo "Starting UnrealIRCd in foreground..."
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        else
-            exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        fi
-        ;;
-    *)
-        echo "Running command: $1"
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
-        else
-            exec /home/unrealircd/unrealircd/unrealircd "$@"
-        fi
-        ;;
+  start)
+    shift
+    echo "Starting UnrealIRCd in foreground..."
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    else
+      exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    fi
+    ;;
+  *)
+    echo "Running command: $1"
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
+    else
+      exec /home/unrealircd/unrealircd/unrealircd "$@"
+    fi
+    ;;
 esac
diff apps/unrealircd/scripts/manage-modules.sh.orig apps/unrealircd/scripts/manage-modules.sh
--- apps/unrealircd/scripts/manage-modules.sh.orig
+++ apps/unrealircd/scripts/manage-modules.sh
@@ -29,280 +29,280 @@
 
 # Check if we're running as the correct user (unrealircd, typically UID from PUID)
 check_user() {
-    local expected_uid="${PUID:-1000}"
-    if [ "$(id -u)" != "$expected_uid" ]; then
-        print_error "This script must run as the unrealircd user (UID ${expected_uid})"
-        print_error "Current user: $(id -un) (UID $(id -u))"
-        exit 1
-    fi
+  local expected_uid="${PUID:-1000}"
+  if [ "$(id -u)" != "$expected_uid" ]; then
+    print_error "This script must run as the unrealircd user (UID ${expected_uid})"
+    print_error "Current user: $(id -un) (UID $(id -u))"
+    exit 1
+  fi
 }
 
 # Check if UnrealIRCd is running
 check_unrealircd_running() {
-    if pgrep -f unrealircd >/dev/null; then
-        print_warning "UnrealIRCd is currently running"
-        print_warning "Some operations may require a restart to take effect"
-        return 0
-    else
-        print_status "UnrealIRCd is not currently running"
-        return 1
-    fi
+  if pgrep -f unrealircd > /dev/null; then
+    print_warning "UnrealIRCd is currently running"
+    print_warning "Some operations may require a restart to take effect"
+    return 0
+  else
+    print_status "UnrealIRCd is not currently running"
+    return 1
+  fi
 }
 
 # List available modules
 list_modules() {
-    print_header "Available Contrib Modules"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Fetching latest module list..."
-    if [ -d "$CONTRIB_DIR" ]; then
-        cd "$CONTRIB_DIR" && git pull --quiet origin main 2>/dev/null || true
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Available modules:"
-    echo
-    "$UNREALIRCD_BIN" module list 2>/dev/null || {
-        print_warning "Module manager not available, showing contrib directory contents:"
-        for item in "$CONTRIB_DIR"/*; do
-            if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
-                basename "$item"
-            fi
-        done | sort
-    }
+  print_header "Available Contrib Modules"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Fetching latest module list..."
+  if [ -d "$CONTRIB_DIR" ]; then
+    cd "$CONTRIB_DIR" && git pull --quiet origin main 2> /dev/null || true
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Available modules:"
+  echo
+  "$UNREALIRCD_BIN" module list 2> /dev/null || {
+    print_warning "Module manager not available, showing contrib directory contents:"
+    for item in "$CONTRIB_DIR"/*; do
+      if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
+        basename "$item"
+      fi
+    done | sort
+  }
 }
 
 # Show module information
 show_module_info() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 info <module-name>"
-        return 1
-    fi
-
-    print_header "Module Information: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to get info via module manager first
-    if "$UNREALIRCD_BIN" module info "third/$module_name" 2>/dev/null; then
-        return 0
-    fi
-
-    # Fallback to showing contrib directory info
-    if [ -d "$CONTRIB_DIR/$module_name" ]; then
-        print_status "Module found in contrib directory:"
-        ls -la "$CONTRIB_DIR/$module_name"
-
-        if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README.md"
-        fi
-
-        if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README"
-        fi
-    else
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 info <module-name>"
+    return 1
+  fi
+
+  print_header "Module Information: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to get info via module manager first
+  if "$UNREALIRCD_BIN" module info "third/$module_name" 2> /dev/null; then
+    return 0
+  fi
+
+  # Fallback to showing contrib directory info
+  if [ -d "$CONTRIB_DIR/$module_name" ]; then
+    print_status "Module found in contrib directory:"
+    ls -la "$CONTRIB_DIR/$module_name"
+
+    if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README.md"
+    fi
+
+    if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README"
+    fi
+  else
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
 }
 
 # Install a module
 install_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 install <module-name>"
-        return 1
-    fi
-
-    print_header "Installing Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Check if module is already installed
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        print_warning "Module '$module_name' is already installed"
-        return 0
-    fi
-
-    # Try to install via module manager
-    print_status "Installing via module manager..."
-    if "$UNREALIRCD_BIN" module install "third/$module_name"; then
-        print_success "Module '$module_name' installed successfully"
-
-        # Check if we need to add loadmodule to config
-        if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
-            print_status "Checking if loadmodule line needs to be added..."
-            if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
-                print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-                print_warning "Then REHASH or restart UnrealIRCd"
-            fi
-        fi
-
-        return 0
-    fi
-
-    # Fallback to manual installation
-    print_warning "Module manager failed, attempting manual installation..."
-
-    if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR/$module_name" || exit 1
-
-    if [ -f "Makefile" ]; then
-        print_status "Compiling module..."
-        make clean 2>/dev/null || true
-        make
-
-        if [ -f "$module_name.so" ]; then
-            print_status "Installing module..."
-            cp "$module_name.so" "$MODULES_DIR/third/"
-            print_success "Module '$module_name' installed manually"
-
-            print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-            print_warning "Then REHASH or restart UnrealIRCd"
-        else
-            print_error "Module compilation failed"
-            return 1
-        fi
-    else
-        print_error "No Makefile found for module '$module_name'"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 install <module-name>"
+    return 1
+  fi
+
+  print_header "Installing Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Check if module is already installed
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    print_warning "Module '$module_name' is already installed"
+    return 0
+  fi
+
+  # Try to install via module manager
+  print_status "Installing via module manager..."
+  if "$UNREALIRCD_BIN" module install "third/$module_name"; then
+    print_success "Module '$module_name' installed successfully"
+
+    # Check if we need to add loadmodule to config
+    if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
+      print_status "Checking if loadmodule line needs to be added..."
+      if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
+        print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+        print_warning "Then REHASH or restart UnrealIRCd"
+      fi
+    fi
+
+    return 0
+  fi
+
+  # Fallback to manual installation
+  print_warning "Module manager failed, attempting manual installation..."
+
+  if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR/$module_name" || exit 1
+
+  if [ -f "Makefile" ]; then
+    print_status "Compiling module..."
+    make clean 2> /dev/null || true
+    make
+
+    if [ -f "$module_name.so" ]; then
+      print_status "Installing module..."
+      cp "$module_name.so" "$MODULES_DIR/third/"
+      print_success "Module '$module_name' installed manually"
+
+      print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+      print_warning "Then REHASH or restart UnrealIRCd"
+    else
+      print_error "Module compilation failed"
+      return 1
+    fi
+  else
+    print_error "No Makefile found for module '$module_name'"
+    return 1
+  fi
 }
 
 # Uninstall a module
 uninstall_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 uninstall <module-name>"
-        return 1
-    fi
-
-    print_header "Uninstalling Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to uninstall via module manager first
-    if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2>/dev/null; then
-        print_success "Module '$module_name' uninstalled successfully"
-        return 0
-    fi
-
-    # Fallback to manual removal
-    print_warning "Module manager failed, attempting manual removal..."
-
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        rm -f "$MODULES_DIR/third/$module_name.so"
-        print_success "Module '$module_name' removed manually"
-
-        print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
-        print_warning "Then REHASH or restart UnrealIRCd"
-    else
-        print_error "Module '$module_name' not found in modules directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 uninstall <module-name>"
+    return 1
+  fi
+
+  print_header "Uninstalling Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to uninstall via module manager first
+  if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2> /dev/null; then
+    print_success "Module '$module_name' uninstalled successfully"
+    return 0
+  fi
+
+  # Fallback to manual removal
+  print_warning "Module manager failed, attempting manual removal..."
+
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    rm -f "$MODULES_DIR/third/$module_name.so"
+    print_success "Module '$module_name' removed manually"
+
+    print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
+    print_warning "Then REHASH or restart UnrealIRCd"
+  else
+    print_error "Module '$module_name' not found in modules directory"
+    return 1
+  fi
 }
 
 # Upgrade modules
 upgrade_modules() {
-    local module_name="$1"
-
-    print_header "Upgrading Modules"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    if [ -n "$module_name" ]; then
-        print_status "Upgrading specific module: $module_name"
-        if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2>/dev/null; then
-            print_success "Module '$module_name' upgraded successfully"
-        else
-            print_error "Failed to upgrade module '$module_name'"
-            return 1
-        fi
-    else
-        print_status "Upgrading all modules..."
-        if "$UNREALIRCD_BIN" module upgrade 2>/dev/null; then
-            print_success "All modules upgraded successfully"
-        else
-            print_error "Failed to upgrade modules"
-            return 1
-        fi
-    fi
-
-    print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
+  local module_name="$1"
+
+  print_header "Upgrading Modules"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  if [ -n "$module_name" ]; then
+    print_status "Upgrading specific module: $module_name"
+    if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2> /dev/null; then
+      print_success "Module '$module_name' upgraded successfully"
+    else
+      print_error "Failed to upgrade module '$module_name'"
+      return 1
+    fi
+  else
+    print_status "Upgrading all modules..."
+    if "$UNREALIRCD_BIN" module upgrade 2> /dev/null; then
+      print_success "All modules upgraded successfully"
+    else
+      print_error "Failed to upgrade modules"
+      return 1
+    fi
+  fi
+
+  print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
 }
 
 # Update contrib repository
 update_contrib() {
-    print_header "Updating Contrib Repository"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR" || exit 1
-
-    print_status "Pulling latest changes from unrealircd-contrib..."
-    if git pull --quiet origin main; then
-        print_success "Contrib repository updated successfully"
-        print_status "New modules may now be available"
-    else
-        print_error "Failed to update contrib repository"
-        return 1
-    fi
+  print_header "Updating Contrib Repository"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR" || exit 1
+
+  print_status "Pulling latest changes from unrealircd-contrib..."
+  if git pull --quiet origin main; then
+    print_success "Contrib repository updated successfully"
+    print_status "New modules may now be available"
+  else
+    print_error "Failed to update contrib repository"
+    return 1
+  fi
 }
 
 # Show installed modules
 show_installed() {
-    print_header "Installed Modules"
-
-    if [ ! -d "$MODULES_DIR/third" ]; then
-        print_status "No third-party modules installed"
-        return 0
-    fi
-
-    cd "$MODULES_DIR/third" || exit 1
-
-    local count=0
-    for module in *.so; do
-        if [ -f "$module" ]; then
-            echo "  - third/${module%.so}"
-            ((count++))
-        fi
-    done
-
-    if [ $count -eq 0 ]; then
-        print_status "No third-party modules installed"
-    else
-        print_status "Total installed modules: $count"
-    fi
+  print_header "Installed Modules"
+
+  if [ ! -d "$MODULES_DIR/third" ]; then
+    print_status "No third-party modules installed"
+    return 0
+  fi
+
+  cd "$MODULES_DIR/third" || exit 1
+
+  local count=0
+  for module in *.so; do
+    if [ -f "$module" ]; then
+      echo "  - third/${module%.so}"
+      ((count++))
+    fi
+  done
+
+  if [ $count -eq 0 ]; then
+    print_status "No third-party modules installed"
+  else
+    print_status "Total installed modules: $count"
+  fi
 }
 
 # Show usage
 show_usage() {
-    cat <<EOF
+  cat << EOF
 UnrealIRCd Contrib Modules Management Script
 
 Usage: $0 <command> [options]
@@ -344,40 +344,40 @@
 
 # Main execution
 main() {
-    check_user
-
-    case "${1:-help}" in
+  check_user
+
+  case "${1:-help}" in
     list)
-        list_modules
-        ;;
+      list_modules
+      ;;
     info)
-        show_module_info "$2"
-        ;;
+      show_module_info "$2"
+      ;;
     install)
-        install_module "$2"
-        ;;
+      install_module "$2"
+      ;;
     uninstall)
-        uninstall_module "$2"
-        ;;
+      uninstall_module "$2"
+      ;;
     upgrade)
-        upgrade_modules "$2"
-        ;;
+      upgrade_modules "$2"
+      ;;
     update)
-        update_contrib
-        ;;
+      update_contrib
+      ;;
     installed)
-        show_installed
-        ;;
+      show_installed
+      ;;
     help | --help | -h)
-        show_usage
-        ;;
+      show_usage
+      ;;
     *)
-        print_error "Unknown command: $1"
-        echo
-        show_usage
-        exit 1
-        ;;
-    esac
+      print_error "Unknown command: $1"
+      echo
+      show_usage
+      exit 1
+      ;;
+  esac
 }
 
 # Run main function with all arguments
diff scripts/cert-manager/run.sh.orig scripts/cert-manager/run.sh
--- scripts/cert-manager/run.sh.orig
+++ scripts/cert-manager/run.sh
@@ -9,26 +9,26 @@
 
 # Validate domain format (alphanumeric, hyphens, dots only - prevent injection)
 case "$ROOT_DOMAIN" in
-    *[!a-zA-Z0-9.-]*)
-        echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Domain cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9.-]*)
+    echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Domain cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Validate email format (basic check - no spaces or shell metacharacters)
 case "$EMAIL" in
-    *[!a-zA-Z0-9@._+-]*)
-        echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Email cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9@._+-]*)
+    echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Email cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Lego outputs: certificates/_.<domain>.crt and _.<domain>.key for wildcards
@@ -38,9 +38,9 @@
 
 # Ensure we have credentials
 if [ -z "$CLOUDFLARE_DNS_API_TOKEN" ]; then
-    echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
-    echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
-    exec sleep infinity
+  echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
+  echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
+  exec sleep infinity
 fi
 
 # Initial issuance
@@ -49,8 +49,8 @@
 
 # Renewal loop
 while true; do
-    echo "Sleeping for 24 hours..."
-    sleep 86400
-    echo "Checking for renewal..."
-    lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
+  echo "Sleeping for 24 hours..."
+  sleep 86400
+  echo "Checking for renewal..."
+  lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
 done
diff scripts/gencloak-update-env.sh.orig scripts/gencloak-update-env.sh
--- scripts/gencloak-update-env.sh.orig
+++ scripts/gencloak-update-env.sh
@@ -12,23 +12,23 @@
 
 cd "$PROJECT_ROOT"
 
-output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2>/dev/null)
+output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2> /dev/null)
 echo "$output"
 
 mapfile -t keys < <(echo "$output" | grep -oE '"[^"]{50,}"' | tr -d '"')
 if [ ${#keys[@]} -ne 3 ]; then
-    echo "Failed to parse 3 cloak keys from gencloak output"
-    exit 1
+  echo "Failed to parse 3 cloak keys from gencloak output"
+  exit 1
 fi
 
 [ -f "$ENV_FILE" ] || cp .env.example "$ENV_FILE"
 
 if grep -q '^IRC_CLOAK_KEY_1=' "$ENV_FILE"; then
-    sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
 else
-    sed -i "/^IRC_CLOAK_PREFIX=/a\\
+  sed -i "/^IRC_CLOAK_PREFIX=/a\\
 # Cloak keys - keep secret; must be identical on all servers. Generate with: just irc gencloak\\
 IRC_CLOAK_KEY_1=${keys[0]}\\
 IRC_CLOAK_KEY_2=${keys[1]}\\
diff scripts/init.sh.orig scripts/init.sh
--- scripts/init.sh.orig
+++ scripts/init.sh
@@ -11,16 +11,16 @@
 
 # Load environment variables: .env (base) then .env.dev (overrides for just dev)
 if [ -f "$PROJECT_ROOT/.env" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env"
+  set +a
 fi
 if [ -f "$PROJECT_ROOT/.env.dev" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env.dev"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env.dev"
+  set +a
 fi
 
 # Ensure Atheme JSON-RPC port has a default (for existing .env without it)
@@ -35,386 +35,386 @@
 
 # Helper functions
 log_info() {
-    echo -e "${BLUE}[INFO]${NC} $1"
+  echo -e "${BLUE}[INFO]${NC} $1"
 }
 
 log_success() {
-    echo -e "${GREEN}[SUCCESS]${NC} $1"
+  echo -e "${GREEN}[SUCCESS]${NC} $1"
 }
 
 log_warning() {
-    echo -e "${YELLOW}[WARNING]${NC} $1"
+  echo -e "${YELLOW}[WARNING]${NC} $1"
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $1"
+  echo -e "${RED}[ERROR]${NC} $1"
 }
 
 # Function to create directory structure
 create_directories() {
-    log_info "Creating required directory structure..."
-
-    # Data directories (must match compose volume mounts)
-    local data_dirs=(
-        "$PROJECT_ROOT/data/irc/data"
-        "$PROJECT_ROOT/data/irc/logs"
-        "$PROJECT_ROOT/data/irc/webpanel-data"
-        "$PROJECT_ROOT/data/atheme/data"
-        "$PROJECT_ROOT/data/atheme/logs"
-        "$PROJECT_ROOT/data/xmpp/data"
-        "$PROJECT_ROOT/data/xmpp/logs"
-        "$PROJECT_ROOT/data/xmpp/uploads"
-        "$PROJECT_ROOT/data/thelounge"
-        "$PROJECT_ROOT/data/certs"
-    )
-
-    # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
-    local ssl_dirs=(
-        "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    )
-
-    # Create all directories
-    for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
-        if [ ! -d "$dir" ]; then
-            mkdir -p "$dir"
-            log_info "Created directory: $dir"
-    else
-            log_info "Directory already exists: $dir"
+  log_info "Creating required directory structure..."
+
+  # Data directories (must match compose volume mounts)
+  local data_dirs=(
+    "$PROJECT_ROOT/data/irc/data"
+    "$PROJECT_ROOT/data/irc/logs"
+    "$PROJECT_ROOT/data/irc/webpanel-data"
+    "$PROJECT_ROOT/data/atheme/data"
+    "$PROJECT_ROOT/data/atheme/logs"
+    "$PROJECT_ROOT/data/xmpp/data"
+    "$PROJECT_ROOT/data/xmpp/logs"
+    "$PROJECT_ROOT/data/xmpp/uploads"
+    "$PROJECT_ROOT/data/thelounge"
+    "$PROJECT_ROOT/data/certs"
+  )
+
+  # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
+  local ssl_dirs=(
+    "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  )
+
+  # Create all directories
+  for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
+    if [ ! -d "$dir" ]; then
+      mkdir -p "$dir"
+      log_info "Created directory: $dir"
+    else
+      log_info "Directory already exists: $dir"
     fi
   done
 
-    log_success "Directory structure created successfully"
+  log_success "Directory structure created successfully"
 }
 
 # Function to set proper permissions
 set_permissions() {
-    log_info "Setting proper permissions..."
-
-    # Get current user ID and group ID
-    local current_uid
-    local current_gid
-    # Use actual user ID instead of hardcoded values
-    current_uid=$(id -u)
-    current_gid=$(id -g)
-
-    # Use same UID for all services to avoid permission issues
-    local atheme_uid=$current_uid
-    local atheme_gid=$current_gid
-
-    log_info "Current user: $current_uid:$current_gid"
-
-    # Set ownership for data directories (if they exist)
-    if [ -d "$PROJECT_ROOT/data" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
-        # Ensure directories are writable by owner (critical for socket creation)
-        find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
-        log_info "Set ownership for data directory"
-  fi
-
-    # Set ownership for IRC data directory
-    if [ -d "$PROJECT_ROOT/data/irc" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
-        chmod -R 755 "$PROJECT_ROOT/data/irc"
-        log_info "Set permissions for IRC data directory"
-  fi
-
-    # Set ownership for Atheme data directory with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
-        chmod 755 "$PROJECT_ROOT/data/atheme"
-        log_info "Set permissions for Atheme data directory"
-  fi
-
-    # Set ownership for UnrealIRCd logs
-    if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
-        chmod 755 "$PROJECT_ROOT/data/irc/logs"
-        log_info "Set ownership for UnrealIRCd logs"
-    fi
-
-    # Set ownership for Atheme logs with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
-        chmod 755 "$PROJECT_ROOT/data/atheme/logs"
-        log_info "Set permissions for Atheme logs directory"
-    fi
-
-    # Set permissions for SSL certificates
-    if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
-        sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    fi
-    sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
-    chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
-    log_info "Set permissions for SSL directory"
-
-    # Make sure data directories are writable
-    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
-    find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
-
-    log_success "Permissions set successfully"
+  log_info "Setting proper permissions..."
+
+  # Get current user ID and group ID
+  local current_uid
+  local current_gid
+  # Use actual user ID instead of hardcoded values
+  current_uid=$(id -u)
+  current_gid=$(id -g)
+
+  # Use same UID for all services to avoid permission issues
+  local atheme_uid=$current_uid
+  local atheme_gid=$current_gid
+
+  log_info "Current user: $current_uid:$current_gid"
+
+  # Set ownership for data directories (if they exist)
+  if [ -d "$PROJECT_ROOT/data" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
+    # Ensure directories are writable by owner (critical for socket creation)
+    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
+    log_info "Set ownership for data directory"
+  fi
+
+  # Set ownership for IRC data directory
+  if [ -d "$PROJECT_ROOT/data/irc" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
+    chmod -R 755 "$PROJECT_ROOT/data/irc"
+    log_info "Set permissions for IRC data directory"
+  fi
+
+  # Set ownership for Atheme data directory with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
+    chmod 755 "$PROJECT_ROOT/data/atheme"
+    log_info "Set permissions for Atheme data directory"
+  fi
+
+  # Set ownership for UnrealIRCd logs
+  if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
+    chmod 755 "$PROJECT_ROOT/data/irc/logs"
+    log_info "Set ownership for UnrealIRCd logs"
+  fi
+
+  # Set ownership for Atheme logs with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
+    chmod 755 "$PROJECT_ROOT/data/atheme/logs"
+    log_info "Set permissions for Atheme logs directory"
+  fi
+
+  # Set permissions for SSL certificates
+  if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
+    sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  fi
+  sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
+  chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
+  log_info "Set permissions for SSL directory"
+
+  # Make sure data directories are writable
+  find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
+  find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
+
+  log_success "Permissions set successfully"
 }
 
 # Function to set up CA certificate bundle
 setup_ca_bundle() {
-    log_info "Setting up CA certificate bundle..."
-
-    local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
-    local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
-    local ca_bundle_file="curl-ca-bundle.crt"
-
-    # Ensure runtime directory exists
-    if [ ! -d "$ca_runtime_dir" ]; then
-        mkdir -p "$ca_runtime_dir"
-        log_info "Created TLS runtime directory: $ca_runtime_dir"
-  fi
-
-    # Ensure template directory exists
-    if [ ! -d "$ca_template_dir" ]; then
-        mkdir -p "$ca_template_dir"
-        log_info "Created TLS template directory: $ca_template_dir"
-  fi
-
-    # Check if system CA bundle exists
-    local system_ca_bundle=""
-    if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
-        system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
-  elif   [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
-        system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
-  fi
-
-    if [ -n "$system_ca_bundle" ]; then
-        # Create template if it doesn't exist
-        if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle template"
-      else
-                log_warning "Could not create CA certificate bundle template"
-                return 1
-      fi
-    fi
-
-        # Copy to runtime directory if it doesn't exist
-        if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle in runtime directory"
-      else
-                log_warning "Could not create CA certificate bundle in runtime directory"
-                return 1
-      fi
-    else
-            log_info "CA certificate bundle already exists in runtime directory"
-    fi
-  else
-        log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
-        return 1
-  fi
-
-    # Remove obsolete cert files from config/tls (server certs now live in data/certs)
-    rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2>/dev/null || true
-    rm -rf "$ca_runtime_dir/live" 2>/dev/null || true
-
-    log_success "CA certificate bundle setup completed"
+  log_info "Setting up CA certificate bundle..."
+
+  local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
+  local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
+  local ca_bundle_file="curl-ca-bundle.crt"
+
+  # Ensure runtime directory exists
+  if [ ! -d "$ca_runtime_dir" ]; then
+    mkdir -p "$ca_runtime_dir"
+    log_info "Created TLS runtime directory: $ca_runtime_dir"
+  fi
+
+  # Ensure template directory exists
+  if [ ! -d "$ca_template_dir" ]; then
+    mkdir -p "$ca_template_dir"
+    log_info "Created TLS template directory: $ca_template_dir"
+  fi
+
+  # Check if system CA bundle exists
+  local system_ca_bundle=""
+  if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
+    system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
+  elif [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+    system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
+  fi
+
+  if [ -n "$system_ca_bundle" ]; then
+    # Create template if it doesn't exist
+    if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle template"
+      else
+        log_warning "Could not create CA certificate bundle template"
+        return 1
+      fi
+    fi
+
+    # Copy to runtime directory if it doesn't exist
+    if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle in runtime directory"
+      else
+        log_warning "Could not create CA certificate bundle in runtime directory"
+        return 1
+      fi
+    else
+      log_info "CA certificate bundle already exists in runtime directory"
+    fi
+  else
+    log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
+    return 1
+  fi
+
+  # Remove obsolete cert files from config/tls (server certs now live in data/certs)
+  rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2> /dev/null || true
+  rm -rf "$ca_runtime_dir/live" 2> /dev/null || true
+
+  log_success "CA certificate bundle setup completed"
 }
 
 # Function to generate self-signed certificates for dev mode
 generate_cert() {
-    local domain="$1"
-    local base_dir="$2"
-    local live_dir="$base_dir/live/$domain"
-
-    # Ensure directory exists
-    mkdir -p "$live_dir"
-
-    # Generate self-signed cert if it doesn't exist
-    if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
-        log_info "Generating self-signed certificate for $domain..."
-        # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
-        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-            -keyout "$live_dir/privkey.pem" \
-            -out "$live_dir/fullchain.pem" \
-            -subj "/CN=$domain" \
-            -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-        log_success "Generated self-signed certificate for $domain"
-    else
-        log_info "Self-signed certificate already exists for $domain"
-    fi
-
-    # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
-    chmod 644 "$live_dir/privkey.pem" 2>/dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2>/dev/null || true
+  local domain="$1"
+  local base_dir="$2"
+  local live_dir="$base_dir/live/$domain"
+
+  # Ensure directory exists
+  mkdir -p "$live_dir"
+
+  # Generate self-signed cert if it doesn't exist
+  if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
+    log_info "Generating self-signed certificate for $domain..."
+    # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
+    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+      -keyout "$live_dir/privkey.pem" \
+      -out "$live_dir/fullchain.pem" \
+      -subj "/CN=$domain" \
+      -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+    log_success "Generated self-signed certificate for $domain"
+  else
+    log_info "Self-signed certificate already exists for $domain"
+  fi
+
+  # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
+  chmod 644 "$live_dir/privkey.pem" 2> /dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2> /dev/null || true
 }
 
 generate_dev_certs() {
-    log_info "Setting up self-signed certificates for dev mode..."
-
-    # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
-    local shared_cert_dir="$PROJECT_ROOT/data/certs"
-    generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
-    generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
-
-    log_success "Dev certificate setup completed"
+  log_info "Setting up self-signed certificates for dev mode..."
+
+  # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
+  local shared_cert_dir="$PROJECT_ROOT/data/certs"
+  generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
+  generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
+
+  log_success "Dev certificate setup completed"
 }
 
 # Function to prepare configuration files from templates
 prepare_config_files() {
-    log_info "Preparing configuration files from templates..."
-
-    # Load environment variables from .env if it exists
-    if [ -f "$PROJECT_ROOT/.env" ]; then
-        log_info "Loading environment variables from .env"
-        set -a
-        # shellcheck disable=SC1091
-        source "$PROJECT_ROOT/.env"
-        set +a
-        log_info "Environment variables loaded"
-    fi
-
-    # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
-    export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
-    export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
-
-    if [ ! -f "$PROJECT_ROOT/.env" ]; then
-        log_warning ".env file not found. Configuration will use defaults."
-        return 1
-  fi
-
-    # Check if envsubst is available
-    if ! command -v envsubst > /dev/null 2>&1; then
-        log_error "envsubst command not found. Please install gettext package."
-        return 1
-  fi
-
-    # Prepare UnrealIRCd configuration
-    local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
-    local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
-
-    if [ -f "$unreal_template" ]; then
-        log_info "Creating UnrealIRCd configuration from template..."
-        if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
-            log_success "UnrealIRCd configuration created"
-    else
-            log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
-    fi
-  elif   [ -f "$unreal_config" ]; then
-        log_info "UnrealIRCd configuration already exists"
-  else
-        log_warning "No UnrealIRCd configuration template found"
-  fi
-
-    # Prepare Atheme configuration
-    local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
-    local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
-
-    if [ -f "$atheme_template" ]; then
-        log_info "Creating Atheme configuration from template..."
-        envsubst < "$atheme_template" > "$atheme_config"
-        log_success "Atheme configuration created"
-  elif   [ -f "$atheme_config" ]; then
-        log_info "Atheme configuration already exists"
-  else
-        log_warning "No Atheme configuration template found"
-  fi
-
-    # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
-    if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
-        log_info "Running prepare-config.sh (bridge config, etc.)..."
-        # shellcheck source=prepare-config.sh
-        "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
-    fi
-
-    # Show substituted values for verification
-    log_info "Configuration values:"
-    echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
-    echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
-    echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
-    echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
-    echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
+  log_info "Preparing configuration files from templates..."
+
+  # Load environment variables from .env if it exists
+  if [ -f "$PROJECT_ROOT/.env" ]; then
+    log_info "Loading environment variables from .env"
+    set -a
+    # shellcheck disable=SC1091
+    source "$PROJECT_ROOT/.env"
+    set +a
+    log_info "Environment variables loaded"
+  fi
+
+  # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
+  export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
+  export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
+
+  if [ ! -f "$PROJECT_ROOT/.env" ]; then
+    log_warning ".env file not found. Configuration will use defaults."
+    return 1
+  fi
+
+  # Check if envsubst is available
+  if ! command -v envsubst > /dev/null 2>&1; then
+    log_error "envsubst command not found. Please install gettext package."
+    return 1
+  fi
+
+  # Prepare UnrealIRCd configuration
+  local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
+  local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
+
+  if [ -f "$unreal_template" ]; then
+    log_info "Creating UnrealIRCd configuration from template..."
+    if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
+      log_success "UnrealIRCd configuration created"
+    else
+      log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
+    fi
+  elif [ -f "$unreal_config" ]; then
+    log_info "UnrealIRCd configuration already exists"
+  else
+    log_warning "No UnrealIRCd configuration template found"
+  fi
+
+  # Prepare Atheme configuration
+  local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
+  local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
+
+  if [ -f "$atheme_template" ]; then
+    log_info "Creating Atheme configuration from template..."
+    envsubst < "$atheme_template" > "$atheme_config"
+    log_success "Atheme configuration created"
+  elif [ -f "$atheme_config" ]; then
+    log_info "Atheme configuration already exists"
+  else
+    log_warning "No Atheme configuration template found"
+  fi
+
+  # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
+  if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
+    log_info "Running prepare-config.sh (bridge config, etc.)..."
+    # shellcheck source=prepare-config.sh
+    "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
+  fi
+
+  # Show substituted values for verification
+  log_info "Configuration values:"
+  echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
+  echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
+  echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
+  echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
+  echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
 }
 
 # Function to create .env template if it doesn't exist
 create_env_template() {
-    local env_file="$PROJECT_ROOT/.env"
-    local env_example="$PROJECT_ROOT/.env.example"
-
-    if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
-        cp "$env_example" "$env_file"
-        log_info "Created .env file from template"
-        log_warning "Please edit .env file with your configuration before starting services"
-  elif   [ -f "$env_file" ]; then
-        log_info ".env file already exists"
-  else
-        log_warning "No .env template found. You may need to create environment variables manually"
+  local env_file="$PROJECT_ROOT/.env"
+  local env_example="$PROJECT_ROOT/.env.example"
+
+  if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
+    cp "$env_example" "$env_file"
+    log_info "Created .env file from template"
+    log_warning "Please edit .env file with your configuration before starting services"
+  elif [ -f "$env_file" ]; then
+    log_info ".env file already exists"
+  else
+    log_warning "No .env template found. You may need to create environment variables manually"
   fi
 }
 
 # Function to check Docker availability
 check_docker() {
-    log_info "Checking Docker availability..."
-
-    if ! command -v docker > /dev/null 2>&1; then
-        log_error "Docker is not installed or not in PATH"
-        exit 1
-  fi
-
-    if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
-        log_error "Docker Compose is not available"
-        exit 1
-  fi
-
-    log_success "Docker is available"
+  log_info "Checking Docker availability..."
+
+  if ! command -v docker > /dev/null 2>&1; then
+    log_error "Docker is not installed or not in PATH"
+    exit 1
+  fi
+
+  if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
+    log_error "Docker Compose is not available"
+    exit 1
+  fi
+
+  log_success "Docker is available"
 }
 
 # Function to show next steps
 show_next_steps() {
-    echo ""
-    log_info "Next steps:"
-    echo "  1. Edit .env file with your configuration (optional)"
-    echo "  3. Or run: docker compose up -d"
-    echo ""
-    log_info "Data will be stored in:"
-    echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
+  echo ""
+  log_info "Next steps:"
+  echo "  1. Edit .env file with your configuration (optional)"
+  echo "  3. Or run: docker compose up -d"
+  echo ""
+  log_info "Data will be stored in:"
+  echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
 }
 
 # Main function
 main() {
-    log_info "ATL Chat Infrastructure Initialization"
-    log_info "======================================"
-
-    # Check if we're running as root (for permission info)
-    if [ "$(id -u)" = "0" ]; then
-        log_warning "Running as root - this is fine for initial setup"
-  fi
-
-    # Check Docker availability
-    check_docker
-
-    # Create directory structure
-    create_directories
-
-    # Set permissions
-    set_permissions
-
-    # Set up CA certificate bundle
-    setup_ca_bundle
-
-    # Generate dev certs
-    generate_dev_certs
-
-    # Create .env if needed
-    create_env_template
-
-    # Prepare configuration files from templates
-    prepare_config_files
-
-    # Show next steps
-    show_next_steps
-
-    log_success "Initialization completed successfully!"
+  log_info "ATL Chat Infrastructure Initialization"
+  log_info "======================================"
+
+  # Check if we're running as root (for permission info)
+  if [ "$(id -u)" = "0" ]; then
+    log_warning "Running as root - this is fine for initial setup"
+  fi
+
+  # Check Docker availability
+  check_docker
+
+  # Create directory structure
+  create_directories
+
+  # Set permissions
+  set_permissions
+
+  # Set up CA certificate bundle
+  setup_ca_bundle
+
+  # Generate dev certs
+  generate_dev_certs
+
+  # Create .env if needed
+  create_env_template
+
+  # Prepare configuration files from templates
+  prepare_config_files
+
+  # Show next steps
+  show_next_steps
+
+  log_success "Initialization completed successfully!"
 }
 
 # Run main function
 if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
-    main "$@"
+  main "$@"
 fi
diff scripts/prepare-config.sh.orig scripts/prepare-config.sh
--- scripts/prepare-config.sh.orig
+++ scripts/prepare-config.sh
@@ -180,7 +180,7 @@
     log_info "Preparing bridge configuration from template..."
     local temp_file="/tmp/bridge-config.yaml.tmp"
     envsubst < "$bridge_template" > "$temp_file"
-    if cp "$temp_file" "$bridge_config" 2>/dev/null || sudo cp "$temp_file" "$bridge_config" 2>/dev/null; then
+    if cp "$temp_file" "$bridge_config" 2> /dev/null || sudo cp "$temp_file" "$bridge_config" 2> /dev/null; then
       log_success "Bridge configuration prepared"
     else
       log_warning "Could not write bridge config to $bridge_config"
@@ -189,7 +189,7 @@
   elif [ ! -f "$bridge_config" ]; then
     log_warning "Bridge config not found. Copy apps/bridge/config.example.yaml to apps/bridge/config.yaml and customize."
     if [ -f "$PROJECT_ROOT/apps/bridge/config.example.yaml" ]; then
-      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2>/dev/null || true
+      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2> /dev/null || true
       log_info "Copied config.example.yaml to apps/bridge/config.yaml - edit with your Discord channel ID"
     fi
   fi
@@ -202,7 +202,7 @@
     mkdir -p "$(dirname "$lounge_config")"
     local temp_file="/tmp/thelounge-config.js.tmp"
     envsubst < "$lounge_template" > "$temp_file"
-    if cp "$temp_file" "$lounge_config" 2>/dev/null || sudo cp "$temp_file" "$lounge_config" 2>/dev/null; then
+    if cp "$temp_file" "$lounge_config" 2> /dev/null || sudo cp "$temp_file" "$lounge_config" 2> /dev/null; then
       log_success "The Lounge configuration prepared"
     else
       log_warning "Could not write The Lounge config to $lounge_config"
----------

You can reformat the above files to meet shfmt's requirements by typing:

  shfmt  -w filename


Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/services/irc/CI_CD.md (1)

389-398: ⚠️ Potential issue | 🟡 Minor

Use explicit production in the deployment step example.

The section is now production-only, but the example still uses ${{ env.ENVIRONMENT }}. That can mislead readers into thinking multi-environment dispatch still exists.

Suggested doc fix
-#### Environment-Based Deployment
+#### Production Deployment

 ```yaml
 jobs:
   deploy:
     environment: production
     runs-on: ubuntu-latest
-- name: Deploy to ${{ env.ENVIRONMENT }}
+- name: Deploy to production
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/services/irc/CI_CD.md` around lines 389 - 398, Update the deployment
step example to use an explicit production label: locate the deploy job (the
"deploy:" block with "environment: production" and "runs-on: ubuntu-latest") and
change the step that currently reads "Deploy to ${{ env.ENVIRONMENT }}" to
"Deploy to production" so the example matches the documented production-only
scope.
♻️ Duplicate comments (1)
AGENTS.md (1)

91-91: ⚠️ Potential issue | 🟠 Major

Replace world-writable Docker socket guidance with a safer option.

Line 91 still recommends sudo chmod 666 /var/run/docker.sock, which is unsafe (root-equivalent Docker access for all local users/processes). Please update this to either: (a) add the user to the docker group and re-login, or (b) run Docker commands with sudo / use root:docker + 660 on the socket.

🔐 Suggested doc update
-Docker daemon must be started manually: `sudo dockerd &>/tmp/dockerd.log &` then ensure socket permissions: `sudo chmod 666 /var/run/docker.sock`.
+Docker daemon must be started manually: `sudo dockerd &>/tmp/dockerd.log &`.
+For non-root Docker access, add your user to the `docker` group (`sudo usermod -aG docker $USER`) and start a new login session.
+Alternatively, run Docker commands with `sudo`, or set socket ownership/permissions to `root:docker` with mode `660`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` at line 91, Update the unsafe guidance in AGENTS.md that currently
tells users to run "sudo chmod 666 /var/run/docker.sock": replace it with
instructions to add the user to the docker group (e.g., "sudo usermod -aG docker
$USER" and re-login) as the recommended approach, and provide an alternative to
run Docker with sudo or set the socket ownership to root:docker with permission
660 (e.g., "sudo chown root:docker /var/run/docker.sock && sudo chmod 660
/var/run/docker.sock") so we avoid world-writable socket permissions.
🧹 Nitpick comments (1)
README.md (1)

75-79: Clarify pre-bootstrap identity wording to avoid a chicken-and-egg interpretation.

Nice addition overall. Minor wording tweak: before SRA bootstrap, “admin account” can be misread as already having elevated services privileges.

Suggested doc tweak
-After starting the production stack for the first time, you must manually bootstrap the Services Root Administrator (SRA) to avoid being locked out of IRC services:
+After starting the production stack for the first time, manually bootstrap the Services Root Administrator (SRA) to avoid IRC services lockout:

-1. Connect to IRC using your admin account.
+1. Connect to IRC using the nick that should become the initial SRA.
 2. In the terminal, run: `just irc sra-bootstrap <your_nick>`
 3. OperServ will then recognize you as a network administrator.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 75 - 79, Update the README wording around the SRA
bootstrap to avoid implying the user already has elevated services privileges:
change the phrase "Connect to IRC using your admin account" to something
explicit like "Connect to IRC using your regular IRC account or nickname (this
does not yet have Services privileges)" and keep the rest of the steps
(including the `just irc sra-bootstrap <your_nick>` command and the note about
OperServ recognizing you) intact so readers understand they must bootstrap SRA
from a normal account/nick.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@docs/services/irc/CI_CD.md`:
- Around line 389-398: Update the deployment step example to use an explicit
production label: locate the deploy job (the "deploy:" block with "environment:
production" and "runs-on: ubuntu-latest") and change the step that currently
reads "Deploy to ${{ env.ENVIRONMENT }}" to "Deploy to production" so the
example matches the documented production-only scope.

---

Duplicate comments:
In `@AGENTS.md`:
- Line 91: Update the unsafe guidance in AGENTS.md that currently tells users to
run "sudo chmod 666 /var/run/docker.sock": replace it with instructions to add
the user to the docker group (e.g., "sudo usermod -aG docker $USER" and
re-login) as the recommended approach, and provide an alternative to run Docker
with sudo or set the socket ownership to root:docker with permission 660 (e.g.,
"sudo chown root:docker /var/run/docker.sock && sudo chmod 660
/var/run/docker.sock") so we avoid world-writable socket permissions.

---

Nitpick comments:
In `@README.md`:
- Around line 75-79: Update the README wording around the SRA bootstrap to avoid
implying the user already has elevated services privileges: change the phrase
"Connect to IRC using your admin account" to something explicit like "Connect to
IRC using your regular IRC account or nickname (this does not yet have Services
privileges)" and keep the rest of the steps (including the `just irc
sra-bootstrap <your_nick>` command and the note about OperServ recognizing you)
intact so readers understand they must bootstrap SRA from a normal account/nick.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dee34b9 and 3a865fe.

📒 Files selected for processing (13)
  • .cursor/commands/database-migration.md
  • .cursorignore
  • .env.example
  • AGENTS.md
  • README.md
  • apps/atheme/config/atheme.conf.template
  • apps/prosody/config/prosody.cfg.lua
  • docs/audits/dev-prod-lifecycle-audit.md
  • docs/deployment.md
  • docs/services/irc/CI_CD.md
  • docs/services/irc/CONFIG.md
  • justfile
  • scripts/prepare-config.sh
💤 Files with no reviewable changes (1)
  • .cursorignore
🚧 Files skipped from review as they are similar to previous changes (3)
  • justfile
  • apps/atheme/config/atheme.conf.template
  • docs/deployment.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/prepare-config.sh (1)
scripts/init.sh (1)
  • log_info (37-39)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 12-12: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 16-16: [UnorderedKey] The PGID key should go before the PUID key

(UnorderedKey)


[warning] 21-21: [UnorderedKey] The ATL_CHAT_IP key should go before the ATL_GATEWAY_IP key

(UnorderedKey)


[warning] 25-25: [UnorderedKey] The CLOUDFLARE_DNS_API_TOKEN key should go before the LETSENCRYPT_EMAIL key

(UnorderedKey)


[warning] 32-32: [UnorderedKey] The ATHEME_VERSION key should go before the UNREALIRCD_VERSION key

(UnorderedKey)


[warning] 37-37: [UnorderedKey] The IRC_NETWORK_NAME key should go before the IRC_ROOT_DOMAIN key

(UnorderedKey)


[warning] 38-38: [UnorderedKey] The IRC_CLOAK_PREFIX key should go before the IRC_DOMAIN key

(UnorderedKey)


[warning] 42-42: [UnorderedKey] The IRC_SERVER_PORT key should go before the IRC_TLS_PORT key

(UnorderedKey)


[warning] 43-43: [UnorderedKey] The IRC_RPC_PORT key should go before the IRC_SERVER_PORT key

(UnorderedKey)


[warning] 52-52: [UnorderedKey] The IRC_DRPASS key should go before the IRC_OPER_PASSWORD key

(UnorderedKey)


[warning] 54-54: [UnorderedKey] The ATL_WEBIRC_PASSWORD key should go before the IRC_CLOAK_KEY_1 key

(UnorderedKey)


[warning] 58-58: [UnorderedKey] The IRC_ADMIN_EMAIL key should go before the IRC_ADMIN_NAME key

(UnorderedKey)


[warning] 71-71: [UnorderedKey] The ATHEME_SERVER_NAME key should go before the IRC_SERVICES_SERVER key

(UnorderedKey)


[warning] 72-72: [UnorderedKey] The ATHEME_SERVER_DESC key should go before the ATHEME_SERVER_NAME key

(UnorderedKey)


[warning] 73-73: [UnorderedKey] The ATHEME_UPLINK_HOST key should go before the IRC_SERVICES_SERVER key

(UnorderedKey)


[warning] 74-74: [UnorderedKey] The ATHEME_UPLINK_PORT key should go before the IRC_SERVICES_SERVER key

(UnorderedKey)


[warning] 75-75: [UnorderedKey] The ATHEME_NUMERIC key should go before the ATHEME_SERVER_DESC key

(UnorderedKey)


[warning] 76-76: [UnorderedKey] The ATHEME_RECONTIME key should go before the ATHEME_SERVER_DESC key

(UnorderedKey)


[warning] 77-77: [UnorderedKey] The ATHEME_HTTPD_PORT key should go before the ATHEME_NUMERIC key

(UnorderedKey)


[warning] 173-173: [UnorderedKey] The WEBPANEL_RPC_PASSWORD key should go before the WEBPANEL_RPC_USER key

(UnorderedKey)


[warning] 178-178: [UnorderedKey] The THELOUNGE_DELETE_UPLOADS_AFTER_MINUTES key should go before the THELOUNGE_PORT key

(UnorderedKey)


[warning] 184-184: [UnorderedKey] The PROSODY_ADMIN_EMAIL key should go before the XMPP_DOMAIN key

(UnorderedKey)


[warning] 193-193: [UnorderedKey] The PROSODY_DB_NAME key should go before the PROSODY_DB_PORT key

(UnorderedKey)


[warning] 195-195: [UnorderedKey] The PROSODY_DB_PASSWORD key should go before the PROSODY_DB_PORT key

(UnorderedKey)


[warning] 201-201: [UnorderedKey] The PROSODY_HTTP_PORT key should go before the PROSODY_S2S_PORT key

(UnorderedKey)


[warning] 202-202: [UnorderedKey] The PROSODY_HTTPS_PORT key should go before the PROSODY_HTTP_PORT key

(UnorderedKey)


[warning] 203-203: [UnorderedKey] The PROSODY_C2S_DIRECT_TLS_PORT key should go before the PROSODY_C2S_PORT key

(UnorderedKey)


[warning] 204-204: [UnorderedKey] The PROSODY_S2S_DIRECT_TLS_PORT key should go before the PROSODY_S2S_PORT key

(UnorderedKey)


[warning] 205-205: [UnorderedKey] The PROSODY_PROXY65_PORT key should go before the PROSODY_S2S_DIRECT_TLS_PORT key

(UnorderedKey)


[warning] 209-209: [UnorderedKey] The TURNS_PORT key should go before the TURN_PORT key

(UnorderedKey)


[warning] 211-211: [UnorderedKey] The TURN_EXTERNAL_HOST key should go before the TURN_PORT key

(UnorderedKey)


[warning] 215-215: [UnorderedKey] The PROSODY_ALLOW_REGISTRATION key should go before the PROSODY_OAUTH2_REGISTRATION_KEY key

(UnorderedKey)


[warning] 216-216: [UnorderedKey] The PROSODY_C2S_REQUIRE_ENCRYPTION key should go before the PROSODY_OAUTH2_REGISTRATION_KEY key

(UnorderedKey)


[warning] 219-219: [UnorderedKey] The PROSODY_ALLOW_UNENCRYPTED_PLAIN_AUTH key should go before the PROSODY_C2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 220-220: [UnorderedKey] The PROSODY_MAX_CONNECTIONS_PER_IP key should go before the PROSODY_OAUTH2_REGISTRATION_KEY key

(UnorderedKey)


[warning] 221-221: [UnorderedKey] The PROSODY_REGISTRATION_THROTTLE_MAX key should go before the PROSODY_S2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 222-222: [UnorderedKey] The PROSODY_REGISTRATION_THROTTLE_PERIOD key should go before the PROSODY_S2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 223-223: [UnorderedKey] The PROSODY_BLOCK_REGISTRATIONS_REQUIRE key should go before the PROSODY_C2S_REQUIRE_ENCRYPTION key

(UnorderedKey)


[warning] 230-230: [UnorderedKey] The PROSODY_PROXY_ADDRESS key should go before the PROSODY_UPLOAD_EXTERNAL_URL key

(UnorderedKey)


[warning] 242-242: [UnorderedKey] The PROSODY_OPENMETRICS_IP key should go before the PROSODY_STATISTICS key

(UnorderedKey)


[warning] 243-243: [UnorderedKey] The PROSODY_OPENMETRICS_CIDR key should go before the PROSODY_OPENMETRICS_IP key

(UnorderedKey)


[warning] 248-248: [UnorderedKey] The PROSODY_ARCHIVE_COMPRESSION key should go before the PROSODY_ARCHIVE_EXPIRES_AFTER key

(UnorderedKey)


[warning] 250-250: [UnorderedKey] The PROSODY_ARCHIVE_MAX_QUERY_RESULTS key should go before the PROSODY_ARCHIVE_POLICY key

(UnorderedKey)


[warning] 284-284: [UnorderedKey] The PROSODY_PUSH_MAX_DEVICES key should go before the PROSODY_PUSH_MAX_ERRORS key

(UnorderedKey)


[warning] 291-291: [UnorderedKey] The PROSODY_ACCOUNT_GRACE_PERIOD key should go before the PROSODY_ACCOUNT_INACTIVE_PERIOD key

(UnorderedKey)


[warning] 296-296: [UnorderedKey] The PROSODY_SERVER_DESCRIPTION key should go before the PROSODY_SERVER_NAME key

(UnorderedKey)


[warning] 300-300: [UnorderedKey] The LUA_GC_PAUSE key should go before the LUA_GC_STEP_SIZE key

(UnorderedKey)


[warning] 301-301: [UnorderedKey] The LUA_GC_SPEED key should go before the LUA_GC_STEP_SIZE key

(UnorderedKey)


[warning] 311-311: [UnorderedKey] The BRIDGE_DISCORD_CHANNEL_ID key should go before the BRIDGE_DISCORD_TOKEN key

(UnorderedKey)


[warning] 319-319: [UnorderedKey] The BRIDGE_XMPP_COMPONENT_PORT key should go before the BRIDGE_XMPP_COMPONENT_SECRET key

(UnorderedKey)


[warning] 341-341: [UnorderedKey] The IRC_UNREAL_RPC_PASSWORD key should go before the IRC_UNREAL_RPC_USER key

(UnorderedKey)

🪛 LanguageTool
docs/audits/dev-prod-lifecycle-audit.md

[style] ~72-~72: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...s: ["prod"]. So just prodstarts the exact same services asdocker compose up -d` (eve...

(EN_WORDINESS_PREMIUM_EXACT_SAME)


[style] ~119-~119: To form a complete sentence, be sure to include a subject or ‘there’.
Context: ...d into the "production" example file**. Should be the prod domain. The .env.dev over...

(MISSING_IT_THERE)

🔇 Additional comments (13)
docs/services/irc/CI_CD.md (1)

372-372: Good production-only clarification.

This wording is clear and aligns with the PR’s production-first deployment direction.

docs/services/irc/CONFIG.md (1)

443-444: LGTM!

The Docker Compose override example correctly reflects the production domain, aligning with the PR's goal to remove staging references.

.cursor/commands/database-migration.md (1)

26-27: LGTM!

The updated testing strategy guidance aligns with the PR's simplification to dev/prod environments.

docs/audits/dev-prod-lifecycle-audit.md (2)

235-238: Audit finding may be outdated after this PR's changes.

Line 237 states that prepare-config.sh unconditionally sources .env.dev if it exists. However, scripts/prepare-config.sh in this same PR now gates .env.dev loading on ATL_ENVIRONMENT=dev (line 59). Consider updating this section to reflect the fix, or note that this issue has been addressed.


1-12: Well-documented audit with clear findings and priorities.

This comprehensive lifecycle audit provides valuable insight into the dev vs prod deployment gaps. The executive summary and verdict format make it easy to understand the current state and required fixes.

scripts/prepare-config.sh (1)

57-66: LGTM! The environment gating logic correctly addresses the previous concern.

The condition [ "${ATL_ENVIRONMENT:-}" = "dev" ] ensures .env.dev is only sourced when ATL_ENVIRONMENT is explicitly set to dev, preventing accidental dev override loading in production.

apps/prosody/config/prosody.cfg.lua (3)

566-584: OAuth2 configuration properly hardened.

Good security improvements:

  • Password grant removed (commented out on line 575)
  • PKCE enforced via oauth2_require_code_challenge = true
  • Registration key now requires explicit environment variable with no insecure fallback

The error() call ensures Prosody fails fast if PROSODY_OAUTH2_REGISTRATION_KEY is missing, which is the correct behavior for a required secret.


447-448: LGTM! Status endpoint properly restricted.

The CIDR restriction to Docker network (172.16.0.0/12) and localhost (127.0.0.0/8) prevents external access to the status endpoint while allowing internal monitoring.


200-204: LGTM! Archiving exclusions now active.

Excluding chat states and Jingle message signaling from archives reduces storage overhead without losing message content.

.env.example (4)

4-5: The "No orphans" claim still contradicts the Portal section.

Lines 4-5 state every variable is consumed in-repo, but lines 334-337 explicitly note that Portal variables are consumed by an external service, not this monorepo. Consider qualifying the statement as suggested in the previous review.


214-214: Good: OAuth2 registration key requires explicit configuration.

The placeholder value here aligns with prosody.cfg.lua which now calls error() if PROSODY_OAUTH2_REGISTRATION_KEY is not set. This ensures deployments cannot accidentally run with an insecure default.


46-54: Secrets section is properly structured with placeholder values.

The change_me_* pattern clearly indicates values that must be replaced. The argon2id hash for IRC_OPER_PASSWORD serves as a format example. Ensure deployment documentation emphasizes regenerating all secrets before production use.


322-322: UnrealIRCd operator block and IRC services configuration are correctly updated.

The operator block in unrealircd.conf.template already uses the new nick bridge with proper mask restrictions (*@*bridge*), and the associated bridge-oper operclass has appropriate minimal permissions for bridging. IRC services are properly linked and configured. No stale references to the old nick remain in the configuration.

… validation

The healthcheck command is updated to use `pgrep` to check for the
`bridge.__main__` process instead of a Python command that always
exits successfully. This change ensures that the healthcheck
accurately reflects the running state of the service by verifying
the actual process, improving reliability and correctness of the
health status.
Add a shellcheck directive to disable SC2016 warning, which is
triggered by the use of envsubst with variable placeholders. This
change ensures that the script passes shellcheck validation without
unnecessary warnings, improving maintainability and clarity for
developers.
@github-actions
Copy link

sh-checker report

To get the full details, please check in the job output.

shellcheck errors
'shellcheck ' found no issues.

shfmt errors

'shfmt ' returned error 1 finding the following formatting issues:

----------
diff .github/scripts/docker.sh.orig .github/scripts/docker.sh
--- .github/scripts/docker.sh.orig
+++ .github/scripts/docker.sh
@@ -57,11 +57,11 @@
 shift || true
 
 case "$COMMAND" in
-  generate-pr-version)        generate_pr_version "$@" ;;
-  generate-release-version)   generate_release_version "$@" ;;
-  validate-build-config)      validate_build_config "$@" ;;
+  generate-pr-version) generate_pr_version "$@" ;;
+  generate-release-version) generate_release_version "$@" ;;
+  validate-build-config) validate_build_config "$@" ;;
   calculate-source-date-epoch) calculate_source_date_epoch "$@" ;;
-  generate-build-date)        generate_build_date "$@" ;;
+  generate-build-date) generate_build_date "$@" ;;
   *)
     echo "Usage: docker.sh {generate-pr-version|generate-release-version|validate-build-config|calculate-source-date-epoch|generate-build-date} [args...]"
     exit 1
diff apps/atheme/docker-entrypoint.sh.orig apps/atheme/docker-entrypoint.sh
--- apps/atheme/docker-entrypoint.sh.orig
+++ apps/atheme/docker-entrypoint.sh
@@ -7,9 +7,9 @@
 
 # Ensure we have proper permissions
 if [ "$(id -u)" = "0" ]; then
-    echo "ERROR: Atheme should not run as root for security reasons"
-    echo "Please run with a non-root user (UID 1000 recommended)"
-    exit 1
+  echo "ERROR: Atheme should not run as root for security reasons"
+  echo "Please run with a non-root user (UID 1000 recommended)"
+  exit 1
 fi
 
 # Create directories with proper ownership
@@ -17,9 +17,9 @@
 
 # Validate configuration exists
 if [ ! -f "/usr/local/atheme/etc/atheme.conf" ]; then
-    echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
-    echo "Please ensure the configuration is properly mounted"
-    exit 1
+  echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
+  echo "Please ensure the configuration is properly mounted"
+  exit 1
 fi
 
 # Clean up stale PID file
@@ -27,25 +27,25 @@
 
 # Validate database directory is writable
 if [ ! -w "/usr/local/atheme/data" ]; then
-    echo "ERROR: Data directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Data directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Validate logs directory is writable
 if [ ! -w "/usr/local/atheme/logs" ]; then
-    echo "ERROR: Logs directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Logs directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Check if this is first run (no database exists)
 if [ ! -f "/usr/local/atheme/data/services.db" ]; then
-    echo "First run detected - creating initial database..."
-    /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
-    echo "Database created successfully"
+  echo "First run detected - creating initial database..."
+  /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
+  echo "Database created successfully"
 else
-    echo "Existing database found - starting with existing data"
+  echo "Existing database found - starting with existing data"
 fi
 
 # Start Atheme services
diff apps/prosody/docker-entrypoint.sh.orig apps/prosody/docker-entrypoint.sh
--- apps/prosody/docker-entrypoint.sh.orig
+++ apps/prosody/docker-entrypoint.sh
@@ -29,21 +29,21 @@
 # ============================================================================
 
 log_info() {
-    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_warn() {
-    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_debug() {
-    if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
-        echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
-    fi
+  if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
+    echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  fi
 }
 
 # ============================================================================
@@ -51,45 +51,45 @@
 # ============================================================================
 
 validate_environment() {
-    log_info "Validating environment configuration..."
-
-    # Validate required domain
-    if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
-        log_error "PROSODY_DOMAIN is required but not set"
-        exit 1
-    fi
-
-    # Validate domain format
-    if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
-        log_error "Invalid domain format: ${PROSODY_DOMAIN}"
-        exit 1
-    fi
-
-    # Set default admin if not provided
-    if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
-        export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
-        log_info "Using default admin: ${PROSODY_ADMIN_JID}"
-    fi
-
-    # Validate database configuration for SQL storage
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        local required_vars=(
-            "PROSODY_DB_DRIVER"
-            "PROSODY_DB_NAME"
-            "PROSODY_DB_USER"
-            "PROSODY_DB_PASSWORD"
-            "PROSODY_DB_HOST"
-        )
-
-        for var in "${required_vars[@]}"; do
-            if [[ -z "${!var:-}" ]]; then
-                log_error "Database variable ${var} is required for SQL storage"
-                exit 1
-            fi
-        done
-    fi
-
-    log_info "Environment validation complete"
+  log_info "Validating environment configuration..."
+
+  # Validate required domain
+  if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
+    log_error "PROSODY_DOMAIN is required but not set"
+    exit 1
+  fi
+
+  # Validate domain format
+  if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
+    log_error "Invalid domain format: ${PROSODY_DOMAIN}"
+    exit 1
+  fi
+
+  # Set default admin if not provided
+  if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
+    export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
+    log_info "Using default admin: ${PROSODY_ADMIN_JID}"
+  fi
+
+  # Validate database configuration for SQL storage
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    local required_vars=(
+      "PROSODY_DB_DRIVER"
+      "PROSODY_DB_NAME"
+      "PROSODY_DB_USER"
+      "PROSODY_DB_PASSWORD"
+      "PROSODY_DB_HOST"
+    )
+
+    for var in "${required_vars[@]}"; do
+      if [[ -z "${!var:-}" ]]; then
+        log_error "Database variable ${var} is required for SQL storage"
+        exit 1
+      fi
+    done
+  fi
+
+  log_info "Environment validation complete"
 }
 
 # ============================================================================
@@ -97,186 +97,186 @@
 # ============================================================================
 
 setup_directories() {
-    log_info "Setting up directories..."
-
-    local dirs=(
-        "$PROSODY_DATA_DIR"
-        "$PROSODY_LOG_DIR"
-        "$PROSODY_CERT_DIR"
-        "$PROSODY_UPLOAD_DIR"
-        "$(dirname "$PROSODY_PID_FILE")"
-    )
-
-    for dir in "${dirs[@]}"; do
-        if [[ ! -d "$dir" ]]; then
-            log_debug "Creating directory: $dir"
-            mkdir -p "$dir"
-        fi
-
-        # Ensure proper ownership (only if running as root)
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
-        fi
-    done
-
-    # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
-    # Prosody needs write access for SQLite (MAM, PEP, etc.)
-    if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
-        if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
-            log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
-        fi
-    fi
-
-    log_info "Directory setup complete"
+  log_info "Setting up directories..."
+
+  local dirs=(
+    "$PROSODY_DATA_DIR"
+    "$PROSODY_LOG_DIR"
+    "$PROSODY_CERT_DIR"
+    "$PROSODY_UPLOAD_DIR"
+    "$(dirname "$PROSODY_PID_FILE")"
+  )
+
+  for dir in "${dirs[@]}"; do
+    if [[ ! -d "$dir" ]]; then
+      log_debug "Creating directory: $dir"
+      mkdir -p "$dir"
+    fi
+
+    # Ensure proper ownership (only if running as root)
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
+    fi
+  done
+
+  # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
+  # Prosody needs write access for SQLite (MAM, PEP, etc.)
+  if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
+    if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
+      log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
+    fi
+  fi
+
+  log_info "Directory setup complete"
 }
 
 setup_certificates() {
-    log_info "Setting up SSL certificates..."
-
-    # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
-    local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
-    local cert_file="${live_dir}/fullchain.pem"
-    local key_file="${live_dir}/privkey.pem"
-
-    if [[ -f "$cert_file" && -f "$key_file" ]]; then
-        log_info "Certificates found for ${PROSODY_DOMAIN}"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file" 2> /dev/null || true
-        chmod 600 "$key_file" 2> /dev/null || true
-        # HTTPS service automatic discovery (Prosody Certificates doc):
-        # - https/fullchain.pem + https/privkey.pem (directory symlink)
-        # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
-    local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
-    local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
-    if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
-        log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
-        mkdir -p "$live_dir"
-        cp "$legacy_cert" "$cert_file"
-        cp "$legacy_key" "$key_file"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file"
-        chmod 600 "$key_file"
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Generate self-signed certificate for development/testing
-    log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
-    log_warn "This is suitable for development only - use proper certificates in production"
-
+  log_info "Setting up SSL certificates..."
+
+  # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
+  local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
+  local cert_file="${live_dir}/fullchain.pem"
+  local key_file="${live_dir}/privkey.pem"
+
+  if [[ -f "$cert_file" && -f "$key_file" ]]; then
+    log_info "Certificates found for ${PROSODY_DOMAIN}"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    fi
+    chmod 644 "$cert_file" 2> /dev/null || true
+    chmod 600 "$key_file" 2> /dev/null || true
+    # HTTPS service automatic discovery (Prosody Certificates doc):
+    # - https/fullchain.pem + https/privkey.pem (directory symlink)
+    # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
+  local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
+  local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
+  if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
+    log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
     mkdir -p "$live_dir"
-    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-        -keyout "$key_file" \
-        -out "$cert_file" \
-        -subj "/CN=${PROSODY_DOMAIN}" \
-        -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    cp "$legacy_cert" "$cert_file"
+    cp "$legacy_key" "$key_file"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
     fi
     chmod 644 "$cert_file"
     chmod 600 "$key_file"
-
-    # HTTPS service automatic discovery
-    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-
-    log_info "Self-signed certificate generated successfully"
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Generate self-signed certificate for development/testing
+  log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
+  log_warn "This is suitable for development only - use proper certificates in production"
+
+  mkdir -p "$live_dir"
+  openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+    -keyout "$key_file" \
+    -out "$cert_file" \
+    -subj "/CN=${PROSODY_DOMAIN}" \
+    -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+  fi
+  chmod 644 "$cert_file"
+  chmod 600 "$key_file"
+
+  # HTTPS service automatic discovery
+  ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+
+  log_info "Self-signed certificate generated successfully"
 }
 
 wait_for_database() {
-    if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
-        log_debug "Not using SQL storage, skipping database wait"
-        return 0
-    fi
-
-    local host="${PROSODY_DB_HOST}"
-    local port="${PROSODY_DB_PORT:-5432}"
-    local max_attempts=30
-    local attempt=1
-
-    log_info "Waiting for database connection to ${host}:${port}..."
-
-    while [[ $attempt -le $max_attempts ]]; do
-        if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
-            log_info "Database connection established"
-            return 0
-        fi
-
-        log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
-        sleep 2
-        ((attempt++))
-    done
-
-    log_error "Database connection timeout after ${max_attempts} attempts"
-    exit 1
+  if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
+    log_debug "Not using SQL storage, skipping database wait"
+    return 0
+  fi
+
+  local host="${PROSODY_DB_HOST}"
+  local port="${PROSODY_DB_PORT:-5432}"
+  local max_attempts=30
+  local attempt=1
+
+  log_info "Waiting for database connection to ${host}:${port}..."
+
+  while [[ $attempt -le $max_attempts ]]; do
+    if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
+      log_info "Database connection established"
+      return 0
+    fi
+
+    log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
+    sleep 2
+    ((attempt++))
+  done
+
+  log_error "Database connection timeout after ${max_attempts} attempts"
+  exit 1
 }
 
 validate_configuration() {
-    log_info "Validating Prosody configuration..."
-
-    # Check if config file exists
-    if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
-        log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
-        exit 1
-    fi
-
-    # Validate configuration using prosodyctl (allow warnings in development)
-    log_info "Validating Prosody configuration..."
-    if ! prosodyctl check config; then
-        log_error "Prosody configuration validation failed"
-        log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
-        if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
-            exit 1
-        else
-            log_warn "Development mode: continuing despite configuration warnings"
-        fi
-    fi
-
-    log_info "Configuration validation successful"
-}
-
-setup_community_modules() {
-    log_info "Setting up community modules..."
-
-    # Check if community modules source exists
-    local source_dir="/usr/local/lib/prosody/prosody-modules"
-    if [[ ! -d "$source_dir" ]]; then
-        log_warn "Community modules repository not found at $source_dir"
-        log_warn "Modules will need to be installed manually or via prosodyctl"
-        return 0
-    fi
-
-    # Check if modules are available from enabled directory
-    local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
-    if [[ ! -d "$enabled_dir" ]]; then
-        log_warn "Enabled community modules directory not found at $enabled_dir"
-        return 0
+  log_info "Validating Prosody configuration..."
+
+  # Check if config file exists
+  if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
+    log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
+    exit 1
+  fi
+
+  # Validate configuration using prosodyctl (allow warnings in development)
+  log_info "Validating Prosody configuration..."
+  if ! prosodyctl check config; then
+    log_error "Prosody configuration validation failed"
+    log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
+    if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
+      exit 1
     else
-        log_info "Community modules found in $enabled_dir"
-        local module_count
-        module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
-        log_info "Enabled modules: $module_count"
-    fi
-
-    # Ensure proper ownership (only if running as root)
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
-    fi
+      log_warn "Development mode: continuing despite configuration warnings"
+    fi
+  fi
+
+  log_info "Configuration validation successful"
+}
+
+setup_community_modules() {
+  log_info "Setting up community modules..."
+
+  # Check if community modules source exists
+  local source_dir="/usr/local/lib/prosody/prosody-modules"
+  if [[ ! -d "$source_dir" ]]; then
+    log_warn "Community modules repository not found at $source_dir"
+    log_warn "Modules will need to be installed manually or via prosodyctl"
+    return 0
+  fi
+
+  # Check if modules are available from enabled directory
+  local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
+  if [[ ! -d "$enabled_dir" ]]; then
+    log_warn "Enabled community modules directory not found at $enabled_dir"
+    return 0
+  else
+    log_info "Community modules found in $enabled_dir"
+    local module_count
+    module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
+    log_info "Enabled modules: $module_count"
+  fi
+
+  # Ensure proper ownership (only if running as root)
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
+  fi
 }
 
 # ============================================================================
@@ -285,28 +285,28 @@
 
 # shellcheck disable=SC2317,SC2329  # Function is called by signal handlers via trap
 cleanup() {
-    log_info "Received shutdown signal, stopping Prosody..."
-
-    if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
-        # Send SIGTERM for graceful shutdown
-        kill -TERM "$PROSODY_PID" 2> /dev/null || true
-
-        # Wait for graceful shutdown (max 30 seconds)
-        local timeout=30
-        while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
-            sleep 1
-            ((timeout--))
-        done
-
-        # Force kill if still running
-        if kill -0 "$PROSODY_PID" 2> /dev/null; then
-            log_warn "Prosody did not shut down gracefully, forcing termination"
-            kill -KILL "$PROSODY_PID" 2> /dev/null || true
-        fi
-    fi
-
-    log_info "Prosody shutdown complete"
-    exit 0
+  log_info "Received shutdown signal, stopping Prosody..."
+
+  if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
+    # Send SIGTERM for graceful shutdown
+    kill -TERM "$PROSODY_PID" 2> /dev/null || true
+
+    # Wait for graceful shutdown (max 30 seconds)
+    local timeout=30
+    while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
+      sleep 1
+      ((timeout--))
+    done
+
+    # Force kill if still running
+    if kill -0 "$PROSODY_PID" 2> /dev/null; then
+      log_warn "Prosody did not shut down gracefully, forcing termination"
+      kill -KILL "$PROSODY_PID" 2> /dev/null || true
+    fi
+  fi
+
+  log_info "Prosody shutdown complete"
+  exit 0
 }
 
 # Setup signal handlers
@@ -317,57 +317,57 @@
 # ============================================================================
 
 main() {
-    log_info "Starting Professional Prosody XMPP Server..."
-
-    # Display version information
-    local prosody_version
-    prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
-    log_info "Prosody version: $prosody_version"
-
-    # Prefer mounted config if present
-    if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
-        log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
-        cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
-        chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-        chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-    fi
-
-    # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
-    if [[ -d "/etc/prosody/config/conf.d" ]]; then
-        log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
-        mkdir -p "/etc/prosody/conf.d"
-        rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
-        chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
-        find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
-    fi
-
-    # Environment and setup
-    validate_environment
-    setup_directories
-    setup_certificates
-    wait_for_database
-    validate_configuration
-    setup_community_modules
-
-    # Display configuration summary
-    log_info "Configuration summary:"
-    log_info "  Domain: ${PROSODY_DOMAIN}"
-    log_info "  Admins: ${PROSODY_ADMIN_JID}"
-    log_info "  Storage: ${PROSODY_STORAGE:-sql}"
-    log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
-    log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
-
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
-    fi
-
-    # Start Prosody
-    log_info "Starting Prosody XMPP server..."
-
-    # Switch to prosody user and start in foreground
-    exec gosu "$PROSODY_USER" prosody \
-        --config="$PROSODY_CONFIG_FILE" \
-        --foreground
+  log_info "Starting Professional Prosody XMPP Server..."
+
+  # Display version information
+  local prosody_version
+  prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
+  log_info "Prosody version: $prosody_version"
+
+  # Prefer mounted config if present
+  if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
+    log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
+    cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
+    chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+    chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+  fi
+
+  # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
+  if [[ -d "/etc/prosody/config/conf.d" ]]; then
+    log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
+    mkdir -p "/etc/prosody/conf.d"
+    rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
+    chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
+    find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
+  fi
+
+  # Environment and setup
+  validate_environment
+  setup_directories
+  setup_certificates
+  wait_for_database
+  validate_configuration
+  setup_community_modules
+
+  # Display configuration summary
+  log_info "Configuration summary:"
+  log_info "  Domain: ${PROSODY_DOMAIN}"
+  log_info "  Admins: ${PROSODY_ADMIN_JID}"
+  log_info "  Storage: ${PROSODY_STORAGE:-sql}"
+  log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
+  log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
+
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
+  fi
+
+  # Start Prosody
+  log_info "Starting Prosody XMPP server..."
+
+  # Switch to prosody user and start in foreground
+  exec gosu "$PROSODY_USER" prosody \
+    --config="$PROSODY_CONFIG_FILE" \
+    --foreground
 }
 
 # ============================================================================
@@ -376,8 +376,8 @@
 
 # Ensure we're running as root initially (for setup)
 if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root for initial setup"
-    exit 1
+  log_error "This script must be run as root for initial setup"
+  exit 1
 fi
 
 # Execute main function
diff apps/unrealircd/docker-entrypoint.sh.orig apps/unrealircd/docker-entrypoint.sh
--- apps/unrealircd/docker-entrypoint.sh.orig
+++ apps/unrealircd/docker-entrypoint.sh
@@ -21,8 +21,8 @@
 
 # Validate config exists
 if [ ! -f "/home/unrealircd/unrealircd/config/unrealircd.conf" ]; then
-    echo "ERROR: Configuration file not found!"
-    exit 1
+  echo "ERROR: Configuration file not found!"
+  exit 1
 fi
 
 # Ownership and permissions are handled by Containerfile and user switching
@@ -29,21 +29,21 @@
 
 # Handle commands
 case "$1" in
-    start)
-        shift
-        echo "Starting UnrealIRCd in foreground..."
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        else
-            exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        fi
-        ;;
-    *)
-        echo "Running command: $1"
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
-        else
-            exec /home/unrealircd/unrealircd/unrealircd "$@"
-        fi
-        ;;
+  start)
+    shift
+    echo "Starting UnrealIRCd in foreground..."
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    else
+      exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    fi
+    ;;
+  *)
+    echo "Running command: $1"
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
+    else
+      exec /home/unrealircd/unrealircd/unrealircd "$@"
+    fi
+    ;;
 esac
diff apps/unrealircd/scripts/manage-modules.sh.orig apps/unrealircd/scripts/manage-modules.sh
--- apps/unrealircd/scripts/manage-modules.sh.orig
+++ apps/unrealircd/scripts/manage-modules.sh
@@ -29,280 +29,280 @@
 
 # Check if we're running as the correct user (unrealircd, typically UID from PUID)
 check_user() {
-    local expected_uid="${PUID:-1000}"
-    if [ "$(id -u)" != "$expected_uid" ]; then
-        print_error "This script must run as the unrealircd user (UID ${expected_uid})"
-        print_error "Current user: $(id -un) (UID $(id -u))"
-        exit 1
-    fi
+  local expected_uid="${PUID:-1000}"
+  if [ "$(id -u)" != "$expected_uid" ]; then
+    print_error "This script must run as the unrealircd user (UID ${expected_uid})"
+    print_error "Current user: $(id -un) (UID $(id -u))"
+    exit 1
+  fi
 }
 
 # Check if UnrealIRCd is running
 check_unrealircd_running() {
-    if pgrep -f unrealircd >/dev/null; then
-        print_warning "UnrealIRCd is currently running"
-        print_warning "Some operations may require a restart to take effect"
-        return 0
-    else
-        print_status "UnrealIRCd is not currently running"
-        return 1
-    fi
+  if pgrep -f unrealircd > /dev/null; then
+    print_warning "UnrealIRCd is currently running"
+    print_warning "Some operations may require a restart to take effect"
+    return 0
+  else
+    print_status "UnrealIRCd is not currently running"
+    return 1
+  fi
 }
 
 # List available modules
 list_modules() {
-    print_header "Available Contrib Modules"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Fetching latest module list..."
-    if [ -d "$CONTRIB_DIR" ]; then
-        cd "$CONTRIB_DIR" && git pull --quiet origin main 2>/dev/null || true
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Available modules:"
-    echo
-    "$UNREALIRCD_BIN" module list 2>/dev/null || {
-        print_warning "Module manager not available, showing contrib directory contents:"
-        for item in "$CONTRIB_DIR"/*; do
-            if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
-                basename "$item"
-            fi
-        done | sort
-    }
+  print_header "Available Contrib Modules"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Fetching latest module list..."
+  if [ -d "$CONTRIB_DIR" ]; then
+    cd "$CONTRIB_DIR" && git pull --quiet origin main 2> /dev/null || true
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Available modules:"
+  echo
+  "$UNREALIRCD_BIN" module list 2> /dev/null || {
+    print_warning "Module manager not available, showing contrib directory contents:"
+    for item in "$CONTRIB_DIR"/*; do
+      if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
+        basename "$item"
+      fi
+    done | sort
+  }
 }
 
 # Show module information
 show_module_info() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 info <module-name>"
-        return 1
-    fi
-
-    print_header "Module Information: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to get info via module manager first
-    if "$UNREALIRCD_BIN" module info "third/$module_name" 2>/dev/null; then
-        return 0
-    fi
-
-    # Fallback to showing contrib directory info
-    if [ -d "$CONTRIB_DIR/$module_name" ]; then
-        print_status "Module found in contrib directory:"
-        ls -la "$CONTRIB_DIR/$module_name"
-
-        if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README.md"
-        fi
-
-        if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README"
-        fi
-    else
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 info <module-name>"
+    return 1
+  fi
+
+  print_header "Module Information: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to get info via module manager first
+  if "$UNREALIRCD_BIN" module info "third/$module_name" 2> /dev/null; then
+    return 0
+  fi
+
+  # Fallback to showing contrib directory info
+  if [ -d "$CONTRIB_DIR/$module_name" ]; then
+    print_status "Module found in contrib directory:"
+    ls -la "$CONTRIB_DIR/$module_name"
+
+    if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README.md"
+    fi
+
+    if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README"
+    fi
+  else
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
 }
 
 # Install a module
 install_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 install <module-name>"
-        return 1
-    fi
-
-    print_header "Installing Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Check if module is already installed
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        print_warning "Module '$module_name' is already installed"
-        return 0
-    fi
-
-    # Try to install via module manager
-    print_status "Installing via module manager..."
-    if "$UNREALIRCD_BIN" module install "third/$module_name"; then
-        print_success "Module '$module_name' installed successfully"
-
-        # Check if we need to add loadmodule to config
-        if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
-            print_status "Checking if loadmodule line needs to be added..."
-            if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
-                print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-                print_warning "Then REHASH or restart UnrealIRCd"
-            fi
-        fi
-
-        return 0
-    fi
-
-    # Fallback to manual installation
-    print_warning "Module manager failed, attempting manual installation..."
-
-    if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR/$module_name" || exit 1
-
-    if [ -f "Makefile" ]; then
-        print_status "Compiling module..."
-        make clean 2>/dev/null || true
-        make
-
-        if [ -f "$module_name.so" ]; then
-            print_status "Installing module..."
-            cp "$module_name.so" "$MODULES_DIR/third/"
-            print_success "Module '$module_name' installed manually"
-
-            print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-            print_warning "Then REHASH or restart UnrealIRCd"
-        else
-            print_error "Module compilation failed"
-            return 1
-        fi
-    else
-        print_error "No Makefile found for module '$module_name'"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 install <module-name>"
+    return 1
+  fi
+
+  print_header "Installing Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Check if module is already installed
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    print_warning "Module '$module_name' is already installed"
+    return 0
+  fi
+
+  # Try to install via module manager
+  print_status "Installing via module manager..."
+  if "$UNREALIRCD_BIN" module install "third/$module_name"; then
+    print_success "Module '$module_name' installed successfully"
+
+    # Check if we need to add loadmodule to config
+    if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
+      print_status "Checking if loadmodule line needs to be added..."
+      if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
+        print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+        print_warning "Then REHASH or restart UnrealIRCd"
+      fi
+    fi
+
+    return 0
+  fi
+
+  # Fallback to manual installation
+  print_warning "Module manager failed, attempting manual installation..."
+
+  if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR/$module_name" || exit 1
+
+  if [ -f "Makefile" ]; then
+    print_status "Compiling module..."
+    make clean 2> /dev/null || true
+    make
+
+    if [ -f "$module_name.so" ]; then
+      print_status "Installing module..."
+      cp "$module_name.so" "$MODULES_DIR/third/"
+      print_success "Module '$module_name' installed manually"
+
+      print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+      print_warning "Then REHASH or restart UnrealIRCd"
+    else
+      print_error "Module compilation failed"
+      return 1
+    fi
+  else
+    print_error "No Makefile found for module '$module_name'"
+    return 1
+  fi
 }
 
 # Uninstall a module
 uninstall_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 uninstall <module-name>"
-        return 1
-    fi
-
-    print_header "Uninstalling Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to uninstall via module manager first
-    if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2>/dev/null; then
-        print_success "Module '$module_name' uninstalled successfully"
-        return 0
-    fi
-
-    # Fallback to manual removal
-    print_warning "Module manager failed, attempting manual removal..."
-
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        rm -f "$MODULES_DIR/third/$module_name.so"
-        print_success "Module '$module_name' removed manually"
-
-        print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
-        print_warning "Then REHASH or restart UnrealIRCd"
-    else
-        print_error "Module '$module_name' not found in modules directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 uninstall <module-name>"
+    return 1
+  fi
+
+  print_header "Uninstalling Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to uninstall via module manager first
+  if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2> /dev/null; then
+    print_success "Module '$module_name' uninstalled successfully"
+    return 0
+  fi
+
+  # Fallback to manual removal
+  print_warning "Module manager failed, attempting manual removal..."
+
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    rm -f "$MODULES_DIR/third/$module_name.so"
+    print_success "Module '$module_name' removed manually"
+
+    print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
+    print_warning "Then REHASH or restart UnrealIRCd"
+  else
+    print_error "Module '$module_name' not found in modules directory"
+    return 1
+  fi
 }
 
 # Upgrade modules
 upgrade_modules() {
-    local module_name="$1"
-
-    print_header "Upgrading Modules"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    if [ -n "$module_name" ]; then
-        print_status "Upgrading specific module: $module_name"
-        if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2>/dev/null; then
-            print_success "Module '$module_name' upgraded successfully"
-        else
-            print_error "Failed to upgrade module '$module_name'"
-            return 1
-        fi
-    else
-        print_status "Upgrading all modules..."
-        if "$UNREALIRCD_BIN" module upgrade 2>/dev/null; then
-            print_success "All modules upgraded successfully"
-        else
-            print_error "Failed to upgrade modules"
-            return 1
-        fi
-    fi
-
-    print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
+  local module_name="$1"
+
+  print_header "Upgrading Modules"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  if [ -n "$module_name" ]; then
+    print_status "Upgrading specific module: $module_name"
+    if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2> /dev/null; then
+      print_success "Module '$module_name' upgraded successfully"
+    else
+      print_error "Failed to upgrade module '$module_name'"
+      return 1
+    fi
+  else
+    print_status "Upgrading all modules..."
+    if "$UNREALIRCD_BIN" module upgrade 2> /dev/null; then
+      print_success "All modules upgraded successfully"
+    else
+      print_error "Failed to upgrade modules"
+      return 1
+    fi
+  fi
+
+  print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
 }
 
 # Update contrib repository
 update_contrib() {
-    print_header "Updating Contrib Repository"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR" || exit 1
-
-    print_status "Pulling latest changes from unrealircd-contrib..."
-    if git pull --quiet origin main; then
-        print_success "Contrib repository updated successfully"
-        print_status "New modules may now be available"
-    else
-        print_error "Failed to update contrib repository"
-        return 1
-    fi
+  print_header "Updating Contrib Repository"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR" || exit 1
+
+  print_status "Pulling latest changes from unrealircd-contrib..."
+  if git pull --quiet origin main; then
+    print_success "Contrib repository updated successfully"
+    print_status "New modules may now be available"
+  else
+    print_error "Failed to update contrib repository"
+    return 1
+  fi
 }
 
 # Show installed modules
 show_installed() {
-    print_header "Installed Modules"
-
-    if [ ! -d "$MODULES_DIR/third" ]; then
-        print_status "No third-party modules installed"
-        return 0
-    fi
-
-    cd "$MODULES_DIR/third" || exit 1
-
-    local count=0
-    for module in *.so; do
-        if [ -f "$module" ]; then
-            echo "  - third/${module%.so}"
-            ((count++))
-        fi
-    done
-
-    if [ $count -eq 0 ]; then
-        print_status "No third-party modules installed"
-    else
-        print_status "Total installed modules: $count"
-    fi
+  print_header "Installed Modules"
+
+  if [ ! -d "$MODULES_DIR/third" ]; then
+    print_status "No third-party modules installed"
+    return 0
+  fi
+
+  cd "$MODULES_DIR/third" || exit 1
+
+  local count=0
+  for module in *.so; do
+    if [ -f "$module" ]; then
+      echo "  - third/${module%.so}"
+      ((count++))
+    fi
+  done
+
+  if [ $count -eq 0 ]; then
+    print_status "No third-party modules installed"
+  else
+    print_status "Total installed modules: $count"
+  fi
 }
 
 # Show usage
 show_usage() {
-    cat <<EOF
+  cat << EOF
 UnrealIRCd Contrib Modules Management Script
 
 Usage: $0 <command> [options]
@@ -344,40 +344,40 @@
 
 # Main execution
 main() {
-    check_user
-
-    case "${1:-help}" in
+  check_user
+
+  case "${1:-help}" in
     list)
-        list_modules
-        ;;
+      list_modules
+      ;;
     info)
-        show_module_info "$2"
-        ;;
+      show_module_info "$2"
+      ;;
     install)
-        install_module "$2"
-        ;;
+      install_module "$2"
+      ;;
     uninstall)
-        uninstall_module "$2"
-        ;;
+      uninstall_module "$2"
+      ;;
     upgrade)
-        upgrade_modules "$2"
-        ;;
+      upgrade_modules "$2"
+      ;;
     update)
-        update_contrib
-        ;;
+      update_contrib
+      ;;
     installed)
-        show_installed
-        ;;
+      show_installed
+      ;;
     help | --help | -h)
-        show_usage
-        ;;
+      show_usage
+      ;;
     *)
-        print_error "Unknown command: $1"
-        echo
-        show_usage
-        exit 1
-        ;;
-    esac
+      print_error "Unknown command: $1"
+      echo
+      show_usage
+      exit 1
+      ;;
+  esac
 }
 
 # Run main function with all arguments
diff scripts/cert-manager/run.sh.orig scripts/cert-manager/run.sh
--- scripts/cert-manager/run.sh.orig
+++ scripts/cert-manager/run.sh
@@ -9,26 +9,26 @@
 
 # Validate domain format (alphanumeric, hyphens, dots only - prevent injection)
 case "$ROOT_DOMAIN" in
-    *[!a-zA-Z0-9.-]*)
-        echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Domain cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9.-]*)
+    echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Domain cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Validate email format (basic check - no spaces or shell metacharacters)
 case "$EMAIL" in
-    *[!a-zA-Z0-9@._+-]*)
-        echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Email cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9@._+-]*)
+    echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Email cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Lego outputs: certificates/_.<domain>.crt and _.<domain>.key for wildcards
@@ -38,9 +38,9 @@
 
 # Ensure we have credentials
 if [ -z "$CLOUDFLARE_DNS_API_TOKEN" ]; then
-    echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
-    echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
-    exec sleep infinity
+  echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
+  echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
+  exec sleep infinity
 fi
 
 # Initial issuance
@@ -49,8 +49,8 @@
 
 # Renewal loop
 while true; do
-    echo "Sleeping for 24 hours..."
-    sleep 86400
-    echo "Checking for renewal..."
-    lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
+  echo "Sleeping for 24 hours..."
+  sleep 86400
+  echo "Checking for renewal..."
+  lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
 done
diff scripts/gencloak-update-env.sh.orig scripts/gencloak-update-env.sh
--- scripts/gencloak-update-env.sh.orig
+++ scripts/gencloak-update-env.sh
@@ -12,23 +12,23 @@
 
 cd "$PROJECT_ROOT"
 
-output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2>/dev/null)
+output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2> /dev/null)
 echo "$output"
 
 mapfile -t keys < <(echo "$output" | grep -oE '"[^"]{50,}"' | tr -d '"')
 if [ ${#keys[@]} -ne 3 ]; then
-    echo "Failed to parse 3 cloak keys from gencloak output"
-    exit 1
+  echo "Failed to parse 3 cloak keys from gencloak output"
+  exit 1
 fi
 
 [ -f "$ENV_FILE" ] || cp .env.example "$ENV_FILE"
 
 if grep -q '^IRC_CLOAK_KEY_1=' "$ENV_FILE"; then
-    sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
 else
-    sed -i "/^IRC_CLOAK_PREFIX=/a\\
+  sed -i "/^IRC_CLOAK_PREFIX=/a\\
 # Cloak keys - keep secret; must be identical on all servers. Generate with: just irc gencloak\\
 IRC_CLOAK_KEY_1=${keys[0]}\\
 IRC_CLOAK_KEY_2=${keys[1]}\\
diff scripts/init.sh.orig scripts/init.sh
--- scripts/init.sh.orig
+++ scripts/init.sh
@@ -11,16 +11,16 @@
 
 # Load environment variables: .env (base) then .env.dev (overrides for just dev)
 if [ -f "$PROJECT_ROOT/.env" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env"
+  set +a
 fi
 if [ -f "$PROJECT_ROOT/.env.dev" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env.dev"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env.dev"
+  set +a
 fi
 
 # Ensure Atheme JSON-RPC port has a default (for existing .env without it)
@@ -35,386 +35,386 @@
 
 # Helper functions
 log_info() {
-    echo -e "${BLUE}[INFO]${NC} $1"
+  echo -e "${BLUE}[INFO]${NC} $1"
 }
 
 log_success() {
-    echo -e "${GREEN}[SUCCESS]${NC} $1"
+  echo -e "${GREEN}[SUCCESS]${NC} $1"
 }
 
 log_warning() {
-    echo -e "${YELLOW}[WARNING]${NC} $1"
+  echo -e "${YELLOW}[WARNING]${NC} $1"
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $1"
+  echo -e "${RED}[ERROR]${NC} $1"
 }
 
 # Function to create directory structure
 create_directories() {
-    log_info "Creating required directory structure..."
-
-    # Data directories (must match compose volume mounts)
-    local data_dirs=(
-        "$PROJECT_ROOT/data/irc/data"
-        "$PROJECT_ROOT/data/irc/logs"
-        "$PROJECT_ROOT/data/irc/webpanel-data"
-        "$PROJECT_ROOT/data/atheme/data"
-        "$PROJECT_ROOT/data/atheme/logs"
-        "$PROJECT_ROOT/data/xmpp/data"
-        "$PROJECT_ROOT/data/xmpp/logs"
-        "$PROJECT_ROOT/data/xmpp/uploads"
-        "$PROJECT_ROOT/data/thelounge"
-        "$PROJECT_ROOT/data/certs"
-    )
-
-    # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
-    local ssl_dirs=(
-        "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    )
-
-    # Create all directories
-    for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
-        if [ ! -d "$dir" ]; then
-            mkdir -p "$dir"
-            log_info "Created directory: $dir"
-    else
-            log_info "Directory already exists: $dir"
+  log_info "Creating required directory structure..."
+
+  # Data directories (must match compose volume mounts)
+  local data_dirs=(
+    "$PROJECT_ROOT/data/irc/data"
+    "$PROJECT_ROOT/data/irc/logs"
+    "$PROJECT_ROOT/data/irc/webpanel-data"
+    "$PROJECT_ROOT/data/atheme/data"
+    "$PROJECT_ROOT/data/atheme/logs"
+    "$PROJECT_ROOT/data/xmpp/data"
+    "$PROJECT_ROOT/data/xmpp/logs"
+    "$PROJECT_ROOT/data/xmpp/uploads"
+    "$PROJECT_ROOT/data/thelounge"
+    "$PROJECT_ROOT/data/certs"
+  )
+
+  # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
+  local ssl_dirs=(
+    "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  )
+
+  # Create all directories
+  for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
+    if [ ! -d "$dir" ]; then
+      mkdir -p "$dir"
+      log_info "Created directory: $dir"
+    else
+      log_info "Directory already exists: $dir"
     fi
   done
 
-    log_success "Directory structure created successfully"
+  log_success "Directory structure created successfully"
 }
 
 # Function to set proper permissions
 set_permissions() {
-    log_info "Setting proper permissions..."
-
-    # Get current user ID and group ID
-    local current_uid
-    local current_gid
-    # Use actual user ID instead of hardcoded values
-    current_uid=$(id -u)
-    current_gid=$(id -g)
-
-    # Use same UID for all services to avoid permission issues
-    local atheme_uid=$current_uid
-    local atheme_gid=$current_gid
-
-    log_info "Current user: $current_uid:$current_gid"
-
-    # Set ownership for data directories (if they exist)
-    if [ -d "$PROJECT_ROOT/data" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
-        # Ensure directories are writable by owner (critical for socket creation)
-        find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
-        log_info "Set ownership for data directory"
-  fi
-
-    # Set ownership for IRC data directory
-    if [ -d "$PROJECT_ROOT/data/irc" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
-        chmod -R 755 "$PROJECT_ROOT/data/irc"
-        log_info "Set permissions for IRC data directory"
-  fi
-
-    # Set ownership for Atheme data directory with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
-        chmod 755 "$PROJECT_ROOT/data/atheme"
-        log_info "Set permissions for Atheme data directory"
-  fi
-
-    # Set ownership for UnrealIRCd logs
-    if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
-        chmod 755 "$PROJECT_ROOT/data/irc/logs"
-        log_info "Set ownership for UnrealIRCd logs"
-    fi
-
-    # Set ownership for Atheme logs with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
-        chmod 755 "$PROJECT_ROOT/data/atheme/logs"
-        log_info "Set permissions for Atheme logs directory"
-    fi
-
-    # Set permissions for SSL certificates
-    if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
-        sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    fi
-    sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
-    chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
-    log_info "Set permissions for SSL directory"
-
-    # Make sure data directories are writable
-    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
-    find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
-
-    log_success "Permissions set successfully"
+  log_info "Setting proper permissions..."
+
+  # Get current user ID and group ID
+  local current_uid
+  local current_gid
+  # Use actual user ID instead of hardcoded values
+  current_uid=$(id -u)
+  current_gid=$(id -g)
+
+  # Use same UID for all services to avoid permission issues
+  local atheme_uid=$current_uid
+  local atheme_gid=$current_gid
+
+  log_info "Current user: $current_uid:$current_gid"
+
+  # Set ownership for data directories (if they exist)
+  if [ -d "$PROJECT_ROOT/data" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
+    # Ensure directories are writable by owner (critical for socket creation)
+    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
+    log_info "Set ownership for data directory"
+  fi
+
+  # Set ownership for IRC data directory
+  if [ -d "$PROJECT_ROOT/data/irc" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
+    chmod -R 755 "$PROJECT_ROOT/data/irc"
+    log_info "Set permissions for IRC data directory"
+  fi
+
+  # Set ownership for Atheme data directory with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
+    chmod 755 "$PROJECT_ROOT/data/atheme"
+    log_info "Set permissions for Atheme data directory"
+  fi
+
+  # Set ownership for UnrealIRCd logs
+  if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
+    chmod 755 "$PROJECT_ROOT/data/irc/logs"
+    log_info "Set ownership for UnrealIRCd logs"
+  fi
+
+  # Set ownership for Atheme logs with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
+    chmod 755 "$PROJECT_ROOT/data/atheme/logs"
+    log_info "Set permissions for Atheme logs directory"
+  fi
+
+  # Set permissions for SSL certificates
+  if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
+    sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  fi
+  sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
+  chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
+  log_info "Set permissions for SSL directory"
+
+  # Make sure data directories are writable
+  find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
+  find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
+
+  log_success "Permissions set successfully"
 }
 
 # Function to set up CA certificate bundle
 setup_ca_bundle() {
-    log_info "Setting up CA certificate bundle..."
-
-    local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
-    local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
-    local ca_bundle_file="curl-ca-bundle.crt"
-
-    # Ensure runtime directory exists
-    if [ ! -d "$ca_runtime_dir" ]; then
-        mkdir -p "$ca_runtime_dir"
-        log_info "Created TLS runtime directory: $ca_runtime_dir"
-  fi
-
-    # Ensure template directory exists
-    if [ ! -d "$ca_template_dir" ]; then
-        mkdir -p "$ca_template_dir"
-        log_info "Created TLS template directory: $ca_template_dir"
-  fi
-
-    # Check if system CA bundle exists
-    local system_ca_bundle=""
-    if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
-        system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
-  elif   [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
-        system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
-  fi
-
-    if [ -n "$system_ca_bundle" ]; then
-        # Create template if it doesn't exist
-        if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle template"
-      else
-                log_warning "Could not create CA certificate bundle template"
-                return 1
-      fi
-    fi
-
-        # Copy to runtime directory if it doesn't exist
-        if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle in runtime directory"
-      else
-                log_warning "Could not create CA certificate bundle in runtime directory"
-                return 1
-      fi
-    else
-            log_info "CA certificate bundle already exists in runtime directory"
-    fi
-  else
-        log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
-        return 1
-  fi
-
-    # Remove obsolete cert files from config/tls (server certs now live in data/certs)
-    rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2>/dev/null || true
-    rm -rf "$ca_runtime_dir/live" 2>/dev/null || true
-
-    log_success "CA certificate bundle setup completed"
+  log_info "Setting up CA certificate bundle..."
+
+  local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
+  local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
+  local ca_bundle_file="curl-ca-bundle.crt"
+
+  # Ensure runtime directory exists
+  if [ ! -d "$ca_runtime_dir" ]; then
+    mkdir -p "$ca_runtime_dir"
+    log_info "Created TLS runtime directory: $ca_runtime_dir"
+  fi
+
+  # Ensure template directory exists
+  if [ ! -d "$ca_template_dir" ]; then
+    mkdir -p "$ca_template_dir"
+    log_info "Created TLS template directory: $ca_template_dir"
+  fi
+
+  # Check if system CA bundle exists
+  local system_ca_bundle=""
+  if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
+    system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
+  elif [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+    system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
+  fi
+
+  if [ -n "$system_ca_bundle" ]; then
+    # Create template if it doesn't exist
+    if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle template"
+      else
+        log_warning "Could not create CA certificate bundle template"
+        return 1
+      fi
+    fi
+
+    # Copy to runtime directory if it doesn't exist
+    if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle in runtime directory"
+      else
+        log_warning "Could not create CA certificate bundle in runtime directory"
+        return 1
+      fi
+    else
+      log_info "CA certificate bundle already exists in runtime directory"
+    fi
+  else
+    log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
+    return 1
+  fi
+
+  # Remove obsolete cert files from config/tls (server certs now live in data/certs)
+  rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2> /dev/null || true
+  rm -rf "$ca_runtime_dir/live" 2> /dev/null || true
+
+  log_success "CA certificate bundle setup completed"
 }
 
 # Function to generate self-signed certificates for dev mode
 generate_cert() {
-    local domain="$1"
-    local base_dir="$2"
-    local live_dir="$base_dir/live/$domain"
-
-    # Ensure directory exists
-    mkdir -p "$live_dir"
-
-    # Generate self-signed cert if it doesn't exist
-    if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
-        log_info "Generating self-signed certificate for $domain..."
-        # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
-        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-            -keyout "$live_dir/privkey.pem" \
-            -out "$live_dir/fullchain.pem" \
-            -subj "/CN=$domain" \
-            -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-        log_success "Generated self-signed certificate for $domain"
-    else
-        log_info "Self-signed certificate already exists for $domain"
-    fi
-
-    # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
-    chmod 644 "$live_dir/privkey.pem" 2>/dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2>/dev/null || true
+  local domain="$1"
+  local base_dir="$2"
+  local live_dir="$base_dir/live/$domain"
+
+  # Ensure directory exists
+  mkdir -p "$live_dir"
+
+  # Generate self-signed cert if it doesn't exist
+  if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
+    log_info "Generating self-signed certificate for $domain..."
+    # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
+    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+      -keyout "$live_dir/privkey.pem" \
+      -out "$live_dir/fullchain.pem" \
+      -subj "/CN=$domain" \
+      -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+    log_success "Generated self-signed certificate for $domain"
+  else
+    log_info "Self-signed certificate already exists for $domain"
+  fi
+
+  # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
+  chmod 644 "$live_dir/privkey.pem" 2> /dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2> /dev/null || true
 }
 
 generate_dev_certs() {
-    log_info "Setting up self-signed certificates for dev mode..."
-
-    # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
-    local shared_cert_dir="$PROJECT_ROOT/data/certs"
-    generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
-    generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
-
-    log_success "Dev certificate setup completed"
+  log_info "Setting up self-signed certificates for dev mode..."
+
+  # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
+  local shared_cert_dir="$PROJECT_ROOT/data/certs"
+  generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
+  generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
+
+  log_success "Dev certificate setup completed"
 }
 
 # Function to prepare configuration files from templates
 prepare_config_files() {
-    log_info "Preparing configuration files from templates..."
-
-    # Load environment variables from .env if it exists
-    if [ -f "$PROJECT_ROOT/.env" ]; then
-        log_info "Loading environment variables from .env"
-        set -a
-        # shellcheck disable=SC1091
-        source "$PROJECT_ROOT/.env"
-        set +a
-        log_info "Environment variables loaded"
-    fi
-
-    # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
-    export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
-    export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
-
-    if [ ! -f "$PROJECT_ROOT/.env" ]; then
-        log_warning ".env file not found. Configuration will use defaults."
-        return 1
-  fi
-
-    # Check if envsubst is available
-    if ! command -v envsubst > /dev/null 2>&1; then
-        log_error "envsubst command not found. Please install gettext package."
-        return 1
-  fi
-
-    # Prepare UnrealIRCd configuration
-    local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
-    local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
-
-    if [ -f "$unreal_template" ]; then
-        log_info "Creating UnrealIRCd configuration from template..."
-        if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
-            log_success "UnrealIRCd configuration created"
-    else
-            log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
-    fi
-  elif   [ -f "$unreal_config" ]; then
-        log_info "UnrealIRCd configuration already exists"
-  else
-        log_warning "No UnrealIRCd configuration template found"
-  fi
-
-    # Prepare Atheme configuration
-    local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
-    local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
-
-    if [ -f "$atheme_template" ]; then
-        log_info "Creating Atheme configuration from template..."
-        envsubst < "$atheme_template" > "$atheme_config"
-        log_success "Atheme configuration created"
-  elif   [ -f "$atheme_config" ]; then
-        log_info "Atheme configuration already exists"
-  else
-        log_warning "No Atheme configuration template found"
-  fi
-
-    # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
-    if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
-        log_info "Running prepare-config.sh (bridge config, etc.)..."
-        # shellcheck source=prepare-config.sh
-        "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
-    fi
-
-    # Show substituted values for verification
-    log_info "Configuration values:"
-    echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
-    echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
-    echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
-    echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
-    echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
+  log_info "Preparing configuration files from templates..."
+
+  # Load environment variables from .env if it exists
+  if [ -f "$PROJECT_ROOT/.env" ]; then
+    log_info "Loading environment variables from .env"
+    set -a
+    # shellcheck disable=SC1091
+    source "$PROJECT_ROOT/.env"
+    set +a
+    log_info "Environment variables loaded"
+  fi
+
+  # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
+  export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
+  export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
+
+  if [ ! -f "$PROJECT_ROOT/.env" ]; then
+    log_warning ".env file not found. Configuration will use defaults."
+    return 1
+  fi
+
+  # Check if envsubst is available
+  if ! command -v envsubst > /dev/null 2>&1; then
+    log_error "envsubst command not found. Please install gettext package."
+    return 1
+  fi
+
+  # Prepare UnrealIRCd configuration
+  local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
+  local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
+
+  if [ -f "$unreal_template" ]; then
+    log_info "Creating UnrealIRCd configuration from template..."
+    if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
+      log_success "UnrealIRCd configuration created"
+    else
+      log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
+    fi
+  elif [ -f "$unreal_config" ]; then
+    log_info "UnrealIRCd configuration already exists"
+  else
+    log_warning "No UnrealIRCd configuration template found"
+  fi
+
+  # Prepare Atheme configuration
+  local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
+  local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
+
+  if [ -f "$atheme_template" ]; then
+    log_info "Creating Atheme configuration from template..."
+    envsubst < "$atheme_template" > "$atheme_config"
+    log_success "Atheme configuration created"
+  elif [ -f "$atheme_config" ]; then
+    log_info "Atheme configuration already exists"
+  else
+    log_warning "No Atheme configuration template found"
+  fi
+
+  # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
+  if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
+    log_info "Running prepare-config.sh (bridge config, etc.)..."
+    # shellcheck source=prepare-config.sh
+    "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
+  fi
+
+  # Show substituted values for verification
+  log_info "Configuration values:"
+  echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
+  echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
+  echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
+  echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
+  echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
 }
 
 # Function to create .env template if it doesn't exist
 create_env_template() {
-    local env_file="$PROJECT_ROOT/.env"
-    local env_example="$PROJECT_ROOT/.env.example"
-
-    if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
-        cp "$env_example" "$env_file"
-        log_info "Created .env file from template"
-        log_warning "Please edit .env file with your configuration before starting services"
-  elif   [ -f "$env_file" ]; then
-        log_info ".env file already exists"
-  else
-        log_warning "No .env template found. You may need to create environment variables manually"
+  local env_file="$PROJECT_ROOT/.env"
+  local env_example="$PROJECT_ROOT/.env.example"
+
+  if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
+    cp "$env_example" "$env_file"
+    log_info "Created .env file from template"
+    log_warning "Please edit .env file with your configuration before starting services"
+  elif [ -f "$env_file" ]; then
+    log_info ".env file already exists"
+  else
+    log_warning "No .env template found. You may need to create environment variables manually"
   fi
 }
 
 # Function to check Docker availability
 check_docker() {
-    log_info "Checking Docker availability..."
-
-    if ! command -v docker > /dev/null 2>&1; then
-        log_error "Docker is not installed or not in PATH"
-        exit 1
-  fi
-
-    if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
-        log_error "Docker Compose is not available"
-        exit 1
-  fi
-
-    log_success "Docker is available"
+  log_info "Checking Docker availability..."
+
+  if ! command -v docker > /dev/null 2>&1; then
+    log_error "Docker is not installed or not in PATH"
+    exit 1
+  fi
+
+  if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
+    log_error "Docker Compose is not available"
+    exit 1
+  fi
+
+  log_success "Docker is available"
 }
 
 # Function to show next steps
 show_next_steps() {
-    echo ""
-    log_info "Next steps:"
-    echo "  1. Edit .env file with your configuration (optional)"
-    echo "  3. Or run: docker compose up -d"
-    echo ""
-    log_info "Data will be stored in:"
-    echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
+  echo ""
+  log_info "Next steps:"
+  echo "  1. Edit .env file with your configuration (optional)"
+  echo "  3. Or run: docker compose up -d"
+  echo ""
+  log_info "Data will be stored in:"
+  echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
 }
 
 # Main function
 main() {
-    log_info "ATL Chat Infrastructure Initialization"
-    log_info "======================================"
-
-    # Check if we're running as root (for permission info)
-    if [ "$(id -u)" = "0" ]; then
-        log_warning "Running as root - this is fine for initial setup"
-  fi
-
-    # Check Docker availability
-    check_docker
-
-    # Create directory structure
-    create_directories
-
-    # Set permissions
-    set_permissions
-
-    # Set up CA certificate bundle
-    setup_ca_bundle
-
-    # Generate dev certs
-    generate_dev_certs
-
-    # Create .env if needed
-    create_env_template
-
-    # Prepare configuration files from templates
-    prepare_config_files
-
-    # Show next steps
-    show_next_steps
-
-    log_success "Initialization completed successfully!"
+  log_info "ATL Chat Infrastructure Initialization"
+  log_info "======================================"
+
+  # Check if we're running as root (for permission info)
+  if [ "$(id -u)" = "0" ]; then
+    log_warning "Running as root - this is fine for initial setup"
+  fi
+
+  # Check Docker availability
+  check_docker
+
+  # Create directory structure
+  create_directories
+
+  # Set permissions
+  set_permissions
+
+  # Set up CA certificate bundle
+  setup_ca_bundle
+
+  # Generate dev certs
+  generate_dev_certs
+
+  # Create .env if needed
+  create_env_template
+
+  # Prepare configuration files from templates
+  prepare_config_files
+
+  # Show next steps
+  show_next_steps
+
+  log_success "Initialization completed successfully!"
 }
 
 # Run main function
 if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
-    main "$@"
+  main "$@"
 fi
diff scripts/prepare-config.sh.orig scripts/prepare-config.sh
--- scripts/prepare-config.sh.orig
+++ scripts/prepare-config.sh
@@ -180,7 +180,7 @@
     log_info "Preparing bridge configuration from template..."
     local temp_file="/tmp/bridge-config.yaml.tmp"
     envsubst < "$bridge_template" > "$temp_file"
-    if cp "$temp_file" "$bridge_config" 2>/dev/null || sudo cp "$temp_file" "$bridge_config" 2>/dev/null; then
+    if cp "$temp_file" "$bridge_config" 2> /dev/null || sudo cp "$temp_file" "$bridge_config" 2> /dev/null; then
       log_success "Bridge configuration prepared"
     else
       log_warning "Could not write bridge config to $bridge_config"
@@ -189,7 +189,7 @@
   elif [ ! -f "$bridge_config" ]; then
     log_warning "Bridge config not found. Copy apps/bridge/config.example.yaml to apps/bridge/config.yaml and customize."
     if [ -f "$PROJECT_ROOT/apps/bridge/config.example.yaml" ]; then
-      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2>/dev/null || true
+      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2> /dev/null || true
       log_info "Copied config.example.yaml to apps/bridge/config.yaml - edit with your Discord channel ID"
     fi
   fi
@@ -202,7 +202,7 @@
     mkdir -p "$(dirname "$lounge_config")"
     local temp_file="/tmp/thelounge-config.js.tmp"
     envsubst < "$lounge_template" > "$temp_file"
-    if cp "$temp_file" "$lounge_config" 2>/dev/null || sudo cp "$temp_file" "$lounge_config" 2>/dev/null; then
+    if cp "$temp_file" "$lounge_config" 2> /dev/null || sudo cp "$temp_file" "$lounge_config" 2> /dev/null; then
       log_success "The Lounge configuration prepared"
     else
       log_warning "Could not write The Lounge config to $lounge_config"
----------

You can reformat the above files to meet shfmt's requirements by typing:

  shfmt  -w filename


kzndotsh and others added 6 commits February 27, 2026 23:11
Introduces the new apps/docs documentation application built on
Fumadocs + Next.js, replacing the flat docs/ directory. Includes
full content coverage for all services (IRC, XMPP, Bridge, Atheme,
The Lounge, WebPanel, Web), architecture, operations, and reference
pages, audited and corrected against official Prosody, Atheme, and
UnrealIRCd reference documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce a new script `download_references.py` to automate the
downloading of documentation for various IRC/XMPP services. This
script fetches documentation from multiple sources, including
Prosody, UnrealIRCd, Atheme, Slixmpp, and Pydle, and saves them
as markdown files. It supports retry mechanisms for network
requests and checks for content updates to avoid unnecessary
downloads. This addition aims to streamline the process of
keeping local documentation up-to-date, enhancing productivity
and ensuring access to the latest information.
Add a new "Docs" section to the .env.example file to support
Cloudflare Workers via Alchemy. This includes a placeholder for
ALCHEMY_PASSWORD, which is necessary for encrypting secrets in
deployment state. Additionally, update the IRC_OPER_PASSWORD with
a placeholder and instructions for generating a secure password.
These changes ensure that the example environment file is up-to-date
and provides clear guidance for setting up necessary environment
variables for new features and security practices.
Add new npm scripts to facilitate the development, login, deployment,
and destruction of documentation. These scripts streamline the process
of managing documentation environments, both for development and
production.

build(package.json): configure pnpm for specific dependencies

Add pnpm configuration to ensure only specific dependencies are built,
and override the version of esbuild to ensure compatibility. This
enhancement optimizes the build process and maintains consistency
across environments.
…documentation files

Additions to the .gitignore file include `wrangler.jsonc`,
`references`, and several directories under `apps/docs/`. These
changes prevent configuration files and old documentation from being
tracked, ensuring that only relevant files are included in the
repository. This helps maintain a clean working directory and avoids
unnecessary clutter in version control.
Update the Docker access instructions to recommend adding the user
to the `docker` group for non-root access instead of using `chmod 666`
on the Docker socket, which poses a security risk by granting
root-equivalent access to all local users.

docs: add initial changelog and contributing guidelines

Introduce a `CHANGELOG.md` file to document the release process
and history, leveraging semantic-release for automated versioning
and changelog generation. Add `CONTRIBUTING.md` to guide new
contributors on setting up the development environment, commit
conventions, and code style. These documents aim to streamline
the contribution process and maintain consistency across the project.
@github-actions
Copy link

sh-checker report

To get the full details, please check in the job output.

shellcheck errors
'shellcheck ' found no issues.

shfmt errors

'shfmt ' returned error 1 finding the following formatting issues:

----------
diff .github/scripts/docker.sh.orig .github/scripts/docker.sh
--- .github/scripts/docker.sh.orig
+++ .github/scripts/docker.sh
@@ -57,11 +57,11 @@
 shift || true
 
 case "$COMMAND" in
-  generate-pr-version)        generate_pr_version "$@" ;;
-  generate-release-version)   generate_release_version "$@" ;;
-  validate-build-config)      validate_build_config "$@" ;;
+  generate-pr-version) generate_pr_version "$@" ;;
+  generate-release-version) generate_release_version "$@" ;;
+  validate-build-config) validate_build_config "$@" ;;
   calculate-source-date-epoch) calculate_source_date_epoch "$@" ;;
-  generate-build-date)        generate_build_date "$@" ;;
+  generate-build-date) generate_build_date "$@" ;;
   *)
     echo "Usage: docker.sh {generate-pr-version|generate-release-version|validate-build-config|calculate-source-date-epoch|generate-build-date} [args...]"
     exit 1
diff apps/atheme/docker-entrypoint.sh.orig apps/atheme/docker-entrypoint.sh
--- apps/atheme/docker-entrypoint.sh.orig
+++ apps/atheme/docker-entrypoint.sh
@@ -7,9 +7,9 @@
 
 # Ensure we have proper permissions
 if [ "$(id -u)" = "0" ]; then
-    echo "ERROR: Atheme should not run as root for security reasons"
-    echo "Please run with a non-root user (UID 1000 recommended)"
-    exit 1
+  echo "ERROR: Atheme should not run as root for security reasons"
+  echo "Please run with a non-root user (UID 1000 recommended)"
+  exit 1
 fi
 
 # Create directories with proper ownership
@@ -17,9 +17,9 @@
 
 # Validate configuration exists
 if [ ! -f "/usr/local/atheme/etc/atheme.conf" ]; then
-    echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
-    echo "Please ensure the configuration is properly mounted"
-    exit 1
+  echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
+  echo "Please ensure the configuration is properly mounted"
+  exit 1
 fi
 
 # Clean up stale PID file
@@ -27,25 +27,25 @@
 
 # Validate database directory is writable
 if [ ! -w "/usr/local/atheme/data" ]; then
-    echo "ERROR: Data directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Data directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Validate logs directory is writable
 if [ ! -w "/usr/local/atheme/logs" ]; then
-    echo "ERROR: Logs directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Logs directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Check if this is first run (no database exists)
 if [ ! -f "/usr/local/atheme/data/services.db" ]; then
-    echo "First run detected - creating initial database..."
-    /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
-    echo "Database created successfully"
+  echo "First run detected - creating initial database..."
+  /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
+  echo "Database created successfully"
 else
-    echo "Existing database found - starting with existing data"
+  echo "Existing database found - starting with existing data"
 fi
 
 # Start Atheme services
diff apps/prosody/docker-entrypoint.sh.orig apps/prosody/docker-entrypoint.sh
--- apps/prosody/docker-entrypoint.sh.orig
+++ apps/prosody/docker-entrypoint.sh
@@ -29,21 +29,21 @@
 # ============================================================================
 
 log_info() {
-    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_warn() {
-    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_debug() {
-    if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
-        echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
-    fi
+  if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
+    echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  fi
 }
 
 # ============================================================================
@@ -51,45 +51,45 @@
 # ============================================================================
 
 validate_environment() {
-    log_info "Validating environment configuration..."
-
-    # Validate required domain
-    if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
-        log_error "PROSODY_DOMAIN is required but not set"
-        exit 1
-    fi
-
-    # Validate domain format
-    if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
-        log_error "Invalid domain format: ${PROSODY_DOMAIN}"
-        exit 1
-    fi
-
-    # Set default admin if not provided
-    if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
-        export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
-        log_info "Using default admin: ${PROSODY_ADMIN_JID}"
-    fi
-
-    # Validate database configuration for SQL storage
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        local required_vars=(
-            "PROSODY_DB_DRIVER"
-            "PROSODY_DB_NAME"
-            "PROSODY_DB_USER"
-            "PROSODY_DB_PASSWORD"
-            "PROSODY_DB_HOST"
-        )
-
-        for var in "${required_vars[@]}"; do
-            if [[ -z "${!var:-}" ]]; then
-                log_error "Database variable ${var} is required for SQL storage"
-                exit 1
-            fi
-        done
-    fi
-
-    log_info "Environment validation complete"
+  log_info "Validating environment configuration..."
+
+  # Validate required domain
+  if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
+    log_error "PROSODY_DOMAIN is required but not set"
+    exit 1
+  fi
+
+  # Validate domain format
+  if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
+    log_error "Invalid domain format: ${PROSODY_DOMAIN}"
+    exit 1
+  fi
+
+  # Set default admin if not provided
+  if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
+    export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
+    log_info "Using default admin: ${PROSODY_ADMIN_JID}"
+  fi
+
+  # Validate database configuration for SQL storage
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    local required_vars=(
+      "PROSODY_DB_DRIVER"
+      "PROSODY_DB_NAME"
+      "PROSODY_DB_USER"
+      "PROSODY_DB_PASSWORD"
+      "PROSODY_DB_HOST"
+    )
+
+    for var in "${required_vars[@]}"; do
+      if [[ -z "${!var:-}" ]]; then
+        log_error "Database variable ${var} is required for SQL storage"
+        exit 1
+      fi
+    done
+  fi
+
+  log_info "Environment validation complete"
 }
 
 # ============================================================================
@@ -97,186 +97,186 @@
 # ============================================================================
 
 setup_directories() {
-    log_info "Setting up directories..."
-
-    local dirs=(
-        "$PROSODY_DATA_DIR"
-        "$PROSODY_LOG_DIR"
-        "$PROSODY_CERT_DIR"
-        "$PROSODY_UPLOAD_DIR"
-        "$(dirname "$PROSODY_PID_FILE")"
-    )
-
-    for dir in "${dirs[@]}"; do
-        if [[ ! -d "$dir" ]]; then
-            log_debug "Creating directory: $dir"
-            mkdir -p "$dir"
-        fi
-
-        # Ensure proper ownership (only if running as root)
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
-        fi
-    done
-
-    # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
-    # Prosody needs write access for SQLite (MAM, PEP, etc.)
-    if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
-        if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
-            log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
-        fi
-    fi
-
-    log_info "Directory setup complete"
+  log_info "Setting up directories..."
+
+  local dirs=(
+    "$PROSODY_DATA_DIR"
+    "$PROSODY_LOG_DIR"
+    "$PROSODY_CERT_DIR"
+    "$PROSODY_UPLOAD_DIR"
+    "$(dirname "$PROSODY_PID_FILE")"
+  )
+
+  for dir in "${dirs[@]}"; do
+    if [[ ! -d "$dir" ]]; then
+      log_debug "Creating directory: $dir"
+      mkdir -p "$dir"
+    fi
+
+    # Ensure proper ownership (only if running as root)
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
+    fi
+  done
+
+  # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
+  # Prosody needs write access for SQLite (MAM, PEP, etc.)
+  if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
+    if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
+      log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
+    fi
+  fi
+
+  log_info "Directory setup complete"
 }
 
 setup_certificates() {
-    log_info "Setting up SSL certificates..."
-
-    # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
-    local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
-    local cert_file="${live_dir}/fullchain.pem"
-    local key_file="${live_dir}/privkey.pem"
-
-    if [[ -f "$cert_file" && -f "$key_file" ]]; then
-        log_info "Certificates found for ${PROSODY_DOMAIN}"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file" 2> /dev/null || true
-        chmod 600 "$key_file" 2> /dev/null || true
-        # HTTPS service automatic discovery (Prosody Certificates doc):
-        # - https/fullchain.pem + https/privkey.pem (directory symlink)
-        # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
-    local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
-    local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
-    if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
-        log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
-        mkdir -p "$live_dir"
-        cp "$legacy_cert" "$cert_file"
-        cp "$legacy_key" "$key_file"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file"
-        chmod 600 "$key_file"
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Generate self-signed certificate for development/testing
-    log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
-    log_warn "This is suitable for development only - use proper certificates in production"
-
+  log_info "Setting up SSL certificates..."
+
+  # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
+  local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
+  local cert_file="${live_dir}/fullchain.pem"
+  local key_file="${live_dir}/privkey.pem"
+
+  if [[ -f "$cert_file" && -f "$key_file" ]]; then
+    log_info "Certificates found for ${PROSODY_DOMAIN}"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    fi
+    chmod 644 "$cert_file" 2> /dev/null || true
+    chmod 600 "$key_file" 2> /dev/null || true
+    # HTTPS service automatic discovery (Prosody Certificates doc):
+    # - https/fullchain.pem + https/privkey.pem (directory symlink)
+    # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
+  local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
+  local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
+  if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
+    log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
     mkdir -p "$live_dir"
-    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-        -keyout "$key_file" \
-        -out "$cert_file" \
-        -subj "/CN=${PROSODY_DOMAIN}" \
-        -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    cp "$legacy_cert" "$cert_file"
+    cp "$legacy_key" "$key_file"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
     fi
     chmod 644 "$cert_file"
     chmod 600 "$key_file"
-
-    # HTTPS service automatic discovery
-    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-
-    log_info "Self-signed certificate generated successfully"
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Generate self-signed certificate for development/testing
+  log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
+  log_warn "This is suitable for development only - use proper certificates in production"
+
+  mkdir -p "$live_dir"
+  openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+    -keyout "$key_file" \
+    -out "$cert_file" \
+    -subj "/CN=${PROSODY_DOMAIN}" \
+    -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+  fi
+  chmod 644 "$cert_file"
+  chmod 600 "$key_file"
+
+  # HTTPS service automatic discovery
+  ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+
+  log_info "Self-signed certificate generated successfully"
 }
 
 wait_for_database() {
-    if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
-        log_debug "Not using SQL storage, skipping database wait"
-        return 0
-    fi
-
-    local host="${PROSODY_DB_HOST}"
-    local port="${PROSODY_DB_PORT:-5432}"
-    local max_attempts=30
-    local attempt=1
-
-    log_info "Waiting for database connection to ${host}:${port}..."
-
-    while [[ $attempt -le $max_attempts ]]; do
-        if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
-            log_info "Database connection established"
-            return 0
-        fi
-
-        log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
-        sleep 2
-        ((attempt++))
-    done
-
-    log_error "Database connection timeout after ${max_attempts} attempts"
-    exit 1
+  if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
+    log_debug "Not using SQL storage, skipping database wait"
+    return 0
+  fi
+
+  local host="${PROSODY_DB_HOST}"
+  local port="${PROSODY_DB_PORT:-5432}"
+  local max_attempts=30
+  local attempt=1
+
+  log_info "Waiting for database connection to ${host}:${port}..."
+
+  while [[ $attempt -le $max_attempts ]]; do
+    if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
+      log_info "Database connection established"
+      return 0
+    fi
+
+    log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
+    sleep 2
+    ((attempt++))
+  done
+
+  log_error "Database connection timeout after ${max_attempts} attempts"
+  exit 1
 }
 
 validate_configuration() {
-    log_info "Validating Prosody configuration..."
-
-    # Check if config file exists
-    if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
-        log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
-        exit 1
-    fi
-
-    # Validate configuration using prosodyctl (allow warnings in development)
-    log_info "Validating Prosody configuration..."
-    if ! prosodyctl check config; then
-        log_error "Prosody configuration validation failed"
-        log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
-        if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
-            exit 1
-        else
-            log_warn "Development mode: continuing despite configuration warnings"
-        fi
-    fi
-
-    log_info "Configuration validation successful"
-}
-
-setup_community_modules() {
-    log_info "Setting up community modules..."
-
-    # Check if community modules source exists
-    local source_dir="/usr/local/lib/prosody/prosody-modules"
-    if [[ ! -d "$source_dir" ]]; then
-        log_warn "Community modules repository not found at $source_dir"
-        log_warn "Modules will need to be installed manually or via prosodyctl"
-        return 0
-    fi
-
-    # Check if modules are available from enabled directory
-    local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
-    if [[ ! -d "$enabled_dir" ]]; then
-        log_warn "Enabled community modules directory not found at $enabled_dir"
-        return 0
+  log_info "Validating Prosody configuration..."
+
+  # Check if config file exists
+  if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
+    log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
+    exit 1
+  fi
+
+  # Validate configuration using prosodyctl (allow warnings in development)
+  log_info "Validating Prosody configuration..."
+  if ! prosodyctl check config; then
+    log_error "Prosody configuration validation failed"
+    log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
+    if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
+      exit 1
     else
-        log_info "Community modules found in $enabled_dir"
-        local module_count
-        module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
-        log_info "Enabled modules: $module_count"
-    fi
-
-    # Ensure proper ownership (only if running as root)
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
-    fi
+      log_warn "Development mode: continuing despite configuration warnings"
+    fi
+  fi
+
+  log_info "Configuration validation successful"
+}
+
+setup_community_modules() {
+  log_info "Setting up community modules..."
+
+  # Check if community modules source exists
+  local source_dir="/usr/local/lib/prosody/prosody-modules"
+  if [[ ! -d "$source_dir" ]]; then
+    log_warn "Community modules repository not found at $source_dir"
+    log_warn "Modules will need to be installed manually or via prosodyctl"
+    return 0
+  fi
+
+  # Check if modules are available from enabled directory
+  local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
+  if [[ ! -d "$enabled_dir" ]]; then
+    log_warn "Enabled community modules directory not found at $enabled_dir"
+    return 0
+  else
+    log_info "Community modules found in $enabled_dir"
+    local module_count
+    module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
+    log_info "Enabled modules: $module_count"
+  fi
+
+  # Ensure proper ownership (only if running as root)
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
+  fi
 }
 
 # ============================================================================
@@ -285,28 +285,28 @@
 
 # shellcheck disable=SC2317,SC2329  # Function is called by signal handlers via trap
 cleanup() {
-    log_info "Received shutdown signal, stopping Prosody..."
-
-    if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
-        # Send SIGTERM for graceful shutdown
-        kill -TERM "$PROSODY_PID" 2> /dev/null || true
-
-        # Wait for graceful shutdown (max 30 seconds)
-        local timeout=30
-        while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
-            sleep 1
-            ((timeout--))
-        done
-
-        # Force kill if still running
-        if kill -0 "$PROSODY_PID" 2> /dev/null; then
-            log_warn "Prosody did not shut down gracefully, forcing termination"
-            kill -KILL "$PROSODY_PID" 2> /dev/null || true
-        fi
-    fi
-
-    log_info "Prosody shutdown complete"
-    exit 0
+  log_info "Received shutdown signal, stopping Prosody..."
+
+  if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
+    # Send SIGTERM for graceful shutdown
+    kill -TERM "$PROSODY_PID" 2> /dev/null || true
+
+    # Wait for graceful shutdown (max 30 seconds)
+    local timeout=30
+    while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
+      sleep 1
+      ((timeout--))
+    done
+
+    # Force kill if still running
+    if kill -0 "$PROSODY_PID" 2> /dev/null; then
+      log_warn "Prosody did not shut down gracefully, forcing termination"
+      kill -KILL "$PROSODY_PID" 2> /dev/null || true
+    fi
+  fi
+
+  log_info "Prosody shutdown complete"
+  exit 0
 }
 
 # Setup signal handlers
@@ -317,57 +317,57 @@
 # ============================================================================
 
 main() {
-    log_info "Starting Professional Prosody XMPP Server..."
-
-    # Display version information
-    local prosody_version
-    prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
-    log_info "Prosody version: $prosody_version"
-
-    # Prefer mounted config if present
-    if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
-        log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
-        cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
-        chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-        chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-    fi
-
-    # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
-    if [[ -d "/etc/prosody/config/conf.d" ]]; then
-        log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
-        mkdir -p "/etc/prosody/conf.d"
-        rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
-        chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
-        find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
-    fi
-
-    # Environment and setup
-    validate_environment
-    setup_directories
-    setup_certificates
-    wait_for_database
-    validate_configuration
-    setup_community_modules
-
-    # Display configuration summary
-    log_info "Configuration summary:"
-    log_info "  Domain: ${PROSODY_DOMAIN}"
-    log_info "  Admins: ${PROSODY_ADMIN_JID}"
-    log_info "  Storage: ${PROSODY_STORAGE:-sql}"
-    log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
-    log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
-
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
-    fi
-
-    # Start Prosody
-    log_info "Starting Prosody XMPP server..."
-
-    # Switch to prosody user and start in foreground
-    exec gosu "$PROSODY_USER" prosody \
-        --config="$PROSODY_CONFIG_FILE" \
-        --foreground
+  log_info "Starting Professional Prosody XMPP Server..."
+
+  # Display version information
+  local prosody_version
+  prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
+  log_info "Prosody version: $prosody_version"
+
+  # Prefer mounted config if present
+  if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
+    log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
+    cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
+    chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+    chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+  fi
+
+  # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
+  if [[ -d "/etc/prosody/config/conf.d" ]]; then
+    log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
+    mkdir -p "/etc/prosody/conf.d"
+    rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
+    chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
+    find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
+  fi
+
+  # Environment and setup
+  validate_environment
+  setup_directories
+  setup_certificates
+  wait_for_database
+  validate_configuration
+  setup_community_modules
+
+  # Display configuration summary
+  log_info "Configuration summary:"
+  log_info "  Domain: ${PROSODY_DOMAIN}"
+  log_info "  Admins: ${PROSODY_ADMIN_JID}"
+  log_info "  Storage: ${PROSODY_STORAGE:-sql}"
+  log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
+  log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
+
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
+  fi
+
+  # Start Prosody
+  log_info "Starting Prosody XMPP server..."
+
+  # Switch to prosody user and start in foreground
+  exec gosu "$PROSODY_USER" prosody \
+    --config="$PROSODY_CONFIG_FILE" \
+    --foreground
 }
 
 # ============================================================================
@@ -376,8 +376,8 @@
 
 # Ensure we're running as root initially (for setup)
 if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root for initial setup"
-    exit 1
+  log_error "This script must be run as root for initial setup"
+  exit 1
 fi
 
 # Execute main function
diff apps/unrealircd/docker-entrypoint.sh.orig apps/unrealircd/docker-entrypoint.sh
--- apps/unrealircd/docker-entrypoint.sh.orig
+++ apps/unrealircd/docker-entrypoint.sh
@@ -21,8 +21,8 @@
 
 # Validate config exists
 if [ ! -f "/home/unrealircd/unrealircd/config/unrealircd.conf" ]; then
-    echo "ERROR: Configuration file not found!"
-    exit 1
+  echo "ERROR: Configuration file not found!"
+  exit 1
 fi
 
 # Ownership and permissions are handled by Containerfile and user switching
@@ -29,21 +29,21 @@
 
 # Handle commands
 case "$1" in
-    start)
-        shift
-        echo "Starting UnrealIRCd in foreground..."
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        else
-            exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        fi
-        ;;
-    *)
-        echo "Running command: $1"
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
-        else
-            exec /home/unrealircd/unrealircd/unrealircd "$@"
-        fi
-        ;;
+  start)
+    shift
+    echo "Starting UnrealIRCd in foreground..."
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    else
+      exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    fi
+    ;;
+  *)
+    echo "Running command: $1"
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
+    else
+      exec /home/unrealircd/unrealircd/unrealircd "$@"
+    fi
+    ;;
 esac
diff apps/unrealircd/scripts/manage-modules.sh.orig apps/unrealircd/scripts/manage-modules.sh
--- apps/unrealircd/scripts/manage-modules.sh.orig
+++ apps/unrealircd/scripts/manage-modules.sh
@@ -29,280 +29,280 @@
 
 # Check if we're running as the correct user (unrealircd, typically UID from PUID)
 check_user() {
-    local expected_uid="${PUID:-1000}"
-    if [ "$(id -u)" != "$expected_uid" ]; then
-        print_error "This script must run as the unrealircd user (UID ${expected_uid})"
-        print_error "Current user: $(id -un) (UID $(id -u))"
-        exit 1
-    fi
+  local expected_uid="${PUID:-1000}"
+  if [ "$(id -u)" != "$expected_uid" ]; then
+    print_error "This script must run as the unrealircd user (UID ${expected_uid})"
+    print_error "Current user: $(id -un) (UID $(id -u))"
+    exit 1
+  fi
 }
 
 # Check if UnrealIRCd is running
 check_unrealircd_running() {
-    if pgrep -f unrealircd >/dev/null; then
-        print_warning "UnrealIRCd is currently running"
-        print_warning "Some operations may require a restart to take effect"
-        return 0
-    else
-        print_status "UnrealIRCd is not currently running"
-        return 1
-    fi
+  if pgrep -f unrealircd > /dev/null; then
+    print_warning "UnrealIRCd is currently running"
+    print_warning "Some operations may require a restart to take effect"
+    return 0
+  else
+    print_status "UnrealIRCd is not currently running"
+    return 1
+  fi
 }
 
 # List available modules
 list_modules() {
-    print_header "Available Contrib Modules"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Fetching latest module list..."
-    if [ -d "$CONTRIB_DIR" ]; then
-        cd "$CONTRIB_DIR" && git pull --quiet origin main 2>/dev/null || true
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Available modules:"
-    echo
-    "$UNREALIRCD_BIN" module list 2>/dev/null || {
-        print_warning "Module manager not available, showing contrib directory contents:"
-        for item in "$CONTRIB_DIR"/*; do
-            if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
-                basename "$item"
-            fi
-        done | sort
-    }
+  print_header "Available Contrib Modules"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Fetching latest module list..."
+  if [ -d "$CONTRIB_DIR" ]; then
+    cd "$CONTRIB_DIR" && git pull --quiet origin main 2> /dev/null || true
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Available modules:"
+  echo
+  "$UNREALIRCD_BIN" module list 2> /dev/null || {
+    print_warning "Module manager not available, showing contrib directory contents:"
+    for item in "$CONTRIB_DIR"/*; do
+      if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
+        basename "$item"
+      fi
+    done | sort
+  }
 }
 
 # Show module information
 show_module_info() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 info <module-name>"
-        return 1
-    fi
-
-    print_header "Module Information: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to get info via module manager first
-    if "$UNREALIRCD_BIN" module info "third/$module_name" 2>/dev/null; then
-        return 0
-    fi
-
-    # Fallback to showing contrib directory info
-    if [ -d "$CONTRIB_DIR/$module_name" ]; then
-        print_status "Module found in contrib directory:"
-        ls -la "$CONTRIB_DIR/$module_name"
-
-        if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README.md"
-        fi
-
-        if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README"
-        fi
-    else
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 info <module-name>"
+    return 1
+  fi
+
+  print_header "Module Information: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to get info via module manager first
+  if "$UNREALIRCD_BIN" module info "third/$module_name" 2> /dev/null; then
+    return 0
+  fi
+
+  # Fallback to showing contrib directory info
+  if [ -d "$CONTRIB_DIR/$module_name" ]; then
+    print_status "Module found in contrib directory:"
+    ls -la "$CONTRIB_DIR/$module_name"
+
+    if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README.md"
+    fi
+
+    if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README"
+    fi
+  else
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
 }
 
 # Install a module
 install_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 install <module-name>"
-        return 1
-    fi
-
-    print_header "Installing Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Check if module is already installed
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        print_warning "Module '$module_name' is already installed"
-        return 0
-    fi
-
-    # Try to install via module manager
-    print_status "Installing via module manager..."
-    if "$UNREALIRCD_BIN" module install "third/$module_name"; then
-        print_success "Module '$module_name' installed successfully"
-
-        # Check if we need to add loadmodule to config
-        if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
-            print_status "Checking if loadmodule line needs to be added..."
-            if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
-                print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-                print_warning "Then REHASH or restart UnrealIRCd"
-            fi
-        fi
-
-        return 0
-    fi
-
-    # Fallback to manual installation
-    print_warning "Module manager failed, attempting manual installation..."
-
-    if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR/$module_name" || exit 1
-
-    if [ -f "Makefile" ]; then
-        print_status "Compiling module..."
-        make clean 2>/dev/null || true
-        make
-
-        if [ -f "$module_name.so" ]; then
-            print_status "Installing module..."
-            cp "$module_name.so" "$MODULES_DIR/third/"
-            print_success "Module '$module_name' installed manually"
-
-            print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-            print_warning "Then REHASH or restart UnrealIRCd"
-        else
-            print_error "Module compilation failed"
-            return 1
-        fi
-    else
-        print_error "No Makefile found for module '$module_name'"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 install <module-name>"
+    return 1
+  fi
+
+  print_header "Installing Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Check if module is already installed
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    print_warning "Module '$module_name' is already installed"
+    return 0
+  fi
+
+  # Try to install via module manager
+  print_status "Installing via module manager..."
+  if "$UNREALIRCD_BIN" module install "third/$module_name"; then
+    print_success "Module '$module_name' installed successfully"
+
+    # Check if we need to add loadmodule to config
+    if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
+      print_status "Checking if loadmodule line needs to be added..."
+      if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
+        print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+        print_warning "Then REHASH or restart UnrealIRCd"
+      fi
+    fi
+
+    return 0
+  fi
+
+  # Fallback to manual installation
+  print_warning "Module manager failed, attempting manual installation..."
+
+  if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR/$module_name" || exit 1
+
+  if [ -f "Makefile" ]; then
+    print_status "Compiling module..."
+    make clean 2> /dev/null || true
+    make
+
+    if [ -f "$module_name.so" ]; then
+      print_status "Installing module..."
+      cp "$module_name.so" "$MODULES_DIR/third/"
+      print_success "Module '$module_name' installed manually"
+
+      print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+      print_warning "Then REHASH or restart UnrealIRCd"
+    else
+      print_error "Module compilation failed"
+      return 1
+    fi
+  else
+    print_error "No Makefile found for module '$module_name'"
+    return 1
+  fi
 }
 
 # Uninstall a module
 uninstall_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 uninstall <module-name>"
-        return 1
-    fi
-
-    print_header "Uninstalling Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to uninstall via module manager first
-    if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2>/dev/null; then
-        print_success "Module '$module_name' uninstalled successfully"
-        return 0
-    fi
-
-    # Fallback to manual removal
-    print_warning "Module manager failed, attempting manual removal..."
-
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        rm -f "$MODULES_DIR/third/$module_name.so"
-        print_success "Module '$module_name' removed manually"
-
-        print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
-        print_warning "Then REHASH or restart UnrealIRCd"
-    else
-        print_error "Module '$module_name' not found in modules directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 uninstall <module-name>"
+    return 1
+  fi
+
+  print_header "Uninstalling Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to uninstall via module manager first
+  if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2> /dev/null; then
+    print_success "Module '$module_name' uninstalled successfully"
+    return 0
+  fi
+
+  # Fallback to manual removal
+  print_warning "Module manager failed, attempting manual removal..."
+
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    rm -f "$MODULES_DIR/third/$module_name.so"
+    print_success "Module '$module_name' removed manually"
+
+    print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
+    print_warning "Then REHASH or restart UnrealIRCd"
+  else
+    print_error "Module '$module_name' not found in modules directory"
+    return 1
+  fi
 }
 
 # Upgrade modules
 upgrade_modules() {
-    local module_name="$1"
-
-    print_header "Upgrading Modules"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    if [ -n "$module_name" ]; then
-        print_status "Upgrading specific module: $module_name"
-        if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2>/dev/null; then
-            print_success "Module '$module_name' upgraded successfully"
-        else
-            print_error "Failed to upgrade module '$module_name'"
-            return 1
-        fi
-    else
-        print_status "Upgrading all modules..."
-        if "$UNREALIRCD_BIN" module upgrade 2>/dev/null; then
-            print_success "All modules upgraded successfully"
-        else
-            print_error "Failed to upgrade modules"
-            return 1
-        fi
-    fi
-
-    print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
+  local module_name="$1"
+
+  print_header "Upgrading Modules"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  if [ -n "$module_name" ]; then
+    print_status "Upgrading specific module: $module_name"
+    if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2> /dev/null; then
+      print_success "Module '$module_name' upgraded successfully"
+    else
+      print_error "Failed to upgrade module '$module_name'"
+      return 1
+    fi
+  else
+    print_status "Upgrading all modules..."
+    if "$UNREALIRCD_BIN" module upgrade 2> /dev/null; then
+      print_success "All modules upgraded successfully"
+    else
+      print_error "Failed to upgrade modules"
+      return 1
+    fi
+  fi
+
+  print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
 }
 
 # Update contrib repository
 update_contrib() {
-    print_header "Updating Contrib Repository"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR" || exit 1
-
-    print_status "Pulling latest changes from unrealircd-contrib..."
-    if git pull --quiet origin main; then
-        print_success "Contrib repository updated successfully"
-        print_status "New modules may now be available"
-    else
-        print_error "Failed to update contrib repository"
-        return 1
-    fi
+  print_header "Updating Contrib Repository"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR" || exit 1
+
+  print_status "Pulling latest changes from unrealircd-contrib..."
+  if git pull --quiet origin main; then
+    print_success "Contrib repository updated successfully"
+    print_status "New modules may now be available"
+  else
+    print_error "Failed to update contrib repository"
+    return 1
+  fi
 }
 
 # Show installed modules
 show_installed() {
-    print_header "Installed Modules"
-
-    if [ ! -d "$MODULES_DIR/third" ]; then
-        print_status "No third-party modules installed"
-        return 0
-    fi
-
-    cd "$MODULES_DIR/third" || exit 1
-
-    local count=0
-    for module in *.so; do
-        if [ -f "$module" ]; then
-            echo "  - third/${module%.so}"
-            ((count++))
-        fi
-    done
-
-    if [ $count -eq 0 ]; then
-        print_status "No third-party modules installed"
-    else
-        print_status "Total installed modules: $count"
-    fi
+  print_header "Installed Modules"
+
+  if [ ! -d "$MODULES_DIR/third" ]; then
+    print_status "No third-party modules installed"
+    return 0
+  fi
+
+  cd "$MODULES_DIR/third" || exit 1
+
+  local count=0
+  for module in *.so; do
+    if [ -f "$module" ]; then
+      echo "  - third/${module%.so}"
+      ((count++))
+    fi
+  done
+
+  if [ $count -eq 0 ]; then
+    print_status "No third-party modules installed"
+  else
+    print_status "Total installed modules: $count"
+  fi
 }
 
 # Show usage
 show_usage() {
-    cat <<EOF
+  cat << EOF
 UnrealIRCd Contrib Modules Management Script
 
 Usage: $0 <command> [options]
@@ -344,40 +344,40 @@
 
 # Main execution
 main() {
-    check_user
-
-    case "${1:-help}" in
+  check_user
+
+  case "${1:-help}" in
     list)
-        list_modules
-        ;;
+      list_modules
+      ;;
     info)
-        show_module_info "$2"
-        ;;
+      show_module_info "$2"
+      ;;
     install)
-        install_module "$2"
-        ;;
+      install_module "$2"
+      ;;
     uninstall)
-        uninstall_module "$2"
-        ;;
+      uninstall_module "$2"
+      ;;
     upgrade)
-        upgrade_modules "$2"
-        ;;
+      upgrade_modules "$2"
+      ;;
     update)
-        update_contrib
-        ;;
+      update_contrib
+      ;;
     installed)
-        show_installed
-        ;;
+      show_installed
+      ;;
     help | --help | -h)
-        show_usage
-        ;;
+      show_usage
+      ;;
     *)
-        print_error "Unknown command: $1"
-        echo
-        show_usage
-        exit 1
-        ;;
-    esac
+      print_error "Unknown command: $1"
+      echo
+      show_usage
+      exit 1
+      ;;
+  esac
 }
 
 # Run main function with all arguments
diff scripts/cert-manager/run.sh.orig scripts/cert-manager/run.sh
--- scripts/cert-manager/run.sh.orig
+++ scripts/cert-manager/run.sh
@@ -9,26 +9,26 @@
 
 # Validate domain format (alphanumeric, hyphens, dots only - prevent injection)
 case "$ROOT_DOMAIN" in
-    *[!a-zA-Z0-9.-]*)
-        echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Domain cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9.-]*)
+    echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Domain cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Validate email format (basic check - no spaces or shell metacharacters)
 case "$EMAIL" in
-    *[!a-zA-Z0-9@._+-]*)
-        echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Email cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9@._+-]*)
+    echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Email cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Lego outputs: certificates/_.<domain>.crt and _.<domain>.key for wildcards
@@ -38,9 +38,9 @@
 
 # Ensure we have credentials
 if [ -z "$CLOUDFLARE_DNS_API_TOKEN" ]; then
-    echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
-    echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
-    exec sleep infinity
+  echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
+  echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
+  exec sleep infinity
 fi
 
 # Initial issuance
@@ -49,8 +49,8 @@
 
 # Renewal loop
 while true; do
-    echo "Sleeping for 24 hours..."
-    sleep 86400
-    echo "Checking for renewal..."
-    lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
+  echo "Sleeping for 24 hours..."
+  sleep 86400
+  echo "Checking for renewal..."
+  lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
 done
diff scripts/gencloak-update-env.sh.orig scripts/gencloak-update-env.sh
--- scripts/gencloak-update-env.sh.orig
+++ scripts/gencloak-update-env.sh
@@ -12,23 +12,23 @@
 
 cd "$PROJECT_ROOT"
 
-output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2>/dev/null)
+output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2> /dev/null)
 echo "$output"
 
 mapfile -t keys < <(echo "$output" | grep -oE '"[^"]{50,}"' | tr -d '"')
 if [ ${#keys[@]} -ne 3 ]; then
-    echo "Failed to parse 3 cloak keys from gencloak output"
-    exit 1
+  echo "Failed to parse 3 cloak keys from gencloak output"
+  exit 1
 fi
 
 [ -f "$ENV_FILE" ] || cp .env.example "$ENV_FILE"
 
 if grep -q '^IRC_CLOAK_KEY_1=' "$ENV_FILE"; then
-    sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
 else
-    sed -i "/^IRC_CLOAK_PREFIX=/a\\
+  sed -i "/^IRC_CLOAK_PREFIX=/a\\
 # Cloak keys - keep secret; must be identical on all servers. Generate with: just irc gencloak\\
 IRC_CLOAK_KEY_1=${keys[0]}\\
 IRC_CLOAK_KEY_2=${keys[1]}\\
diff scripts/init.sh.orig scripts/init.sh
--- scripts/init.sh.orig
+++ scripts/init.sh
@@ -11,16 +11,16 @@
 
 # Load environment variables: .env (base) then .env.dev (overrides for just dev)
 if [ -f "$PROJECT_ROOT/.env" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env"
+  set +a
 fi
 if [ -f "$PROJECT_ROOT/.env.dev" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env.dev"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env.dev"
+  set +a
 fi
 
 # Ensure Atheme JSON-RPC port has a default (for existing .env without it)
@@ -35,386 +35,386 @@
 
 # Helper functions
 log_info() {
-    echo -e "${BLUE}[INFO]${NC} $1"
+  echo -e "${BLUE}[INFO]${NC} $1"
 }
 
 log_success() {
-    echo -e "${GREEN}[SUCCESS]${NC} $1"
+  echo -e "${GREEN}[SUCCESS]${NC} $1"
 }
 
 log_warning() {
-    echo -e "${YELLOW}[WARNING]${NC} $1"
+  echo -e "${YELLOW}[WARNING]${NC} $1"
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $1"
+  echo -e "${RED}[ERROR]${NC} $1"
 }
 
 # Function to create directory structure
 create_directories() {
-    log_info "Creating required directory structure..."
-
-    # Data directories (must match compose volume mounts)
-    local data_dirs=(
-        "$PROJECT_ROOT/data/irc/data"
-        "$PROJECT_ROOT/data/irc/logs"
-        "$PROJECT_ROOT/data/irc/webpanel-data"
-        "$PROJECT_ROOT/data/atheme/data"
-        "$PROJECT_ROOT/data/atheme/logs"
-        "$PROJECT_ROOT/data/xmpp/data"
-        "$PROJECT_ROOT/data/xmpp/logs"
-        "$PROJECT_ROOT/data/xmpp/uploads"
-        "$PROJECT_ROOT/data/thelounge"
-        "$PROJECT_ROOT/data/certs"
-    )
-
-    # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
-    local ssl_dirs=(
-        "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    )
-
-    # Create all directories
-    for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
-        if [ ! -d "$dir" ]; then
-            mkdir -p "$dir"
-            log_info "Created directory: $dir"
-    else
-            log_info "Directory already exists: $dir"
+  log_info "Creating required directory structure..."
+
+  # Data directories (must match compose volume mounts)
+  local data_dirs=(
+    "$PROJECT_ROOT/data/irc/data"
+    "$PROJECT_ROOT/data/irc/logs"
+    "$PROJECT_ROOT/data/irc/webpanel-data"
+    "$PROJECT_ROOT/data/atheme/data"
+    "$PROJECT_ROOT/data/atheme/logs"
+    "$PROJECT_ROOT/data/xmpp/data"
+    "$PROJECT_ROOT/data/xmpp/logs"
+    "$PROJECT_ROOT/data/xmpp/uploads"
+    "$PROJECT_ROOT/data/thelounge"
+    "$PROJECT_ROOT/data/certs"
+  )
+
+  # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
+  local ssl_dirs=(
+    "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  )
+
+  # Create all directories
+  for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
+    if [ ! -d "$dir" ]; then
+      mkdir -p "$dir"
+      log_info "Created directory: $dir"
+    else
+      log_info "Directory already exists: $dir"
     fi
   done
 
-    log_success "Directory structure created successfully"
+  log_success "Directory structure created successfully"
 }
 
 # Function to set proper permissions
 set_permissions() {
-    log_info "Setting proper permissions..."
-
-    # Get current user ID and group ID
-    local current_uid
-    local current_gid
-    # Use actual user ID instead of hardcoded values
-    current_uid=$(id -u)
-    current_gid=$(id -g)
-
-    # Use same UID for all services to avoid permission issues
-    local atheme_uid=$current_uid
-    local atheme_gid=$current_gid
-
-    log_info "Current user: $current_uid:$current_gid"
-
-    # Set ownership for data directories (if they exist)
-    if [ -d "$PROJECT_ROOT/data" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
-        # Ensure directories are writable by owner (critical for socket creation)
-        find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
-        log_info "Set ownership for data directory"
-  fi
-
-    # Set ownership for IRC data directory
-    if [ -d "$PROJECT_ROOT/data/irc" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
-        chmod -R 755 "$PROJECT_ROOT/data/irc"
-        log_info "Set permissions for IRC data directory"
-  fi
-
-    # Set ownership for Atheme data directory with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
-        chmod 755 "$PROJECT_ROOT/data/atheme"
-        log_info "Set permissions for Atheme data directory"
-  fi
-
-    # Set ownership for UnrealIRCd logs
-    if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
-        chmod 755 "$PROJECT_ROOT/data/irc/logs"
-        log_info "Set ownership for UnrealIRCd logs"
-    fi
-
-    # Set ownership for Atheme logs with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
-        chmod 755 "$PROJECT_ROOT/data/atheme/logs"
-        log_info "Set permissions for Atheme logs directory"
-    fi
-
-    # Set permissions for SSL certificates
-    if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
-        sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    fi
-    sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
-    chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
-    log_info "Set permissions for SSL directory"
-
-    # Make sure data directories are writable
-    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
-    find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
-
-    log_success "Permissions set successfully"
+  log_info "Setting proper permissions..."
+
+  # Get current user ID and group ID
+  local current_uid
+  local current_gid
+  # Use actual user ID instead of hardcoded values
+  current_uid=$(id -u)
+  current_gid=$(id -g)
+
+  # Use same UID for all services to avoid permission issues
+  local atheme_uid=$current_uid
+  local atheme_gid=$current_gid
+
+  log_info "Current user: $current_uid:$current_gid"
+
+  # Set ownership for data directories (if they exist)
+  if [ -d "$PROJECT_ROOT/data" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
+    # Ensure directories are writable by owner (critical for socket creation)
+    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
+    log_info "Set ownership for data directory"
+  fi
+
+  # Set ownership for IRC data directory
+  if [ -d "$PROJECT_ROOT/data/irc" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
+    chmod -R 755 "$PROJECT_ROOT/data/irc"
+    log_info "Set permissions for IRC data directory"
+  fi
+
+  # Set ownership for Atheme data directory with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
+    chmod 755 "$PROJECT_ROOT/data/atheme"
+    log_info "Set permissions for Atheme data directory"
+  fi
+
+  # Set ownership for UnrealIRCd logs
+  if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
+    chmod 755 "$PROJECT_ROOT/data/irc/logs"
+    log_info "Set ownership for UnrealIRCd logs"
+  fi
+
+  # Set ownership for Atheme logs with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
+    chmod 755 "$PROJECT_ROOT/data/atheme/logs"
+    log_info "Set permissions for Atheme logs directory"
+  fi
+
+  # Set permissions for SSL certificates
+  if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
+    sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  fi
+  sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
+  chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
+  log_info "Set permissions for SSL directory"
+
+  # Make sure data directories are writable
+  find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
+  find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
+
+  log_success "Permissions set successfully"
 }
 
 # Function to set up CA certificate bundle
 setup_ca_bundle() {
-    log_info "Setting up CA certificate bundle..."
-
-    local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
-    local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
-    local ca_bundle_file="curl-ca-bundle.crt"
-
-    # Ensure runtime directory exists
-    if [ ! -d "$ca_runtime_dir" ]; then
-        mkdir -p "$ca_runtime_dir"
-        log_info "Created TLS runtime directory: $ca_runtime_dir"
-  fi
-
-    # Ensure template directory exists
-    if [ ! -d "$ca_template_dir" ]; then
-        mkdir -p "$ca_template_dir"
-        log_info "Created TLS template directory: $ca_template_dir"
-  fi
-
-    # Check if system CA bundle exists
-    local system_ca_bundle=""
-    if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
-        system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
-  elif   [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
-        system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
-  fi
-
-    if [ -n "$system_ca_bundle" ]; then
-        # Create template if it doesn't exist
-        if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle template"
-      else
-                log_warning "Could not create CA certificate bundle template"
-                return 1
-      fi
-    fi
-
-        # Copy to runtime directory if it doesn't exist
-        if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle in runtime directory"
-      else
-                log_warning "Could not create CA certificate bundle in runtime directory"
-                return 1
-      fi
-    else
-            log_info "CA certificate bundle already exists in runtime directory"
-    fi
-  else
-        log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
-        return 1
-  fi
-
-    # Remove obsolete cert files from config/tls (server certs now live in data/certs)
-    rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2>/dev/null || true
-    rm -rf "$ca_runtime_dir/live" 2>/dev/null || true
-
-    log_success "CA certificate bundle setup completed"
+  log_info "Setting up CA certificate bundle..."
+
+  local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
+  local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
+  local ca_bundle_file="curl-ca-bundle.crt"
+
+  # Ensure runtime directory exists
+  if [ ! -d "$ca_runtime_dir" ]; then
+    mkdir -p "$ca_runtime_dir"
+    log_info "Created TLS runtime directory: $ca_runtime_dir"
+  fi
+
+  # Ensure template directory exists
+  if [ ! -d "$ca_template_dir" ]; then
+    mkdir -p "$ca_template_dir"
+    log_info "Created TLS template directory: $ca_template_dir"
+  fi
+
+  # Check if system CA bundle exists
+  local system_ca_bundle=""
+  if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
+    system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
+  elif [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+    system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
+  fi
+
+  if [ -n "$system_ca_bundle" ]; then
+    # Create template if it doesn't exist
+    if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle template"
+      else
+        log_warning "Could not create CA certificate bundle template"
+        return 1
+      fi
+    fi
+
+    # Copy to runtime directory if it doesn't exist
+    if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle in runtime directory"
+      else
+        log_warning "Could not create CA certificate bundle in runtime directory"
+        return 1
+      fi
+    else
+      log_info "CA certificate bundle already exists in runtime directory"
+    fi
+  else
+    log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
+    return 1
+  fi
+
+  # Remove obsolete cert files from config/tls (server certs now live in data/certs)
+  rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2> /dev/null || true
+  rm -rf "$ca_runtime_dir/live" 2> /dev/null || true
+
+  log_success "CA certificate bundle setup completed"
 }
 
 # Function to generate self-signed certificates for dev mode
 generate_cert() {
-    local domain="$1"
-    local base_dir="$2"
-    local live_dir="$base_dir/live/$domain"
-
-    # Ensure directory exists
-    mkdir -p "$live_dir"
-
-    # Generate self-signed cert if it doesn't exist
-    if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
-        log_info "Generating self-signed certificate for $domain..."
-        # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
-        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-            -keyout "$live_dir/privkey.pem" \
-            -out "$live_dir/fullchain.pem" \
-            -subj "/CN=$domain" \
-            -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-        log_success "Generated self-signed certificate for $domain"
-    else
-        log_info "Self-signed certificate already exists for $domain"
-    fi
-
-    # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
-    chmod 644 "$live_dir/privkey.pem" 2>/dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2>/dev/null || true
+  local domain="$1"
+  local base_dir="$2"
+  local live_dir="$base_dir/live/$domain"
+
+  # Ensure directory exists
+  mkdir -p "$live_dir"
+
+  # Generate self-signed cert if it doesn't exist
+  if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
+    log_info "Generating self-signed certificate for $domain..."
+    # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
+    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+      -keyout "$live_dir/privkey.pem" \
+      -out "$live_dir/fullchain.pem" \
+      -subj "/CN=$domain" \
+      -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+    log_success "Generated self-signed certificate for $domain"
+  else
+    log_info "Self-signed certificate already exists for $domain"
+  fi
+
+  # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
+  chmod 644 "$live_dir/privkey.pem" 2> /dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2> /dev/null || true
 }
 
 generate_dev_certs() {
-    log_info "Setting up self-signed certificates for dev mode..."
-
-    # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
-    local shared_cert_dir="$PROJECT_ROOT/data/certs"
-    generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
-    generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
-
-    log_success "Dev certificate setup completed"
+  log_info "Setting up self-signed certificates for dev mode..."
+
+  # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
+  local shared_cert_dir="$PROJECT_ROOT/data/certs"
+  generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
+  generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
+
+  log_success "Dev certificate setup completed"
 }
 
 # Function to prepare configuration files from templates
 prepare_config_files() {
-    log_info "Preparing configuration files from templates..."
-
-    # Load environment variables from .env if it exists
-    if [ -f "$PROJECT_ROOT/.env" ]; then
-        log_info "Loading environment variables from .env"
-        set -a
-        # shellcheck disable=SC1091
-        source "$PROJECT_ROOT/.env"
-        set +a
-        log_info "Environment variables loaded"
-    fi
-
-    # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
-    export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
-    export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
-
-    if [ ! -f "$PROJECT_ROOT/.env" ]; then
-        log_warning ".env file not found. Configuration will use defaults."
-        return 1
-  fi
-
-    # Check if envsubst is available
-    if ! command -v envsubst > /dev/null 2>&1; then
-        log_error "envsubst command not found. Please install gettext package."
-        return 1
-  fi
-
-    # Prepare UnrealIRCd configuration
-    local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
-    local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
-
-    if [ -f "$unreal_template" ]; then
-        log_info "Creating UnrealIRCd configuration from template..."
-        if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
-            log_success "UnrealIRCd configuration created"
-    else
-            log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
-    fi
-  elif   [ -f "$unreal_config" ]; then
-        log_info "UnrealIRCd configuration already exists"
-  else
-        log_warning "No UnrealIRCd configuration template found"
-  fi
-
-    # Prepare Atheme configuration
-    local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
-    local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
-
-    if [ -f "$atheme_template" ]; then
-        log_info "Creating Atheme configuration from template..."
-        envsubst < "$atheme_template" > "$atheme_config"
-        log_success "Atheme configuration created"
-  elif   [ -f "$atheme_config" ]; then
-        log_info "Atheme configuration already exists"
-  else
-        log_warning "No Atheme configuration template found"
-  fi
-
-    # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
-    if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
-        log_info "Running prepare-config.sh (bridge config, etc.)..."
-        # shellcheck source=prepare-config.sh
-        "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
-    fi
-
-    # Show substituted values for verification
-    log_info "Configuration values:"
-    echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
-    echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
-    echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
-    echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
-    echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
+  log_info "Preparing configuration files from templates..."
+
+  # Load environment variables from .env if it exists
+  if [ -f "$PROJECT_ROOT/.env" ]; then
+    log_info "Loading environment variables from .env"
+    set -a
+    # shellcheck disable=SC1091
+    source "$PROJECT_ROOT/.env"
+    set +a
+    log_info "Environment variables loaded"
+  fi
+
+  # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
+  export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
+  export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
+
+  if [ ! -f "$PROJECT_ROOT/.env" ]; then
+    log_warning ".env file not found. Configuration will use defaults."
+    return 1
+  fi
+
+  # Check if envsubst is available
+  if ! command -v envsubst > /dev/null 2>&1; then
+    log_error "envsubst command not found. Please install gettext package."
+    return 1
+  fi
+
+  # Prepare UnrealIRCd configuration
+  local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
+  local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
+
+  if [ -f "$unreal_template" ]; then
+    log_info "Creating UnrealIRCd configuration from template..."
+    if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
+      log_success "UnrealIRCd configuration created"
+    else
+      log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
+    fi
+  elif [ -f "$unreal_config" ]; then
+    log_info "UnrealIRCd configuration already exists"
+  else
+    log_warning "No UnrealIRCd configuration template found"
+  fi
+
+  # Prepare Atheme configuration
+  local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
+  local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
+
+  if [ -f "$atheme_template" ]; then
+    log_info "Creating Atheme configuration from template..."
+    envsubst < "$atheme_template" > "$atheme_config"
+    log_success "Atheme configuration created"
+  elif [ -f "$atheme_config" ]; then
+    log_info "Atheme configuration already exists"
+  else
+    log_warning "No Atheme configuration template found"
+  fi
+
+  # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
+  if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
+    log_info "Running prepare-config.sh (bridge config, etc.)..."
+    # shellcheck source=prepare-config.sh
+    "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
+  fi
+
+  # Show substituted values for verification
+  log_info "Configuration values:"
+  echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
+  echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
+  echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
+  echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
+  echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
 }
 
 # Function to create .env template if it doesn't exist
 create_env_template() {
-    local env_file="$PROJECT_ROOT/.env"
-    local env_example="$PROJECT_ROOT/.env.example"
-
-    if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
-        cp "$env_example" "$env_file"
-        log_info "Created .env file from template"
-        log_warning "Please edit .env file with your configuration before starting services"
-  elif   [ -f "$env_file" ]; then
-        log_info ".env file already exists"
-  else
-        log_warning "No .env template found. You may need to create environment variables manually"
+  local env_file="$PROJECT_ROOT/.env"
+  local env_example="$PROJECT_ROOT/.env.example"
+
+  if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
+    cp "$env_example" "$env_file"
+    log_info "Created .env file from template"
+    log_warning "Please edit .env file with your configuration before starting services"
+  elif [ -f "$env_file" ]; then
+    log_info ".env file already exists"
+  else
+    log_warning "No .env template found. You may need to create environment variables manually"
   fi
 }
 
 # Function to check Docker availability
 check_docker() {
-    log_info "Checking Docker availability..."
-
-    if ! command -v docker > /dev/null 2>&1; then
-        log_error "Docker is not installed or not in PATH"
-        exit 1
-  fi
-
-    if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
-        log_error "Docker Compose is not available"
-        exit 1
-  fi
-
-    log_success "Docker is available"
+  log_info "Checking Docker availability..."
+
+  if ! command -v docker > /dev/null 2>&1; then
+    log_error "Docker is not installed or not in PATH"
+    exit 1
+  fi
+
+  if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
+    log_error "Docker Compose is not available"
+    exit 1
+  fi
+
+  log_success "Docker is available"
 }
 
 # Function to show next steps
 show_next_steps() {
-    echo ""
-    log_info "Next steps:"
-    echo "  1. Edit .env file with your configuration (optional)"
-    echo "  3. Or run: docker compose up -d"
-    echo ""
-    log_info "Data will be stored in:"
-    echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
+  echo ""
+  log_info "Next steps:"
+  echo "  1. Edit .env file with your configuration (optional)"
+  echo "  3. Or run: docker compose up -d"
+  echo ""
+  log_info "Data will be stored in:"
+  echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
 }
 
 # Main function
 main() {
-    log_info "ATL Chat Infrastructure Initialization"
-    log_info "======================================"
-
-    # Check if we're running as root (for permission info)
-    if [ "$(id -u)" = "0" ]; then
-        log_warning "Running as root - this is fine for initial setup"
-  fi
-
-    # Check Docker availability
-    check_docker
-
-    # Create directory structure
-    create_directories
-
-    # Set permissions
-    set_permissions
-
-    # Set up CA certificate bundle
-    setup_ca_bundle
-
-    # Generate dev certs
-    generate_dev_certs
-
-    # Create .env if needed
-    create_env_template
-
-    # Prepare configuration files from templates
-    prepare_config_files
-
-    # Show next steps
-    show_next_steps
-
-    log_success "Initialization completed successfully!"
+  log_info "ATL Chat Infrastructure Initialization"
+  log_info "======================================"
+
+  # Check if we're running as root (for permission info)
+  if [ "$(id -u)" = "0" ]; then
+    log_warning "Running as root - this is fine for initial setup"
+  fi
+
+  # Check Docker availability
+  check_docker
+
+  # Create directory structure
+  create_directories
+
+  # Set permissions
+  set_permissions
+
+  # Set up CA certificate bundle
+  setup_ca_bundle
+
+  # Generate dev certs
+  generate_dev_certs
+
+  # Create .env if needed
+  create_env_template
+
+  # Prepare configuration files from templates
+  prepare_config_files
+
+  # Show next steps
+  show_next_steps
+
+  log_success "Initialization completed successfully!"
 }
 
 # Run main function
 if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
-    main "$@"
+  main "$@"
 fi
diff scripts/prepare-config.sh.orig scripts/prepare-config.sh
--- scripts/prepare-config.sh.orig
+++ scripts/prepare-config.sh
@@ -180,7 +180,7 @@
     log_info "Preparing bridge configuration from template..."
     local temp_file="/tmp/bridge-config.yaml.tmp"
     envsubst < "$bridge_template" > "$temp_file"
-    if cp "$temp_file" "$bridge_config" 2>/dev/null || sudo cp "$temp_file" "$bridge_config" 2>/dev/null; then
+    if cp "$temp_file" "$bridge_config" 2> /dev/null || sudo cp "$temp_file" "$bridge_config" 2> /dev/null; then
       log_success "Bridge configuration prepared"
     else
       log_warning "Could not write bridge config to $bridge_config"
@@ -189,7 +189,7 @@
   elif [ ! -f "$bridge_config" ]; then
     log_warning "Bridge config not found. Copy apps/bridge/config.example.yaml to apps/bridge/config.yaml and customize."
     if [ -f "$PROJECT_ROOT/apps/bridge/config.example.yaml" ]; then
-      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2>/dev/null || true
+      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2> /dev/null || true
       log_info "Copied config.example.yaml to apps/bridge/config.yaml - edit with your Discord channel ID"
     fi
   fi
@@ -202,7 +202,7 @@
     mkdir -p "$(dirname "$lounge_config")"
     local temp_file="/tmp/thelounge-config.js.tmp"
     envsubst < "$lounge_template" > "$temp_file"
-    if cp "$temp_file" "$lounge_config" 2>/dev/null || sudo cp "$temp_file" "$lounge_config" 2>/dev/null; then
+    if cp "$temp_file" "$lounge_config" 2> /dev/null || sudo cp "$temp_file" "$lounge_config" 2> /dev/null; then
       log_success "The Lounge configuration prepared"
     else
       log_warning "Could not write The Lounge config to $lounge_config"
----------

You can reformat the above files to meet shfmt's requirements by typing:

  shfmt  -w filename


Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 issues found across 173 files (changes from recent commits).

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/docs/content/docs/operations/monitoring.mdx">

<violation number="1" location="apps/docs/content/docs/operations/monitoring.mdx:117">
P2: The Atheme log example targets `atl-irc-server`, which is the UnrealIRCd container. This command should use `atl-irc-services` to match the Atheme service and avoid misleading log output.</violation>

<violation number="2" location="apps/docs/content/docs/operations/monitoring.mdx:170">
P3: This sentence is misspelled and incomplete. Clarify that the endpoint is available at `/metrics` before instructing users to add it to `prometheus.yml`.</violation>
</file>

<file name="apps/docs/content/docs/services/atheme/operations.mdx">

<violation number="1" location="apps/docs/content/docs/services/atheme/operations.mdx:253">
P2: The HostServ command block is malformed: the VHOST command is split across lines and the code fence isn’t closed, which breaks markdown rendering and truncates the command. Consolidate the VHOST command and close the code block before starting the BotServ section.</violation>
</file>

<file name="apps/docs/content/docs/development/adding-a-service.mdx">

<violation number="1" location="apps/docs/content/docs/development/adding-a-service.mdx:174">
P2: The compose.yaml example is malformed: the include list is truncated (`- in`) and the code block continues with non‑YAML environment variable lines. This makes the example invalid and mixes two different snippets.</violation>
</file>

<file name="apps/docs/components/mdx/mermaid.tsx">

<violation number="1" location="apps/docs/components/mdx/mermaid.tsx:35">
P1: `securityLevel: 'loose'` allows HTML tags in Mermaid text, and the rendered SVG is injected with `dangerouslySetInnerHTML`. If any chart content is untrusted (e.g., from MDX), this enables XSS. Prefer `strict` or `sandbox` (default behavior encodes HTML) unless you are sanitizing the diagram source.</violation>
</file>

<file name="apps/docs/content/docs/operations/security.mdx">

<violation number="1" location="apps/docs/content/docs/operations/security.mdx:255">
P3: Fix the typo in the TLS section—"tore private keys" is grammatically incorrect and makes the guidance unclear.</violation>
</file>

<file name="apps/docs/content/docs/services/webpanel/configuration.mdx">

<violation number="1" location="apps/docs/content/docs/services/webpanel/configuration.mdx:141">
P2: The documentation recommends enabling `X-XSS-Protection: 1; mode=block`, but this header is deprecated and can introduce XSS vulnerabilities; MDN advises avoiding it and using CSP instead. Update the guidance to disable it or remove the header.</violation>
</file>

<file name="apps/docs/content/docs/services/bridge/configuration.mdx">

<violation number="1" location="apps/docs/content/docs/services/bridge/configuration.mdx:38">
P2: The config example references `IRC_TLS_VERIFY`, but the rest of this page documents `BRIDGE_IRC_TLS_VERIFY`. Align the example with the documented env var so readers don’t set the wrong variable.</violation>
</file>

<file name="apps/docs/content/docs/operations/backups.mdx">

<violation number="1" location="apps/docs/content/docs/operations/backups.mdx:349">
P2: The cleanup `find` command can delete the root backup directory itself if it’s older than 7 days. Add `-mindepth 1` so only child directories are eligible for removal.</violation>
</file>

<file name="apps/docs/content/docs/services/thelounge/configuration.mdx">

<violation number="1" location="apps/docs/content/docs/services/thelounge/configuration.mdx:14">
P3: This step implies .env.dev is always loaded when present, but prepare-config.sh only sources .env.dev when ATL_ENVIRONMENT=dev. The doc should reflect that condition to avoid misleading production setups.</violation>
</file>

<file name=".env.example">

<violation number="1" location=".env.example:52">
P2: IRC_OPER_PASSWORD in .env.example should still demonstrate an Argon2 hash format (and quoting) since UnrealIRCd requires a hashed oper password. The current plaintext placeholder can lead to invalid oper credentials or misconfiguration.</violation>
</file>

<file name="apps/docs/content/docs/operations/ssl-tls.mdx">

<violation number="1" location="apps/docs/content/docs/operations/ssl-tls.mdx:65">
P2: The doc claims production certificates are stored under `data/certs/live/<domain>/...`, but cert-manager currently writes Lego output to `data/certs/certificates/` with no live/ mapping. This mismatch can send operators to non-existent paths and lead to TLS config failures. Update the doc to match the actual cert-manager output or document the required copy/symlink step.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

docker compose logs atl-irc-server 2>&1 | grep -iE "error|split|refused"

# Atheme — failed authentication attempts
docker compose logs atl-irc-server 2>&1 | grep -i "SASL"
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The Atheme log example targets atl-irc-server, which is the UnrealIRCd container. This command should use atl-irc-services to match the Atheme service and avoid misleading log output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/operations/monitoring.mdx, line 117:

<comment>The Atheme log example targets `atl-irc-server`, which is the UnrealIRCd container. This command should use `atl-irc-services` to match the Atheme service and avoid misleading log output.</comment>

<file context>
@@ -0,0 +1,300 @@
+docker compose logs atl-irc-server 2>&1 | grep -iE "error|split|refused"
+
+# Atheme — failed authentication attempts
+docker compose logs atl-irc-server 2>&1 | grep -i "SASL"
+
+# Prosody — authentication failures and certificate issues
</file context>
Suggested change
docker compose logs atl-irc-server 2>&1 | grep -i "SASL"
docker compose logs atl-irc-services 2>&1 | grep -i "SASL"
Fix with Cubic

/msg HostServ OFFER <pattern> <vhost>
/msg HostServ ACTIVATE <nickname>
/msg HostServ REJECT <nickname> <reason>
/msg HostServ VHOST <nick
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The HostServ command block is malformed: the VHOST command is split across lines and the code fence isn’t closed, which breaks markdown rendering and truncates the command. Consolidate the VHOST command and close the code block before starting the BotServ section.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/services/atheme/operations.mdx, line 253:

<comment>The HostServ command block is malformed: the VHOST command is split across lines and the code fence isn’t closed, which breaks markdown rendering and truncates the command. Consolidate the VHOST command and close the code block before starting the BotServ section.</comment>

<file context>
@@ -0,0 +1,459 @@
+/msg HostServ OFFER <pattern> <vhost>
+/msg HostServ ACTIVATE <nickname>
+/msg HostServ REJECT <nickname> <reason>
+/msg HostServ VHOST <nick
+els:
+
</file context>
Fix with Cubic

- infra/compose/xmpp.yaml
- infra/compose/bridge.yaml
- infra/compose/thelounge.yaml
- in
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The compose.yaml example is malformed: the include list is truncated (- in) and the code block continues with non‑YAML environment variable lines. This makes the example invalid and mixes two different snippets.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/development/adding-a-service.mdx, line 174:

<comment>The compose.yaml example is malformed: the include list is truncated (`- in`) and the code block continues with non‑YAML environment variable lines. This makes the example invalid and mixes two different snippets.</comment>

<file context>
@@ -0,0 +1,376 @@
+  - infra/compose/xmpp.yaml
+  - infra/compose/bridge.yaml
+  - infra/compose/thelounge.yaml
+  - in
+==========================
+# <SERVICE NAME>
</file context>
Fix with Cubic

```nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The documentation recommends enabling X-XSS-Protection: 1; mode=block, but this header is deprecated and can introduce XSS vulnerabilities; MDN advises avoiding it and using CSP instead. Update the guidance to disable it or remove the header.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/services/webpanel/configuration.mdx, line 141:

<comment>The documentation recommends enabling `X-XSS-Protection: 1; mode=block`, but this header is deprecated and can introduce XSS vulnerabilities; MDN advises avoiding it and using CSP instead. Update the guidance to disable it or remove the header.</comment>

<file context>
@@ -0,0 +1,203 @@
+```nginx
+add_header X-Frame-Options "SAMEORIGIN" always;
+add_header X-Content-Type-Options "nosniff" always;
+add_header X-XSS-Protection "1; mode=block" always;
+```
+
</file context>
Fix with Cubic

IRC_CLOAK_KEY_2=899874eda706ee805bd34792bfd7bd62711f1938dea920c8bdf8396fe136ab6a83785a3ce54eB298
IRC_CLOAK_KEY_3=d8936d8fff38eace5c379c94578abfa802088bd241329c64506513fe8e4de3e2304f7dd00355A8d6
# Generate with: docker run --rm ghcr.io/allthingslinux/unrealircd ./unrealircd mkpasswd argon2 <password>
IRC_OPER_PASSWORD=change_me_irc_oper_password
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: IRC_OPER_PASSWORD in .env.example should still demonstrate an Argon2 hash format (and quoting) since UnrealIRCd requires a hashed oper password. The current plaintext placeholder can lead to invalid oper credentials or misconfiguration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .env.example, line 52:

<comment>IRC_OPER_PASSWORD in .env.example should still demonstrate an Argon2 hash format (and quoting) since UnrealIRCd requires a hashed oper password. The current plaintext placeholder can lead to invalid oper credentials or misconfiguration.</comment>

<file context>
@@ -48,7 +48,8 @@ IRC_WEBSOCKET_PORT=8000
 IRC_CLOAK_KEY_3=d8936d8fff38eace5c379c94578abfa802088bd241329c64506513fe8e4de3e2304f7dd00355A8d6
-IRC_OPER_PASSWORD='$argon2id$v=19$m=6144,t=2,p=2$WXOLpTE+DPDr8q6OBVTx3w$bqXpBsaAK6lkXfR/IPn+TcE0VJEKjUFD7xordE6pFSo'
+# Generate with: docker run --rm ghcr.io/allthingslinux/unrealircd ./unrealircd mkpasswd argon2 <password>
+IRC_OPER_PASSWORD=change_me_irc_oper_password
 IRC_DRPASS=change_me_drpass
 IRC_SERVICES_PASSWORD=change_me_secure_services_pass
</file context>
Fix with Cubic


## Certificate paths per service

All services read certificates from the shared `data/certs/` directory, mounted at different container paths. Certificates follow a `live/<domain>/` layout with `fullchain.pem` and `privkey.pem` files.
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The doc claims production certificates are stored under data/certs/live/<domain>/..., but cert-manager currently writes Lego output to data/certs/certificates/ with no live/ mapping. This mismatch can send operators to non-existent paths and lead to TLS config failures. Update the doc to match the actual cert-manager output or document the required copy/symlink step.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/operations/ssl-tls.mdx, line 65:

<comment>The doc claims production certificates are stored under `data/certs/live/<domain>/...`, but cert-manager currently writes Lego output to `data/certs/certificates/` with no live/ mapping. This mismatch can send operators to non-existent paths and lead to TLS config failures. Update the doc to match the actual cert-manager output or document the required copy/symlink step.</comment>

<file context>
@@ -0,0 +1,263 @@
+
+## Certificate paths per service
+
+All services read certificates from the shared `data/certs/` directory, mounted at different container paths. Certificates follow a `live/<domain>/` layout with `fullchain.pem` and `privkey.pem` files.
+
+### Host paths
</file context>
Fix with Cubic


### Prosody OpenMetrics endpoint

The endpoint is availabl
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This sentence is misspelled and incomplete. Clarify that the endpoint is available at /metrics before instructing users to add it to prometheus.yml.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/operations/monitoring.mdx, line 170:

<comment>This sentence is misspelled and incomplete. Clarify that the endpoint is available at `/metrics` before instructing users to add it to `prometheus.yml`.</comment>

<file context>
@@ -0,0 +1,300 @@
+
+### Prosody OpenMetrics endpoint
+
+The endpoint is availabl
+ to your `prometheus.yml` under `scrape_configs`:
+
</file context>
Fix with Cubic

## TLS configuration best practices

Detailed certificate management is covered in the [SSL/TLS page](/docs/operations/ssl-tls). This section
tore private keys in the repository. The `data/` directory is in `.gitignore`.
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Fix the typo in the TLS section—"tore private keys" is grammatically incorrect and makes the guidance unclear.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/operations/security.mdx, line 255:

<comment>Fix the typo in the TLS section—"tore private keys" is grammatically incorrect and makes the guidance unclear.</comment>

<file context>
@@ -0,0 +1,396 @@
+## TLS configuration best practices
+
+Detailed certificate management is covered in the [SSL/TLS page](/docs/operations/ssl-tls). This section
+tore private keys in the repository. The `data/` directory is in `.gitignore`.
+- In development, `just init` generates self-signed certificates. These are not suitable for production.
+
</file context>
Fix with Cubic


1. You set values in `.env` (copied from `.env.example`)
2. Running `just init` or `just dev` triggers `scripts/prepare-config.sh`
3. The script sources `.env`, then `.env.dev` if it exists (dev overrides)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This step implies .env.dev is always loaded when present, but prepare-config.sh only sources .env.dev when ATL_ENVIRONMENT=dev. The doc should reflect that condition to avoid misleading production setups.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/docs/content/docs/services/thelounge/configuration.mdx, line 14:

<comment>This step implies .env.dev is always loaded when present, but prepare-config.sh only sources .env.dev when ATL_ENVIRONMENT=dev. The doc should reflect that condition to avoid misleading production setups.</comment>

<file context>
@@ -0,0 +1,225 @@
+
+1. You set values in `.env` (copied from `.env.example`)
+2. Running `just init` or `just dev` triggers `scripts/prepare-config.sh`
+3. The script sources `.env`, then `.env.dev` if it exists (dev overrides)
+4. `envsubst` replaces all `${VAR}` references in `config.js.template`
+5. The rendered `config.js` is written to `data/thelounge/config.js`
</file context>
Fix with Cubic

Add security warnings to emphasize the importance of replacing
placeholder values in production environments. This change aims
to prevent the accidental use of default credentials in production,
which could lead to security vulnerabilities. Additionally, provide
recommendations for generating secure passwords and keys using
OpenSSL. Introduce a new placeholder for the Atheme bootstrap
account to guide users in setting up their environment correctly.
These updates enhance the security and usability of the environment
configuration file.
Comment out deprecated PBKDF2 v1 module to prevent its accidental use.
Add the `set_enforceprefix` command to improve operserv capabilities.
Enable exttarget match types for more flexible target matching.
Introduce `scram_mechanisms` to limit advertised SCRAM mechanisms.
Revise logging configuration to split logs by category, enhancing
production efficiency and security. Add a bootstrap SRA account for
initial operator setup, facilitating easier management of operator
privileges. These changes aim to improve security, maintainability,
and operational flexibility of the Atheme configuration.
Add XMPP_DOMAIN as a fallback for PROSODY_DOMAIN to provide more
flexibility in configuration. This allows the system to use either
environment variable, enhancing compatibility with different setups.

fix(prosody.cfg.lua): improve security and flexibility of OAuth2 registration key

Introduce a check for the OAuth2 registration key to ensure it is set
to a secure value. This prevents the use of default or insecure keys
in production environments, enhancing security.

chore(prosody.cfg.lua): update HTTP status endpoint access and headers

Add IPv6 localhost to the allowed CIDR list for the HTTP status
endpoint, ensuring compatibility with both IPv4 and IPv6. Remove
the deprecated X-XSS-Protection header, as modern browsers no longer
require it.
…nhanced security

The securityLevel configuration in the Mermaid library is updated from 'loose' to 'strict' to enhance security. This change mitigates potential security risks by enforcing stricter content policies, reducing the likelihood of malicious content being executed within the Mermaid diagrams.
…es Root Administrator account

Introduce a new task `sra-bootstrap` to guide users through the process of setting up the first Services Root Administrator (SRA) account. This addition provides a clear, step-by-step procedure to ensure that users can easily establish the initial SRA account, which is crucial for managing IRC services. The instructions also include security advice to remove the bootstrap account from the environment file once the setup is complete.
…on templates

Add the export of the DOLLAR variable to ensure that the dollar sign
can be used in configuration templates. This change is necessary for
templates that require the dollar sign to be interpreted correctly
within the script, enhancing the script's flexibility and usability.
The AUDIT_REPORT.md file is deleted as it is outdated and no longer
relevant to the current configuration or project requirements. Keeping
obsolete documentation can lead to confusion and misalignment with the
current state of the project. Removing it helps maintain a clean and
accurate codebase.
@github-actions
Copy link

sh-checker report

To get the full details, please check in the job output.

shellcheck errors
'shellcheck ' found no issues.

shfmt errors

'shfmt ' returned error 1 finding the following formatting issues:

----------
diff .github/scripts/docker.sh.orig .github/scripts/docker.sh
--- .github/scripts/docker.sh.orig
+++ .github/scripts/docker.sh
@@ -57,11 +57,11 @@
 shift || true
 
 case "$COMMAND" in
-  generate-pr-version)        generate_pr_version "$@" ;;
-  generate-release-version)   generate_release_version "$@" ;;
-  validate-build-config)      validate_build_config "$@" ;;
+  generate-pr-version) generate_pr_version "$@" ;;
+  generate-release-version) generate_release_version "$@" ;;
+  validate-build-config) validate_build_config "$@" ;;
   calculate-source-date-epoch) calculate_source_date_epoch "$@" ;;
-  generate-build-date)        generate_build_date "$@" ;;
+  generate-build-date) generate_build_date "$@" ;;
   *)
     echo "Usage: docker.sh {generate-pr-version|generate-release-version|validate-build-config|calculate-source-date-epoch|generate-build-date} [args...]"
     exit 1
diff apps/atheme/docker-entrypoint.sh.orig apps/atheme/docker-entrypoint.sh
--- apps/atheme/docker-entrypoint.sh.orig
+++ apps/atheme/docker-entrypoint.sh
@@ -7,9 +7,9 @@
 
 # Ensure we have proper permissions
 if [ "$(id -u)" = "0" ]; then
-    echo "ERROR: Atheme should not run as root for security reasons"
-    echo "Please run with a non-root user (UID 1000 recommended)"
-    exit 1
+  echo "ERROR: Atheme should not run as root for security reasons"
+  echo "Please run with a non-root user (UID 1000 recommended)"
+  exit 1
 fi
 
 # Create directories with proper ownership
@@ -17,9 +17,9 @@
 
 # Validate configuration exists
 if [ ! -f "/usr/local/atheme/etc/atheme.conf" ]; then
-    echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
-    echo "Please ensure the configuration is properly mounted"
-    exit 1
+  echo "ERROR: Configuration file not found at /usr/local/atheme/etc/atheme.conf"
+  echo "Please ensure the configuration is properly mounted"
+  exit 1
 fi
 
 # Clean up stale PID file
@@ -27,25 +27,25 @@
 
 # Validate database directory is writable
 if [ ! -w "/usr/local/atheme/data" ]; then
-    echo "ERROR: Data directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Data directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Validate logs directory is writable
 if [ ! -w "/usr/local/atheme/logs" ]; then
-    echo "ERROR: Logs directory is not writable"
-    echo "Please check volume mount permissions"
-    exit 1
+  echo "ERROR: Logs directory is not writable"
+  echo "Please check volume mount permissions"
+  exit 1
 fi
 
 # Check if this is first run (no database exists)
 if [ ! -f "/usr/local/atheme/data/services.db" ]; then
-    echo "First run detected - creating initial database..."
-    /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
-    echo "Database created successfully"
+  echo "First run detected - creating initial database..."
+  /usr/local/atheme/bin/atheme-services -b -c /usr/local/atheme/etc/atheme.conf -D /usr/local/atheme/data
+  echo "Database created successfully"
 else
-    echo "Existing database found - starting with existing data"
+  echo "Existing database found - starting with existing data"
 fi
 
 # Start Atheme services
diff apps/prosody/docker-entrypoint.sh.orig apps/prosody/docker-entrypoint.sh
--- apps/prosody/docker-entrypoint.sh.orig
+++ apps/prosody/docker-entrypoint.sh
@@ -29,21 +29,21 @@
 # ============================================================================
 
 log_info() {
-    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_warn() {
-    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
 }
 
 log_debug() {
-    if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
-        echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
-    fi
+  if [[ "${PROSODY_LOG_LEVEL:-info}" == "debug" ]]; then
+    echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
+  fi
 }
 
 # ============================================================================
@@ -51,45 +51,45 @@
 # ============================================================================
 
 validate_environment() {
-    log_info "Validating environment configuration..."
-
-    # Validate required domain
-    if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
-        log_error "PROSODY_DOMAIN is required but not set"
-        exit 1
-    fi
-
-    # Validate domain format
-    if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
-        log_error "Invalid domain format: ${PROSODY_DOMAIN}"
-        exit 1
-    fi
-
-    # Set default admin if not provided
-    if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
-        export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
-        log_info "Using default admin: ${PROSODY_ADMIN_JID}"
-    fi
-
-    # Validate database configuration for SQL storage
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        local required_vars=(
-            "PROSODY_DB_DRIVER"
-            "PROSODY_DB_NAME"
-            "PROSODY_DB_USER"
-            "PROSODY_DB_PASSWORD"
-            "PROSODY_DB_HOST"
-        )
-
-        for var in "${required_vars[@]}"; do
-            if [[ -z "${!var:-}" ]]; then
-                log_error "Database variable ${var} is required for SQL storage"
-                exit 1
-            fi
-        done
-    fi
-
-    log_info "Environment validation complete"
+  log_info "Validating environment configuration..."
+
+  # Validate required domain
+  if [[ -z "${PROSODY_DOMAIN:-}" ]]; then
+    log_error "PROSODY_DOMAIN is required but not set"
+    exit 1
+  fi
+
+  # Validate domain format
+  if [[ ! "${PROSODY_DOMAIN}" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
+    log_error "Invalid domain format: ${PROSODY_DOMAIN}"
+    exit 1
+  fi
+
+  # Set default admin if not provided
+  if [[ -z "${PROSODY_ADMIN_JID:-}" ]]; then
+    export PROSODY_ADMIN_JID="admin@${PROSODY_DOMAIN}"
+    log_info "Using default admin: ${PROSODY_ADMIN_JID}"
+  fi
+
+  # Validate database configuration for SQL storage
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    local required_vars=(
+      "PROSODY_DB_DRIVER"
+      "PROSODY_DB_NAME"
+      "PROSODY_DB_USER"
+      "PROSODY_DB_PASSWORD"
+      "PROSODY_DB_HOST"
+    )
+
+    for var in "${required_vars[@]}"; do
+      if [[ -z "${!var:-}" ]]; then
+        log_error "Database variable ${var} is required for SQL storage"
+        exit 1
+      fi
+    done
+  fi
+
+  log_info "Environment validation complete"
 }
 
 # ============================================================================
@@ -97,186 +97,186 @@
 # ============================================================================
 
 setup_directories() {
-    log_info "Setting up directories..."
-
-    local dirs=(
-        "$PROSODY_DATA_DIR"
-        "$PROSODY_LOG_DIR"
-        "$PROSODY_CERT_DIR"
-        "$PROSODY_UPLOAD_DIR"
-        "$(dirname "$PROSODY_PID_FILE")"
-    )
-
-    for dir in "${dirs[@]}"; do
-        if [[ ! -d "$dir" ]]; then
-            log_debug "Creating directory: $dir"
-            mkdir -p "$dir"
-        fi
-
-        # Ensure proper ownership (only if running as root)
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
-        fi
-    done
-
-    # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
-    # Prosody needs write access for SQLite (MAM, PEP, etc.)
-    if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
-        if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
-            log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
-        fi
-    fi
-
-    log_info "Directory setup complete"
+  log_info "Setting up directories..."
+
+  local dirs=(
+    "$PROSODY_DATA_DIR"
+    "$PROSODY_LOG_DIR"
+    "$PROSODY_CERT_DIR"
+    "$PROSODY_UPLOAD_DIR"
+    "$(dirname "$PROSODY_PID_FILE")"
+  )
+
+  for dir in "${dirs[@]}"; do
+    if [[ ! -d "$dir" ]]; then
+      log_debug "Creating directory: $dir"
+      mkdir -p "$dir"
+    fi
+
+    # Ensure proper ownership (only if running as root)
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$dir" 2> /dev/null || true
+    fi
+  done
+
+  # Critical: bind-mounted data dir (e.g. data/xmpp/data) may have host ownership;
+  # Prosody needs write access for SQLite (MAM, PEP, etc.)
+  if [[ $EUID -eq 0 ]] && [[ -d "${PROSODY_DATA_DIR}/data" ]]; then
+    if ! chown -R "$PROSODY_USER:$PROSODY_USER" "${PROSODY_DATA_DIR}/data"; then
+      log_warn "chown of data dir failed - SQL/MAM may be read-only (check volume permissions)"
+    fi
+  fi
+
+  log_info "Directory setup complete"
 }
 
 setup_certificates() {
-    log_info "Setting up SSL certificates..."
-
-    # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
-    local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
-    local cert_file="${live_dir}/fullchain.pem"
-    local key_file="${live_dir}/privkey.pem"
-
-    if [[ -f "$cert_file" && -f "$key_file" ]]; then
-        log_info "Certificates found for ${PROSODY_DOMAIN}"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file" 2> /dev/null || true
-        chmod 600 "$key_file" 2> /dev/null || true
-        # HTTPS service automatic discovery (Prosody Certificates doc):
-        # - https/fullchain.pem + https/privkey.pem (directory symlink)
-        # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
-    local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
-    local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
-    if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
-        log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
-        mkdir -p "$live_dir"
-        cp "$legacy_cert" "$cert_file"
-        cp "$legacy_key" "$key_file"
-        if [[ $EUID -eq 0 ]]; then
-            chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
-        fi
-        chmod 644 "$cert_file"
-        chmod 600 "$key_file"
-        ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-        ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-        return 0
-    fi
-
-    # Generate self-signed certificate for development/testing
-    log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
-    log_warn "This is suitable for development only - use proper certificates in production"
-
+  log_info "Setting up SSL certificates..."
+
+  # Use live/<domain>/ layout (matches init.sh and Let's Encrypt)
+  local live_dir="${PROSODY_CERT_DIR}/live/${PROSODY_DOMAIN}"
+  local cert_file="${live_dir}/fullchain.pem"
+  local key_file="${live_dir}/privkey.pem"
+
+  if [[ -f "$cert_file" && -f "$key_file" ]]; then
+    log_info "Certificates found for ${PROSODY_DOMAIN}"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    fi
+    chmod 644 "$cert_file" 2> /dev/null || true
+    chmod 600 "$key_file" 2> /dev/null || true
+    # HTTPS service automatic discovery (Prosody Certificates doc):
+    # - https/fullchain.pem + https/privkey.pem (directory symlink)
+    # - https.crt + https.key (file symlinks; alternate format Prosody may prefer)
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Fallback: check legacy layout (xmpp.localhost.crt / .key) for backwards compat
+  local legacy_cert="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.crt"
+  local legacy_key="${PROSODY_CERT_DIR}/${PROSODY_DOMAIN}.key"
+  if [[ -f "$legacy_cert" && -f "$legacy_key" ]]; then
+    log_info "Legacy certificates found, copying to live/${PROSODY_DOMAIN}/"
     mkdir -p "$live_dir"
-    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-        -keyout "$key_file" \
-        -out "$cert_file" \
-        -subj "/CN=${PROSODY_DOMAIN}" \
-        -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+    cp "$legacy_cert" "$cert_file"
+    cp "$legacy_key" "$key_file"
+    if [[ $EUID -eq 0 ]]; then
+      chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
     fi
     chmod 644 "$cert_file"
     chmod 600 "$key_file"
-
-    # HTTPS service automatic discovery
-    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2>/dev/null || true
-    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2>/dev/null || true
-
-    log_info "Self-signed certificate generated successfully"
+    ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+    ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+    return 0
+  fi
+
+  # Generate self-signed certificate for development/testing
+  log_warn "No certificates found, generating self-signed certificate for ${PROSODY_DOMAIN}"
+  log_warn "This is suitable for development only - use proper certificates in production"
+
+  mkdir -p "$live_dir"
+  openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+    -keyout "$key_file" \
+    -out "$cert_file" \
+    -subj "/CN=${PROSODY_DOMAIN}" \
+    -addext "subjectAltName=DNS:${PROSODY_DOMAIN},DNS:*.${PROSODY_DOMAIN},DNS:muc.${PROSODY_DOMAIN},DNS:upload.${PROSODY_DOMAIN},DNS:proxy.${PROSODY_DOMAIN},DNS:pubsub.${PROSODY_DOMAIN},DNS:bridge.${PROSODY_DOMAIN},DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$live_dir" || true
+  fi
+  chmod 644 "$cert_file"
+  chmod 600 "$key_file"
+
+  # HTTPS service automatic discovery
+  ln -sfn "live/${PROSODY_DOMAIN}" "${PROSODY_CERT_DIR}/https" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/fullchain.pem" "${PROSODY_CERT_DIR}/https.crt" 2> /dev/null || true
+  ln -sfn "live/${PROSODY_DOMAIN}/privkey.pem" "${PROSODY_CERT_DIR}/https.key" 2> /dev/null || true
+
+  log_info "Self-signed certificate generated successfully"
 }
 
 wait_for_database() {
-    if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
-        log_debug "Not using SQL storage, skipping database wait"
-        return 0
-    fi
-
-    local host="${PROSODY_DB_HOST}"
-    local port="${PROSODY_DB_PORT:-5432}"
-    local max_attempts=30
-    local attempt=1
-
-    log_info "Waiting for database connection to ${host}:${port}..."
-
-    while [[ $attempt -le $max_attempts ]]; do
-        if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
-            log_info "Database connection established"
-            return 0
-        fi
-
-        log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
-        sleep 2
-        ((attempt++))
-    done
-
-    log_error "Database connection timeout after ${max_attempts} attempts"
-    exit 1
+  if [[ "${PROSODY_STORAGE:-sql}" != "sql" ]]; then
+    log_debug "Not using SQL storage, skipping database wait"
+    return 0
+  fi
+
+  local host="${PROSODY_DB_HOST}"
+  local port="${PROSODY_DB_PORT:-5432}"
+  local max_attempts=30
+  local attempt=1
+
+  log_info "Waiting for database connection to ${host}:${port}..."
+
+  while [[ $attempt -le $max_attempts ]]; do
+    if timeout 5 bash -c "</dev/tcp/${host}/${port}" 2> /dev/null; then
+      log_info "Database connection established"
+      return 0
+    fi
+
+    log_debug "Database not ready, attempt ${attempt}/${max_attempts}"
+    sleep 2
+    ((attempt++))
+  done
+
+  log_error "Database connection timeout after ${max_attempts} attempts"
+  exit 1
 }
 
 validate_configuration() {
-    log_info "Validating Prosody configuration..."
-
-    # Check if config file exists
-    if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
-        log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
-        exit 1
-    fi
-
-    # Validate configuration using prosodyctl (allow warnings in development)
-    log_info "Validating Prosody configuration..."
-    if ! prosodyctl check config; then
-        log_error "Prosody configuration validation failed"
-        log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
-        if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
-            exit 1
-        else
-            log_warn "Development mode: continuing despite configuration warnings"
-        fi
-    fi
-
-    log_info "Configuration validation successful"
-}
-
-setup_community_modules() {
-    log_info "Setting up community modules..."
-
-    # Check if community modules source exists
-    local source_dir="/usr/local/lib/prosody/prosody-modules"
-    if [[ ! -d "$source_dir" ]]; then
-        log_warn "Community modules repository not found at $source_dir"
-        log_warn "Modules will need to be installed manually or via prosodyctl"
-        return 0
-    fi
-
-    # Check if modules are available from enabled directory
-    local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
-    if [[ ! -d "$enabled_dir" ]]; then
-        log_warn "Enabled community modules directory not found at $enabled_dir"
-        return 0
+  log_info "Validating Prosody configuration..."
+
+  # Check if config file exists
+  if [[ ! -f "$PROSODY_CONFIG_FILE" ]]; then
+    log_error "Configuration file not found: $PROSODY_CONFIG_FILE"
+    exit 1
+  fi
+
+  # Validate configuration using prosodyctl (allow warnings in development)
+  log_info "Validating Prosody configuration..."
+  if ! prosodyctl check config; then
+    log_error "Prosody configuration validation failed"
+    log_error "Please check your configuration file: $PROSODY_CONFIG_FILE"
+    if [[ "${PROSODY_DEVELOPMENT_MODE:-false}" != "true" ]]; then
+      exit 1
     else
-        log_info "Community modules found in $enabled_dir"
-        local module_count
-        module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
-        log_info "Enabled modules: $module_count"
-    fi
-
-    # Ensure proper ownership (only if running as root)
-    if [[ $EUID -eq 0 ]]; then
-        chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
-    fi
+      log_warn "Development mode: continuing despite configuration warnings"
+    fi
+  fi
+
+  log_info "Configuration validation successful"
+}
+
+setup_community_modules() {
+  log_info "Setting up community modules..."
+
+  # Check if community modules source exists
+  local source_dir="/usr/local/lib/prosody/prosody-modules"
+  if [[ ! -d "$source_dir" ]]; then
+    log_warn "Community modules repository not found at $source_dir"
+    log_warn "Modules will need to be installed manually or via prosodyctl"
+    return 0
+  fi
+
+  # Check if modules are available from enabled directory
+  local enabled_dir="/usr/local/lib/prosody/prosody-modules-enabled"
+  if [[ ! -d "$enabled_dir" ]]; then
+    log_warn "Enabled community modules directory not found at $enabled_dir"
+    return 0
+  else
+    log_info "Community modules found in $enabled_dir"
+    local module_count
+    module_count=$(find "$enabled_dir" -maxdepth 1 -type l 2> /dev/null | wc -l)
+    log_info "Enabled modules: $module_count"
+  fi
+
+  # Ensure proper ownership (only if running as root)
+  if [[ $EUID -eq 0 ]]; then
+    chown -R "$PROSODY_USER:$PROSODY_USER" "$source_dir" "$enabled_dir" 2> /dev/null || true
+  fi
 }
 
 # ============================================================================
@@ -285,28 +285,28 @@
 
 # shellcheck disable=SC2317,SC2329  # Function is called by signal handlers via trap
 cleanup() {
-    log_info "Received shutdown signal, stopping Prosody..."
-
-    if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
-        # Send SIGTERM for graceful shutdown
-        kill -TERM "$PROSODY_PID" 2> /dev/null || true
-
-        # Wait for graceful shutdown (max 30 seconds)
-        local timeout=30
-        while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
-            sleep 1
-            ((timeout--))
-        done
-
-        # Force kill if still running
-        if kill -0 "$PROSODY_PID" 2> /dev/null; then
-            log_warn "Prosody did not shut down gracefully, forcing termination"
-            kill -KILL "$PROSODY_PID" 2> /dev/null || true
-        fi
-    fi
-
-    log_info "Prosody shutdown complete"
-    exit 0
+  log_info "Received shutdown signal, stopping Prosody..."
+
+  if [[ -n "${PROSODY_PID:-}" ]] && kill -0 "$PROSODY_PID" 2> /dev/null; then
+    # Send SIGTERM for graceful shutdown
+    kill -TERM "$PROSODY_PID" 2> /dev/null || true
+
+    # Wait for graceful shutdown (max 30 seconds)
+    local timeout=30
+    while kill -0 "$PROSODY_PID" 2> /dev/null && [[ $timeout -gt 0 ]]; do
+      sleep 1
+      ((timeout--))
+    done
+
+    # Force kill if still running
+    if kill -0 "$PROSODY_PID" 2> /dev/null; then
+      log_warn "Prosody did not shut down gracefully, forcing termination"
+      kill -KILL "$PROSODY_PID" 2> /dev/null || true
+    fi
+  fi
+
+  log_info "Prosody shutdown complete"
+  exit 0
 }
 
 # Setup signal handlers
@@ -317,57 +317,57 @@
 # ============================================================================
 
 main() {
-    log_info "Starting Professional Prosody XMPP Server..."
-
-    # Display version information
-    local prosody_version
-    prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
-    log_info "Prosody version: $prosody_version"
-
-    # Prefer mounted config if present
-    if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
-        log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
-        cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
-        chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-        chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
-    fi
-
-    # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
-    if [[ -d "/etc/prosody/config/conf.d" ]]; then
-        log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
-        mkdir -p "/etc/prosody/conf.d"
-        rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
-        chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
-        find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
-    fi
-
-    # Environment and setup
-    validate_environment
-    setup_directories
-    setup_certificates
-    wait_for_database
-    validate_configuration
-    setup_community_modules
-
-    # Display configuration summary
-    log_info "Configuration summary:"
-    log_info "  Domain: ${PROSODY_DOMAIN}"
-    log_info "  Admins: ${PROSODY_ADMIN_JID}"
-    log_info "  Storage: ${PROSODY_STORAGE:-sql}"
-    log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
-    log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
-
-    if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
-        log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
-    fi
-
-    # Start Prosody
-    log_info "Starting Prosody XMPP server..."
-
-    # Switch to prosody user and start in foreground
-    exec gosu "$PROSODY_USER" prosody \
-        --config="$PROSODY_CONFIG_FILE" \
-        --foreground
+  log_info "Starting Professional Prosody XMPP Server..."
+
+  # Display version information
+  local prosody_version
+  prosody_version=$(prosody --version 2> /dev/null | head -n1 || echo "Unknown")
+  log_info "Prosody version: $prosody_version"
+
+  # Prefer mounted config if present
+  if [[ -f "/etc/prosody/config/prosody.cfg.lua" ]]; then
+    log_info "Detected mounted config at /etc/prosody/config/prosody.cfg.lua; syncing to ${PROSODY_CONFIG_FILE}"
+    cp -f "/etc/prosody/config/prosody.cfg.lua" "${PROSODY_CONFIG_FILE}"
+    chown root:prosody "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+    chmod 640 "${PROSODY_CONFIG_FILE}" 2> /dev/null || true
+  fi
+
+  # If a mounted conf.d exists, sync it into /etc/prosody/conf.d so Include() works
+  if [[ -d "/etc/prosody/config/conf.d" ]]; then
+    log_info "Detected mounted conf.d include directory; syncing to /etc/prosody/conf.d"
+    mkdir -p "/etc/prosody/conf.d"
+    rsync -a --delete "/etc/prosody/config/conf.d/" "/etc/prosody/conf.d/"
+    chown -R root:prosody "/etc/prosody/conf.d" 2> /dev/null || true
+    find /etc/prosody/conf.d -type f -name '*.lua' -exec chmod 640 {} + 2> /dev/null || true
+  fi
+
+  # Environment and setup
+  validate_environment
+  setup_directories
+  setup_certificates
+  wait_for_database
+  validate_configuration
+  setup_community_modules
+
+  # Display configuration summary
+  log_info "Configuration summary:"
+  log_info "  Domain: ${PROSODY_DOMAIN}"
+  log_info "  Admins: ${PROSODY_ADMIN_JID}"
+  log_info "  Storage: ${PROSODY_STORAGE:-sql}"
+  log_info "  Log level: ${PROSODY_LOG_LEVEL:-info}"
+  log_info "  Allow registration: ${PROSODY_ALLOW_REGISTRATION:-false} (Portal provisions via mod_http_admin_api when false)"
+
+  if [[ "${PROSODY_STORAGE:-sql}" == "sql" ]]; then
+    log_info "  Database: ${PROSODY_DB_DRIVER} on ${PROSODY_DB_HOST}:${PROSODY_DB_PORT:-5432}"
+  fi
+
+  # Start Prosody
+  log_info "Starting Prosody XMPP server..."
+
+  # Switch to prosody user and start in foreground
+  exec gosu "$PROSODY_USER" prosody \
+    --config="$PROSODY_CONFIG_FILE" \
+    --foreground
 }
 
 # ============================================================================
@@ -376,8 +376,8 @@
 
 # Ensure we're running as root initially (for setup)
 if [[ $EUID -ne 0 ]]; then
-    log_error "This script must be run as root for initial setup"
-    exit 1
+  log_error "This script must be run as root for initial setup"
+  exit 1
 fi
 
 # Execute main function
diff apps/unrealircd/docker-entrypoint.sh.orig apps/unrealircd/docker-entrypoint.sh
--- apps/unrealircd/docker-entrypoint.sh.orig
+++ apps/unrealircd/docker-entrypoint.sh
@@ -21,8 +21,8 @@
 
 # Validate config exists
 if [ ! -f "/home/unrealircd/unrealircd/config/unrealircd.conf" ]; then
-    echo "ERROR: Configuration file not found!"
-    exit 1
+  echo "ERROR: Configuration file not found!"
+  exit 1
 fi
 
 # Ownership and permissions are handled by Containerfile and user switching
@@ -29,21 +29,21 @@
 
 # Handle commands
 case "$1" in
-    start)
-        shift
-        echo "Starting UnrealIRCd in foreground..."
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        else
-            exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
-        fi
-        ;;
-    *)
-        echo "Running command: $1"
-        if [ "$(id -u)" = "0" ]; then
-            exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
-        else
-            exec /home/unrealircd/unrealircd/unrealircd "$@"
-        fi
-        ;;
+  start)
+    shift
+    echo "Starting UnrealIRCd in foreground..."
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    else
+      exec /home/unrealircd/unrealircd/bin/unrealircd -F "$@"
+    fi
+    ;;
+  *)
+    echo "Running command: $1"
+    if [ "$(id -u)" = "0" ]; then
+      exec su-exec "${USER_ID}:${GROUP_ID}" /home/unrealircd/unrealircd/unrealircd "$@"
+    else
+      exec /home/unrealircd/unrealircd/unrealircd "$@"
+    fi
+    ;;
 esac
diff apps/unrealircd/scripts/manage-modules.sh.orig apps/unrealircd/scripts/manage-modules.sh
--- apps/unrealircd/scripts/manage-modules.sh.orig
+++ apps/unrealircd/scripts/manage-modules.sh
@@ -29,280 +29,280 @@
 
 # Check if we're running as the correct user (unrealircd, typically UID from PUID)
 check_user() {
-    local expected_uid="${PUID:-1000}"
-    if [ "$(id -u)" != "$expected_uid" ]; then
-        print_error "This script must run as the unrealircd user (UID ${expected_uid})"
-        print_error "Current user: $(id -un) (UID $(id -u))"
-        exit 1
-    fi
+  local expected_uid="${PUID:-1000}"
+  if [ "$(id -u)" != "$expected_uid" ]; then
+    print_error "This script must run as the unrealircd user (UID ${expected_uid})"
+    print_error "Current user: $(id -un) (UID $(id -u))"
+    exit 1
+  fi
 }
 
 # Check if UnrealIRCd is running
 check_unrealircd_running() {
-    if pgrep -f unrealircd >/dev/null; then
-        print_warning "UnrealIRCd is currently running"
-        print_warning "Some operations may require a restart to take effect"
-        return 0
-    else
-        print_status "UnrealIRCd is not currently running"
-        return 1
-    fi
+  if pgrep -f unrealircd > /dev/null; then
+    print_warning "UnrealIRCd is currently running"
+    print_warning "Some operations may require a restart to take effect"
+    return 0
+  else
+    print_status "UnrealIRCd is not currently running"
+    return 1
+  fi
 }
 
 # List available modules
 list_modules() {
-    print_header "Available Contrib Modules"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Fetching latest module list..."
-    if [ -d "$CONTRIB_DIR" ]; then
-        cd "$CONTRIB_DIR" && git pull --quiet origin main 2>/dev/null || true
-    fi
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    print_status "Available modules:"
-    echo
-    "$UNREALIRCD_BIN" module list 2>/dev/null || {
-        print_warning "Module manager not available, showing contrib directory contents:"
-        for item in "$CONTRIB_DIR"/*; do
-            if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
-                basename "$item"
-            fi
-        done | sort
-    }
+  print_header "Available Contrib Modules"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Fetching latest module list..."
+  if [ -d "$CONTRIB_DIR" ]; then
+    cd "$CONTRIB_DIR" && git pull --quiet origin main 2> /dev/null || true
+  fi
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  print_status "Available modules:"
+  echo
+  "$UNREALIRCD_BIN" module list 2> /dev/null || {
+    print_warning "Module manager not available, showing contrib directory contents:"
+    for item in "$CONTRIB_DIR"/*; do
+      if [ -d "$item" ] && [ "$(basename "$item")" != "*" ]; then
+        basename "$item"
+      fi
+    done | sort
+  }
 }
 
 # Show module information
 show_module_info() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 info <module-name>"
-        return 1
-    fi
-
-    print_header "Module Information: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to get info via module manager first
-    if "$UNREALIRCD_BIN" module info "third/$module_name" 2>/dev/null; then
-        return 0
-    fi
-
-    # Fallback to showing contrib directory info
-    if [ -d "$CONTRIB_DIR/$module_name" ]; then
-        print_status "Module found in contrib directory:"
-        ls -la "$CONTRIB_DIR/$module_name"
-
-        if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README.md"
-        fi
-
-        if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
-            echo
-            print_status "README content:"
-            cat "$CONTRIB_DIR/$module_name/README"
-        fi
-    else
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 info <module-name>"
+    return 1
+  fi
+
+  print_header "Module Information: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to get info via module manager first
+  if "$UNREALIRCD_BIN" module info "third/$module_name" 2> /dev/null; then
+    return 0
+  fi
+
+  # Fallback to showing contrib directory info
+  if [ -d "$CONTRIB_DIR/$module_name" ]; then
+    print_status "Module found in contrib directory:"
+    ls -la "$CONTRIB_DIR/$module_name"
+
+    if [ -f "$CONTRIB_DIR/$module_name/README.md" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README.md"
+    fi
+
+    if [ -f "$CONTRIB_DIR/$module_name/README" ]; then
+      echo
+      print_status "README content:"
+      cat "$CONTRIB_DIR/$module_name/README"
+    fi
+  else
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
 }
 
 # Install a module
 install_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 install <module-name>"
-        return 1
-    fi
-
-    print_header "Installing Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Check if module is already installed
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        print_warning "Module '$module_name' is already installed"
-        return 0
-    fi
-
-    # Try to install via module manager
-    print_status "Installing via module manager..."
-    if "$UNREALIRCD_BIN" module install "third/$module_name"; then
-        print_success "Module '$module_name' installed successfully"
-
-        # Check if we need to add loadmodule to config
-        if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
-            print_status "Checking if loadmodule line needs to be added..."
-            if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
-                print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-                print_warning "Then REHASH or restart UnrealIRCd"
-            fi
-        fi
-
-        return 0
-    fi
-
-    # Fallback to manual installation
-    print_warning "Module manager failed, attempting manual installation..."
-
-    if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
-        print_error "Module '$module_name' not found in contrib directory"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR/$module_name" || exit 1
-
-    if [ -f "Makefile" ]; then
-        print_status "Compiling module..."
-        make clean 2>/dev/null || true
-        make
-
-        if [ -f "$module_name.so" ]; then
-            print_status "Installing module..."
-            cp "$module_name.so" "$MODULES_DIR/third/"
-            print_success "Module '$module_name' installed manually"
-
-            print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
-            print_warning "Then REHASH or restart UnrealIRCd"
-        else
-            print_error "Module compilation failed"
-            return 1
-        fi
-    else
-        print_error "No Makefile found for module '$module_name'"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 install <module-name>"
+    return 1
+  fi
+
+  print_header "Installing Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Check if module is already installed
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    print_warning "Module '$module_name' is already installed"
+    return 0
+  fi
+
+  # Try to install via module manager
+  print_status "Installing via module manager..."
+  if "$UNREALIRCD_BIN" module install "third/$module_name"; then
+    print_success "Module '$module_name' installed successfully"
+
+    # Check if we need to add loadmodule to config
+    if [ -f "$CONFIG_DIR/unrealircd.conf" ]; then
+      print_status "Checking if loadmodule line needs to be added..."
+      if ! grep -q "loadmodule.*$module_name" "$CONFIG_DIR/unrealircd.conf"; then
+        print_warning "You may need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+        print_warning "Then REHASH or restart UnrealIRCd"
+      fi
+    fi
+
+    return 0
+  fi
+
+  # Fallback to manual installation
+  print_warning "Module manager failed, attempting manual installation..."
+
+  if [ ! -d "$CONTRIB_DIR/$module_name" ]; then
+    print_error "Module '$module_name' not found in contrib directory"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR/$module_name" || exit 1
+
+  if [ -f "Makefile" ]; then
+    print_status "Compiling module..."
+    make clean 2> /dev/null || true
+    make
+
+    if [ -f "$module_name.so" ]; then
+      print_status "Installing module..."
+      cp "$module_name.so" "$MODULES_DIR/third/"
+      print_success "Module '$module_name' installed manually"
+
+      print_warning "You need to add 'loadmodule \"third/$module_name\";' to your unrealircd.conf"
+      print_warning "Then REHASH or restart UnrealIRCd"
+    else
+      print_error "Module compilation failed"
+      return 1
+    fi
+  else
+    print_error "No Makefile found for module '$module_name'"
+    return 1
+  fi
 }
 
 # Uninstall a module
 uninstall_module() {
-    local module_name="$1"
-
-    if [ -z "$module_name" ]; then
-        print_error "Module name required"
-        print_error "Usage: $0 uninstall <module-name>"
-        return 1
-    fi
-
-    print_header "Uninstalling Module: $module_name"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    # Try to uninstall via module manager first
-    if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2>/dev/null; then
-        print_success "Module '$module_name' uninstalled successfully"
-        return 0
-    fi
-
-    # Fallback to manual removal
-    print_warning "Module manager failed, attempting manual removal..."
-
-    if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
-        rm -f "$MODULES_DIR/third/$module_name.so"
-        print_success "Module '$module_name' removed manually"
-
-        print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
-        print_warning "Then REHASH or restart UnrealIRCd"
-    else
-        print_error "Module '$module_name' not found in modules directory"
-        return 1
-    fi
+  local module_name="$1"
+
+  if [ -z "$module_name" ]; then
+    print_error "Module name required"
+    print_error "Usage: $0 uninstall <module-name>"
+    return 1
+  fi
+
+  print_header "Uninstalling Module: $module_name"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  # Try to uninstall via module manager first
+  if "$UNREALIRCD_BIN" module uninstall "third/$module_name" 2> /dev/null; then
+    print_success "Module '$module_name' uninstalled successfully"
+    return 0
+  fi
+
+  # Fallback to manual removal
+  print_warning "Module manager failed, attempting manual removal..."
+
+  if [ -f "$MODULES_DIR/third/$module_name.so" ]; then
+    rm -f "$MODULES_DIR/third/$module_name.so"
+    print_success "Module '$module_name' removed manually"
+
+    print_warning "You should remove 'loadmodule \"third/$module_name\";' from your unrealircd.conf"
+    print_warning "Then REHASH or restart UnrealIRCd"
+  else
+    print_error "Module '$module_name' not found in modules directory"
+    return 1
+  fi
 }
 
 # Upgrade modules
 upgrade_modules() {
-    local module_name="$1"
-
-    print_header "Upgrading Modules"
-
-    cd "$UNREALIRCD_DIR" || exit 1
-
-    if [ -n "$module_name" ]; then
-        print_status "Upgrading specific module: $module_name"
-        if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2>/dev/null; then
-            print_success "Module '$module_name' upgraded successfully"
-        else
-            print_error "Failed to upgrade module '$module_name'"
-            return 1
-        fi
-    else
-        print_status "Upgrading all modules..."
-        if "$UNREALIRCD_BIN" module upgrade 2>/dev/null; then
-            print_success "All modules upgraded successfully"
-        else
-            print_error "Failed to upgrade modules"
-            return 1
-        fi
-    fi
-
-    print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
+  local module_name="$1"
+
+  print_header "Upgrading Modules"
+
+  cd "$UNREALIRCD_DIR" || exit 1
+
+  if [ -n "$module_name" ]; then
+    print_status "Upgrading specific module: $module_name"
+    if "$UNREALIRCD_BIN" module upgrade "third/$module_name" 2> /dev/null; then
+      print_success "Module '$module_name' upgraded successfully"
+    else
+      print_error "Failed to upgrade module '$module_name'"
+      return 1
+    fi
+  else
+    print_status "Upgrading all modules..."
+    if "$UNREALIRCD_BIN" module upgrade 2> /dev/null; then
+      print_success "All modules upgraded successfully"
+    else
+      print_error "Failed to upgrade modules"
+      return 1
+    fi
+  fi
+
+  print_warning "After upgrading, you may need to REHASH or restart UnrealIRCd"
 }
 
 # Update contrib repository
 update_contrib() {
-    print_header "Updating Contrib Repository"
-
-    if [ ! -d "$CONTRIB_DIR" ]; then
-        print_error "Contrib directory not found: $CONTRIB_DIR"
-        return 1
-    fi
-
-    cd "$CONTRIB_DIR" || exit 1
-
-    print_status "Pulling latest changes from unrealircd-contrib..."
-    if git pull --quiet origin main; then
-        print_success "Contrib repository updated successfully"
-        print_status "New modules may now be available"
-    else
-        print_error "Failed to update contrib repository"
-        return 1
-    fi
+  print_header "Updating Contrib Repository"
+
+  if [ ! -d "$CONTRIB_DIR" ]; then
+    print_error "Contrib directory not found: $CONTRIB_DIR"
+    return 1
+  fi
+
+  cd "$CONTRIB_DIR" || exit 1
+
+  print_status "Pulling latest changes from unrealircd-contrib..."
+  if git pull --quiet origin main; then
+    print_success "Contrib repository updated successfully"
+    print_status "New modules may now be available"
+  else
+    print_error "Failed to update contrib repository"
+    return 1
+  fi
 }
 
 # Show installed modules
 show_installed() {
-    print_header "Installed Modules"
-
-    if [ ! -d "$MODULES_DIR/third" ]; then
-        print_status "No third-party modules installed"
-        return 0
-    fi
-
-    cd "$MODULES_DIR/third" || exit 1
-
-    local count=0
-    for module in *.so; do
-        if [ -f "$module" ]; then
-            echo "  - third/${module%.so}"
-            ((count++))
-        fi
-    done
-
-    if [ $count -eq 0 ]; then
-        print_status "No third-party modules installed"
-    else
-        print_status "Total installed modules: $count"
-    fi
+  print_header "Installed Modules"
+
+  if [ ! -d "$MODULES_DIR/third" ]; then
+    print_status "No third-party modules installed"
+    return 0
+  fi
+
+  cd "$MODULES_DIR/third" || exit 1
+
+  local count=0
+  for module in *.so; do
+    if [ -f "$module" ]; then
+      echo "  - third/${module%.so}"
+      ((count++))
+    fi
+  done
+
+  if [ $count -eq 0 ]; then
+    print_status "No third-party modules installed"
+  else
+    print_status "Total installed modules: $count"
+  fi
 }
 
 # Show usage
 show_usage() {
-    cat <<EOF
+  cat << EOF
 UnrealIRCd Contrib Modules Management Script
 
 Usage: $0 <command> [options]
@@ -344,40 +344,40 @@
 
 # Main execution
 main() {
-    check_user
-
-    case "${1:-help}" in
+  check_user
+
+  case "${1:-help}" in
     list)
-        list_modules
-        ;;
+      list_modules
+      ;;
     info)
-        show_module_info "$2"
-        ;;
+      show_module_info "$2"
+      ;;
     install)
-        install_module "$2"
-        ;;
+      install_module "$2"
+      ;;
     uninstall)
-        uninstall_module "$2"
-        ;;
+      uninstall_module "$2"
+      ;;
     upgrade)
-        upgrade_modules "$2"
-        ;;
+      upgrade_modules "$2"
+      ;;
     update)
-        update_contrib
-        ;;
+      update_contrib
+      ;;
     installed)
-        show_installed
-        ;;
+      show_installed
+      ;;
     help | --help | -h)
-        show_usage
-        ;;
+      show_usage
+      ;;
     *)
-        print_error "Unknown command: $1"
-        echo
-        show_usage
-        exit 1
-        ;;
-    esac
+      print_error "Unknown command: $1"
+      echo
+      show_usage
+      exit 1
+      ;;
+  esac
 }
 
 # Run main function with all arguments
diff scripts/cert-manager/run.sh.orig scripts/cert-manager/run.sh
--- scripts/cert-manager/run.sh.orig
+++ scripts/cert-manager/run.sh
@@ -9,26 +9,26 @@
 
 # Validate domain format (alphanumeric, hyphens, dots only - prevent injection)
 case "$ROOT_DOMAIN" in
-    *[!a-zA-Z0-9.-]*)
-        echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Domain cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9.-]*)
+    echo "ERROR: Invalid domain format (contains disallowed characters): $ROOT_DOMAIN"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Domain cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Validate email format (basic check - no spaces or shell metacharacters)
 case "$EMAIL" in
-    *[!a-zA-Z0-9@._+-]*)
-        echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
-        exit 1
-        ;;
-    "")
-        echo "ERROR: Email cannot be empty"
-        exit 1
-        ;;
+  *[!a-zA-Z0-9@._+-]*)
+    echo "ERROR: Invalid email format (contains disallowed characters): $EMAIL"
+    exit 1
+    ;;
+  "")
+    echo "ERROR: Email cannot be empty"
+    exit 1
+    ;;
 esac
 
 # Lego outputs: certificates/_.<domain>.crt and _.<domain>.key for wildcards
@@ -38,9 +38,9 @@
 
 # Ensure we have credentials
 if [ -z "$CLOUDFLARE_DNS_API_TOKEN" ]; then
-    echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
-    echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
-    exec sleep infinity
+  echo "Warning: CLOUDFLARE_DNS_API_TOKEN is not set. Certificate generation skipped."
+  echo "To enable Let's Encrypt, set CLOUDFLARE_DNS_API_TOKEN in your .env file."
+  exec sleep infinity
 fi
 
 # Initial issuance
@@ -49,8 +49,8 @@
 
 # Renewal loop
 while true; do
-    echo "Sleeping for 24 hours..."
-    sleep 86400
-    echo "Checking for renewal..."
-    lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
+  echo "Sleeping for 24 hours..."
+  sleep 86400
+  echo "Checking for renewal..."
+  lego --email "$EMAIL" --dns cloudflare --domains "$WILDCARD_DOMAIN" --domains "$ROOT_DOMAIN" --path "$DATA_DIR" --accept-tos renew
 done
diff scripts/gencloak-update-env.sh.orig scripts/gencloak-update-env.sh
--- scripts/gencloak-update-env.sh.orig
+++ scripts/gencloak-update-env.sh
@@ -12,23 +12,23 @@
 
 cd "$PROJECT_ROOT"
 
-output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2>/dev/null)
+output=$(docker compose -f compose.yaml -p atl-chat run --rm atl-irc-server gencloak 2> /dev/null)
 echo "$output"
 
 mapfile -t keys < <(echo "$output" | grep -oE '"[^"]{50,}"' | tr -d '"')
 if [ ${#keys[@]} -ne 3 ]; then
-    echo "Failed to parse 3 cloak keys from gencloak output"
-    exit 1
+  echo "Failed to parse 3 cloak keys from gencloak output"
+  exit 1
 fi
 
 [ -f "$ENV_FILE" ] || cp .env.example "$ENV_FILE"
 
 if grep -q '^IRC_CLOAK_KEY_1=' "$ENV_FILE"; then
-    sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
-    sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_1=.*|IRC_CLOAK_KEY_1=${keys[0]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_2=.*|IRC_CLOAK_KEY_2=${keys[1]}|" "$ENV_FILE"
+  sed -i "s|^IRC_CLOAK_KEY_3=.*|IRC_CLOAK_KEY_3=${keys[2]}|" "$ENV_FILE"
 else
-    sed -i "/^IRC_CLOAK_PREFIX=/a\\
+  sed -i "/^IRC_CLOAK_PREFIX=/a\\
 # Cloak keys - keep secret; must be identical on all servers. Generate with: just irc gencloak\\
 IRC_CLOAK_KEY_1=${keys[0]}\\
 IRC_CLOAK_KEY_2=${keys[1]}\\
diff scripts/init.sh.orig scripts/init.sh
--- scripts/init.sh.orig
+++ scripts/init.sh
@@ -11,16 +11,16 @@
 
 # Load environment variables: .env (base) then .env.dev (overrides for just dev)
 if [ -f "$PROJECT_ROOT/.env" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env"
+  set +a
 fi
 if [ -f "$PROJECT_ROOT/.env.dev" ]; then
-    set -a
-    # shellcheck disable=SC1091
-    source "$PROJECT_ROOT/.env.dev"
-    set +a
+  set -a
+  # shellcheck disable=SC1091
+  source "$PROJECT_ROOT/.env.dev"
+  set +a
 fi
 
 # Ensure Atheme JSON-RPC port has a default (for existing .env without it)
@@ -35,386 +35,386 @@
 
 # Helper functions
 log_info() {
-    echo -e "${BLUE}[INFO]${NC} $1"
+  echo -e "${BLUE}[INFO]${NC} $1"
 }
 
 log_success() {
-    echo -e "${GREEN}[SUCCESS]${NC} $1"
+  echo -e "${GREEN}[SUCCESS]${NC} $1"
 }
 
 log_warning() {
-    echo -e "${YELLOW}[WARNING]${NC} $1"
+  echo -e "${YELLOW}[WARNING]${NC} $1"
 }
 
 log_error() {
-    echo -e "${RED}[ERROR]${NC} $1"
+  echo -e "${RED}[ERROR]${NC} $1"
 }
 
 # Function to create directory structure
 create_directories() {
-    log_info "Creating required directory structure..."
-
-    # Data directories (must match compose volume mounts)
-    local data_dirs=(
-        "$PROJECT_ROOT/data/irc/data"
-        "$PROJECT_ROOT/data/irc/logs"
-        "$PROJECT_ROOT/data/irc/webpanel-data"
-        "$PROJECT_ROOT/data/atheme/data"
-        "$PROJECT_ROOT/data/atheme/logs"
-        "$PROJECT_ROOT/data/xmpp/data"
-        "$PROJECT_ROOT/data/xmpp/logs"
-        "$PROJECT_ROOT/data/xmpp/uploads"
-        "$PROJECT_ROOT/data/thelounge"
-        "$PROJECT_ROOT/data/certs"
-    )
-
-    # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
-    local ssl_dirs=(
-        "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    )
-
-    # Create all directories
-    for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
-        if [ ! -d "$dir" ]; then
-            mkdir -p "$dir"
-            log_info "Created directory: $dir"
-    else
-            log_info "Directory already exists: $dir"
+  log_info "Creating required directory structure..."
+
+  # Data directories (must match compose volume mounts)
+  local data_dirs=(
+    "$PROJECT_ROOT/data/irc/data"
+    "$PROJECT_ROOT/data/irc/logs"
+    "$PROJECT_ROOT/data/irc/webpanel-data"
+    "$PROJECT_ROOT/data/atheme/data"
+    "$PROJECT_ROOT/data/atheme/logs"
+    "$PROJECT_ROOT/data/xmpp/data"
+    "$PROJECT_ROOT/data/xmpp/logs"
+    "$PROJECT_ROOT/data/xmpp/uploads"
+    "$PROJECT_ROOT/data/thelounge"
+    "$PROJECT_ROOT/data/certs"
+  )
+
+  # SSL/TLS: config/tls holds CA bundle only; server certs live in data/certs (mounted as certs/)
+  local ssl_dirs=(
+    "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  )
+
+  # Create all directories
+  for dir in "${data_dirs[@]}" "${ssl_dirs[@]}"; do
+    if [ ! -d "$dir" ]; then
+      mkdir -p "$dir"
+      log_info "Created directory: $dir"
+    else
+      log_info "Directory already exists: $dir"
     fi
   done
 
-    log_success "Directory structure created successfully"
+  log_success "Directory structure created successfully"
 }
 
 # Function to set proper permissions
 set_permissions() {
-    log_info "Setting proper permissions..."
-
-    # Get current user ID and group ID
-    local current_uid
-    local current_gid
-    # Use actual user ID instead of hardcoded values
-    current_uid=$(id -u)
-    current_gid=$(id -g)
-
-    # Use same UID for all services to avoid permission issues
-    local atheme_uid=$current_uid
-    local atheme_gid=$current_gid
-
-    log_info "Current user: $current_uid:$current_gid"
-
-    # Set ownership for data directories (if they exist)
-    if [ -d "$PROJECT_ROOT/data" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
-        # Ensure directories are writable by owner (critical for socket creation)
-        find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
-        log_info "Set ownership for data directory"
-  fi
-
-    # Set ownership for IRC data directory
-    if [ -d "$PROJECT_ROOT/data/irc" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
-        chmod -R 755 "$PROJECT_ROOT/data/irc"
-        log_info "Set permissions for IRC data directory"
-  fi
-
-    # Set ownership for Atheme data directory with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
-        chmod 755 "$PROJECT_ROOT/data/atheme"
-        log_info "Set permissions for Atheme data directory"
-  fi
-
-    # Set ownership for UnrealIRCd logs
-    if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
-        sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
-        chmod 755 "$PROJECT_ROOT/data/irc/logs"
-        log_info "Set ownership for UnrealIRCd logs"
-    fi
-
-    # Set ownership for Atheme logs with correct UID
-    if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
-        sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
-        chmod 755 "$PROJECT_ROOT/data/atheme/logs"
-        log_info "Set permissions for Atheme logs directory"
-    fi
-
-    # Set permissions for SSL certificates
-    if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
-        sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
-    fi
-    sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
-    chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
-    log_info "Set permissions for SSL directory"
-
-    # Make sure data directories are writable
-    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
-    find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
-
-    log_success "Permissions set successfully"
+  log_info "Setting proper permissions..."
+
+  # Get current user ID and group ID
+  local current_uid
+  local current_gid
+  # Use actual user ID instead of hardcoded values
+  current_uid=$(id -u)
+  current_gid=$(id -g)
+
+  # Use same UID for all services to avoid permission issues
+  local atheme_uid=$current_uid
+  local atheme_gid=$current_gid
+
+  log_info "Current user: $current_uid:$current_gid"
+
+  # Set ownership for data directories (if they exist)
+  if [ -d "$PROJECT_ROOT/data" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data"
+    # Ensure directories are writable by owner (critical for socket creation)
+    find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \;
+    log_info "Set ownership for data directory"
+  fi
+
+  # Set ownership for IRC data directory
+  if [ -d "$PROJECT_ROOT/data/irc" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc"
+    chmod -R 755 "$PROJECT_ROOT/data/irc"
+    log_info "Set permissions for IRC data directory"
+  fi
+
+  # Set ownership for Atheme data directory with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme"
+    chmod 755 "$PROJECT_ROOT/data/atheme"
+    log_info "Set permissions for Atheme data directory"
+  fi
+
+  # Set ownership for UnrealIRCd logs
+  if [ -d "$PROJECT_ROOT/data/irc/logs" ]; then
+    sudo chown -R "$current_uid:$current_gid" "$PROJECT_ROOT/data/irc/logs"
+    chmod 755 "$PROJECT_ROOT/data/irc/logs"
+    log_info "Set ownership for UnrealIRCd logs"
+  fi
+
+  # Set ownership for Atheme logs with correct UID
+  if [ -d "$PROJECT_ROOT/data/atheme/logs" ]; then
+    sudo chown -R "$atheme_uid:$atheme_gid" "$PROJECT_ROOT/data/atheme/logs"
+    chmod 755 "$PROJECT_ROOT/data/atheme/logs"
+    log_info "Set permissions for Atheme logs directory"
+  fi
+
+  # Set permissions for SSL certificates
+  if [ ! -d "$PROJECT_ROOT/apps/unrealircd/config/tls" ]; then
+    sudo mkdir -p "$PROJECT_ROOT/apps/unrealircd/config/tls"
+  fi
+  sudo chown "$current_uid:$current_gid" "$PROJECT_ROOT/apps/unrealircd/config/tls" 2> /dev/null || true
+  chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || sudo chmod 755 "$PROJECT_ROOT/apps/unrealircd/config/tls" || log_warning "Could not set permissions for SSL directory"
+  log_info "Set permissions for SSL directory"
+
+  # Make sure data directories are writable
+  find "$PROJECT_ROOT/data" -type d -exec chmod 755 {} \; 2> /dev/null || true
+  find "$PROJECT_ROOT/data/atheme" -type d -exec chmod 755 {} \; 2> /dev/null || true
+
+  log_success "Permissions set successfully"
 }
 
 # Function to set up CA certificate bundle
 setup_ca_bundle() {
-    log_info "Setting up CA certificate bundle..."
-
-    local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
-    local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
-    local ca_bundle_file="curl-ca-bundle.crt"
-
-    # Ensure runtime directory exists
-    if [ ! -d "$ca_runtime_dir" ]; then
-        mkdir -p "$ca_runtime_dir"
-        log_info "Created TLS runtime directory: $ca_runtime_dir"
-  fi
-
-    # Ensure template directory exists
-    if [ ! -d "$ca_template_dir" ]; then
-        mkdir -p "$ca_template_dir"
-        log_info "Created TLS template directory: $ca_template_dir"
-  fi
-
-    # Check if system CA bundle exists
-    local system_ca_bundle=""
-    if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
-        system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
-  elif   [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
-        system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
-  fi
-
-    if [ -n "$system_ca_bundle" ]; then
-        # Create template if it doesn't exist
-        if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle template"
-      else
-                log_warning "Could not create CA certificate bundle template"
-                return 1
-      fi
-    fi
-
-        # Copy to runtime directory if it doesn't exist
-        if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
-            if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
-                log_success "Created CA certificate bundle in runtime directory"
-      else
-                log_warning "Could not create CA certificate bundle in runtime directory"
-                return 1
-      fi
-    else
-            log_info "CA certificate bundle already exists in runtime directory"
-    fi
-  else
-        log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
-        return 1
-  fi
-
-    # Remove obsolete cert files from config/tls (server certs now live in data/certs)
-    rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2>/dev/null || true
-    rm -rf "$ca_runtime_dir/live" 2>/dev/null || true
-
-    log_success "CA certificate bundle setup completed"
+  log_info "Setting up CA certificate bundle..."
+
+  local ca_template_dir="$PROJECT_ROOT/docs/examples/unrealircd/tls"
+  local ca_runtime_dir="$PROJECT_ROOT/apps/unrealircd/config/tls"
+  local ca_bundle_file="curl-ca-bundle.crt"
+
+  # Ensure runtime directory exists
+  if [ ! -d "$ca_runtime_dir" ]; then
+    mkdir -p "$ca_runtime_dir"
+    log_info "Created TLS runtime directory: $ca_runtime_dir"
+  fi
+
+  # Ensure template directory exists
+  if [ ! -d "$ca_template_dir" ]; then
+    mkdir -p "$ca_template_dir"
+    log_info "Created TLS template directory: $ca_template_dir"
+  fi
+
+  # Check if system CA bundle exists
+  local system_ca_bundle=""
+  if [ -f "/etc/ca-certificates/extracted/tls-ca-bundle.pem" ]; then
+    system_ca_bundle="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
+  elif [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+    system_ca_bundle="/etc/ssl/certs/ca-certificates.crt"
+  fi
+
+  if [ -n "$system_ca_bundle" ]; then
+    # Create template if it doesn't exist
+    if [ ! -f "$ca_template_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_template_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle template"
+      else
+        log_warning "Could not create CA certificate bundle template"
+        return 1
+      fi
+    fi
+
+    # Copy to runtime directory if it doesn't exist
+    if [ ! -f "$ca_runtime_dir/$ca_bundle_file" ]; then
+      if cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file" 2> /dev/null || sudo cp "$system_ca_bundle" "$ca_runtime_dir/$ca_bundle_file"; then
+        log_success "Created CA certificate bundle in runtime directory"
+      else
+        log_warning "Could not create CA certificate bundle in runtime directory"
+        return 1
+      fi
+    else
+      log_info "CA certificate bundle already exists in runtime directory"
+    fi
+  else
+    log_warning "System CA certificate bundle not found. SSL certificate validation may not work properly."
+    return 1
+  fi
+
+  # Remove obsolete cert files from config/tls (server certs now live in data/certs)
+  rm -f "$ca_runtime_dir/server.cert.pem" "$ca_runtime_dir/server.key.pem" 2> /dev/null || true
+  rm -rf "$ca_runtime_dir/live" 2> /dev/null || true
+
+  log_success "CA certificate bundle setup completed"
 }
 
 # Function to generate self-signed certificates for dev mode
 generate_cert() {
-    local domain="$1"
-    local base_dir="$2"
-    local live_dir="$base_dir/live/$domain"
-
-    # Ensure directory exists
-    mkdir -p "$live_dir"
-
-    # Generate self-signed cert if it doesn't exist
-    if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
-        log_info "Generating self-signed certificate for $domain..."
-        # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
-        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-            -keyout "$live_dir/privkey.pem" \
-            -out "$live_dir/fullchain.pem" \
-            -subj "/CN=$domain" \
-            -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2>/dev/null
-
-        log_success "Generated self-signed certificate for $domain"
-    else
-        log_info "Self-signed certificate already exists for $domain"
-    fi
-
-    # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
-    chmod 644 "$live_dir/privkey.pem" 2>/dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2>/dev/null || true
+  local domain="$1"
+  local base_dir="$2"
+  local live_dir="$base_dir/live/$domain"
+
+  # Ensure directory exists
+  mkdir -p "$live_dir"
+
+  # Generate self-signed cert if it doesn't exist
+  if [ ! -f "$live_dir/fullchain.pem" ] || [ ! -f "$live_dir/privkey.pem" ]; then
+    log_info "Generating self-signed certificate for $domain..."
+    # SANs: main, wildcard, Prosody components (muc/upload/proxy/pubsub/bridge), localhost
+    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
+      -keyout "$live_dir/privkey.pem" \
+      -out "$live_dir/fullchain.pem" \
+      -subj "/CN=$domain" \
+      -addext "subjectAltName=DNS:$domain,DNS:*.$domain,DNS:muc.$domain,DNS:upload.$domain,DNS:proxy.$domain,DNS:pubsub.$domain,DNS:bridge.$domain,DNS:localhost,IP:127.0.0.1" 2> /dev/null
+
+    log_success "Generated self-signed certificate for $domain"
+  else
+    log_info "Self-signed certificate already exists for $domain"
+  fi
+
+  # Ensure privkey is readable by container user (openssl defaults to 0600; container runs as PUID)
+  chmod 644 "$live_dir/privkey.pem" 2> /dev/null || sudo chmod 644 "$live_dir/privkey.pem" 2> /dev/null || true
 }
 
 generate_dev_certs() {
-    log_info "Setting up self-signed certificates for dev mode..."
-
-    # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
-    local shared_cert_dir="$PROJECT_ROOT/data/certs"
-    generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
-    generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
-
-    log_success "Dev certificate setup completed"
+  log_info "Setting up self-signed certificates for dev mode..."
+
+  # IRC and XMPP both use data/certs (Let's Encrypt layout: live/<domain>/fullchain.pem, privkey.pem)
+  local shared_cert_dir="$PROJECT_ROOT/data/certs"
+  generate_cert "${IRC_DOMAIN:-irc.localhost}" "$shared_cert_dir"
+  generate_cert "${PROSODY_DOMAIN:-xmpp.localhost}" "$shared_cert_dir"
+
+  log_success "Dev certificate setup completed"
 }
 
 # Function to prepare configuration files from templates
 prepare_config_files() {
-    log_info "Preparing configuration files from templates..."
-
-    # Load environment variables from .env if it exists
-    if [ -f "$PROJECT_ROOT/.env" ]; then
-        log_info "Loading environment variables from .env"
-        set -a
-        # shellcheck disable=SC1091
-        source "$PROJECT_ROOT/.env"
-        set +a
-        log_info "Environment variables loaded"
-    fi
-
-    # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
-    export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
-    export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
-
-    if [ ! -f "$PROJECT_ROOT/.env" ]; then
-        log_warning ".env file not found. Configuration will use defaults."
-        return 1
-  fi
-
-    # Check if envsubst is available
-    if ! command -v envsubst > /dev/null 2>&1; then
-        log_error "envsubst command not found. Please install gettext package."
-        return 1
-  fi
-
-    # Prepare UnrealIRCd configuration
-    local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
-    local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
-
-    if [ -f "$unreal_template" ]; then
-        log_info "Creating UnrealIRCd configuration from template..."
-        if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
-            log_success "UnrealIRCd configuration created"
-    else
-            log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
-    fi
-  elif   [ -f "$unreal_config" ]; then
-        log_info "UnrealIRCd configuration already exists"
-  else
-        log_warning "No UnrealIRCd configuration template found"
-  fi
-
-    # Prepare Atheme configuration
-    local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
-    local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
-
-    if [ -f "$atheme_template" ]; then
-        log_info "Creating Atheme configuration from template..."
-        envsubst < "$atheme_template" > "$atheme_config"
-        log_success "Atheme configuration created"
-  elif   [ -f "$atheme_config" ]; then
-        log_info "Atheme configuration already exists"
-  else
-        log_warning "No Atheme configuration template found"
-  fi
-
-    # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
-    if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
-        log_info "Running prepare-config.sh (bridge config, etc.)..."
-        # shellcheck source=prepare-config.sh
-        "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
-    fi
-
-    # Show substituted values for verification
-    log_info "Configuration values:"
-    echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
-    echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
-    echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
-    echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
-    echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
-    echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
+  log_info "Preparing configuration files from templates..."
+
+  # Load environment variables from .env if it exists
+  if [ -f "$PROJECT_ROOT/.env" ]; then
+    log_info "Loading environment variables from .env"
+    set -a
+    # shellcheck disable=SC1091
+    source "$PROJECT_ROOT/.env"
+    set +a
+    log_info "Environment variables loaded"
+  fi
+
+  # IRC cert paths: use shared data/certs (Let's Encrypt layout), matching Prosody
+  export IRC_SSL_CERT_PATH="${IRC_SSL_CERT_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/fullchain.pem}"
+  export IRC_SSL_KEY_PATH="${IRC_SSL_KEY_PATH:-/home/unrealircd/unrealircd/certs/live/${IRC_DOMAIN:-irc.localhost}/privkey.pem}"
+
+  if [ ! -f "$PROJECT_ROOT/.env" ]; then
+    log_warning ".env file not found. Configuration will use defaults."
+    return 1
+  fi
+
+  # Check if envsubst is available
+  if ! command -v envsubst > /dev/null 2>&1; then
+    log_error "envsubst command not found. Please install gettext package."
+    return 1
+  fi
+
+  # Prepare UnrealIRCd configuration
+  local unreal_template="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf.template"
+  local unreal_config="$PROJECT_ROOT/apps/unrealircd/config/unrealircd.conf"
+
+  if [ -f "$unreal_template" ]; then
+    log_info "Creating UnrealIRCd configuration from template..."
+    if envsubst < "$unreal_template" > "$unreal_config" 2> /dev/null; then
+      log_success "UnrealIRCd configuration created"
+    else
+      log_warning "Could not create UnrealIRCd configuration (permission denied). Using existing file."
+    fi
+  elif [ -f "$unreal_config" ]; then
+    log_info "UnrealIRCd configuration already exists"
+  else
+    log_warning "No UnrealIRCd configuration template found"
+  fi
+
+  # Prepare Atheme configuration
+  local atheme_template="$PROJECT_ROOT/apps/atheme/config/atheme.conf.template"
+  local atheme_config="$PROJECT_ROOT/apps/atheme/config/atheme.conf"
+
+  if [ -f "$atheme_template" ]; then
+    log_info "Creating Atheme configuration from template..."
+    envsubst < "$atheme_template" > "$atheme_config"
+    log_success "Atheme configuration created"
+  elif [ -f "$atheme_config" ]; then
+    log_info "Atheme configuration already exists"
+  else
+    log_warning "No Atheme configuration template found"
+  fi
+
+  # Run prepare-config.sh (UnrealIRCd, Atheme, Bridge config from templates)
+  if [ -f "$SCRIPT_DIR/prepare-config.sh" ]; then
+    log_info "Running prepare-config.sh (bridge config, etc.)..."
+    # shellcheck source=prepare-config.sh
+    "$SCRIPT_DIR/prepare-config.sh" || log_warning "prepare-config.sh reported issues"
+  fi
+
+  # Show substituted values for verification
+  log_info "Configuration values:"
+  echo "  IRC_DOMAIN: ${IRC_DOMAIN:-'not set'}"
+  echo "  IRC_NETWORK_NAME: ${IRC_NETWORK_NAME:-'not set'}"
+  echo "  IRC_ADMIN_NAME: ${IRC_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_SERVER_NAME: ${ATHEME_SERVER_NAME:-'not set'}"
+  echo "  ATHEME_NETNAME: ${ATHEME_NETNAME:-'not set'}"
+  echo "  ATHEME_ADMIN_NAME: ${ATHEME_ADMIN_NAME:-'not set'}"
+  echo "  ATHEME_ADMIN_EMAIL: ${ATHEME_ADMIN_EMAIL:-'not set'}"
 }
 
 # Function to create .env template if it doesn't exist
 create_env_template() {
-    local env_file="$PROJECT_ROOT/.env"
-    local env_example="$PROJECT_ROOT/.env.example"
-
-    if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
-        cp "$env_example" "$env_file"
-        log_info "Created .env file from template"
-        log_warning "Please edit .env file with your configuration before starting services"
-  elif   [ -f "$env_file" ]; then
-        log_info ".env file already exists"
-  else
-        log_warning "No .env template found. You may need to create environment variables manually"
+  local env_file="$PROJECT_ROOT/.env"
+  local env_example="$PROJECT_ROOT/.env.example"
+
+  if [ ! -f "$env_file" ] && [ -f "$env_example" ]; then
+    cp "$env_example" "$env_file"
+    log_info "Created .env file from template"
+    log_warning "Please edit .env file with your configuration before starting services"
+  elif [ -f "$env_file" ]; then
+    log_info ".env file already exists"
+  else
+    log_warning "No .env template found. You may need to create environment variables manually"
   fi
 }
 
 # Function to check Docker availability
 check_docker() {
-    log_info "Checking Docker availability..."
-
-    if ! command -v docker > /dev/null 2>&1; then
-        log_error "Docker is not installed or not in PATH"
-        exit 1
-  fi
-
-    if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
-        log_error "Docker Compose is not available"
-        exit 1
-  fi
-
-    log_success "Docker is available"
+  log_info "Checking Docker availability..."
+
+  if ! command -v docker > /dev/null 2>&1; then
+    log_error "Docker is not installed or not in PATH"
+    exit 1
+  fi
+
+  if ! command -v docker compose > /dev/null 2>&1 && ! docker compose version > /dev/null 2>&1; then
+    log_error "Docker Compose is not available"
+    exit 1
+  fi
+
+  log_success "Docker is available"
 }
 
 # Function to show next steps
 show_next_steps() {
-    echo ""
-    log_info "Next steps:"
-    echo "  1. Edit .env file with your configuration (optional)"
-    echo "  3. Or run: docker compose up -d"
-    echo ""
-    log_info "Data will be stored in:"
-    echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
+  echo ""
+  log_info "Next steps:"
+  echo "  1. Edit .env file with your configuration (optional)"
+  echo "  3. Or run: docker compose up -d"
+  echo ""
+  log_info "Data will be stored in:"
+  echo "  - $PROJECT_ROOT/data/ (persistent data: irc/, atheme/, xmpp/, certs/)"
 }
 
 # Main function
 main() {
-    log_info "ATL Chat Infrastructure Initialization"
-    log_info "======================================"
-
-    # Check if we're running as root (for permission info)
-    if [ "$(id -u)" = "0" ]; then
-        log_warning "Running as root - this is fine for initial setup"
-  fi
-
-    # Check Docker availability
-    check_docker
-
-    # Create directory structure
-    create_directories
-
-    # Set permissions
-    set_permissions
-
-    # Set up CA certificate bundle
-    setup_ca_bundle
-
-    # Generate dev certs
-    generate_dev_certs
-
-    # Create .env if needed
-    create_env_template
-
-    # Prepare configuration files from templates
-    prepare_config_files
-
-    # Show next steps
-    show_next_steps
-
-    log_success "Initialization completed successfully!"
+  log_info "ATL Chat Infrastructure Initialization"
+  log_info "======================================"
+
+  # Check if we're running as root (for permission info)
+  if [ "$(id -u)" = "0" ]; then
+    log_warning "Running as root - this is fine for initial setup"
+  fi
+
+  # Check Docker availability
+  check_docker
+
+  # Create directory structure
+  create_directories
+
+  # Set permissions
+  set_permissions
+
+  # Set up CA certificate bundle
+  setup_ca_bundle
+
+  # Generate dev certs
+  generate_dev_certs
+
+  # Create .env if needed
+  create_env_template
+
+  # Prepare configuration files from templates
+  prepare_config_files
+
+  # Show next steps
+  show_next_steps
+
+  log_success "Initialization completed successfully!"
 }
 
 # Run main function
 if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
-    main "$@"
+  main "$@"
 fi
diff scripts/prepare-config.sh.orig scripts/prepare-config.sh
--- scripts/prepare-config.sh.orig
+++ scripts/prepare-config.sh
@@ -181,7 +181,7 @@
     log_info "Preparing bridge configuration from template..."
     local temp_file="/tmp/bridge-config.yaml.tmp"
     envsubst < "$bridge_template" > "$temp_file"
-    if cp "$temp_file" "$bridge_config" 2>/dev/null || sudo cp "$temp_file" "$bridge_config" 2>/dev/null; then
+    if cp "$temp_file" "$bridge_config" 2> /dev/null || sudo cp "$temp_file" "$bridge_config" 2> /dev/null; then
       log_success "Bridge configuration prepared"
     else
       log_warning "Could not write bridge config to $bridge_config"
@@ -190,7 +190,7 @@
   elif [ ! -f "$bridge_config" ]; then
     log_warning "Bridge config not found. Copy apps/bridge/config.example.yaml to apps/bridge/config.yaml and customize."
     if [ -f "$PROJECT_ROOT/apps/bridge/config.example.yaml" ]; then
-      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2>/dev/null || true
+      cp "$PROJECT_ROOT/apps/bridge/config.example.yaml" "$bridge_config" 2> /dev/null || true
       log_info "Copied config.example.yaml to apps/bridge/config.yaml - edit with your Discord channel ID"
     fi
   fi
@@ -203,7 +203,7 @@
     mkdir -p "$(dirname "$lounge_config")"
     local temp_file="/tmp/thelounge-config.js.tmp"
     envsubst < "$lounge_template" > "$temp_file"
-    if cp "$temp_file" "$lounge_config" 2>/dev/null || sudo cp "$temp_file" "$lounge_config" 2>/dev/null; then
+    if cp "$temp_file" "$lounge_config" 2> /dev/null || sudo cp "$temp_file" "$lounge_config" 2> /dev/null; then
       log_success "The Lounge configuration prepared"
     else
       log_warning "Could not write The Lounge config to $lounge_config"
----------

You can reformat the above files to meet shfmt's requirements by typing:

  shfmt  -w filename


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants