Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ updates:
wazuh:
patterns: ["wazuh/*"]

# The media stack on smaug. One image, so no groups. Present before the pool
# is, for the reason the entries above give — but this one was absent while
# three documents said it was here: ADR-0040 keeps stacks/media in this
# repository rather than making it a TrueNAS catalogue app precisely so
# "Dependabot, the digest pins and make validate keep reaching it", and
# build-the-nas.md §6 and the stack's own README repeat the claim. The pins
# and make validate did reach it. This is the third.
- package-ecosystem: docker-compose
directory: /stacks/media
schedule:
interval: weekly
day: sunday
open-pull-requests-limit: 5
commit-message:
prefix: "chore(deps)"
labels: ["dependencies", "media"]

- package-ecosystem: github-actions
directory: /
schedule:
Expand Down
19 changes: 14 additions & 5 deletions docs/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,20 @@ Televisions and consoles. Internet only.
and running TrueNAS by
[ADR-0040](adr/0040-run-truenas-on-smaug-and-keep-the-media-stack-in-this-repository.md)
([#413](https://github.com/Gerrrt/HomeLab/issues/413)). Its ZFS mirror does
not exist yet and neither does `stacks/media`; what exists is a host on its
address. **It does not change the *Reaches* column**, and that is the point
ADR-0016 made in advance: nothing on this segment initiates anywhere, and the
four rules created that day all let a more trusted segment reach **in**.
That is the direction this row records, and it is the one that is unchanged.
not exist yet, and `stacks/media` is authored and undeployed against that
absence; what exists is a host on its address. **It does not change the
*Reaches* column**, and that is the point ADR-0016 made in advance: nothing
on this segment initiates anywhere, and the rules created that day all let a
more trusted segment reach **in**. That is the direction this row records,
and it is the one that is unchanged. **They are counted in the next bullet
and nowhere else** — this bullet used to count them too, the two drifted
apart, and this one still said three long after four existed.
- **`smaug` is on port 15 of the switch**, untagged with PVID 40. That port was
ImaginationLAN's when the map was last read on 2026-09-04 and was moved for
the install ([#481](https://github.com/Gerrrt/HomeLab/issues/481)). It is
recorded because a single-NIC host on an access port has its segment decided
at the switch and nowhere else: put it back on 30 and the address, the
reservation and every inbound rule are silently pointless.
- **Inbound is no longer nothing, and that is deliberate.** Since 2026-09-16
Hicks reaches `10.0.40.30` on `443` and `8096`, and `10.0.99.20` reaches it
on `9100` and `22` — four host-scoped, port-scoped passes above *Block access
Expand Down
141 changes: 107 additions & 34 deletions docs/runbooks/build-the-nas.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
`10.0.40.30`
**Time:** §0 is about half an hour and needs no drives. §1–§7 is about twenty
minutes once the drives are in hand.
**You will need:** a console on `smaug` (or its web UI), `neo`'s web UI at
`http://10.7.7.2` and a yellow Cat6 lead for §0.2b, the pfSense UI on
`morpheus`, a shell on the monitoring host for §0.6, and the two Exos X20
drives for §1 onward.
**You will need:** a console on `smaug` its web UI cannot run §2 or §6, and
SSH is off — `neo`'s web UI at `http://10.7.7.2` and a yellow Cat6 lead for
§0.2b, the pfSense UI on `morpheus`, a shell on the monitoring host for §0.6,
and the two Exos X20 drives for §1 onward.

> **Status — 2026-09-16: §0 is the work that can be done before the drives
> land, and it is the whole of what is blocking.**
Expand All @@ -30,10 +30,20 @@ drives for §1 onward.
> records a reading rather than a day's work.
>
> §0.6 verified from `morpheus` rather than from the UI: every pass sits above
> *Block access to CasaBonita* on its interface (167–168 before 169 on
> `igc0.99`, 194–195 before 196 on `igc0.50`), Winterfell is still correctly
> refused on `443`, and the `igc0.40` tripwire reads **118,621 evaluations and
> zero packets**.
> *Block access to CasaBonita* on its interface, Winterfell is still correctly
> refused on `443`, and the `igc0.40` tripwire matched **zero packets**.
>
> **No rule numbers are recorded here, and that is deliberate.** This block
> first carried them — 167–168 before 169 on `igc0.99`, 194–195 before 196 on
> `igc0.50`. Those are right for `pfctl -sr | grep -n` and wrong for
> `pfctl -sr -vv`, which on 2026-09-17 numbered the same four rules `@148`,
> `@149`, `@175` and `@176`. Both readings came off the live ruleset in one
> invocation: `-vv` numbers each ruleset from zero, `grep -n` counts output
> lines, and the two therefore disagree by however many `scrub` rules precede
> the filter set. **The command §0.6 needs is the one the numbers do not
> match**, because a tripwire check reads a counter and counters only come
> from `-vv`. So match on the rule descriptions, which do not depend on how
> the ruleset is being printed.
>
> What is left is the drives. The pool does not exist and nothing is deployed.

Expand Down Expand Up @@ -141,6 +151,11 @@ matches nothing and leaves the question passing for the wrong reason; this one
leaves it failing for the wrong reason, and sends you to the pfSense UI, where
the answer is not.

**It is lettered rather than renumbered, and it has to stay that way.**
Shifting §0.3 onward to make room would move §0.5, and **ADR-0016 and ADR-0040
both cite §0.5 by name** — ADRs are immutable (ADR-0001), so renumbering would
break the only pointer two settled decisions have at the rule table.

**Write the port number down**, which is what this section is for. §0.3 and
§0.4 hold the address twice because either alone is a single point of drift; a
port that nothing records is worse than either, because a successor re-cabling
Expand Down Expand Up @@ -174,18 +189,25 @@ the box and in the firewall, not here.
The static is set on the host and the reservation is set on the server, and
both are done because either alone is a single point of drift.

### §0.5 — Create the three rules, in order and in position
### §0.5 — Create the four rules, in order and in position

**Position is the whole difficulty.** Two of these sit above a deny that has
been in place since 2025; appended where new rules naturally land they would
match nothing, and *"can I reach the NAS"* would still pass for the wrong
reason.
**Position is the whole difficulty.** All four sit above a deny that has been
in place since 2025; appended where new rules naturally land they would match
nothing, and *"can I reach the NAS"* would still pass for the wrong reason.

| On interface | Protocol / source → destination | Position |
| --- | --- | --- |
| Hicks (50) | `tcp` `vlan50 net` → `10.0.40.30` ports `443,8096` | **above** *Block access to CasaBonita* |
| Winterfell (99) | `tcp` `10.0.99.20` → `10.0.40.30` port `9100` | **above** *Block access to CasaBonita* |
| Winterfell (99) | `tcp` `10.0.99.20` → `10.0.40.30` port `22` | **above** *Block access to CasaBonita* |
| On interface | Protocol / source → destination | Description | Position |
| --- | --- | --- | --- |
| Hicks (50) | `tcp` `vlan50 net` → `10.0.40.30` port `443` | `Allow HTTPS to smaug` | **above** *Block access to CasaBonita* |
| Hicks (50) | `tcp` `vlan50 net` → `10.0.40.30` port `8096` | `Allow 8096 to smaug` | **above** *Block access to CasaBonita* |
| Winterfell (99) | `tcp` `10.0.99.20` → `10.0.40.30` port `9100` | `Allow 9100 to smaug` | **above** *Block access to CasaBonita* |
| Winterfell (99) | `tcp` `10.0.99.20` → `10.0.40.30` port `22` | `Allow SSH to smaug` | **above** *Block access to CasaBonita* |

**Four, where [ADR-0016](../adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md)
wrote three.** The Hicks pass is one rule per port rather than one rule
carrying a port list — functionally identical, and worth the extra row because
the description is what §0.6 matches on and a description naming one port is
unambiguous about which rule answered. **Set these descriptions exactly**; they
are load-bearing in the next section, not decoration.

**The Hicks rule's ports differ from ADR-0016's table, and deliberately.** That
table says `22,8096`, which assumed a box administered over SSH — ADR-0016
Expand All @@ -206,7 +228,25 @@ bought by putting the server with its clients.

### §0.6 — Prove the rules did what you meant

Two checks, and the second is the one that matters.
**Do this from `morpheus` and from the monitoring host, not from the pfSense
UI.** An appended rule matches nothing while looking perfectly present in the
UI, and that is the failure this whole section exists to catch.

**First, position — the check the other three assume.** On `morpheus`:

```bash
pfctl -sr -vv \
| grep -E 'on igc0\.(99|50) ' \
| grep -E 'descr=(Allow .* to smaug|Block access to CasaBonita)'
```

Each pass must appear **above** the *Block access to CasaBonita* rule on its
own interface: `Allow 9100`/`Allow SSH` before the block on `igc0.99`, and
`Allow HTTPS`/`Allow 8096` before it on `igc0.50`. **Read the order, not the
numbers.** `-vv` numbers each ruleset from zero rather than counting output
lines, so its `@` indices match neither `pfctl -sr | grep -n` nor anything
written down here — they are a printing artefact, and only the sequence is a
fact about the firewall.

From a Hicks workstation, the NAS's UI should answer:

Expand All @@ -215,8 +255,9 @@ curl -kIs https://10.0.40.30 | head -1
```

From the monitoring host, which is on Winterfell, `443` should **still be
refused** — that rule is scoped to `10.0.99.20` and to port `9100`, so a
success here would mean the rule is wider than it reads:
refused** — the two Winterfell rules are scoped to `10.0.99.20` and to ports
`9100` and `22`, so a success here would mean one of them is wider than it
reads:

```bash
nc -z -w3 10.0.40.30 443 && echo "WRONG: 99 can reach 443" || echo "correct: blocked"
Expand All @@ -225,9 +266,11 @@ nc -z -w3 10.0.40.30 443 && echo "WRONG: 99 can reach 443" || echo "correct: blo
Then read **the tripwire counter on `igc0.40`**
([#223](https://github.com/Gerrrt/HomeLab/issues/223)). It matches packets
*originating* on CasaBonita, and the return traffic for a session Hicks opened
is carried by state and never reaches the ruleset. **It must still be zero.**
If it has moved, something on 40 is initiating outward and that is a bigger
finding than anything in this runbook.
is carried by state and never reaches the ruleset. **Its packet count must
still be zero.** The evaluation counter beside it climbs constantly and means
nothing — it is every packet the rule was tested against. If *packets* have
moved, something on 40 is initiating outward and that is a bigger finding than
anything in this runbook.

---

Expand All @@ -245,7 +288,11 @@ drives, and two 7200 rpm Exos under a scrub will want it.

## §2 — Read the drives before trusting them

From **option 8, Open Linux Shell**, or over SSH once enabled:
From **option 8, Open Linux Shell**, at the console — **not over SSH**.
TrueNAS ships SSH disabled, and §0.5's port-22 pass is inert until someone
turns it on. Enabling it here to save a walk to the machine widens this host's
attack surface for the sake of five commands; §8's backup path is the reason
to turn it on, and this is not it.

```bash
lsblk
Expand Down Expand Up @@ -331,19 +378,38 @@ a compose file this repository owns, run under TrueNAS's app runtime, **not** a
catalogue app. That is what keeps Dependabot, the digest pins and
`make validate` reaching it.

Copy `stacks/media/.env.example` to `.env`, confirm `RENDER_GID` still matches
what this host reports, and bring it up:

```bash
make up STACK=media
```

Jellyfin binds `8096`, reads `erebor/media`, and writes its state to
`erebor/apps`.

> **The check ADR-0040 named as its reopen condition belongs here.** Confirm
> the iGPU reaches the container:
> **The check ADR-0040 named as its reopen condition belongs here, and it has
> to run *inside* the container.** The host half is already settled —
> `Active Video: IGD` on an E3-1225 v6, and render node `107 render` read off
> this machine on 2026-09-16. Running `ls -l /dev/dri` on the host re-confirms
> the half that was never in doubt and says nothing about the condition, which
> is whether the device reaches a container:
>
> ```bash
> docker exec media-jellyfin ls -l /dev/dri
> ```
>
> The node has to be present **and** the container's supplementary groups have
> to include the render GID — which is what `group_add` in the compose file is
> there to do, and the thing most likely to be silently wrong:
>
> ```bash
> ls -l /dev/dri
> docker exec media-jellyfin id
> ```
>
> and that Jellyfin's playback settings offer **QSV** hardware transcoding. The
> CPU half is already confirmed — `Active Video: IGD` on an E3-1225 v6 — but a
> live P630 and a P630 a container can use are different claims.
> Then check that Jellyfin's playback settings offer **QSV** hardware
> transcoding, and transcode something with it. A device node a container can
> list and a device node it can *use* are still different claims.
>
> If it does not pass, decision 2 of ADR-0040 reopens: catalogue apps that
> manage the passthrough, or the media stack moves off this host. **Check it
Expand All @@ -354,11 +420,18 @@ Jellyfin binds `8096`, reads `erebor/media`, and writes its state to
- A television on CasaBonita finds Jellyfin and plays something **without** any
firewall rule being involved
- A Hicks workstation reaches `https://10.0.40.30` and `http://10.0.40.30:8096`
- The monitoring host reaches `9100` and **nothing else**
- The `igc0.40` tripwire counter is **still zero**
- The monitoring host reaches `9100` and **nothing else** — but note that
*nothing in §1–§6 stands anything up on `9100`*. Until
[#256](https://github.com/Gerrrt/HomeLab/issues/256) settles whether the
target is `node_exporter` or TrueNAS's own metrics endpoint, only the **and
nothing else** half of this line is checkable: `443` and `8096` must both be
refused from the monitoring host. A listener answering on `9100` is that
issue's to deliver, not this runbook's
- Port 15 on `neo` reads PVID **40**, untagged, with `smaug`'s MAC learned on it
in VLAN 40 — read in the switch UI, and **not** inferred from the host having
an address
an address (§0.2b)
- The `igc0.40` tripwire's **packet** count is **still zero** — its evaluation
count will have climbed, and that is not a finding
- `zpool status erebor` is `ONLINE` with no errors
- Both Exos self-tests from §2 completed without error

Expand Down
2 changes: 1 addition & 1 deletion stacks/media/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ every television would have to trust, and a second thing to be down.
| Who | Reaches it how |
| --- | --- |
| Televisions on CasaBonita | Natively, same broadcast domain — the firewall never sees the packet |
| A Hicks workstation | The one rule in [`build-the-nas.md`] §0.5, `50 → 10.0.40.30:443,8096` |
| A Hicks workstation | Two of the four rules in [`build-the-nas.md`] §0.5`50 → 10.0.40.30:443` and `50 → 10.0.40.30:8096`, one per port |
| Everything else on the estate | Not at all — default deny |

[ADR-0012] asks for a named off-host consumer before a port is published, and
Expand Down
Loading