Problem: node and cross-cutting narratives lived at the repo root
(containers/, vms/, infrastructure/, host .md files), interleaved with the
machine-readable substrate.
Change:
- Move containers/ -> knowledge/wiki/containers/, vms/ -> knowledge/wiki/vms/,
infrastructure/ -> knowledge/wiki/infrastructure/, hosts/{hubris,strong}.md ->
knowledge/wiki/hosts/, infrastructure/references/ -> knowledge/sources/references/,
GLOSSARY.md -> knowledge/GLOSSARY.md.
- Add knowledge/{index.md,log.md,sources/index.md} scaffolding.
- Rewrite all relative links repo-wide via a path-resolving mapper (inbound +
outbound + between-moved-files), including .hermes/, runbooks, operations,
investigations, plans, README, AGENTS.
- Repoint inventory.yaml doc_page fields and regenerate hosts/*.yaml (which
embed doc_page); update oikos/gen-topology.py output path, candidate doc
paths, and footer links; update code-comment doc paths.
Substrate untouched in place: inventory.yaml, hosts/*.yaml (regenerated,
idempotent), oikos/ code, mcp/, secrets/, bin/.
Verification:
- Logical broken-link set identical to pre-move baseline (net 128 -> 127; the
topology regen fixed one, introduced none). Remaining are pre-existing refs
to destroyed/archived nodes, out of scope for this move.
- gen-topology.py --check exit 0 (in sync); cards carry knowledge/wiki/ doc paths.
- build_host_files.py idempotent; all inventory doc_page targets resolve.
- MCP contract verified: get_page/search_docs/get_changelog resolve moved pages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.4 KiB
114 — nextcloud
Personal cloud / file collaboration. Source-of-truth for the photo libraries surfaced by mulita (120).
At a glance
- Hostname:
nextcloud - IP:
192.168.8.224 - Privilege: privileged
- Resources: 4 cores / 6 GiB RAM / 25 GiB rootfs
- Mounts:
/mnt/library↔/mnt/library(Nextcloud user files under/mnt/library/homecloud) - Public hostname:
cloud.hubris.network→ caddy
Auth
Native OIDC via user_oidc app. Username override pattern: Authentik user dtoro maps to local Nextcloud user admin via the nc_uid custom-claim scope. Configured via occ user_oidc:provider <name> --mapping-uid=nc_uid and --scope="openid profile email <app>-uid". See Authentik for the full pattern.
Redirect URI: /index.php/apps/user_oidc/code (NOT /apps/... — pretty URLs aren't on).
DNS workaround (load-bearing)
Nextcloud's PHP HTTP client (OC\Http\Client\DnsPinMiddleware) calls dns_get_record() directly — it bypasses /etc/hosts. So the Authentik OIDC discovery URL needs real DNS answering with 192.168.8.175 for auth.hubris.network. Recipe applied:
- Local
dnsmasqlistening on127.0.0.1:53,/etc/dnsmasq.d/hubris-internal.confwithaddress=/auth.hubris.network/192.168.8.175,server=192.168.8.1,server=1.1.1.1,interface=lo,bind-interfaces,no-hosts,no-resolv. pct set 114 --nameserver "127.0.0.1 192.168.8.1 1.1.1.1"so the LXC starts with the right resolver order./etc/hostsoverride kept as belt-and-suspenders.
Also needs allow_local_remote_servers=true in config.php.
Once internal DNS at the router level handles
*.hubris.network, this LXC-local dnsmasq can go away. See DNS.
Storage
Files at /mnt/library/homecloud. Owned by Nextcloud's own permission model — deliberately not on the media group, NC manages it itself and would complain. See media permissions.
Web stack (Apache event MPM + php-fpm)
Apache 2.4 with mpm_event as the MPM and PHP served via php-fpm 8.4 over the Unix socket /run/php/php8.4-fpm.sock (mod_php disabled). Routing to FPM is via the distro's /etc/apache2/conf-available/php8.4-fpm.conf (gated by <IfModule !mod_php8.c>, so just a2dismod php8.4 flips Apache onto FPM).
Why this matters: with mod_php every Apache worker is forced onto mpm_prefork (one heavy process per connection, full PHP interpreter loaded in each). On FPM + event, Apache workers are ~5–8 MB and async; PHP work is done by a separate FPM pool that keeps opcache hot. Concurrent upload throughput jumps significantly because Apache no longer blocks per-connection on a heavy PHP process.
PHP-FPM pool
- Socket:
/run/php/php8.4-fpm.sock(owner/groupwww-data) pm = dynamic,pm.max_children = 30,pm.start_servers = 6,pm.min/max_spare_servers = 4/12,pm.max_requests = 500- Steady-state ~6 idle workers at ~80 MB each — bump
max_childrenonly if peak concurrency exceeds 30.
PHP limits (FPM + CLI both)
Set in /etc/php/8.4/fpm/php.ini and /etc/php/8.4/cli/php.ini (the latter so occ/cron see the same ceilings):
| key | value |
|---|---|
upload_max_filesize |
16G |
post_max_size |
16G |
memory_limit |
512M |
max_execution_time |
3600 |
max_input_time |
3600 |
output_buffering |
Off |
After edits: systemctl reload php8.4-fpm. Caddy in front (cloud.hubris.network) imposes no body-size limit, so these are the effective ceiling.
Opcache + JIT
/etc/php/8.4/mods-available/opcache.ini — full config maintained, not commented stubs:
opcache.enable=1,opcache.memory_consumption=256,opcache.interned_strings_buffer=32,opcache.max_accelerated_files=20000opcache.jit=tracing,opcache.jit_buffer_size=64M
Apache mod_reqtimeout
/etc/apache2/mods-available/reqtimeout.conf — relaxed body trickle so slow-WAN uploads don't get killed:
RequestReadTimeout header=20-40,MinRate=500RequestReadTimeout body=20,MinRate=100(wasbody=10,MinRate=500)
MariaDB tuning
Overrides in /etc/mysql/mariadb.conf.d/99-nextcloud-tuning.cnf:
innodb_buffer_pool_size = 1G(default was 128M)innodb_log_file_size = 256Minnodb_flush_log_at_trx_commit = 2(group-commit; trades a few ms of durability for write throughput — acceptable for NC)innodb_flush_method = O_DIRECT,innodb_io_capacity = 2000/..._max = 4000max_allowed_packet = 256M,tmp_table_size = 64M,max_heap_table_size = 64M
Apply with systemctl restart mariadb (not reload — innodb_log_file_size needs a clean restart).
Related
- mulita (120) — reads NC user trees + writes back via WebDAV
- Authentik (124)
- Caddy (121)
- DNS
- Mesh migration (DNS overrides explained)
- Media permissions
Changelog
2026-05-13 — Apache keepalive bumped (and TCP BBR on host)
Follow-up to the FPM/MPM/MariaDB round earlier the same day. After a WiFi-bound Linux client kept seeing ~2 MB/s downloads despite a 152 Mbps speed test (so the WiFi medium wasn't the cap), bumped Apache KeepAliveTimeout 5 → 60 and MaxKeepAliveRequests 100 → 1000 so the mirall desktop client reuses one TLS+TCP connection across many sequential file fetches instead of paying handshake + slow-start each time. Paired with TCP BBR + fq qdisc enabled on the hubris host, which directly addresses single-stream throughput on jittery links. Server-side loopback baseline post-BBR is ~404 MB/s for a single 23 MB CR2 download via Caddy. Client-side diagnosis pending (curl single-stream + parallel tests) to determine if the remaining gap is mirall config (bandwidth limit / parallelNetworkRequests) or path shaping.
2026-05-13 — throughput tuning: FPM + event MPM + opcache JIT + MariaDB
Investigated upload/download speed. Bottlenecks were inside this LXC — LAN (33.9 Gbit/s caddy→NC iperf3) and /mnt/library NVMe (714 MB/s sustained) were not the limit. Changes:
- Apache mod_php → php-fpm 8.4 (
a2dismod php8.4). Flips PHP from in-process to FPM-over-Unix-socket. Apache workers now ~5–8 MB instead of ~80 MB each. - MPM prefork → event (
a2dismod mpm_prefork && a2enmod mpm_event). Async I/O, far better concurrency for parallel uploads. - opcache JIT enabled (
tracing, 64M buffer),memory_consumption=256,interned_strings_buffer=32,max_accelerated_files=20000. - MariaDB:
innodb_buffer_pool_size 128M → 1G,innodb_log_file_size 96M → 256M,flush_log_at_trx_commit 1 → 2,O_DIRECT, higher I/O capacity. New file99-nextcloud-tuning.cnf. - Apache mod_reqtimeout: body trickle relaxed from
body=10,MinRate=500→body=20,MinRate=100so slow-WAN uploads aren't killed. - PHP-FPM ini synced to match what the apache2/cli inis already had (FPM was stuck at 2M/8M because FPM wasn't serving traffic before, but now it does).
Backups: /etc/php/8.4/fpm/php.ini.bak.20260513, /etc/apache2/mods-available/reqtimeout.conf.bak.20260513, /etc/php/8.4/mods-available/opcache.ini.bak.20260513. MariaDB tuning is a new file (rollback = rm).
Verified: apache2ctl configtest Syntax OK, occ status clean, occ setupchecks shows ✓ on memory_limit / output_buffering / DB indices, status.php returns 200 in <40 ms via Caddy with HTTP/2.
2026-05-10 — PHP upload limits raised
Apache + CLI php.ini bumped from distro defaults (upload_max_filesize=2M, post_max_size=8M, memory_limit=128M, max_execution_time=30) to NC-recommended (16G/16G/512M/3600). Backups at /etc/php/8.4/{apache2,cli}/php.ini.bak-20260510-*. Was causing slow/failing uploads via web UI.
2026-04-28 — wiki entry created
Initial documentation.
2026-04-26 — Nextcloud-rooted libraries shipped (mulita)
mulita (120) now reads photos from /nextcloud-users/<nc_user>/files/... and writes mutations back via WebDAV. Pre-migration DB dump kept at /root/snapshots/mulita-pre-nc-migration-20260426-075132.dump.
2026-04-21 — wired into Authentik (native OIDC + nc_uid override)
Local dnsmasq added on this LXC because Guzzle bypasses /etc/hosts. Resolver order set to 127.0.0.1 192.168.8.1 1.1.1.1. Username override dtoro → admin.