Files
jensandClaude Sonnet 5 92d871f2da Initial docs: alpha.jayfield.org server documentation
Records the host's service inventory, setup runbook, mail account list,
sync plan, package diff vs. clean install, and hardening TODOs, including
today's SSH hardening (key-only, no root login) and the fail2ban sshd
findtime fix for a low-and-slow brute-force evasion pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RdTyvEJkfNLDVAWt8WQ6X9
2026-07-19 23:09:27 +02:00

12 KiB

alpha.jayfield.org — Diff vs. a Clean Ubuntu 22.04 Install

Generated 2026-07-19. Methodology and caveats up front; findings below, organized in three tiers of decreasing mechanical certainty and increasing manual judgment.

Update, same day: three of the findings below (RTMP/nginx, the wireguard tunnel, and samba/cifs-utils) were removed shortly after this diff was run — see the annotations inline and README.md's known quirks for the current state. Left in place here as a record of what was found and why it was removed; this file is a point-in-time investigation, not a live inventory (README.md is the live inventory).

Baseline used: the official jammy-server-cloudimg-amd64 manifest (cloud-images.ubuntu.com/jammy/current/) for package comparisons, and freshly-downloaded .debs from the exact same Ubuntu archive this host uses (matching installed versions exactly — e.g. bind9 9.18.39-0ubuntu0.22.04.4, postfix 3.6.4-1ubuntu1.4) for config-file comparisons. One honest caveat: several signs suggest this host was actually provisioned from the interactive Server ISO installer, not the cloud image (installation-report, tasksel, popularity-contest, and language-pack-en are all manually installed — none of these appear on a cloud-init-provisioned instance). The cloud image manifest is still the best available reference point, but treat tier 1's line count as approximate, not exact.


Tier 1 — Package diff

968 packages installed vs. 600 in the baseline manifest. Most of that gap is transitive dependencies (installing apache2 alone pulls in a dozen supporting libraries nobody explicitly chose). Filtering to packages that are both (a) not in the baseline and (b) marked apt-mark showmanual (i.e. an actual administrative decision, not a dependency) gives 122 packages — the real, meaningful list. Grouped by what they're for:

Purpose Packages
DNS bind9
Mail postfix, postfix-mysql, dovecot-core, dovecot-imapd, dovecot-lmtpd, dovecot-managesieved, dovecot-mysql, dovecot-sieve, rspamd, mailutils, libmailutils6
Database mariadb-server (+ mariadb-server-10.3/galera-3 — stale leftovers, see note below)
Web apache2, php8.0 + extensions, libapache2-mod-php7.4, libapache2-mod-php8.0, nginx (see Tier 2 — was not a web server here; removed same day, was unused RTMP)
TLS certbot, python3-certbot-apache
Docker docker-ce, docker-ce-cli, docker-buildx-plugin, docker-compose-plugin, containerd.io
Security fail2ban
File sharing samba, cifs-utils (→ the vlda-01 CIFS mount; removed same day along with the dead tunnel below)
VPN — not previously documented wireguard (removed same day — dormant, root-caused the CIFS failure, config backed up to /root/removed-configs-backup/)
Utilities at, net-tools, unzip, lz4, ltrace, bsdmainutils, resolvconf, qemu-guest-agent
ISO-installer artifacts installation-report, tasksel, popularity-contest, language-pack-en, language-selector-common

Stale/leftover packages worth a cleanup pass: mariadb-server-10.3, mariadb-client-10.3, galera-3, libssl1.1, libicu66, php8.0 (vs. the active php7.4 module), libperl5.30 — all remnants of the 18.04→20.04→22.04 upgrade history already noted in README.md (same lineage as the python3.8/certbot issue documented there). Not actively harmful, just disk/attack-surface bloat from packages no longer in active use. Candidate for a future apt autoremove pass — worth checking what's actually still linked against before purging.

24 packages "missing" vs. baseline are not removals — they're expected absences for a BIOS-booted QEMU VM with no physical hardware: grub-efi-*, shim-signed, mokutil, efibootmgr (this host boots via legacy BIOS/bios_grub, confirmed in the disk-resize work — never needed EFI tooling), plus libtss2-* (TPM), fuse3/libntfs-3g89 (physical filesystem drivers), systemd-hwe-hwdb (hardware enablement) — none of this is relevant to a cloud VM regardless of who administers it.


Tier 2 — Config file diff (live file vs. pristine package default)

Real diff -u output against genuinely pristine files pulled from the matching package version in Ubuntu's archive — not reconstructed from memory. Only the files with a real static default to diff against are listed; Postfix's main.cf/master.cf and Dovecot's dovecot.conf are not static conffiles (Postfix generates them via debconf at install time; Dovecot's shipped file is a first-install template only), so "diff" for those is a semantic comparison, covered in SETUP.md instead.

/etc/bind/named.conf.options

Exactly what's documented in TODO.md/README.mdversion "unknown", the rate-limit block, and the forwarders block removed in favor of full recursion (with an inline comment explaining why). Plus pre-existing (2022) additions: allow-transfer, allow-query { any; }, allow-recursion { trusted; }, querylog yes, the trusted ACL.

/etc/ssh/sshd_config

  • Port 10022 (from #Port 22)
  • PasswordAuthentication yes and PermitRootLogin yes explicitly set — the shipped default leaves both commented out (relying on OpenSSH's own safer compiled-in defaults). Flagged already in SETUP.md §1 as a hardening gap worth reconsidering.
  • ChallengeResponseAuthentication no — uses the pre-8.7 directive name; current templates use KbdInteractiveAuthentication. Cosmetic, but a dating signal (this file predates a later OpenSSH directive rename).
  • New finding: an entire Match Group sftp block — chrooted, no-shell SFTP-only access (ChrootDirectory /var/sftp/%u, tunneling and forwarding all disabled). Three accounts use it: ftpuser, ftp_jens, ftp_alex (/var/sftp/{ftpuser,ftp_jens,ftp_alex}/). This is a legitimate, separate access mechanism from jens's full shell account — not documented anywhere before this diff.

/etc/systemd/resolved.conf

Exactly the DNS=127.0.0.1 change documented in README.md's resolver section, plus pre-existing DNSSEC=allow-downgrade, LLMNR=no, etc.

/etc/apache2/apache2.conf

  • <Directory /var/www>: AllowOverride all (default: None) — permits .htaccess files to override server config anywhere under /var/www. Broader than the stock posture; worth checking whether any .htaccess files actually exist and rely on this before tightening it.
  • A custom fail2ban LogFormat — ties into the apache-auth jail.
  • New finding, worth a closer look: a global block appended at the end of the file —
    <FilesMatch "\.*">
    SetHandler application/x-httpd-php
    </FilesMatch>
    
    The regex \.* (zero or more literal dots, unanchored) matches nearly any filename, including ones with zero dots. Applied globally (outside any <Directory> scope), this routes essentially all requests through the PHP handler, not just .php files. Whether this has real consequences depends on what mod_php does with non-PHP content (likely passes it through as literal text), but it's a broad, unusual, and previously-undocumented directive — worth understanding the original intent before deciding whether to scope it down to \.php$.

/etc/fail2ban/jail.conf

This host edits jail.conf directly rather than using jail.local for overrides — the intended pattern is to leave the vendor file untouched and override in jail.local so future package upgrades don't silently clobber local changes. Real customizations found: bantime 10m→4h, findtime 10m→30m, maxretry 5→3, port = 10022 on the sshd/dropbear/selinux-ssh jails (matching the moved SSH port), sender = root@jayfield.org, and several jails explicitly enabled = true (sshd, postfix, postfix-rbl, dovecot, sieve, pam-generic, and others) that ship disabled by default. The remaining diff noise (action- syntax %(banaction)s[...] reformatting, comment typo fixes) is from fail2ban's own upstream template evolving between minor releases, not a local change.

/etc/nginx/nginx.confremoved 2026-07-19, same day as this diff

Confirmed Tier 1's finding cleanly: the entire http {} block from the stock config was removed — no web serving capability at all — and include /etc/nginx/rtmp.conf; was the only addition. This nginx instance existed solely to run libnginx-mod-rtmp as a live-streaming ingest server (rtmp.conf: listen 1935; application live { live on; record off; }, no publish authentication configured). Entirely separate from the Docker web (nginx) container and from Apache — no overlap, no conflict, just a second, unrelated nginx process that had gone uncatalogued. No active connections or recent use found before removal; purged (nginx nginx-core nginx-common libnginx-mod-rtmp) along with /etc/nginx.

/etc/apache2/ports.conf

No diff — matches stock exactly (Apache's own Listen 80/mod_ssl's Listen 443 addition).


Tier 3 — Changes with no package-level "default" to diff against

Not mechanically diffable — either data (not config), or infrastructure this host's package manager doesn't track at all.

  • Disk partitioning: root partition was 9.7GB of a 172GB disk for years (see README.md) — grown to 157GB this session via growpart/resize2fs. No "default" partition layout to diff against; this is provisioning-time state.
  • DNS zone content (/etc/bind/db.jayfield.org): all records — there's no baseline zone file to diff against, it's 100% custom data.
  • MySQL vmail database: schema + data for mail accounts/aliases/domains (see SETUP.md §3 for the schema). Data, not config.
  • TLS certificates: 7 Let's Encrypt certs, all custom to this domain.
  • Docker layer: portainer, web (nginx), and the Nextcloud stack (nextcloud, nextcloud-db, nextcloud-redis) — none of this exists in a base install; it's entirely additive infrastructure documented in README.md.
  • WireGuard — removed 2026-07-19, same day as this diff (/etc/wireguard/alpha_at_moon.conf): a client tunnel named "alpha at moon" — Address = 10.8.0.8/24, peer endpoint vpn.dyndns.jayfield.org:51820, AllowedIPs = 10.8.0.0/24, 192.168.22.0/24. Was inactive (wg-quick@wg0 not running) at the time of this diff. Almost certainly why the mnt-vlda-01.mount failure documented in README.md happened — vlda-01 (the CIFS target) was presumably only reachable via this same 192.168.22.0/24 range. Decision made: rather than reviving the tunnel, removed it along with the dead CIFS mount and unused Samba packages. Config backed up to /root/removed-configs-backup/alpha_at_moon.conf.bak on the server (600 root:root) in case the tunnel is wanted again later.
  • SFTP chroot jail (/var/sftp/): three accounts (ftpuser, ftp_jens, ftp_alex) in the sftp group, each chrooted to their own directory with no shell — a controlled file-drop mechanism separate from jens's admin SSH access. Purpose/owners of these accounts not established from config alone.
  • Users, groups, sudoers: not diffed — no baseline "clean install" user set to compare against beyond the default ubuntu/first-boot user, which this host doesn't appear to still have.
  • Cron jobs: certbot renew (weekly), Nextcloud's cron.php (every 5 min) — both documented in README.md, both additive, no baseline.

Summary: what this diff actually surfaced

Beyond confirming the changes already tracked in README.md/TODO.md, this exercise found four previously-undocumented things:

  1. A live RTMP streaming server (nginx + libnginx-mod-rtmp on :1935)removed same day; unused, no auth on the publish endpoint, no reason found to keep it.
  2. A dormant WireGuard tunnel to a home networkremoved same day, along with the CIFS mount it was almost certainly gating and the Samba packages that supported it. Config backed up first.
  3. A chrooted SFTP-only access path for three accounts, separate from the main admin login. Left as-is — appears to be legitimate, ongoing infrastructure, not a stray leftover.
  4. Two Apache config items worth a second look: AllowOverride all on /var/www, and a broad <FilesMatch "\.*"> PHP handler that's wider than it probably needs to be. Still open — not yet addressed.