Phase 1-4: full doc reorg
Phase 1 — fix stale state after strong migration (Phase 1+2, 2026-07-05)
- README: corrected IPs (jellyfin 206→246, arriman 132→245, etc.),
added missing containers (128 trmnl, 129 house, 133 seanime, 134 romm,
124 authentik), updated last-refreshed date, added strong host context
- containers/101-jellyfin.md: IP 206→246, host hubris→strong, mount
/mnt/library→/mnt/media_local, GPU 760M→680M+RX7600, privilege→priv
- containers/118-elementsynapse.md: IP 239→242, added Host: strong
- containers/122-arriman.md: IP 132→245, mount→/mnt/media_local, added Host
- containers/129-house.md: IP 212→244, added Host: strong
- containers/130-grimmory.md: IP 213→247, mount→/mnt/media_local, added Host
- containers/121-caddy.md: fixed site list (books→grimmory, removed auth→VPS,
added house, roms, teddy, trmnl)
- hosts/strong.md: updated At-a-glance to reflect 7 LXCs hosted
- containers/123-claudio-bot.md, 127-mule-photos-new.md: archived to
containers/archive/ (were destroyed LXCs with living pages)
- inventory.yaml: verified correct — no changes needed
Phase 2 — structural cleanup
- infrastructure/index.md: one-page overview of all cross-cutting systems
- runbooks/: moved runbook-budget-from-csv.md and runbook-dpkg-interrupted.md
from operations/ with YAML frontmatter added
- plans/done/: moved 4 completed plans out of active view; updated index
- vms/index.md: added VM index page
Phase 3 — navigation & discoverability
- GLOSSARY.md: term definitions (Authentik, Caddy, LXC, VAAPI, etc.)
- README: added table of contents, links to glossary + infrastructure index
- investigations/: archived 2 resolved cases (crash-loop, authentik-migration)
to investigations/archive/; updated index with active vs archived sections
Phase 4 — ongoing discipline
- CONTRIBUTING.md: documented same-session update rule with explicit checklist
- README: replaced full LXC table with summary + link to containers/index.md
(single source of truth; de-duplication)
This commit is contained in:
@@ -1,202 +0,0 @@
|
||||
# Runbook: Budget import from N26 CSV → Yuvomi
|
||||
|
||||
Distil a bank-export CSV into Yuvomi's Budget and Subscriptions modules using
|
||||
the `yuvomi-mcp` tools. Run this whenever a new CSV period needs to be
|
||||
summarised into targets and fixed costs.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `yuvomi-mcp` is running on LXC 129 and connected as an MCP server in Claude.
|
||||
- The CSV is an N26 export (columns: Booking Date, Value Date, Partner Name,
|
||||
Partner Iban, Type, Payment Reference, Account Name, Amount (EUR), …).
|
||||
- API token: `homelab secret yuvomi-api-token` (decrypts on any enrolled client).
|
||||
- Direct API base: `https://house.hubris.network/api/v1`
|
||||
|
||||
---
|
||||
|
||||
## API quirks (Yuvomi ≤ 0.77.x)
|
||||
|
||||
- **Subscriptions live under `/budget/subscriptions`**, NOT `/subscriptions/`.
|
||||
A top-level `/subscriptions` route returns 404.
|
||||
- `GET /budget/subscriptions` → `{ data: { subscriptions: [...], summary: {...} } }`
|
||||
- `GET /budget/subscriptions/meta` → `{ data: { categories: [...], payment_methods: [...] } }`
|
||||
- `POST /budget/subscriptions` → create a subscription (name, amount, billing_cycle,
|
||||
cycle_interval, next_payment_date, currency, category_id, payment_method_id required)
|
||||
- `GET /budget/` (no month) → returns only **non-recurring** base entries.
|
||||
Use `GET /budget/?month=YYYY-MM` to get all entries (recurring + one-time) for a month.
|
||||
- `GET /budget/categories` → expense category keys + income category names (German keys
|
||||
like `"Erwerbseinkommen"`, `"Sozialleistungen"`, `"Geschenke & Transfers"`).
|
||||
- Budget entries: `amount` positive = income, negative = expense.
|
||||
- Recurring entries: set `is_recurring: 1` + `recurrence_interval: "monthly"`.
|
||||
The `date` field sets the start month.
|
||||
- `recurrence_virtual: 1` smooths non-monthly amounts across all months in the summary
|
||||
(e.g. 55.08 € quarterly → shows as ~18.36 €/month).
|
||||
- Custom RRULE strings (`recurrence_rule`) are **not accepted** by the API — use
|
||||
`cycle_interval` on the subscription instead, or `recurrence_interval` on budget entries.
|
||||
|
||||
---
|
||||
|
||||
## Subscription category IDs (as of 2026-06-26)
|
||||
|
||||
| id | name | budget_subcategory_key |
|
||||
|---|---|---|
|
||||
| 1 | Entertainment | subscription_entertainment |
|
||||
| 2 | Productivity | subscription_productivity |
|
||||
| 3 | Utilities | subscription_utilities |
|
||||
| 4 | Health | subscription_health |
|
||||
| 5 | Education | subscription_education |
|
||||
| 6 | Other | subscription_other |
|
||||
|
||||
## Payment method IDs
|
||||
|
||||
| id | name |
|
||||
|---|---|
|
||||
| 1 | Credit Card |
|
||||
| 2 | Debit Card |
|
||||
| 3 | PayPal |
|
||||
| 6 | Bank Transfer / SEPA |
|
||||
| 7 | Other |
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Categorise the transactions
|
||||
|
||||
Skip these as internal/already-covered:
|
||||
- Fixed costs you'll enter as **subscriptions** (Miete, SWM, SYNVIA, Hundefutter,
|
||||
Netflix, Grover, Rundfunk ARD, KuKita, Lillydoo)
|
||||
- Internal transfers (The Joy Pot ↔ Cookie, Hauptkonto, Tagesgeldkonto splits)
|
||||
- Identified income (Cookie Share, Kindergeld, Pocket Money credits, Distributor)
|
||||
- Fun Money pass-throughs (in and out same month → net zero)
|
||||
|
||||
**Variable expense taxonomy:**
|
||||
|
||||
| Category key | Subcategory key | Examples |
|
||||
|---|---|---|
|
||||
| `food` | `groceries` | E-Center, Knuspr, EDEKA, Tegut, VollCorner, Lidl, Netto, REWE, KoRo, Roast Market |
|
||||
| `food` | `restaurants_bars` | Restaurants, Lieferando, Cafes, Zeit für Brot, Baobab, Höflinger |
|
||||
| `personal_health` | `beauty_cosmetics` | DM, Rossmann |
|
||||
| `personal_health` | `pharmacy` | Apotheke, MVZ Dermatologie |
|
||||
| `transport` | `apps_taxi` | Uber, RYD GMBH, MVG, Handyparken |
|
||||
| `shopping_clothing` | `gifts` | Children products: Schlummersack, Catchy Kids, SP EVERY., Dukal, Berger-Lernwelt |
|
||||
| `shopping_clothing` | `clothes_shoes` | Zalando, Ernsting's, Schuhmair, Thalia, Vinted, Airbnb, Hotel at Booking.com |
|
||||
| `shopping_clothing` | `electronics` | Amazon, AMZN Mktp DE |
|
||||
| `housing` | `renovation_maintenance` | IKEA, Markus Festl, Granit, Sostrene Grene, Mol* tischdecken, Gaertnerei, Dehner |
|
||||
| `education` | `courses_college` | Kathrin Orlob (PEKiP), Nerina Aupperle |
|
||||
| `leisure` | `streaming` | WOW wowtv.de |
|
||||
| `financial_other` | `bank_fees` | Unidentified PayPal, Ratepay, N26 fees |
|
||||
| `Geschenke & Transfers` | *(income)* | One-off incoming transfers |
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Create subscriptions
|
||||
|
||||
```
|
||||
get_subscriptions_meta() ← get category_id and payment_method_id
|
||||
```
|
||||
|
||||
**Standard Cookie household subscriptions (as of 2026-07):**
|
||||
|
||||
| Name | Amount | billing_cycle | cycle_interval | category_id | payment_method_id |
|
||||
|---|---|---|---|---|---|
|
||||
| Miete | 1080.00 | monthly | 1 | 6 (Other) | 6 (Bank Transfer) |
|
||||
| Strom (SWM) | 79.00 | monthly | 1 | 3 (Utilities) | 6 |
|
||||
| Internet / TV / Telefon | 29.99 | monthly | 1 | 3 (Utilities) | 6 |
|
||||
| Hundefutter | 75.00 | monthly | 1 | 6 (Other) | 6 |
|
||||
| Netflix | 8.00 | monthly | 1 | 1 (Entertainment) | 6 |
|
||||
| Grover | 16.90 | monthly | 1 | 6 (Other) | 2 (Debit Card) |
|
||||
| Rundfunk ARD / ZDF | 55.08 | monthly | 3 | 1 (Entertainment) | 6 |
|
||||
| KuKita Daycare (Leon) | 503.00 | monthly | 1 | 5 (Education) | 6 |
|
||||
| Lillydoo diapers | 56.70 | monthly | 2 | 4 (Health) | 3 (PayPal) |
|
||||
|
||||
Monthly equivalent total: **1,838.60 €** (Yuvomi applies cycle_interval to prorate).
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Add recurring income entries
|
||||
|
||||
```
|
||||
stage_add_budget_entry(
|
||||
title="Kindergeld",
|
||||
amount=55.00,
|
||||
category="Sozialleistungen",
|
||||
date="YYYY-MM-01",
|
||||
is_recurring=True,
|
||||
recurrence_interval="monthly",
|
||||
)
|
||||
commit_pending(pending_id)
|
||||
```
|
||||
|
||||
**Standard recurring income:**
|
||||
|
||||
| Title | Amount | category |
|
||||
|---|---|---|
|
||||
| Kindergeld | +55.00 | Sozialleistungen |
|
||||
| Cookie Share | +2650.00 | Erwerbseinkommen *(see recommended amount below)* |
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Post variable transactions
|
||||
|
||||
For each non-skipped CSV row, call `stage_add_budget_entry` with the mapped
|
||||
category/subcategory and the actual transaction amount and date. Use the Partner
|
||||
Name + Payment Reference as the title (truncate to 100 chars).
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Verify
|
||||
|
||||
```
|
||||
get_budget_summary("YYYY-MM")
|
||||
list_subscriptions()
|
||||
```
|
||||
|
||||
Expected for a full month with KuKita:
|
||||
- Fixed expenses ≥ 1,838 € (subscriptions)
|
||||
- Variable expenses ≥ 500 € (groceries alone)
|
||||
|
||||
---
|
||||
|
||||
## Cookie Share: how much to transfer monthly
|
||||
|
||||
Calculated from Jan–Jun 2026 data (Cookie account, one-offs stripped):
|
||||
|
||||
| | €/month |
|
||||
|---|---|
|
||||
| **Fixed costs (subscriptions)** | **1,839** |
|
||||
| Miete | 1,080 |
|
||||
| KuKita *(permanent from Jul 2026)* | 503 |
|
||||
| Strom + SYNVIA + Rundfunk + Netflix + Grover + Hundefutter + Lillydoo | 256 |
|
||||
| **Variable (6-month averages)** | **1,032** |
|
||||
| Groceries | 595 |
|
||||
| Children products | 142 |
|
||||
| Dining & cafes | 100 |
|
||||
| Transport | 66 |
|
||||
| Drugstore | 52 |
|
||||
| Clothing, Amazon, Pharmacy | 77 |
|
||||
| **Total monthly spend** | **≈ 2,871** |
|
||||
| Minus Kindergeld (fixed income) | −55 |
|
||||
| Minus Pocket Money (conservative ~600 €) | −600 |
|
||||
| **→ Recommended Cookie Share** | **≈ 2,650 €** |
|
||||
| With 200 € buffer | **≈ 2,850 €** |
|
||||
|
||||
**Current Cookie Share (Jun 2026): 1,995 € — shortfall ~655 €.**
|
||||
|
||||
The gap was covered by irregular Pocket Money top-ups (avg 962 €/mo over 6 months, but
|
||||
highly variable: 121 €–3,000 €). KuKita starting in June is the biggest step-up; raising
|
||||
Cookie Share to **2,650 €** makes the budget self-sufficient without relying on top-ups.
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2026-06-29 — Corrections from first real import
|
||||
- Subscriptions endpoint is `/budget/subscriptions`, NOT `/subscriptions/` (404).
|
||||
- `recurrence_rule` RRULE strings are rejected by the API; use `cycle_interval` instead.
|
||||
- `GET /budget/` (no filter) returns only non-recurring entries; use `?month=` for full view.
|
||||
- Added Cookie Share recommendation (2,650 €/month) based on 6-month expense analysis.
|
||||
- Added full category taxonomy table.
|
||||
|
||||
### 2026-06-29 — Initial runbook
|
||||
Created from Jan–Jun 2026 N26 Cookie account analysis.
|
||||
@@ -1,108 +0,0 @@
|
||||
# Runbook — recover from dpkg-interrupted state
|
||||
|
||||
You're here because an apt run got killed mid-transaction and the target now
|
||||
has packages that are **unpacked but not configured**. Symptoms:
|
||||
|
||||
- `apt` refuses to do anything new: `Error: dpkg was interrupted, you must
|
||||
manually run 'dpkg --configure -a' to correct the problem.`
|
||||
- `dpkg --audit` lists packages with header
|
||||
`The following packages have been unpacked but not yet configured.`
|
||||
- `homelab apt-audit` shows `DPKG: DIRTY(N)` for the host.
|
||||
|
||||
The system is still running the **old** binaries (still in memory), but the
|
||||
**new** binaries are unpacked and waiting for their postinst to run. Two
|
||||
worst-case manifestations from the 2026-05-21 sweep:
|
||||
|
||||
- LXC 121 caddy: leftover state from a prior aborted apt run; caddy itself was
|
||||
still serving but the new caddy binary on disk hadn't been wired up.
|
||||
- hubris: ssh master died mid-Wave-6 → 135 packages unpacked-not-configured,
|
||||
including `systemd`, `openssh-server`, `sudo`, `netbird`. The half-
|
||||
configured netbird daemon dropped the mesh peer, and we got locked out
|
||||
until we recovered from the PVE web UI Shell.
|
||||
|
||||
**Do not reboot until dpkg is clean.** A reboot tries to start the new
|
||||
binaries' services, which may fail because postinst never ran (missing users,
|
||||
config dirs, capabilities, etc.). The system might not come back up cleanly.
|
||||
|
||||
## Path A — target is still reachable over ssh (preferred)
|
||||
|
||||
```
|
||||
homelab ssh <host> -- bash -c 'DEBIAN_FRONTEND=noninteractive dpkg --configure -a && apt -y -o Dpkg::Options::=--force-confold upgrade'
|
||||
```
|
||||
|
||||
Or for an LXC by name:
|
||||
|
||||
```
|
||||
homelab pct <lxc> exec -- bash -c 'DEBIAN_FRONTEND=noninteractive dpkg --configure -a && apt -y -o Dpkg::Options::=--force-confold upgrade'
|
||||
```
|
||||
|
||||
When that returns, confirm:
|
||||
|
||||
```
|
||||
homelab apt-audit --target <host>
|
||||
```
|
||||
|
||||
Expect `DPKG: ok` and the remaining `UPGR` count to match what's intentionally
|
||||
deferred (kernel/PVE on hubris, 0 elsewhere).
|
||||
|
||||
## Path B — target locked out (mesh broken / ssh dead)
|
||||
|
||||
Most common for hubris when netbird itself went half-configured: the daemon
|
||||
crashed on the new binary, the mesh peer dropped, port 22022 stopped listening,
|
||||
and you can't ssh in.
|
||||
|
||||
1. Open `https://proxmox.hubris.network` in a browser.
|
||||
2. Datacenter → node `hubris` → `>_ Shell` (or `_ Console`). That's a root
|
||||
shell on hubris served by the PVE web UI, independent of the netbird mesh.
|
||||
3. Run the recovery one-liner:
|
||||
|
||||
```
|
||||
DEBIAN_FRONTEND=noninteractive dpkg --configure -a \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt -y -o Dpkg::Options::=--force-confold upgrade \
|
||||
&& systemctl restart netbird \
|
||||
&& dpkg --audit \
|
||||
&& echo RECOVERY_OK
|
||||
```
|
||||
|
||||
Wait for `RECOVERY_OK`. The `systemctl restart netbird` is the bit that
|
||||
heals the mesh — once netbird's daemon comes back up clean, your client's
|
||||
peer state moves from `Connecting` to `Connected` within ~30 seconds and
|
||||
the rest of your tooling works again.
|
||||
|
||||
4. For an **LXC** that's locked out (less common — LXCs reach the world via
|
||||
netbird routed through hubris, so unless hubris itself is broken, you can
|
||||
still `pct enter` from the hubris shell):
|
||||
|
||||
From the PVE web UI shell on hubris:
|
||||
|
||||
```
|
||||
pct enter <id>
|
||||
DEBIAN_FRONTEND=noninteractive dpkg --configure -a && apt -y upgrade
|
||||
exit
|
||||
```
|
||||
|
||||
## Prevention
|
||||
|
||||
The `homelab apt-upgrade` wrapper launches apt inside a `systemd-run --collect`
|
||||
unit on the target, so it survives ssh teardown — the failure mode that put
|
||||
hubris into this state in the first place is no longer reachable through the
|
||||
standard tool. If you absolutely need to run apt manually over ssh, wrap it:
|
||||
|
||||
```
|
||||
ssh <host> systemd-run --unit=apt-recovery --collect bash -c 'apt -y upgrade'
|
||||
```
|
||||
|
||||
Then `systemctl status apt-recovery` from a fresh ssh to check progress.
|
||||
|
||||
## Related
|
||||
|
||||
- [Operations cheatsheet](commands.md)
|
||||
- [Auto-deploy pipelines](../infrastructure/auto-deploy.md)
|
||||
- [Hubris host page](../hosts/hubris.md)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2026-05-21 — initial page
|
||||
Documents the dpkg-interrupted recovery path that came out of the
|
||||
fleet apt sweep (Wave 6 killed mid-transaction; hubris recovered via PVE
|
||||
web Shell).
|
||||
Reference in New Issue
Block a user