diff --git a/README.md b/README.md index 2f40e26..44659d9 100644 --- a/README.md +++ b/README.md @@ -123,12 +123,12 @@ FreeBSD 15.0 over 14.x specifically because PHP 8.5 is a **binary package** on 1 ## Target Architecture ``` -[internet] → [NPM node, Tailscale] → [dracula-new FreeBSD 15.0, Tailscale] → [transilvan-new FreeBSD 15.0, Tailscale] +[internet] → [NPM node, Tailscale] → [zamolxis FreeBSD 15.0, Tailscale] → [decebal FreeBSD 15.0, Tailscale] -dracula-new (FreeBSD 15.0) transilvan-new (FreeBSD 15.0) +zamolxis (FreeBSD 15.0) decebal (FreeBSD 15.0) ────────────────────────── ───────────────────────────── nginx (pkg) ZFS pool → /var/db/mysql -PHP 8.5 (ports build) recordsize=16K (InnoDB optimal) +PHP 8.5.6 (pkg) recordsize=16K (InnoDB optimal) gd, imagick, intl, mbstring, compression=lz4 opcache, pdo_mysql, redis, primarycache=metadata soap, sockets, sodium, MySQL 8.4 LTS (ports) @@ -171,10 +171,10 @@ Proxmox native monitoring | Host | CPU | Logical CPUs | RAM | Storage | Available to VMs | VM | |---|---|---|---|---|---|---| -| dracula host | Xeon E5-1620 v2 @ 3.70GHz | 8 (4 cores + HT) | 64GB | Datacenter HDD | ~full | dracula-new (web) | -| transilvan host | Xeon E5-1620 v2 @ 3.70GHz | 8 (4 cores + HT) | 32GB | SSD (160GB total, ~130GB free after Proxmox) | 130GB | transilvan-new (db) | +| dracula host | Xeon E5-1620 v2 @ 3.70GHz | 8 (4 cores + HT) | 64GB | Datacenter HDD | ~full | zamolxis / ID 900 (web) | +| transilvan host | Xeon E5-1620 v2 @ 3.70GHz | 8 (4 cores + HT) | 32GB | SSD 160GB (~130GB free) | 130GB | decebal / ID 901 (db) — sole VM | -Both hosts run Proxmox VE (confirmed via qemu-guest-agent on current VMs). New VMs are the sole tenants on their respective hosts. +Both hosts run Proxmox VE (confirmed via qemu-guest-agent on current VMs). Decebal's host is dedicated — no other VMs share it. --- @@ -193,34 +193,34 @@ With nginx fastcgi_cache, the vast majority of requests never invoke PHP. On a n | PHP-FPM peak RAM | ~1.4GB | 40 workers × 35MB | | nginx HIT response time | <5ms | Served from UBC, no PHP involved | | nginx MISS response time | 100–400ms | WP query + render | -| InnoDB buffer pool hit rate | >99% | 20GB pool, 9.6GB dataset fits entirely in RAM | +| InnoDB buffer pool hit rate | >99% | 26GB pool, 9.6GB dataset fits entirely in RAM with 16GB to spare | | Active DB connections | ≤ 40 | One per active PHP-FPM worker | -### dracula-new (web VM on HDD host) +### zamolxis (web VM on HDD host) | Parameter | Value | |---|---| | vCPU | 6 | -| RAM | 14GB | +| RAM | 24GB | | Disk | 80GB single virtual disk (HDD-backed) | | Balloon | Enabled (web VM, acceptable) | -RAM breakdown: PHP-FPM peak 1.4GB + Redis 512MB + nginx + UBC page cache for hot articles (~4–6GB effective) + OS 1GB + headroom. +RAM breakdown: PHP-FPM peak 1.4GB + Redis ~150MB + nginx + OS ~1GB + **~20GB available for UBC page cache**. With 20GB of UBC headroom, the entire nginx fastcgi_cache working set lives in RAM regardless of what is on disk — HDD latency becomes irrelevant for cached pages. Cold cache startup and log writes (sequential) are the only HDD operations that matter. -HDD is acceptable for the web node. The fastcgi_cache working set for a news site fits in UBC (RAM). HDD latency only affects cold cache startup and log writes (sequential). The CPU and PHP-FPM worker slots are the actual bottleneck, not disk. - -### transilvan-new (DB VM on SSD host) +### decebal (DB VM on SSD host — sole VM) | Parameter | Value | |---|---| -| vCPU | 4 | -| RAM | 26GB | +| vCPU | 6 | +| RAM | 30GB | | Disk 1 — OS | 20GB virtual (SSD-backed) | | Disk 2 — ZFS data pool | 100GB virtual (SSD-backed) | | Total disk | 120GB — fits within 130GB available, 10GB buffer for Proxmox overhead | | Balloon | **Disabled** — memory ballooning must not reclaim InnoDB buffer pool RAM | -RAM breakdown: innodb_buffer_pool_size 20GB + MySQL overhead 2GB + OS 4GB. +RAM breakdown: innodb_buffer_pool_size 26GB + MySQL overhead ~2GB + OS ~2GB. Decebal is the sole VM on its host — 30GB allocation leaves 2GB for the Proxmox host OS, which is the practical minimum. + +6 vCPU (from 8 logical CPUs on host) gives MySQL adequate threads for InnoDB background I/O, parallel queries, and connection handling while leaving 2 threads for Proxmox. **Disk sizing rationale:** FreeBSD base installs to ~2GB; 20GB for the OS disk is generous for binaries, ports tree fragments, and logs. The 100GB ZFS data pool provides: 9.6GB current dataset (likely 6–7GB after lz4 compression), 7 days of compressed mysqldumps (~4GB each = ~28GB), binlog rotation buffer (expire_logs_days = 3), and substantial growth headroom at the current data trajectory. @@ -230,21 +230,21 @@ Both virtual disks originate from the same physical SSD pool in Proxmox. Separat ## VM Provisioning (Proxmox) -### 1. Download FreeBSD 14.2 ISO on each Proxmox host +### 1. Download FreeBSD 15.0 ISO on each Proxmox host ```sh wget -P /var/lib/vz/template/iso/ \ https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/15.0/FreeBSD-15.0-RELEASE-amd64-disc1.iso ``` -### 2. Create dracula-new (run on the HDD host) +### 2. Create zamolxis / ID 900 (run on the HDD host) Via CLI: ```sh -qm create 200 \ - --name dracula-new \ - --memory 14336 \ - --balloon 14336 \ +qm create 900 \ + --name zamolxis \ + --memory 24576 \ + --balloon 24576 \ --cores 6 \ --cpu host \ --machine q35 \ @@ -261,24 +261,24 @@ qm create 200 \ Via UI: ``` -General → Name: dracula-new +General → Name: zamolxis OS → FreeBSD (other), ISO: FreeBSD-15.0-RELEASE-amd64-disc1.iso System → Machine: q35, BIOS: OVMF (UEFI), Qemu Agent: ✓ Disks → VirtIO SCSI, 80GB, Cache: None CPU → 6 cores, Type: host -Memory → 14336 MB +Memory → 24576 MB Network → VirtIO, vmbr0 ``` -### 3. Create transilvan-new (run on the SSD host) +### 3. Create decebal / ID 901 (run on the SSD host — sole VM) ```sh # VM with OS disk -qm create 201 \ - --name transilvan-new \ - --memory 26624 \ +qm create 901 \ + --name decebal \ + --memory 30720 \ --balloon 0 \ - --cores 4 \ + --cores 6 \ --cpu host \ --machine q35 \ --bios ovmf \ @@ -292,7 +292,7 @@ qm create 201 \ --agent enabled=1 # Add second disk for ZFS data pool -qm set 201 --scsi1 local:100,format=raw,discard=on,ssd=1 +qm set 901 --scsi1 local:100,format=raw,discard=on,ssd=1 ``` `--balloon 0` disables memory ballooning on the DB VM. This is required — ballooning can silently reclaim pages from the InnoDB buffer pool under host memory pressure. @@ -302,7 +302,7 @@ qm set 201 --scsi1 local:100,format=raw,discard=on,ssd=1 ``` Welcome screen → Install Keymap → your preference -Hostname → dracula-new (or transilvan-new) +Hostname → zamolxis (or decebal) Distribution → base, kernel (nothing else) Partitioning → Auto (ZFS) Pool type → stripe (single disk) @@ -339,13 +339,13 @@ service qemu-guest-agent start # Join Tailscale network sysrc tailscaled_enable=YES service tailscaled start -tailscale up --hostname=dracula-new # or transilvan-new +tailscale up --hostname=zamolxis # or decebal # Note the assigned Tailscale IP — plug into pf.conf and wp-config placeholders tailscale ip -4 ``` -After both VMs have Tailscale IPs, replace all `` and `` placeholders throughout this document and in all config files. +After both VMs have Tailscale IPs, replace all `` and `` placeholders throughout this document and in all config files. --- @@ -416,7 +416,7 @@ ssh -i /root/.ssh/id_ed25519 -p 79 root@transilvan \ --- -## Phase 1 — DB Node (transilvan-new) +## Phase 1 — DB Node (decebal) ### 1.1 FreeBSD base + ZFS @@ -444,10 +444,10 @@ sysrc mysql_dbdir=/var/db/mysql `/usr/local/etc/mysql/my.cnf`: ```ini [mysqld] -bind-address = +bind-address = datadir = /var/db/mysql socket = /tmp/mysql.sock -innodb_buffer_pool_size = 24G +innodb_buffer_pool_size = 26G innodb_log_file_size = 1G innodb_flush_log_at_trx_commit = 1 innodb_flush_method = O_DIRECT @@ -463,7 +463,7 @@ skip-name-resolve ``` ext_if = "vtnet0" ts_if = "tailscale0" -web_ts = "" +web_ts = "" set skip on lo0 block all @@ -483,7 +483,7 @@ service pf start pkg install tailscale sysrc tailscaled_enable=YES service tailscaled start -tailscale up --hostname=transilvan-new +tailscale up --hostname=decebal ``` ### 1.5 DB restore @@ -494,14 +494,14 @@ tailscale up --hostname=transilvan-new ssh root@transilvan \ "mysqldump -S /tmp/mysqld.sock --single-transaction \ --routines --triggers --events sql_sibiuindepen" \ - | ssh root@transilvan-new "mysql -u root sql_sibiuindepen" + | ssh root@decebal "mysql -u root sql_sibiuindepen" # Create app user (use same password as current DB_PASSWORD in wp-config) mysql -u root -e " - CREATE USER 'sql_sibiuindepen'@'' + CREATE USER 'sql_sibiuindepen'@'' IDENTIFIED BY ''; GRANT ALL ON sql_sibiuindepen.* TO - 'sql_sibiuindepen'@''; + 'sql_sibiuindepen'@''; FLUSH PRIVILEGES;" ``` @@ -528,7 +528,7 @@ Crontab: --- -## Phase 2 — Web Node (dracula-new) +## Phase 2 — Web Node (zamolxis) ### 2.1 Base packages @@ -703,7 +703,7 @@ service pf start pkg install tailscale sysrc tailscaled_enable=YES service tailscaled start -tailscale up --hostname=dracula-new +tailscale up --hostname=zamolxis ``` ### 2.7 rsync web files @@ -726,7 +726,7 @@ Two changes on the new node only: ```php // 1. Update DB_HOST to new transilvan Tailscale IP -define( 'DB_HOST', '' ); +define( 'DB_HOST', '' ); // 2. WP_CACHE stays true for Redis Object Cache drop-in // Remove the W3TC comment, keep the constant: @@ -851,17 +851,17 @@ curl -s https://connect.facebook.net/ro_RO/sdk.js > ${WEBROOT}/fb-sdk-ro.js ## Phase 3 — Zero-downtime Cutover -### Pre-cutover checklist (run on dracula-new before touching NPM) +### Pre-cutover checklist (run on zamolxis before touching NPM) ```sh # 1. Site responds via new stack directly curl -sk -o /dev/null -w "%{http_code}" \ - http:/// \ + http:/// \ -H "Host: sibiuindependent.ro" # Expected: 200 # 2. DB connection live -mysql -h \ +mysql -h \ -u sql_sibiuindepen -p sql_sibiuindepen \ -e "SELECT COUNT(*) FROM wp_posts WHERE post_status='publish';" @@ -872,7 +872,7 @@ wp --path=/var/www/sibiuindependent.ro --allow-root redis-cache status php85 -m | grep -E "imagick|redis|pdo_mysql|opcache|intl|soap|sodium" # 5. nginx fastcgi_cache working — second request should be HIT -curl -sI http:/// -H "Host: sibiuindependent.ro" \ +curl -sI http:/// -H "Host: sibiuindependent.ro" \ | grep X-Cache-Status # First request: MISS, second request: HIT ``` @@ -893,11 +893,11 @@ T-2 Final DB dump and import: ssh root@transilvan \ "mysqldump -S /tmp/mysqld.sock --single-transaction \ --routines --triggers --events sql_sibiuindepen" \ - | ssh root@transilvan-new "mysql -u root sql_sibiuindepen" + | ssh root@decebal "mysql -u root sql_sibiuindepen" T-3 Switch NPM upstream: old: 100.99.157.56 (dracula Tailscale IP) - new: + new: (single upstream change in NPM — no DNS TTL involved, instant) T-4 Smoke test: @@ -932,11 +932,11 @@ zfs snapshot data/mysql@post-migration-$(date +%Y%m%d) ## Open Items at Time of Planning - [ ] Confirm new Tailscale hostnames for both nodes (determines pf rules and wp-config DB_HOST) -- [ ] Confirm NPM node Tailscale IP (determines pf rule on dracula-new) +- [ ] Confirm NPM node Tailscale IP (determines pf rule on zamolxis) - [x] PHP 8.5 — binary package on FreeBSD 15.0, `pkg install php85`. No compilation. All extensions confirmed available. - [ ] Redis Object Cache plugin — already in plugins dir or fresh install needed - [ ] W3TC features in use beyond caching: minify? If yes, switch to standalone nginx-based minification or keep W3TC minify only with nginx page cache -- [ ] ZFS pool disk — confirm second disk available on transilvan-new VM +- [ ] ZFS pool disk — confirm second disk available on decebal VM - [ ] MySQL 8.4 vs MariaDB 11.4 LTS — plan targets MySQL 8.4; revisit if preferred - [x] Beszel and Telegraf — dropped. Proxmox native VM monitoring is sufficient. -- [x] Redis placement — local to dracula-new (unix socket). No 3rd host. WordPress object cache uses 50–150MB RAM; a dedicated VM would cost more resources than Redis itself consumes. A separate Redis host is only justified when scaling to multiple web nodes sharing a single cache. +- [x] Redis placement — local to zamolxis (unix socket). No 3rd host. WordPress object cache uses 50–150MB RAM; a dedicated VM would cost more resources than Redis itself consumes. A separate Redis host is only justified when scaling to multiple web nodes sharing a single cache.