# Template cleanup — turn a production clone into a clean golden VM Scripts to reset a **clone of VM 105 (logship-production)** into a clean, data-free template VM. Built 2026-07 for VM 113 `logship-template-2`, rebuilt 2026-10-01 as VM 115 `logship-template-3` (192.168.12.67). Full runbook: RECLONE-RUNBOOK.md; short version: ## Keeping the template current without a re-clone `sync-from-prod.sh [--since YYYY-MM-DD] [--dry-run]` (run from a workstation with root ssh to prod + template, `ssh-add` first) diffs prod against the template and applies: apt packages prod has newer, prod's Node/pm2 version, the frontend tree (template `.env`/`data/` kept, prod-only env keys appended blank), new iDempiere tables/columns/CHECKs + AD rows created since the date, Jasper files, jetty.xml, postfix relay config, hosts line, logship-scripts. Config-row changes (price lists, shippers, printers, …) and new AD windows/tabs/menus are reported only. Re-clone instead when the OS or iDempiere itself changed. Last sync: see TEMPLATE-README.md header. ## Order of operations 1. **Clone from PBS backup** (prod untouched), on prox2: ```bash qmrestore pbs-logyou:backup/vm/105/ 115 --storage Disk1-NVMe --unique 1 qm set 115 --name logship-template-3 --onboot 0 --protection 0 \ --ipconfig0 ip=192.168.12.67/24,gw=192.168.12.1 # read MAC from `qm config 115`, then boot quarantined: qm set 115 --net0 virtio=,bridge=vmbr1,link_down=1 qm start 115 ``` 2. **`00-defuse.sh`** — via `qm guest exec` while NIC is down: wipes root crontab (DHL crawler! customer reports!), disables postfix, clears all frontend runtime data (`data/` DBs + dirs, per-user caches), wipes `/root/storage` PDFs + stale deploy copies, removes prod ES bind + data.logship.de hosts entry, drops the Check_MK registration. 3. **`03-neutralize-env.sh`** — blanks external endpoints/credentials in `/opt/erp-nuxt-frontend/.env` (LARAVEL, Sentry, carrier/marketplace/ Amazon-Ads creds, FreePBX, IVR, landing/Laravel tokens, SEPA, mail archive, Anthropic), repoints STRAPIPUBLIC/WEBAUTHN_ORIGIN/MOBILE_APP_PUBLIC_BASE at the clone IP. **`05-apply-env-overrides.sh`** must follow every .env edit: `runtimeConfig` values are baked into the build with PROD's .env, so the neutralized values only take effect through the generated `NUXT_*` override block (and a `pm2 delete` + `pm2 start ecosystem.config.cjs`, not `pm2 restart`). `clone-logship-instance.sh` runs 03 → 05 → 04 automatically. 4. Bring NIC up (`qm set 115 --net0 virtio=,bridge=vmbr1`), then everything else over SSH. 5. **`run-cleanup.sh --dry-run`** first — runs the iDempiere SQL with ROLLBACK and prints the dynamic targets, survivors and zero checks. Then **`run-cleanup.sh`** — safety pg_dumps, stops services, runs `01-idempiere-truncate.sql` + `02-strapi-truncate.sql`, wipes uploads, deletes ES indices, restarts everything, log hygiene. 6. **Review together** the survivor report + row counts before finalizing. 7. Finalize: `rm -rf /root/template-cleanup/backup/`, remove `/root/env.production.bak`, clear shell history, `fstrim -a`, right-size RAM (`qm set 115 --memory 20480`), shutdown. ## What the iDempiere truncation keeps / deletes - **Kept:** client 1000000 config (roles, doc types, price list headers, taxes, warehouses/locators of org 1000000, `cust_commissiontable`, `cust_printer`, `cust_labeltemplate`, `cust_nonworkingdays`, `cust_shippingcost_default`, `cust_softwareplan`, `cust_softwarecatalogitem` (software-offer catalog), `m_attributeset`, Jasper processes), staff users, GardenWorld demo client. - **Deleted:** all transactional documents of client 1000000 (orders, invoices, shipments, payments, fact_acct, stock, requests, …), all global log tables, ALL products + business partners of client 1000000 except rows FK-referenced by kept config, and orgs 1000001+ from every table with an `ad_org_id` column. - Engine: pass-based delete list with FK-retry; rows whose DELETE hits a foreign-key violation after convergence are reported as survivors — that's the "required by config" set, by construction. - **Dynamic catch-all** (pass 1, after the explicit list): every populated `cust_*` table not in `keep_tables`, every table carrying a document FK column (`c_order_id`, `m_inoutline_id`, …) outside dictionary/master data, and all `i_*` / `t_*` / `*log` tables. Each addition is announced as `DYNAMIC target … : N rows` in the log — review them in the dry run. Strapi: every non-infrastructure table is truncated the same way. All scripts carry a **production guard** (hostname/IP check) and are idempotent — safe to re-run after fixing an issue. Kept in `reference/` (tracked, not built/watched) per repo convention.