Customers are admin-created accounts in per-customer groups. They should
read what staff share with them and nothing else. Checked against a test
customer in the UI, and by running the sharee and contacts-menu search
services as that user.
- shareapi_exclude_groups=allow, list [admin]: only staff can share. Exclude
mode ("yes") only disables sharing for users whose groups are ALL
excluded, so it never catches a customer in their own group.
- User/group autocomplete off. By default a customer typing "bas" found the
owner's account. Staff share by exact group name; LibreSign signers are
found by email.
- New shares default to View only (shareapi_default_permissions=1).
- files default_quota 0 B, so customers get no personal storage. Staff in
cloud_debyltech_staff_users are exempted (skipped if not yet created).
- No "Leon Green" sample contact in new address books.
- The verify script fails the deploy if any isolation setting drifts.
- README: staff and customer onboarding checklist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
373 lines
17 KiB
YAML
373 lines
17 KiB
YAML
---
|
|
# Where Nextcloud backup failure alerts are mailed (see containers/cloud-backup.yml).
|
|
backup_alert_email: bastian@debyl.io
|
|
bookstack_path: "{{ podman_volumes }}/bookstack"
|
|
cam2ip_path: "{{ podman_volumes }}/cam2ip"
|
|
cloud_path: "{{ podman_volumes }}/cloud"
|
|
cloud_skudak_path: "{{ podman_volumes }}/skudakcloud"
|
|
cloud_debyltech_path: "{{ podman_volumes }}/debyltechcloud"
|
|
debyltech_path: "{{ podman_volumes }}/debyltech"
|
|
# drone_path: removed - Drone CI decommissioned
|
|
factorio_path: "{{ podman_volumes }}/factorio"
|
|
fulfillr_path: "{{ podman_volumes }}/fulfillr"
|
|
fulfillr_cases_table: "debyltech-cases-prod"
|
|
fulfillr_tickets_table: "debyltech-tickets-prod"
|
|
# Turso ecommerce store (self-hosted checkout).
|
|
# PROD store URL (non-secret); the RW token `fulfillr_prod_store_auth_token` is in the vault.
|
|
fulfillr_prod_store_database_url: "libsql://debyltech-store-prod-debyltech.aws-us-east-1.turso.io"
|
|
# Staging back-office (fulfillr-dev.debyltech.com, port 9055) -> staging Turso store.
|
|
# Its RW token is `fulfillr_dev_store_auth_token` and EasyPost test key is
|
|
# `fulfillr_dev_easypost_api_key`, both in the encrypted vault.
|
|
fulfillr_dev_path: "{{ podman_volumes }}/fulfillr-dev"
|
|
fulfillr_dev_server_name: fulfillr-dev.debyltech.com
|
|
fulfillr_dev_store_database_url: "libsql://debyltech-store-staging-debyltech.aws-us-east-1.turso.io"
|
|
gregtime_path: "{{ podman_volumes }}/gregtime"
|
|
hass_path: "{{ podman_volumes }}/hass"
|
|
# nginx_path: removed - nginx no longer used
|
|
# nosql_path: removed - nosql/redis no longer used
|
|
partsy_path: "{{ podman_volumes }}/partsy"
|
|
partsy_skudak_path: "{{ podman_volumes }}/partsy-skudak"
|
|
photos_path: "{{ podman_volumes }}/photos"
|
|
# rsvp.debyl.io -- invite-only RSVP app (~/src/rsvp-debylio). Both listeners are
|
|
# published on loopback only: Caddy proxies the public one, and the admin one
|
|
# only for LAN clients. Caddy runs with network: host, so 127.0.0.1 reaches them.
|
|
rsvp_path: "{{ podman_volumes }}/rsvp"
|
|
rsvp_public_port: 9080
|
|
rsvp_admin_port: 9081
|
|
# Named rather than hardcoded so the ML service can be stood up beside a broken
|
|
# one without editing tasks. On 2026-08-28 a worker thread wedged in
|
|
# uninterruptible sleep (D state) in exit_mmap, which made the container
|
|
# unremovable -- it survived SIGKILL and `podman rm -f` and kept its name and
|
|
# network alias until the host was rebooted. The reboot cleared it, so this
|
|
# stays on the canonical name; see MACHINE_LEARNING_MODEL_TTL in photos.yml for
|
|
# the change that stops it recurring.
|
|
immich_ml_container: immich-machine-learning
|
|
uptime_kuma_path: "{{ podman_volumes }}/uptime-kuma"
|
|
uptime_kuma_personal_path: "{{ podman_volumes }}/uptime-kuma-personal"
|
|
zomboid_path: "{{ podman_volumes }}/zomboid"
|
|
|
|
# Whether to deploy and run the Zomboid server at all.
|
|
#
|
|
# Was off for months because it was the heaviest tenant on this host: ~9 GB
|
|
# resident and ~10 MB/s of continuous log writes, which on a 4-core box also
|
|
# running Gitea, Immich, Graylog and two Nextclouds saturated the QLC SSD and
|
|
# starved CI -- a firmware build stretched to 17 minutes and one release job
|
|
# was killed outright by Gitea's zombie-task timeout.
|
|
#
|
|
# Back on now that log growth is bounded: the container logs to a rotating
|
|
# k8s-file instead of the shared journal, and logrotate caps the server's own
|
|
# server-console.txt and Logs/ (see containers/home/zomboid.yml). Memory is
|
|
# unchanged -- idle draw is nowhere near MAX_RAM.
|
|
#
|
|
# Pass -e zomboid_enabled=false to keep it down through a CI-heavy stretch.
|
|
# World data under {{ zomboid_path }} is left in place either way.
|
|
zomboid_enabled: true
|
|
|
|
# Server name, and also the on-disk identity: PZ derives Server/<name>.ini,
|
|
# Saves/Multiplayer/<name>/ and db/<name>.db from it. Renaming it therefore
|
|
# starts a brand-new world and leaves the previous one intact for rollback.
|
|
zomboid_server_name: debbzoid
|
|
zomboid_public_name: Debbzoid
|
|
|
|
# Sophie 42 ships map_distanciado as its map mod. Muldraugh must stay last --
|
|
# the base map is the fallback layer.
|
|
zomboid_map: "map_distanciado;Muldraugh, KY"
|
|
|
|
# Zomboid RCON port for remote administration
|
|
zomboid_rcon_port: "27015"
|
|
|
|
# Discord channel the server's own chat bridge posts into. Build 42 replaced
|
|
# B41's DiscordChannel/DiscordChannelID pair with DiscordChatChannel, which
|
|
# takes a channel *name*, not a snowflake ID.
|
|
zomboid_discord_chat_channel: zomboidbot
|
|
|
|
# JVM heap, written into ProjectZomboid64.json by the container entrypoint.
|
|
zomboid_min_ram: 8g
|
|
zomboid_max_ram: 24g
|
|
|
|
# The modlist itself lives in vars/zomboid_sophie_mods.yml -- 283 mod IDs and
|
|
# 256 workshop items vendored from the upstream Sophie 42 preset.
|
|
#
|
|
# The modlist is still the upstream preset. The world settings no longer are:
|
|
# files/zomboid/sophie/SandboxVars.lua is a snapshot of the running debbzoid
|
|
# world taken 2026-08-31, not the preset as shipped. See the seed tasks in
|
|
# containers/home/zomboid.yml.
|
|
zomboid_preset_version: "sophie-42 mods @ 2026-07-31 / live world settings @ 2026-08-31"
|
|
|
|
# Mods subtracted from the vendored Sophie lists before they reach the INI.
|
|
# Explicit and reasoned so that re-vendoring a newer preset cannot silently
|
|
# reintroduce something we deliberately dropped.
|
|
zomboid_mods_excluded:
|
|
# Already absent from the 2026-07-31 preset; listed so it stays that way.
|
|
- workshop_id: "3718412967"
|
|
mod_id: IconsInventory
|
|
reason: Sophie recommends removing Icon Inventory
|
|
# Build 41 mod (versionMin=41.60) that indexes the ModOptions framework, which
|
|
# Sophie's list does not include. Dies at VehicleDoorsHotkey_Options.lua:4 --
|
|
# "attempted index: ModOptions of non-table: null" -- and never initialises, so
|
|
# it is 21 exceptions a boot in exchange for a hotkey that cannot work.
|
|
- workshop_id: "2290459371"
|
|
mod_id: VehicleDoorsHotkey
|
|
reason: needs the absent ModOptions framework; throws on load and never runs
|
|
|
|
# Per-source-IP ceiling on 53-byte Steam/PZ query packets before they are
|
|
# logged and dropped. Set well above anything a real client produces -- opening
|
|
# the server browser or retrying a connection is a handful of queries, while a
|
|
# scanner flood is thousands. The previous 1/hour burst 2 was below normal play
|
|
# and made the server unreachable. See containers/home/zomboid.yml.
|
|
zomboid_query_rate_limit: 60/minute
|
|
zomboid_query_burst: 120
|
|
|
|
# How long to keep PZ's own log archive under data/Logs.
|
|
#
|
|
# PZ rolls logs into dated directories and never prunes them; logrotate cannot
|
|
# bound that, because the growth is in the number of files rather than the size
|
|
# of any one of them. See containers/home/zomboid.yml.
|
|
zomboid_log_retention_days: 14
|
|
|
|
# Mod IDs the Sophie preset's `Mods=` line gets wrong for a 42.20.3 server.
|
|
#
|
|
# Every workshop item below is in the preset's own WorkshopItems= list and
|
|
# downloads fine -- only the ID it is referenced by is stale, so the server
|
|
# logs `required mod ... not found` and quietly runs without it. Verified
|
|
# against the id= field of each mod.info on disk, not guessed from folder
|
|
# names (folder name and mod ID are unrelated). Applied by server.ini.j2 so a
|
|
# re-vendor of a newer preset keeps the corrections.
|
|
zomboid_mods_renamed:
|
|
- old: "FWOBenchPress&Treadmill"
|
|
new: FWOBenchPressTreadmill
|
|
reason: preset carries a stray ampersand; mod.info in 2940354599 has none
|
|
- old: Ladders42131
|
|
new: Ladders4220
|
|
reason: 3629835761 ships per-build variants; 42131 is the 42.16 one, 4220 is ours
|
|
- old: ServingPlatesB42
|
|
new: ServingPlates42
|
|
reason: mod.info in 3399320470 declares ServingPlates42
|
|
- old: NewMusic_OrchestraMix
|
|
new: NewMusic_CMM
|
|
reason: author renamed it to "Classical Music Mix" in 3760471726
|
|
|
|
# Server config is seeded once and then left alone, so it can be hand-edited
|
|
# between world regenerations. Set true to overwrite it from the templates.
|
|
zomboid_config_force: false
|
|
|
|
# Same switch, narrowed to spawnregions.lua and spawnpoints.lua. They are the
|
|
# only config a running world re-reads (at every server start, unlike
|
|
# SandboxVars, which is read only when the world is created), so a spawn edit
|
|
# can be deployed to the live world without a wipe. It is separate from
|
|
# zomboid_config_force so that landing one is not an excuse to force the INI and
|
|
# SandboxVars over the top of whatever the admins have set in-game.
|
|
#
|
|
# make deploy TAGS=zomboid-conf -e zomboid_spawn_force=true
|
|
#
|
|
# The server still has to be restarted, with players offline, before the new
|
|
# spawn config is read.
|
|
zomboid_spawn_force: false
|
|
|
|
sshpass_cron_path: "{{ podman_volumes }}/sshpass_cron"
|
|
caddy_path: "{{ podman_volumes }}/caddy"
|
|
|
|
# Drone CI variables removed - infrastructure decommissioned
|
|
# drone_server_proto, drone_runner_proto, drone_runner_capacity
|
|
|
|
# Server names (used by Caddy)
|
|
base_server_name: bdebyl.net
|
|
assistant_server_name: assistant.bdebyl.net
|
|
bookstack_server_name: wiki.skudakrennsport.com
|
|
# ci_server_name: removed - Drone CI decommissioned
|
|
cloud_server_name: cloud.bdebyl.net
|
|
cloud_skudak_server_name: cloud.skudakrennsport.com
|
|
fulfillr_server_name: fulfillr.debyltech.com
|
|
home_server_name: home.debyl.io
|
|
uptime_kuma_server_name: uptime.debyltech.com
|
|
uptime_kuma_personal_server_name: uptime.debyl.io
|
|
parts_server_name: parts.bdebyl.net
|
|
photos_server_name: photos.bdebyl.net
|
|
|
|
# debyl.io domains (migration from bdebyl.net)
|
|
base_server_name_io: debyl.io
|
|
assistant_server_name_io: assistant.debyl.io
|
|
cloud_server_name_io: cloud.debyl.io
|
|
home_server_name_io: home.debyl.io
|
|
parts_server_name_io: parts.debyl.io
|
|
photos_server_name_io: photos.debyl.io
|
|
gitea_debyl_server_name: git.debyl.io
|
|
rsvp_server_name: rsvp.debyl.io
|
|
|
|
# skudak.com domains (migration from skudakrennsport.com)
|
|
parts_skudak_server_name: parts.skudak.com
|
|
bookstack_server_name_new: wiki.skudak.com
|
|
# partsy_skudak_admin_password: defined in vault
|
|
cloud_skudak_server_name_new: cloud.skudak.com
|
|
gitea_skudak_server_name: git.skudak.com
|
|
|
|
# LibreSign root certificate authority identity for skudak-cloud. This is the
|
|
# issuer name that appears on every signed document, so it must match the
|
|
# entity's legal name -- it was previously generated as "Skudak Rennsport LLP",
|
|
# the pre-rename name. Changing these values does NOT re-issue the CA on its
|
|
# own; see the guarded generate task in containers/skudak/cloud.yml.
|
|
# Skudak brand palette, mirroring ~/src/skudak/skudak-site/src/styles/variables.css.
|
|
# --color-gray-900 for the mail header band and UI chrome; the white signature
|
|
# logo is legible on it. --color-accent is spent only on the CTA button, and
|
|
# lives in the skudakmail app rather than here since theming has no second
|
|
# colour slot.
|
|
theming_skudak_primary: "#0A0A0A"
|
|
|
|
libresign_skudak_cert_cn: Skudak LLP
|
|
libresign_skudak_cert_o: Skudak LLP
|
|
libresign_skudak_cert_c: US
|
|
libresign_skudak_cert_st: New Hampshire
|
|
libresign_skudak_cert_l: Newbury
|
|
|
|
# de Byl Technologies Nextcloud (containers/debyltech/cloud.yml). DNS is a
|
|
# Route53 ALIAS to fulfillr.debyltech.com, managed in ~/src/debyltech/terraform
|
|
# (aws/cloud.tf), so no awsddns container of its own.
|
|
cloud_debyltech_server_name: cloud.debyltech.com
|
|
# debyltech-com $primary-color (copper). Drives the web UI theming; the mail
|
|
# CTA carries the same value as a constant in files/debyltechmail.
|
|
theming_debyltech_primary: "#bc804d"
|
|
# Login/header background. Dark site ink rather than copper so the white
|
|
# wordmark and mark shipped in files/debyltechmail/img stay legible on it.
|
|
theming_debyltech_background: "#0a1a2b"
|
|
# LibreSign root CA identity: the issuer on every signed document. Same caveat
|
|
# as the Skudak block above -- changing these does not re-issue the CA.
|
|
libresign_debyltech_cert_cn: de Byl Technologies LLC
|
|
libresign_debyltech_cert_o: de Byl Technologies LLC
|
|
libresign_debyltech_cert_c: US
|
|
libresign_debyltech_cert_st: New Hampshire
|
|
libresign_debyltech_cert_l: Newbury
|
|
# Outbound mail via AWS SES SMTP as noreply@debyltech.com. The IAM user is
|
|
# NextcloudSMTP in the terraform repo; its SMTP username/password are
|
|
# cloud_debyltech_smtp_user / cloud_debyltech_smtp_pass in the vault.
|
|
cloud_debyltech_smtp_host: email-smtp.us-east-1.amazonaws.com
|
|
cloud_debyltech_smtp_port: 465
|
|
# Staff vs customers (see "customer isolation" in containers/debyltech/cloud.yml).
|
|
# Only this group may share; everyone else -- customers -- can only read what
|
|
# is shared with them. Staff accounts are exempted from the 0 B default quota.
|
|
# Accounts listed here that do not exist yet are skipped, not created.
|
|
cloud_debyltech_staff_group: admin
|
|
cloud_debyltech_staff_users:
|
|
- admin
|
|
- bastian@debyltech.com
|
|
|
|
|
|
# Legacy nginx/ModSecurity configuration removed - Caddy provides built-in security
|
|
|
|
# Web server configuration (Caddy is the default)
|
|
# Legacy nginx variables kept for cleanup tasks
|
|
|
|
# Caddy configuration
|
|
caddy_email: "{{ ssl_email }}"
|
|
# Use staging for testing, production for real certificates
|
|
caddy_acme_ca: https://acme-v02.api.letsencrypt.org/directory
|
|
# For testing/staging:
|
|
# caddy_acme_ca: https://acme-staging-v02.api.letsencrypt.org/directory
|
|
|
|
# Caddy ports
|
|
caddy_admin_port: 2019
|
|
|
|
# Caddy network configuration
|
|
caddy_local_networks:
|
|
- 192.168.0.0/16
|
|
- 127.0.0.1
|
|
|
|
# Caddy logging configuration
|
|
caddy_log_level: INFO
|
|
caddy_log_format: json
|
|
|
|
# Log rotation. Caddy rotates on implicit defaults (100MiB / keep 10 / 90d)
|
|
# without these, and those were demonstrably not holding: 20 rotated files per
|
|
# stream for the busiest logs and a 190-day-old .gz, for 1.3 GB across 16
|
|
# streams. Kept short deliberately -- fluent-bit tails every one of these into
|
|
# Graylog (see roles/common/templates/fluent-bit/fluent-bit.conf.j2), so
|
|
# Graylog is the system of record and the local files are only a buffer.
|
|
caddy_log_roll_size: 10MiB
|
|
caddy_log_roll_keep: 3
|
|
caddy_log_roll_keep_for: 168h
|
|
|
|
# Caddy performance tuning
|
|
caddy_max_request_body_mb: 500
|
|
|
|
# Caddy security headers (global defaults)
|
|
caddy_security_headers:
|
|
Strict-Transport-Security: "max-age=31536000; includeSubDomains"
|
|
X-Content-Type-Options: "nosniff"
|
|
Referrer-Policy: "same-origin"
|
|
X-Frame-Options: "SAMEORIGIN"
|
|
|
|
# Graylog logging stack
|
|
graylog_path: "{{ podman_volumes }}/graylog"
|
|
logs_server_name: logs.debyl.io
|
|
# gelf_auth_token: defined in vault - X-Gelf-Token header for Lambda GELF HTTP auth
|
|
|
|
# Fluent Bit is deployed as a systemd service (not container)
|
|
# for direct journal access - see containers/base/fluent-bit.yml
|
|
|
|
# Fluent-bit Caddy log forwarding
|
|
caddy_log_path: "{{ caddy_path }}/logs"
|
|
caddy_log_names:
|
|
- caddy
|
|
- photos
|
|
- wiki
|
|
- assistant
|
|
- parts
|
|
- uptime-kuma
|
|
- uptime-kuma-personal
|
|
- graylog
|
|
- cloud
|
|
- cloud-skudak
|
|
- cloud-debyltech
|
|
- gitea-debyl
|
|
- gitea-skudak
|
|
- fulfillr
|
|
|
|
# GeoIP configuration for Graylog
|
|
# Requires free MaxMind account: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
|
|
geoip_path: "{{ graylog_path }}/geoip"
|
|
geoip_database_edition: GeoLite2-City
|
|
# geoip_maxmind_account_id: defined in vault
|
|
# geoip_maxmind_license_key: defined in vault
|
|
|
|
# Weekly podman prune (see templates/podman-prune.sh.j2). Both rootless users
|
|
# have their own image store; the git user runs the Gitea pods.
|
|
podman_prune_users:
|
|
- "{{ podman_user }}"
|
|
- "{{ git_user }}"
|
|
# Keep 30 days of unused images so a rollback needs no rebuild or re-pull.
|
|
podman_prune_until: 720h
|
|
|
|
# The CI runners were never pruned and had run away: gitea-runner reached 1205
|
|
# images / 113 GB with 100% reclaimable, actions-runner 137 exited job
|
|
# containers. Build layers carry no rollback value, so they keep a far shorter
|
|
# window than the service stores and their exited containers are reaped too.
|
|
podman_prune_ci_users:
|
|
- gitea-runner
|
|
- actions-runner
|
|
podman_prune_ci_until: 48h
|
|
# The CI base images declare LABEL io.debyl.ci-base="true" in
|
|
# roles/gitea-actions/files/Containerfile.* (and .gitea/workflows/ci-images.yml
|
|
# verifies it before publishing -- keep all three in sync). Images carrying it
|
|
# are skipped below: `until` counts from build time and not pull time, so
|
|
# without the exemption a re-pulled image would be deleted again the next
|
|
# night, a 7.8 GB ESP-IDF re-download after every idle day. Superseded tags
|
|
# (e.g. after an esp_idf_version bump) survive too; remove those by hand.
|
|
podman_prune_ci_keep_label: io.debyl.ci-base
|
|
|
|
# Daily rather than weekly: CI turns over many images a day, and a week of that
|
|
# is what let the store reach 113 GB between runs.
|
|
podman_prune_oncalendar: "*-*-* 02:00:00"
|
|
|
|
# Hardened CIFS options for the TrueNAS shares (see containers/home/photos.yml).
|
|
# x-systemd.automount is what makes a failed mount recoverable without a human.
|
|
cifs_mount_opts: >-
|
|
credentials=/etc/cifs/photos.creds,uid={{ podman_subuid.stdout }},gid={{ podman_subuid.stdout }},_netdev,nofail,soft,x-systemd.automount,x-systemd.mount-timeout=30,x-systemd.idle-timeout=600,vers=3.1.1,resilienthandles,echo_interval=10
|
|
# How often the watchdog checks mount health and heals immich.
|
|
cifs_watchdog_oncalendar: "*:0/5"
|
|
cifs_watchdog_mounts:
|
|
- "{{ photos_path }}/storage"
|
|
- "{{ photos_path }}/immich"
|
|
|
|
# GA4 property for the fulfillr portal Traffic & funnel tab (SCRUM-196, non-secret).
|
|
# Shared by dev and prod. The service-account key `fulfillr_ga4_credentials_json` lives in the vault.
|
|
fulfillr_ga4_property_id: "353859448"
|