Operational runbook for the fk-dev GCP dev-mirror VM: how to connect, how to deploy a branch by bind-mount (no image rebuild), how the two NÚSZ fixes (FKITDEV-8747 + FKITDEV-8959) now ride one branch, and the proven standalone recipe for FKITDEV-8959 test case TC-8959-02 (key-inaccessible image deletion). All facts verified on fk-dev on 2026-07-03.
For Agents
- Box:
fk-dev= GCP dev-mirror VM on tailnettaild4189d.ts.net(100.91.108.61). It replaces the on-premssh Facekom/lederera-447-fk-hardverbox, which is decommissioned (offline since ~2026-06-27) — never use that alias again. Host summary: dev-build-host.- Connect:
command ssh ops@fk-dev.taild4189d.ts.net(userops, Tailscale SSH, no keypair). Gotcha: akakuwrapper shadowsssh/scpin Claude’s non-interactive shell → always usecommand ssh/command scp.- Operator UI: https://oss-fk-dev.taild4189d.ts.net (vuer_oss,
:10081inside the container).- NÚSZ branch:
customization/nusztipd426cc6ae1carries both fixes — 8747 (5099b8ad8b, PR #7929) and 8959 (519d3b933e, PR #8010, merged). Both are vuer_oss-only.- Deployed
customization/nusz→ fk-dev vuer_oss on 2026-07-03. Restore the box’s original commitbd8923d69f(InstaCash line) when NÚSZ testing is done.- TC-8959-02 result: PASS — key-inaccessible image is blanked + archived +
encryptionId=null(not thrown). See Result — PASS.- Provisioning + architecture of fk-dev: tailscale-gcp-dev-box-migration.
fk-dev — connection & operational reality
fk-dev is the deployed, in-use realization of the GCP dev-box mirror designed in tailscale-gcp-dev-box-migration. As of 2026-07-03 the 8b per-service Tailscale-sidecar overlay is live (that note ended at “push gated on user go”; the box is now running it).
| Property | Value |
|---|---|
| Host / tailnet IP | fk-dev.taild4189d.ts.net / 100.91.108.61 |
| Tailnet | taild4189d.ts.net |
| SSH | command ssh ops@fk-dev.taild4189d.ts.net — user ops, Tailscale SSH (no keypair); Tailscale is already up on the Mac |
| Operator UI | https://oss-fk-dev.taild4189d.ts.net (vuer_oss Express :10081 inside) |
| Stack | vuer_docker compose, per-service Tailscale sidecars (oss-fk-dev, css-fk-dev, esign-*-fk-dev, portal-fk-dev), app source bind-mounted at /workspace/<repo>, supervisord inside each container |
| Postgres | container postgresql |
Operational gotchas
kakushadowsssh. In Claude’s non-interactive shell akakuwrapper intercepts plainssh/scp→ usecommand ssh/command scp. (Same class of gotcha as the on-prem box — see dev-box-esign-container-startup-failures-2026-06-01.)- Postgres peer-auth blocks
psql -U postgreson thepostgresqlcontainer. Don’t fight it — reach the DB through the app’s own Sequelize connection instead (the verification harness below does exactly this).nginx_proxyis crash-looping (pre-existing). It doesn’t matter: the per-service Tailscale sidecars are the access path and bypassnginx_proxyentirely.
Deploy recipe — bind-mount branch swap (no image rebuild)
Because app source is bind-mounted at /workspace/<repo> and each container runs supervisord, deploying a branch is a git checkout + in-container rebuild + restart — no image build, no compose recreate.
Git-forward gotcha — the box has no GitHub key
The box’s git remote is
git@github.com:TechTeamer/vuer_oss(SSH), but fk-dev has no GitHub deploy key. Forward your own agent:ssh-add ~/.ssh/id_ed25519on the Mac (keyid_ed25519, no passphrase), then connect withcommand ssh -Asogit fetchon the box authenticates as you.
# From the Mac: load key + connect with agent forwarding
ssh-add ~/.ssh/id_ed25519
command ssh -A ops@fk-dev.taild4189d.ts.net
# On the box, in the bind-mounted source tree:
cd /workspace/vuer_oss
git fetch origin customization/nusz # forwarded key authenticates
git checkout customization/nusz # tip d426cc6ae1
# Rebuild + restart INSIDE the container (node_modules/dist are bind-mounted):
docker exec vuer_oss sh -c 'cd /workspace/vuer_oss && yarn install && yarn build'
docker exec vuer_oss supervisorctl restart allVerify the deploy:
docker exec vuer_oss supervisorctl status all→ every program RUNNING- vuer_oss log shows
Web server is listening on 10081 - operator UI returns HTTP 302:
https://oss-fk-dev.taild4189d.ts.net
Restore after testing
The box’s original vuer_oss commit was
bd8923d69f(the InstaCash line).customization/nuszwas checked out over it for NÚSZ testing on 2026-07-03 — checkbd8923d69fback out (same recipe) when NÚSZ testing is done so fk-dev returns to its prior baseline.
NÚSZ branch consolidation — both fixes on customization/nusz
The two NÚSZ tickets in flight both landed on one branch, both vuer_oss-only:
| Ticket | Commit | PR | State |
|---|---|---|---|
| FKITDEV-8747 daily-stat “eltérés” | 5099b8ad8b | #7929 | merged |
| FKITDEV-8959 image deletion | 519d3b933e | #8010 | merged |
customization/nusz tip = d426cc6ae1 contains both. Deploying that one branch to fk-dev exercises both fixes together (their shared test cases live at FKITDEV-8959-8747-test-cases.md).
Stale remote-tracking ref — the 8959 branch was auto-deleted on merge
The old feature branch
fix/FKITDEV-8959-nusz-image-deletionwas auto-deleted from origin when PR #8010 merged, but the localorigin/fix/FKITDEV-8959-nusz-image-deletionref was still present and stale (facekom clones use narrowed fetch refspecs with no prune — same failure mode as FKITDEV-8959’s parent work and the SSH-topology notes). Alwaysgit ls-remote origin <branch>to confirm a branch actually exists before assuming a localorigin/...ref is real. The fix now lives only oncustomization/nusz(via the merge).
FKITDEV-8959 — TC-8959-02 verification (key-inaccessible image deletion)
Proven on fk-dev, 2026-07-03. TC-8959-02 (see the test-case file) is the scenario the old code died on: an old image whose customer encryption key is offline / inaccessible at cron time. The fixed cron must blank + archive the image instead of throwing The key options property is required… and looping forever.
Code path under test (customization/nusz)
The cron RemoveAttachmentDataCronJob is a thin wrapper over CustomRemoveOldDataCronService.removeAttachmentData():
RemoveAttachmentDataCronJob.run()
→ CustomRemoveOldDataCronService.removeAttachmentData()
→ getOldImageAttachments() # query: type LIKE 'image/%'
# AND isArchived = false
# AND createdAt < cutoff
# batched, excludes the `file` blob
→ removeOldAttachments() # the KEY GUARD:
const encryption = encryptionId ? getEncryption() : null
if (encryption?.key) encryptBuffer(empty, { key }) // normal path
else { file = Buffer.from(''); encryptionId = null; skippedNoKey++ } // key-offline path
isArchived = true
save()Key dependency chain (why a standalone harness needs the full crypto stack)
encryption.key is not a column — it is a Sequelize getter (server/db/model/encryption.js) that resolves the actual key lazily:
Key resolution chain (
encryption.keygetter)graph LR A["encryption.key<br/><i>Sequelize getter</i><br/>model/encryption.js"] --> B["cryptos.data.getActualKey(key, customerId)<br/><i>DataCryptoService</i>"] B -->|"null / absent key"| C["customerKeyStorage.getKey(customerId)"] C -->|"STUB ⇒ null<br/>(models offline key)"| D["encryption.key = null"] D --> E["removeOldAttachments key-guard:<br/>blank file + encryptionId=null + archive"] style A fill:#264653,stroke:#2a9d8f,color:#fff style C fill:#3d2020,stroke:#c77,color:#fff style E fill:#2d2d2d,stroke:#888,color:#fff
So a standalone harness must init serviceContainer.service.cryptos.{media,data,attachment}, CryptoService, and customerKeyStorage. Stubbing customerKeyStorage.getKey() => null faithfully models “customer key offline / inaccessible” — the exact real-world bug trigger (a customer-managed key not loaded at the midnight cron). This is the same encryption-model machinery documented under sms-verification-code-dev-testing (cryptos.data = DataCryptoService, per-row key).
Test harness pattern (worked)
A Node script placed in /workspace/vuer_oss/bin/ (so ../server/... requires resolve), delivered and run inside the container:
require('../server/bootstrap/process-settings')()— load config.- Override
serviceContainer.loggerwith a consoleProxy(so anylogger.x()call just prints). - setupServices — instantiate
cryptos.{media,data,attachment}+CryptoService+ acustomerKeyStoragewhosegetKey()is stubbed to returnnull. - setupDb —
../server/db/sequelizeauthenticate + load../server/db/models. - Seed via raw
sequelize.queryINSERTs (bypasses model hooks):encryptionsrow withkey = NULLattachmentsrow:type = 'image/png', oldcreatedAt,isArchived = false,encryptionId = <enc>
- Call the real
getOldImageAttachments()+removeOldAttachments(), scoped by a synthetic date window around the seeded row. SELECTthe row before and after.
Delivery: command scp the script to /tmp on the box → docker cp into the container → docker exec … node <script> → rm.
Table shapes (fk-dev NÚSZ DB, ids start at 1 — empty DB)
attachments— NOT NULL:name,size,type,createdAt,updatedAt.fileisBYTEAnullable;encryptionIdFK nullable; there is no requiredroomId.encryptions—keyisVARCHAR(44)nullable (holds the wrapped key; NULL models “no key”).
Result — PASS
BEFORE : { isArchived: false, encryptionId: 3, file_bytes: 9 }
TALLY : { processed: 1, archived: 1, skippedNoKey: 1, errored: 0 }
AFTER : { isArchived: true, encryptionId: null, file_bytes: 0 }The key-guard blanks the file (9→0 bytes), nulls encryptionId, and archives the row instead of the old throw — exactly the TC-8959-02 expected behaviour. Matches the devel e2e result recorded in the test-case file (image blanked + archived + encryptionId → null).
Open question — retention period (7 vs 28)
fk-dev config:
attachments.active = true,expiryDays = 7(dev.json) /28(docker.json). The parent report ASSNUSZ-117 says 7 days; the deployed default is 28. The fix deletes at whatever is configured (it does not change the period). Still OPEN — confirm the agreed retention with NÚSZ before sign-off; if 7 is required it is a one-line config change.
Related
- FKITDEV-8959 — image-deletion cron RCA + fix design (the code under test here)
- FKITDEV-8747 — daily-stat “eltérés” fix that rides the same
customization/nuszbranch - tailscale-gcp-dev-box-migration — how fk-dev was provisioned + the 8b Tailscale-sidecar overlay this runbook operates
- sms-verification-code-dev-testing — the
cryptos.data/ per-row-key encryption model the verification harness stubs - dev-box-esign-container-startup-failures-2026-06-01 — the on-prem box’s SSH topology + the same
kaku-shadows-sshand in-containeryarn installgotchas - dev-build-host — canonical host reference (fk-dev current,
ssh Facekomdecommissioned) - instacash-esign-dev-box-deploy — sibling bind-mount deploy recipe (tag-based instead of branch-based; written for the old box, now run it on fk-dev)
- NÚSZ release 1.9.11.48 — the release both fixes ship in
- Test cases:
/Users/levander/coding/facekom/FKITDEV-8959-8747-test-cases.md(TC-8959-02 spec + all cases for both tickets)