Files
deploy_home/ansible/roles/podman/templates/nextcloud/redis-skudak.conf.j2
T
Bastian de Byl fec7d62acb feat(skudak-cloud): repair LibreSign, brand its mail, add Redis
LibreSign had been silently broken since it was first deployed in
January. Every step of the old before-starting hook ended in `|| echo`,
so six months of failures logged nothing.

LibreSign repair
- Root cause was a stale config_path: a valid OpenSSL root CA existed at
  generation 1, a failed CFSSL attempt left an empty generation 2, and
  config_path was left pointing at the empty one. Regenerated as
  "Skudak LLP" (was the pre-rename "Skudak Rennsport LLP").
- Deleted the hook. Java/PDFtk/jSignPdf live under data/appdata_*, a
  persisted volume, so they only ever needed installing once. Install and
  verification are now explicit tasks that actually fail.
- PHP_MEMORY_LIMIT 1024M -- the 512M image default fails opaquely
  mid-signature. LC_ALL/LANG so the JVM is not ANSI_X3.4-1968.
- signature_render_mode=GRAPHIC_ONLY. Any other mode halves the stamp
  width and overlays a name/date block that collides with the drawn mark
  and duplicates what our documents already typeset. The value must be
  exactly GRAPHIC_ONLY; a bare "GRAPHIC" is accepted by occ, matches no
  radio in the UI, and silently reverts to default.
- write_qrcode_on_footer=false, written with --type=boolean because
  FooterHandler reads it via getValueBool and the typed appconfig API
  does not coerce a string "0". The validation URL text is kept.
- identification_documents=0 -- the default gates signing behind an ID
  upload plus admin approval, so signers saw no way to sign.
- shareapi_restrict_user_enumeration_full_match=no, so an email owned by
  an existing account can be added as a signer. Root cause is in core
  (MailPlugin.php:163), not LibreSign. Do NOT set full_match_email=no --
  that disables email signer search entirely.

Mail branding (skudakmail app)
- Two supported extension points, no core patch and no LibreSign fork:
  mail_template_class for layout, subjects, button labels and the footer
  LibreSign never adds; and a BeforeMessageSent listener to embed the
  wordmark as a cid: part so it survives remote-image blocking.
- A third listener adds scoped CSS fixing the signing page being clipped
  on iOS Safari (100vh -> 100dvh). Patched upstream too.
- skudakmail-verify.php.j2 asserts all of the above through the real
  useTemplate() path and fails the play on drift. Every assertion was
  proven to fail when deliberately regressed.

Redis
- memcache.locking was unset, so Nextcloud used DBLockingProvider and
  every file lock became a MariaDB write -- the contention behind the
  intermittent multi-second stalls. Verified after: db locks static,
  redis keys growing.
- requirepass lives in a mounted 0640 conf, not --requirepass, which
  would leak it into podman inspect, the systemd unit and ps. The file is
  chowned to uid 999 because redis-server does not run as root and the
  :ro mount stops the image fixing it itself.
- No maxmemory: cache is evictable, locks are NOT, and evicting a held
  lock permits concurrent writers to one file. No persistence either --
  a restored RDB could reinstate locks whose owner is long dead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:54:52 -04:00

41 lines
1.9 KiB
Django/Jinja

# {{ ansible_managed }}
#
# Redis for skudak-cloud: Nextcloud distributed cache + transactional file
# locking. Reachable only by container name on the `shared` podman network --
# no host port is published.
#
# The password lives HERE rather than on the command line as
# `redis-server --requirepass <pass>`. That is the existing house idiom (see
# the deleted container-nosql.yml in git history), but it leaks the secret into
# `podman inspect`, into the generated systemd unit under
# ~/.config/systemd/user/, and into `ps` for every user on the host. A 0640
# config file mounted read-only keeps it out of all three.
requirepass {{ cloud_skudak_redis_pass }}
# Bind to all interfaces WITHIN the container's network namespace. The
# container publishes no port, so this is reachable only from the `shared`
# podman network -- not from the host and not from the LAN.
bind 0.0.0.0
port 6379
protected-mode yes
# NO maxmemory / eviction policy, deliberately.
#
# Nextcloud puts BOTH the distributed cache and the transactional file locks in
# this instance. Cache entries are safely evictable; LOCKS ARE NOT. An
# `allkeys-lru` policy under memory pressure can evict a lock that a live
# request still believes it holds, which permits concurrent writers to the same
# file -- silent corruption rather than a visible error. With no maxmemory,
# Redis never evicts. The host has ~14 GiB free of 31 GiB and this instance
# holds a few hundred keys, so a cap buys nothing.
#
# If a cap is ever genuinely needed, use `maxmemory-policy noeviction` so Redis
# returns an error instead of silently discarding a lock.
# No persistence. Locks are ephemeral and TTL-bounded, and the cache is
# rebuildable -- there is nothing here worth surviving a restart. Persisting
# would be actively worse: a restored RDB could reinstate locks whose owning
# request died, blocking files until the TTL expired.
save ""
appendonly no