[main]: Add server-notes
This commit is contained in:
18 files changed
+1406
-1
No files matched your search
@@ -0,0 +1,79 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.authentication
|
||||
type: reference
|
||||
scope: [jupiter, authelia, forward-auth, secrets]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Deployed Authelia file matches repository; Portainer supplies environment variables, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "Portainer state added to Backrest plan, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "All stack environment values/secrets managed in Portainer; GitHub access uses PAT, 2026-10-06"
|
||||
- kind: repository
|
||||
repository: jupiter-stacks
|
||||
revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15
|
||||
paths:
|
||||
- stacks/authelia/configuration.yml
|
||||
- stacks/authelia/docker-compose.yml
|
||||
related: [home-server.reference, home-server.networking, home-server.portainer, home-server.backups, home-server.deployment]
|
||||
update_triggers: [access-policy-change, authentication-change, secret-rotation, middleware-change]
|
||||
unknowns:
|
||||
- authelia-file-placeholder-expansion-mechanism
|
||||
- off-host-authelia-secret-and-user-database-recovery
|
||||
---
|
||||
|
||||
# Authentication
|
||||
|
||||
| Component | Configuration |
|
||||
| --- | --- |
|
||||
| Portal | `https://auth.umbra.mom` |
|
||||
| Backend | File `/config/users_database.yml`; Argon2id |
|
||||
| Second factor | TOTP; issuer `Jupiter` |
|
||||
| Policy | Default deny; listed domains require `two_factor` |
|
||||
| Session cookie domain | `umbra.mom` |
|
||||
| Default redirect | `https://sonarr.umbra.mom` |
|
||||
| Storage | SQLite `/data/db.sqlite3`; storage encryption key |
|
||||
| Notifications | Filesystem `/data/notification.txt` |
|
||||
|
||||
Two-factor domains: `sonarr`, `radarr`, `prowlarr`, `logs`, `notes`, `backups`
|
||||
under `umbra.mom`.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Protected routers attach `authelia@docker`:
|
||||
|
||||
```text
|
||||
Traefik -> http://authelia:9091/api/authz/forward-auth
|
||||
```
|
||||
|
||||
`trustForwardHeader=true`; response headers:
|
||||
`Remote-User, Remote-Groups, Remote-Email, Remote-Name`.
|
||||
|
||||
Policy applies only to requests routed through forward-auth. It is not a
|
||||
global gate for every service or directly published port.
|
||||
|
||||
## Configuration / secrets
|
||||
|
||||
Host `/data/authelia/{configuration.yml,users_database.yml}` mounts read-only
|
||||
under `/config`; `/data/authelia` also mounts read-write at `/data`.
|
||||
|
||||
Stack variable overrides/secrets are managed in Portainer; Compose retains
|
||||
non-secret configuration. Authelia environment:
|
||||
|
||||
```text
|
||||
AUTHELIA_SESSION_SECRET
|
||||
AUTHELIA_STORAGE_ENCRYPTION_KEY
|
||||
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET
|
||||
```
|
||||
|
||||
Checked-in YAML also contains `${AUTHELIA_*}` placeholders. Container environment
|
||||
injection alone does not prove mounted-file substitution; expansion mechanism
|
||||
unconfirmed.
|
||||
|
||||
Authelia data remains outside the supplied backup plan.
|
||||
[Portainer state](portainer.md) is covered; secret recovery remains untested.
|
||||
GitHub repository access uses a PAT; host SSH keys and tunnel credential files
|
||||
are separate from stack environment variables.
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.backups
|
||||
type: reference
|
||||
scope: [jupiter, backrest, restic, google-drive, recovery]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Backrest plan JSON, destination, key custody and no restore test, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "Updated four-service Backrest plan and deployed Portainer bind mount/container name, 2026-10-06"
|
||||
- kind: repository
|
||||
repository: jupiter-stacks
|
||||
revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15
|
||||
paths:
|
||||
- stacks/backrest/docker-compose.yml
|
||||
- stacks/gitea/docker-compose.yml
|
||||
- stacks/paperless-ngx/docker-compose.yml
|
||||
- stacks/vaultwarden/docker-compose.yml
|
||||
related: [home-server.reference, home-server.storage, home-server.portainer, home-server.authentication]
|
||||
update_triggers: [backup-plan-change, destination-change, hook-change, credential-change, restore-test]
|
||||
unknowns:
|
||||
- last-successful-backup-and-repository-check
|
||||
- google-drive-repository-path-and-credential-recovery
|
||||
- off-host-backrest-config-recovery
|
||||
- measured-rpo-and-rto
|
||||
---
|
||||
|
||||
# Backups
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Engine | Backrest / restic; plan `jupiter-backup` |
|
||||
| Repository | `jayo60013-gdrive`; Google Drive; account `jayo60013` |
|
||||
| Schedule | `0 3 * * *`; UTC; daily 03:00 |
|
||||
| Retention | `policyKeepLastN: 4`; last four snapshots, not four days |
|
||||
| Encryption key | iCloud password app; no key stored here |
|
||||
| Restore test | Never performed |
|
||||
|
||||
## Coverage
|
||||
|
||||
Backrest bind-mounts host `/data` at container `/userdata:ro`.
|
||||
|
||||
| Host source | Plan path |
|
||||
| --- | --- |
|
||||
| `/data/gitea` | `/userdata/gitea` |
|
||||
| `/data/paperless-ngx` | `/userdata/paperless-ngx` |
|
||||
| `/data/portainer` | `/userdata/portainer` |
|
||||
| `/data/vaultwarden` | `/userdata/vaultwarden` |
|
||||
|
||||
Case-insensitive exclusion: `/userdata/gitea/gitea/packages`.
|
||||
`excludes: []`; CLI `--skip-if-unchanged` supplied despite
|
||||
plan `skipIfUnchanged: false`. Unchanged runs may create no snapshot.
|
||||
|
||||
## Consistency hooks
|
||||
|
||||
Stop timeout `120s`; chains use `&&`. Errors: `ON_ERROR_FATAL`,
|
||||
except Portainer restart (`ON_ERROR_IGNORE`).
|
||||
|
||||
| Event | Ordered commands / containers |
|
||||
| --- | --- |
|
||||
| `SNAPSHOT_START` | Stop `paperless` -> `paperless_db` |
|
||||
| `SNAPSHOT_START` | Stop `gitea_runner` -> `gitea` -> `gitea_db` |
|
||||
| `SNAPSHOT_START` | Stop `vaultwarden` |
|
||||
| `SNAPSHOT_START` | Stop `portainer` |
|
||||
| `SNAPSHOT_END`, `SNAPSHOT_ERROR` | Start `paperless_db` -> `paperless` |
|
||||
| `SNAPSHOT_END`, `SNAPSHOT_ERROR` | Start `gitea_db` -> `gitea` -> `gitea_runner` |
|
||||
| `SNAPSHOT_END`, `ANY_ERROR` | Start `vaultwarden` |
|
||||
| `SNAPSHOT_END`, `SNAPSHOT_ERROR` | Start `portainer`; ignore hook failure |
|
||||
|
||||
Cold filesystem copies of applications/databases; no database dumps supplied.
|
||||
Broker stays running. `docker start` does not wait for database readiness.
|
||||
Hook failure can leave services stopped; recovery is not demonstrated.
|
||||
Portainer restart failure does not fail the backup operation.
|
||||
|
||||
## Backrest state
|
||||
|
||||
| Host path | Container path |
|
||||
| --- | --- |
|
||||
| `/data/backrest/data` | `/data` |
|
||||
| `/data/backrest/config` | `/config`; plan/repository configuration `config.json` |
|
||||
| `/data/backrest/rclone` | `/root/.config/rclone` |
|
||||
| `/home/jay/data/backrest/cache` | `/cache` |
|
||||
| `/data/backrest/restore` | `/restore` |
|
||||
| `/var/run/docker.sock` | `/var/run/docker.sock`; lifecycle hooks |
|
||||
|
||||
## Recovery limits
|
||||
|
||||
Only the four listed directories are covered. No supplied coverage for Authelia,
|
||||
Traefik, Backrest configuration, Docker named volumes, OS, or Mercury.
|
||||
|
||||
Full-host recovery requires independently retrievable repository credentials,
|
||||
configuration and restic key. Key custody alone does not establish recoverability.
|
||||
`/restore` is on the same RAID array; not an independent backup.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.deployment
|
||||
type: reference
|
||||
scope: [jupiter, portainer, github, stack-deployment, secrets]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Portainer GitHub/main/PAT configuration, environment management and manual pull/redeploy workflow, 2026-10-06"
|
||||
related: [home-server.reference, home-server.host, home-server.portainer, home-server.authentication, home-server.backups]
|
||||
update_triggers: [deployment-workflow-change, repository-change, credential-rotation, rollback-policy-change]
|
||||
unknowns:
|
||||
- github-pat-scopes-expiration-and-independent-recovery
|
||||
- rollback-and-post-deployment-health-procedure
|
||||
- rules-for-portainer-ui-versus-repository-drift
|
||||
---
|
||||
|
||||
# Deployment
|
||||
|
||||
| Setting | Source / workflow |
|
||||
| --- | --- |
|
||||
| Stack definitions | <https://github.com/jayo60013/jupiter-stacks> |
|
||||
| Git ref | `main` |
|
||||
| Repository authentication | GitHub PAT configured in Portainer |
|
||||
| Environment values/secrets | Portainer stack environment settings |
|
||||
| Deployment | Manually pull/redeploy each affected stack in Portainer |
|
||||
| Portainer itself | [Host shell script](portainer.md), not a managed Compose stack |
|
||||
|
||||
```text
|
||||
Compose change -> push main -> manual Portainer pull/redeploy -> running stack
|
||||
```
|
||||
|
||||
A Git commit is not deployment proof. Installed host Compose version is not
|
||||
proof of Portainer's Compose implementation.
|
||||
|
||||
Recovering deployment requires Portainer state or independently retrievable
|
||||
repository credentials and stack environment values. [Portainer backups](backups.md)
|
||||
exist; credential/state restoration is untested. Do not put secret values in
|
||||
this site or Git.
|
||||
|
||||
Host apt packages, fstab, RAID assembly, systemd units and Mercury nginx are
|
||||
outside the Compose deployment workflow.
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.host
|
||||
type: reference
|
||||
scope: [jupiter, hardware, operating-system, docker]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Hardware, OS, bare-metal Docker and data-root confirmation, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "hostnamectl, timedatectl, user/group records, Docker info/version and systemd units, 2026-10-06"
|
||||
related: [home-server.reference, home-server.storage, home-server.networking, home-server.deployment, home-server.operations]
|
||||
update_triggers: [hardware-change, os-upgrade, kernel-upgrade, docker-reconfiguration, account-change, unit-change]
|
||||
unknowns:
|
||||
- exact-gpu-model
|
||||
- jupiter-firewall-policy
|
||||
- docker-package-installation-source
|
||||
- backup-puller-account-purpose
|
||||
---
|
||||
|
||||
# Host
|
||||
|
||||
| Component | Configuration |
|
||||
| --- | --- |
|
||||
| Host | Jupiter; bare metal |
|
||||
| OS | Debian GNU/Linux 13 (trixie); x86_64 |
|
||||
| Kernel | `6.12.94+deb13-amd64` |
|
||||
| CPU | AMD Ryzen 5 5600X; 6 cores / 12 threads; reported 4.65 GHz |
|
||||
| RAM | 1 x 16 GB Corsair Vengeance LPX |
|
||||
| GPU | AMD Radeon HD 5000/6000/7350/8350 Series; discrete; exact model unresolved |
|
||||
| Motherboard | Gigabyte B550M DS3H |
|
||||
| Firmware | `FB`; dated `2023-06-08` |
|
||||
| PSU | Corsair CV550 |
|
||||
| SSD | Samsung 860 EVO; 1 TB; OS and SSD-backed caches |
|
||||
| HDD | 2 x Seagate BarraCuda; 2 TB each; RAID 0 |
|
||||
| Docker | Native host engine; data root `/var/lib/docker` |
|
||||
| LAN | `192.168.1.56` |
|
||||
| WireGuard | `10.0.0.2/24`; Mercury peer/server `10.0.0.1/24` |
|
||||
| Time | `Europe/London`; RTC UTC; NTP active; clock synchronized at review |
|
||||
|
||||
## Docker runtime
|
||||
|
||||
| Setting | Observed value |
|
||||
| --- | --- |
|
||||
| Engine / API | Docker CE `29.8.2` / `1.56` |
|
||||
| Compose CLI plugin | `v5.6.0`; not proof of Portainer's Compose implementation |
|
||||
| containerd / runc | `v2.3.6` / `1.5.1` |
|
||||
| Storage | `overlayfs`; containerd snapshotter |
|
||||
| Cgroups | `systemd`; v2 |
|
||||
| Logging | `json-file`; default |
|
||||
| Firewall backend | `iptables` |
|
||||
| Runtime security | AppArmor, seccomp, cgroup namespaces |
|
||||
| Swarm / live restore | Inactive / disabled |
|
||||
|
||||
Docker/containerd services and `docker.socket` are enabled.
|
||||
Docker starts `/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock`.
|
||||
Supplied Docker unit has no explicit `/data` mount dependency or ordering against
|
||||
`rclone-seedbox.service`.
|
||||
|
||||
## Host administration
|
||||
|
||||
| Account / group | Identity |
|
||||
| --- | --- |
|
||||
| `jay` | UID/GID `1000:1000`; `/home/jay`; bash; member of `sudo`, `docker`, `video`, `backups` |
|
||||
| `backup-puller` | UID/GID `1001:1001`; `/home/backup-puller`; bash; member of `backups`; purpose unconfirmed |
|
||||
| `backups` | Shared group; GID `1002` |
|
||||
|
||||
WireGuard and rclone: apt-managed. `wg-quick@wg0.service` is active in the supplied
|
||||
snapshot. No host-level nginx on Jupiter; nginx still exists in application
|
||||
containers. `unattended-upgrades.service` is enabled; effective update policy
|
||||
not supplied.
|
||||
|
||||
Hardware/versions are a dated snapshot, not live telemetry.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.reference
|
||||
type: reference
|
||||
scope: [jupiter, mercury, seedbox, shared-infrastructure]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Owner interview, 2026-10-06"
|
||||
related:
|
||||
- home-server.host
|
||||
- home-server.mercury
|
||||
- home-server.seedbox
|
||||
- home-server.portainer
|
||||
- home-server.deployment
|
||||
- home-server.operations
|
||||
- home-server.networking
|
||||
- home-server.storage
|
||||
- home-server.authentication
|
||||
- home-server.backups
|
||||
update_triggers: [topology-change, source-change, infrastructure-change]
|
||||
unknowns: []
|
||||
---
|
||||
|
||||
# Home server
|
||||
|
||||
| Reference | Scope |
|
||||
| --- | --- |
|
||||
| [Host](host.md) | Jupiter hardware; OS; Docker |
|
||||
| [Mercury VPS](mercury.md) | Hosting; ingress; WireGuard; firewall |
|
||||
| [Seedbox](seedbox.md) | Ultra.cc; qBittorrent; rclone mount |
|
||||
| [Portainer](portainer.md) | Script-managed container; state; backups |
|
||||
| [Deployment](deployment.md) | GitHub; Portainer; environment values |
|
||||
| [Operations / decisions](operations.md) | Tradeoffs; monitoring; recovery gaps |
|
||||
| [Networking](networking.md) | Mercury; WireGuard; DNS; ingress |
|
||||
| [Storage](storage.md) | SSD; RAID 0; mounts; volumes |
|
||||
| [Authentication](authentication.md) | Authelia; policy; secrets |
|
||||
| [Backups](backups.md) | Backrest; coverage; retention; recovery |
|
||||
|
||||
## Evidence / agent contract
|
||||
|
||||
- `owner-report`: supplied host state/configuration; not independently inspected.
|
||||
- `repository`: intended configuration at `revision`; not deployment proof.
|
||||
- `revision: working-tree`: uncommitted source; `base_revision`: base commit.
|
||||
- `unknowns`: unresolved facts; never infer defaults as deployed state.
|
||||
- `related`: stable document IDs; `update_triggers`: re-review conditions.
|
||||
- On change: update affected prose, Mermaid, sources, unknowns, and `last_reviewed`.
|
||||
- No credentials. Preserve evidence distinctions; no live status implied.
|
||||
|
||||
Mermaid renders from versioned text; diagrams are manually maintained, not
|
||||
automatically discovered.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.mercury
|
||||
type: reference
|
||||
scope: [mercury, vps, hosting, nginx, wireguard, firewall]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Fasthosts plan, fastfetch, nginx sites, iptables and fail2ban output, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "nginx/WireGuard apt management and public IPv4/CGNAT rationale, 2026-10-06"
|
||||
related: [home-server.reference, home-server.host, home-server.networking, home-server.operations]
|
||||
update_triggers: [hosting-renewal, vps-resize, os-upgrade, proxy-change, firewall-change, certificate-change]
|
||||
unknowns:
|
||||
- renewal-price-and-renewal-date
|
||||
- creation-timestamp-timezone
|
||||
- certificate-renewal-method
|
||||
- wireguard-allowedips-and-keepalive
|
||||
- backup-and-restore-policy
|
||||
---
|
||||
|
||||
# Mercury VPS
|
||||
|
||||
## Host / subscription
|
||||
|
||||
| Field | Configuration |
|
||||
| --- | --- |
|
||||
| Provider | [Fasthosts](https://www.fasthosts.co.uk/); UK data centre |
|
||||
| Role / OS hostname | Mercury / `my-vps` |
|
||||
| Plan | `1-1-10`; KVM virtual machine |
|
||||
| CPU | 1 vCore; reported AMD EPYC-Milan @ 2.00 GHz |
|
||||
| RAM | 1 GB plan; guest reports 864.37 MiB |
|
||||
| Disk | 10 GB NVMe SSD plan; guest root ext4, 9.64 GiB |
|
||||
| OS | Debian GNU/Linux 13 (trixie); x86_64 |
|
||||
| Kernel | `6.12.85+deb13-amd64` |
|
||||
| Swap | Disabled |
|
||||
| Public interface | `ens6`; `185.230.217.66/32` |
|
||||
| WireGuard | Server `10.0.0.1/24`; Jupiter peer `10.0.0.2/24` |
|
||||
| Created | `2026-03-01 20:48:51`; timezone unconfirmed |
|
||||
| Payment | GBP 10.00 prepaid for one year; renewal terms unconfirmed |
|
||||
|
||||
Capacities distinguish provider allocation from guest-visible values.
|
||||
|
||||
## Ingress
|
||||
|
||||
nginx runs directly in the VM, not in Docker; nginx and WireGuard are
|
||||
apt-managed. Mercury provides static public IPv4 ingress because Jupiter's
|
||||
home connection uses CGNAT. Residential-IP exposure is avoided by design.
|
||||
|
||||
| Hostname | Upstream |
|
||||
| --- | --- |
|
||||
| `umbra.mom`, `*.umbra.mom` | `https://10.0.0.2:443` over WireGuard |
|
||||
| `gitea.umbra.mom` | Same; dedicated registry location `/v2/` |
|
||||
| `john.gatward.dev`, `samstoreymusic.com` | `https://10.0.0.2:443` |
|
||||
| `uptime.umbra.mom` | `http://127.0.0.1:3001` |
|
||||
|
||||
- HTTP -> HTTPS: `301`; unmatched default server: `418`.
|
||||
- TLS terminates at nginx; upstream HTTPS terminates again at Jupiter Traefik.
|
||||
- Upstream certificate verification disabled: `proxy_ssl_verify off`.
|
||||
- Certificates: `/etc/letsencrypt/live/<domain>/{fullchain.pem,privkey.pem}`;
|
||||
`umbra.mom` certificate also serves its subdomains.
|
||||
- Preserves `Host`; sets `X-Real-IP`, `X-Forwarded-For`, `X-Forwarded-Proto`;
|
||||
forwards WebSocket upgrade headers.
|
||||
- Gitea `/v2/`: unlimited request body; proxy timeouts `900s`.
|
||||
|
||||
## Firewall / fail2ban
|
||||
|
||||
- UFW-managed iptables: INPUT/FORWARD `DROP`; OUTPUT `ACCEPT`.
|
||||
- UFW allows TCP `22,80,443`; UDP `80,443,51820`.
|
||||
- Docker forwarding has separate rules; INPUT policy alone does not define
|
||||
container exposure.
|
||||
- Fail2ban jails: `nginx-botsearch`, `nginx-http-auth`, `sshd`.
|
||||
|
||||
Rules and versions are supplied snapshots, not live inspection.
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.networking
|
||||
type: reference
|
||||
scope: [jupiter, mercury, dns, ingress, wireguard, cloudflare]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Network topology, Mercury nginx/iptables/fail2ban output, LAN DNS, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "Cloudflare ingress YAML and client SSH configurations, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "CGNAT rationale, Docker network and container inspection, 2026-10-06"
|
||||
- kind: repository
|
||||
repository: jupiter-stacks
|
||||
revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15
|
||||
paths:
|
||||
- stacks/traefik/docker-compose.yml
|
||||
- stacks/cloudflared/docker-compose.yml
|
||||
- stacks/gitea/docker-compose.yml
|
||||
- stacks/jellyfin/docker-compose.yml
|
||||
- stacks/audiobookshelf/docker-compose.yml
|
||||
related: [home-server.reference, home-server.host, home-server.mercury, home-server.seedbox, home-server.authentication, home-server.operations]
|
||||
update_triggers: [dns-change, proxy-change, tunnel-change, firewall-change, certificate-change]
|
||||
unknowns:
|
||||
- public-dns-record-types-and-cloudflare-proxy-status
|
||||
- cloudflare-access-applications-and-login-policies
|
||||
- wireguard-allowedips-and-keepalive
|
||||
- jupiter-firewall-policy
|
||||
- traefik-forwarded-header-trust-configuration
|
||||
---
|
||||
|
||||
# Networking
|
||||
|
||||
## HTTPS ingress
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
themeCSS: |
|
||||
.node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; }
|
||||
.cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; }
|
||||
.label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; }
|
||||
.edgeLabel, .edgeLabel p { background-color: var(--infra-zone) !important; color: var(--infra-text) !important; }
|
||||
.flowchart-link { stroke: var(--infra-line) !important; }
|
||||
marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; }
|
||||
.infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; }
|
||||
.infraEdge rect { fill: var(--infra-edge-fill) !important; stroke: var(--infra-edge-stroke) !important; }
|
||||
.infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; }
|
||||
flowchart:
|
||||
curve: basis
|
||||
nodeSpacing: 35
|
||||
rankSpacing: 55
|
||||
---
|
||||
flowchart TB
|
||||
Internet["<b>Public clients</b>"]
|
||||
|
||||
subgraph VPS["MERCURY / PUBLIC EDGE"]
|
||||
Nginx["<b>nginx</b><br/>185.230.217.66<br/>WireGuard 10.0.0.1"]
|
||||
Uptime["<b>Uptime service</b><br/>127.0.0.1:3001"]
|
||||
Nginx -->|"uptime.umbra.mom / HTTP"| Uptime
|
||||
end
|
||||
|
||||
subgraph Home["JUPITER / HOME SERVER"]
|
||||
LAN["<b>LAN clients</b><br/>Jellyfin / Bookshelf local DNS"]
|
||||
Traefik["<b>Traefik</b><br/>192.168.1.56<br/>WireGuard 10.0.0.2"]
|
||||
Apps["<b>Application containers</b><br/>Docker network: traefik"]
|
||||
LAN -->|"HTTPS :443"| Traefik
|
||||
Traefik -->|"Application routing"| Apps
|
||||
end
|
||||
|
||||
Internet -->|"HTTPS :443"| Nginx
|
||||
Nginx -->|"HTTPS :443 over WireGuard"| Traefik
|
||||
|
||||
class Internet,LAN infraClient
|
||||
class Nginx,Traefik infraEdge
|
||||
class Apps,Uptime infraService
|
||||
```
|
||||
|
||||
## Cloudflare Tunnel
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
themeCSS: |
|
||||
.node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; }
|
||||
.cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; }
|
||||
.label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; }
|
||||
.edgeLabel, .edgeLabel p { background-color: var(--infra-zone) !important; color: var(--infra-text) !important; }
|
||||
.flowchart-link { stroke: var(--infra-line) !important; }
|
||||
marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; }
|
||||
.infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; }
|
||||
.infraCloud rect { fill: var(--infra-cloud-fill) !important; stroke: var(--infra-cloud-stroke) !important; }
|
||||
.infraEdge rect { fill: var(--infra-edge-fill) !important; stroke: var(--infra-edge-stroke) !important; }
|
||||
.infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; }
|
||||
flowchart:
|
||||
curve: basis
|
||||
nodeSpacing: 35
|
||||
rankSpacing: 45
|
||||
---
|
||||
flowchart TB
|
||||
Shell["<b>SSH client</b><br/>Host jupiter / user jay"]
|
||||
Git["<b>Git over SSH</b><br/>Host git.umbra.mom / user git"]
|
||||
Cloud["<b>Cloudflare Tunnel edge</b><br/>Access policy unconfirmed"]
|
||||
|
||||
subgraph Home["JUPITER / HOME SERVER"]
|
||||
Connector["<b>cloudflared</b><br/>Locally configured ingress"]
|
||||
SSH["<b>Host sshd</b><br/>192.168.1.56:22"]
|
||||
Gitea["<b>Gitea SSH</b><br/>192.168.1.56:2222"]
|
||||
Connector -->|"ssh.umbra.mom / SSH :22"| SSH
|
||||
Connector -->|"git.umbra.mom / SSH :2222"| Gitea
|
||||
end
|
||||
|
||||
Shell -->|"cloudflared access ssh / ssh.umbra.mom"| Cloud
|
||||
Git -->|"cloudflared access ssh / git.umbra.mom"| Cloud
|
||||
Cloud <-->|"Tunnel traffic / initiated outbound by Jupiter"| Connector
|
||||
|
||||
class Shell,Git infraClient
|
||||
class Cloud infraCloud
|
||||
class Connector infraEdge
|
||||
class SSH,Gitea infraService
|
||||
```
|
||||
|
||||
Client `cloudflared` transports SSH through Cloudflare; Jupiter's connector opens
|
||||
the outbound tunnel. Origins still perform SSH authentication. No Mercury,
|
||||
WireGuard, or Traefik hop on this path; no home-router port forwarding.
|
||||
|
||||
| Ingress hostname | Origin | Client SSH user |
|
||||
| --- | --- | --- |
|
||||
| `ssh.umbra.mom` | `ssh://192.168.1.56:22` | `jay` |
|
||||
| `git.umbra.mom` | `ssh://192.168.1.56:2222` | `git` |
|
||||
| Unmatched | `http_status:404` | N/A |
|
||||
|
||||
Both client configurations use `IdentityFile ~/.ssh/id_ed25519`.
|
||||
|
||||
```sshconfig
|
||||
Host jupiter
|
||||
User jay
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
ProxyCommand cloudflared access ssh --hostname ssh.umbra.mom
|
||||
|
||||
Host git.umbra.mom
|
||||
User git
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
ProxyCommand cloudflared access ssh --hostname %h
|
||||
```
|
||||
|
||||
Local tunnel configuration is separate from Cloudflare Access applications.
|
||||
Access login/provider/allow rules remain unconfirmed; `ProxyCommand` alone
|
||||
does not establish enforcement.
|
||||
|
||||
Ingress source: host `/data/cloudflared/config.yaml`, mounted at
|
||||
`/etc/cloudflared/config.yaml`.
|
||||
|
||||
## DNS / routes
|
||||
|
||||
| DNS / route | Destination |
|
||||
| --- | --- |
|
||||
| Public service ingress | Mercury `185.230.217.66` |
|
||||
| `jellyfin.local.umbra.mom` | Jupiter `192.168.1.56` |
|
||||
| `bookshelf.local.umbra.mom` | Jupiter `192.168.1.56` |
|
||||
| `ssh.umbra.mom`, `git.umbra.mom` | Cloudflare Tunnel |
|
||||
| `uptime.umbra.mom` | Mercury nginx -> `http://127.0.0.1:3001` |
|
||||
|
||||
## Mercury
|
||||
|
||||
[Mercury VPS](mercury.md): native nginx; WireGuard server `10.0.0.1/24`;
|
||||
Jupiter client `10.0.0.2/24`. Hosting, TLS, firewall and fail2ban details are
|
||||
maintained on that page.
|
||||
|
||||
Home ISP uses CGNAT. Mercury supplies a static public IPv4 and forwards over
|
||||
WireGuard; owner preference is to avoid public ingress via the residential IP.
|
||||
|
||||
## Observed Docker networks
|
||||
|
||||
Snapshot from supplied inspection, not fixed-address configuration. Container
|
||||
IPs may change; use Docker service names.
|
||||
|
||||
| Network | IPv4 subnet | Internal | Observed role |
|
||||
| --- | --- | --- | --- |
|
||||
| `traefik` | `172.18.0.0/16` | No | Reverse proxy and application ingress |
|
||||
| `gitea_network` | `172.23.0.0/16` | No | Gitea, runner, notes builder, Havox sync |
|
||||
| `cloudflared_cloudflare` | `172.26.0.0/16` | No | Tunnel connector |
|
||||
| `gitea_data` | `192.168.96.0/20` | Yes | Gitea and PostgreSQL |
|
||||
| `paperless_data` | `192.168.112.0/20` | Yes | Paperless, PostgreSQL, Redis |
|
||||
| `dozzle_dozzle` | `172.27.0.0/16` | Yes | Dozzle and socket proxy |
|
||||
|
||||
Cloudflared's observed `172.26.0.2` matches the supplied proxied SSH login source.
|
||||
An internal network alone does not isolate a multi-network container from
|
||||
egress through its other networks. Empty network entries do not establish
|
||||
whether they are obsolete or safe to remove.
|
||||
|
||||
## Jupiter: repository configuration
|
||||
|
||||
- Traefik publishes TCP `80,443`; HTTP redirects to HTTPS.
|
||||
- Docker provider: opt-in `traefik.enable=true`; external network `traefik`.
|
||||
- File provider: `/data/traefik/dynamic`.
|
||||
- ACME: Let's Encrypt; Cloudflare DNS-01; `/data/traefik/acme/acme.json`;
|
||||
apex + `*.umbra.mom` certificate requested.
|
||||
- HTTPS fallback: priority `1`; unmatched non-apex hosts redirect to `https://umbra.mom`.
|
||||
- `cloudflared`: `/data/cloudflared` -> `/etc/cloudflared`;
|
||||
stack-local bridge `cloudflare`; no published ports.
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.operations
|
||||
type: reference
|
||||
scope: [jupiter, mercury, design-decisions, monitoring, recovery]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "RAID/media tradeoff, CGNAT/public IPv4 rationale, no alerting and running monitoring/log containers, 2026-10-06"
|
||||
related: [home-server.reference, home-server.host, home-server.mercury, home-server.networking, home-server.storage, home-server.backups, home-server.deployment]
|
||||
update_triggers: [data-priority-change, architecture-change, alerting-change, recovery-test]
|
||||
unknowns:
|
||||
- acceptable-downtime-and-data-loss-by-service
|
||||
- data-criticality-outside-the-four-backed-up-directories
|
||||
- tested-full-host-recovery-order
|
||||
- monitoring-review-frequency
|
||||
---
|
||||
|
||||
# Operations / decisions
|
||||
|
||||
## Owner decisions
|
||||
|
||||
| Decision | Reason / accepted tradeoff |
|
||||
| --- | --- |
|
||||
| RAID 0 for HDD data | Capacity prioritized over disk redundancy; downloaded media can be reacquired |
|
||||
| Back up important state | Four application directories covered; media redundancy is not the objective |
|
||||
| Mercury public edge | Static public IPv4; home ISP uses CGNAT |
|
||||
| No residential public ingress | Owner preference to avoid exposing the home IP |
|
||||
|
||||
RAID 0 still stores important state alongside media: either HDD failure loses
|
||||
the array; recovery depends on working backups. Owner intent is not evidence
|
||||
that every important dataset is covered.
|
||||
|
||||
## Observability
|
||||
|
||||
| Facility | Current configuration |
|
||||
| --- | --- |
|
||||
| Host/container monitoring | Beszel hub and host-network agent present |
|
||||
| Container logs | Dozzle with Docker socket proxy present |
|
||||
| Automatic alerting | None configured |
|
||||
|
||||
Monitoring does not imply notification delivery. Backup-hook failures,
|
||||
stopped services and storage failures require manual detection.
|
||||
|
||||
## Recovery status
|
||||
|
||||
No restore test performed. Backup scope is documented in [Backups](backups.md);
|
||||
OS, host configuration and named-volume coverage must not be inferred from
|
||||
the four-directory plan. Recovery priorities, RPO/RTO and validated bootstrap
|
||||
instructions remain unconfirmed.
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.portainer
|
||||
type: reference
|
||||
scope: [jupiter, portainer, deployment, configuration-state]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Deployed container, ~/portainer_start.sh and updated Backrest plan, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "GitHub main/PAT access, Portainer environment values, manual redeployment and container inspection, 2026-10-06"
|
||||
- kind: repository
|
||||
repository: jupiter-stacks
|
||||
revision: working-tree
|
||||
base_revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15
|
||||
paths:
|
||||
- stacks/portainer/portainer_start.sh
|
||||
related: [home-server.reference, home-server.storage, home-server.authentication, home-server.backups, home-server.deployment]
|
||||
update_triggers: [script-change, portainer-upgrade, state-migration, backup-plan-change]
|
||||
unknowns: [local-hostname-dns-resolution, successful-state-and-secret-restore]
|
||||
---
|
||||
|
||||
# Portainer
|
||||
|
||||
## Deployment
|
||||
|
||||
Managed by handwritten `~/portainer_start.sh`, **not a Compose stack**.
|
||||
Reference script: [portainer_start.sh](https://github.com/jayo60013/jupiter-stacks).
|
||||
|
||||
| Setting | Deployed value |
|
||||
| --- | --- |
|
||||
| Image | `portainer/portainer-ce:2.45.1` |
|
||||
| Container | `portainer` |
|
||||
| Restart policy | `always` |
|
||||
| Network | External Docker network `traefik` |
|
||||
| State | Host `/data/portainer` -> container `/data` |
|
||||
| Docker API | `/var/run/docker.sock` bind mount |
|
||||
| Host publication | TCP `9123` -> container `9000` |
|
||||
| HTTPS routers | `portainer.umbra.mom`, `portainer.local.umbra.mom` |
|
||||
| Traefik backend | HTTP; container port `9000` |
|
||||
| TLS | `https` entrypoint; `letsencrypt` resolver |
|
||||
|
||||
Script stops/removes the existing container, then recreates it; bind-mounted
|
||||
state persists. Requires Docker, the `traefik` network, and mounted `/data`.
|
||||
Supplied routers attach no Authelia middleware. Local router does not establish
|
||||
local DNS resolution.
|
||||
|
||||
## Stack management
|
||||
|
||||
Portainer fetches <https://github.com/jayo60013/jupiter-stacks>, branch `main`,
|
||||
using a GitHub PAT. Stack environment values/secrets are managed in Portainer.
|
||||
Repository changes are pulled/redeployed manually; committing alone does not
|
||||
update containers. See [deployment](deployment.md).
|
||||
|
||||
## Backups
|
||||
|
||||
Daily 03:00 UTC; Google Drive; last four snapshots. Backrest reads
|
||||
`/userdata/portainer` from host `/data/portainer`.
|
||||
|
||||
- Before snapshot: stop `portainer`; timeout `120s`; errors fatal.
|
||||
- Snapshot end/error: start `portainer`; restart errors ignored.
|
||||
- Restore remains untested; script itself is outside the listed backup paths.
|
||||
|
||||
The former `portainer_data` named volume is not used by the deployed container.
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.seedbox
|
||||
type: reference
|
||||
scope: [seedbox, ultra-cc, qbittorrent, rclone, sftp, media-import]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "Ultra.cc plan, remaining capacity, rclone remote/process and enabled systemd unit, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "apt-managed rclone, active FUSE mount and Radarr/Sonarr bind-mount inspection, 2026-10-06"
|
||||
- kind: repository
|
||||
repository: jupiter-stacks
|
||||
revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15
|
||||
paths:
|
||||
- stacks/radarr/docker-compose.yml
|
||||
- stacks/sonarr/docker-compose.yml
|
||||
related: [home-server.reference, home-server.networking, home-server.storage, home-server.backups]
|
||||
update_triggers: [hosting-renewal, quota-change, remote-change, mount-change, media-import-change]
|
||||
unknowns:
|
||||
- total-traffic-allowance-and-reset-period
|
||||
- renewal-behaviour
|
||||
- rclone-cache-location-and-size-limit
|
||||
- import-copy-move-and-remote-deletion-policy
|
||||
- mount-startup-ordering-relative-to-docker-containers
|
||||
- seedbox-backup-policy
|
||||
---
|
||||
|
||||
# Seedbox
|
||||
|
||||
## Service / subscription
|
||||
|
||||
| Field | Configuration |
|
||||
| --- | --- |
|
||||
| Provider / plan | [Ultra.cc](https://ultra.cc/); `Lancer-v2` |
|
||||
| Host | `terra.usbx.me` |
|
||||
| Account / home | `jayo60013` / `/home/jayo60013` |
|
||||
| Cost | GBP 4.50/month |
|
||||
| Reported expiration | `2026-10-25`; renewal behaviour unconfirmed |
|
||||
| Disk | 1001 GB allocation; 984 GB available at review |
|
||||
| Remaining traffic | 2 TB at review; total allowance/reset period unconfirmed |
|
||||
| Download speed | Provider reports `Unlimited`; no numeric rate supplied |
|
||||
| Upload speed | Provider reports `50000 Mbps`; not measured throughput |
|
||||
| qBittorrent UI | <https://jayo60013.terra.usbx.me/qbittorrent/> |
|
||||
|
||||
## Jupiter connection
|
||||
|
||||
Actual connection: **SFTP via rclone/FUSE**, not WebDAV or scheduled sync.
|
||||
|
||||
| Component | Path / value |
|
||||
| --- | --- |
|
||||
| rclone remote | `seedbox`; type `sftp`; host `terra.usbx.me`; user `jayo60013` |
|
||||
| SSH key file | `/home/jay/.ssh/id_ed25519`; key contents excluded |
|
||||
| Remote source | `seedbox:downloads/qbittorrent` |
|
||||
| Remote absolute path | `/home/jayo60013/downloads/qbittorrent` |
|
||||
| Jupiter mount | `/mnt/seedbox` |
|
||||
| Container source | `/downloads` in Radarr and Sonarr |
|
||||
| Radarr library | Container `/movies` -> host `/data/jellyfin/movies` |
|
||||
| Sonarr library | Container `/tv` -> host `/data/jellyfin/tv_shows` |
|
||||
|
||||
rclone remote: `shell_type=unix`; checksum commands `md5sum`, `sha1sum`.
|
||||
rclone is apt-managed. Observed FUSE mount: UID/GID `1000:1000`;
|
||||
`rw,nosuid,nodev,allow_other`.
|
||||
|
||||
## Mount lifecycle
|
||||
|
||||
`/etc/systemd/system/rclone-seedbox.service`; enabled at boot.
|
||||
User/group `jay`; `Type=simple`; wants/starts after `network-online.target`.
|
||||
|
||||
```sh
|
||||
/usr/bin/rclone mount seedbox:downloads/qbittorrent /mnt/seedbox \
|
||||
--allow-other \
|
||||
--dir-cache-time 1m \
|
||||
--vfs-cache-mode writes
|
||||
```
|
||||
|
||||
Stop: `/bin/fusermount -u /mnt/seedbox`.
|
||||
Restart: `on-failure`; delay `10s`; target `multi-user.target`.
|
||||
|
||||
`--allow-other` permits other local users, subject to filesystem permissions.
|
||||
`writes` caches write/read-write opens; read-only opens stream from the remote.
|
||||
No explicit cache path/size limit is supplied in the unit.
|
||||
|
||||
The remote mount is not a complete local replica or backup. Radarr/Sonarr
|
||||
library destinations are on the HDD array; cross-filesystem hardlinks are
|
||||
impossible. Mount startup ordering relative to Docker containers is unconfirmed.
|
||||
Their `/downloads` binds use `rprivate`; host remounts are not automatically
|
||||
propagated into existing containers.
|
||||
|
||||
The FUSE-reported capacity is not evidence of the account quota; use the
|
||||
provider allocation above.
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
schema_version: 1
|
||||
id: home-server.storage
|
||||
type: reference
|
||||
scope: [jupiter, disks, raid, filesystems, docker-volumes]
|
||||
sensitivity: private-infrastructure
|
||||
last_reviewed: "2026-10-06"
|
||||
sources:
|
||||
- kind: owner-report
|
||||
reference: "lsblk filesystem output, RAID level, mount placement and Docker data root, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "Seedbox rclone mount and systemd unit, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "Deployed Portainer uses /data/portainer bind mount, not portainer_data, 2026-10-06"
|
||||
- kind: owner-report
|
||||
reference: "mdstat, mdadm scans/detail, fstab, findmnt and Docker volume/container inspection, 2026-10-06"
|
||||
- kind: repository
|
||||
repository: jupiter-stacks
|
||||
revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15
|
||||
paths:
|
||||
- stacks/backrest/docker-compose.yml
|
||||
- stacks/jellyfin/docker-compose.yml
|
||||
- stacks/authelia/docker-compose.yml
|
||||
- stacks/traefik/docker-compose.yml
|
||||
related: [home-server.reference, home-server.host, home-server.seedbox, home-server.portainer, home-server.backups, home-server.operations]
|
||||
update_triggers: [disk-change, raid-change, mount-change, volume-change, backup-coverage-change]
|
||||
unknowns: [persistent-mdadm-config-and-initramfs-content, disk-health]
|
||||
---
|
||||
|
||||
# Storage
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
themeCSS: |
|
||||
.node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; }
|
||||
.cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; }
|
||||
.label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; }
|
||||
.flowchart-link { stroke: var(--infra-line) !important; }
|
||||
marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; }
|
||||
.infraStorage rect, .infraStorage path { fill: var(--infra-storage-fill) !important; stroke: var(--infra-storage-stroke) !important; }
|
||||
.infraCloud rect { fill: var(--infra-cloud-fill) !important; stroke: var(--infra-cloud-stroke) !important; }
|
||||
.infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; }
|
||||
.infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; }
|
||||
flowchart:
|
||||
curve: basis
|
||||
nodeSpacing: 30
|
||||
rankSpacing: 45
|
||||
---
|
||||
flowchart LR
|
||||
subgraph Array["HDD ARRAY / APPLICATION DATA"]
|
||||
direction TB
|
||||
A["<b>Seagate BarraCuda</b><br/>sda1 / 2 TB"]
|
||||
B["<b>Seagate BarraCuda</b><br/>sdb1 / 2 TB"]
|
||||
RAID["<b>Linux MD RAID 0</b><br/>md0 / no redundancy"]
|
||||
DATA[("<b>/data</b><br/>ext4")]
|
||||
A --> RAID
|
||||
B --> RAID
|
||||
RAID --> DATA
|
||||
end
|
||||
|
||||
subgraph System["SSD / SYSTEM"]
|
||||
direction TB
|
||||
SSD["<b>Samsung 860 EVO</b><br/>sdc / 1 TB"]
|
||||
EFI["<b>/boot/efi</b><br/>sdc1 / FAT32"]
|
||||
SWAP["<b>Swap</b><br/>sdc2"]
|
||||
ROOT["<b>/</b><br/>sdc3 / ext4"]
|
||||
DOCKER["<b>/var/lib/docker</b><br/>Named volumes"]
|
||||
CACHE["<b>/home/jay/data</b><br/>Backrest / Jellyfin caches"]
|
||||
SSD --> EFI
|
||||
SSD --> SWAP
|
||||
SSD --> ROOT
|
||||
ROOT --> DOCKER
|
||||
ROOT --> CACHE
|
||||
end
|
||||
|
||||
class A,B,SSD,DATA,ROOT infraStorage
|
||||
class RAID infraCloud
|
||||
class EFI,SWAP infraClient
|
||||
class DOCKER,CACHE infraService
|
||||
```
|
||||
|
||||
RAID 0: striping; no redundancy; either HDD failure loses the array.
|
||||
|
||||
## Filesystems
|
||||
|
||||
Device names reflect the supplied snapshot; UUIDs identify filesystems.
|
||||
|
||||
| Mount | Device | Filesystem | UUID |
|
||||
| --- | --- | --- | --- |
|
||||
| `/data` | `/dev/md0` | ext4 | `d60390a1-7db3-44cc-bbde-693c23e42001` |
|
||||
| `/` | `/dev/sdc3` | ext4 | `7a5e0ee6-4a65-4f68-9128-b3b4f3406447` |
|
||||
| `/boot/efi` | `/dev/sdc1` | FAT32 | `1DDE-CDF9` |
|
||||
| swap | `/dev/sdc2` | swap | `4781ea5d-fba5-438d-8d97-6a0801669ca1` |
|
||||
|
||||
MD members: `/dev/sda1`, `/dev/sdb1`; metadata `1.2`; array label `jupiter:0`;
|
||||
MD UUID `c67fccaa-e541-bf2d-2bf6-83221086e0e9`.
|
||||
|
||||
## RAID assembly / geometry
|
||||
|
||||
Linux MD, inspected with mdadm; ext4 directly on the array, no intervening LVM.
|
||||
|
||||
| Setting | Observed value |
|
||||
| --- | --- |
|
||||
| Device / scan name | `/dev/md0` / `/dev/md/0` |
|
||||
| mdadm UUID | `c67fccaa:e541bf2d:2bf68322:1086e0e9` |
|
||||
| Capacity | 3.64 TiB / 4.00 TB |
|
||||
| Layout / chunk | `original` / 512 KiB |
|
||||
| Member order | Slot 0: `/dev/sdb1`; slot 1: `/dev/sda1` |
|
||||
| State at review | `clean`; 2 active members; no failed/spare devices |
|
||||
|
||||
Scan output identifies the array; persisted mdadm configuration/initramfs were
|
||||
not supplied. Assembly is not array creation; do not substitute `mdadm --create`.
|
||||
|
||||
## Mount configuration
|
||||
|
||||
Supplied `/etc/fstab`:
|
||||
|
||||
```fstab
|
||||
UUID=7a5e0ee6-4a65-4f68-9128-b3b4f3406447 / ext4 errors=remount-ro 0 1
|
||||
UUID=1DDE-CDF9 /boot/efi vfat utf8 0 0
|
||||
UUID=d60390a1-7db3-44cc-bbde-693c23e42001 /data ext4 defaults 0 2
|
||||
UUID=4781ea5d-fba5-438d-8d97-6a0801669ca1 none swap sw 0 0
|
||||
```
|
||||
|
||||
Observed `/data`: `rw,relatime,stripe=256`; `/tmp`: tmpfs.
|
||||
Seedbox FUSE mount is service-managed, not listed in fstab.
|
||||
|
||||
## Placement
|
||||
|
||||
| Host path | Backing / purpose |
|
||||
| --- | --- |
|
||||
| `/data/<service>` | HDD array; service bind mounts |
|
||||
| `/mnt/seedbox` | Remote SFTP/FUSE mount; [seedbox](seedbox.md); not a local replica |
|
||||
| `/home/jay/data/backrest/cache` | SSD; Backrest cache |
|
||||
| `/home/jay/data/jellyfin/cache` | SSD; Jellyfin cache |
|
||||
| `/var/lib/docker` | SSD; Docker state and default local named volumes |
|
||||
| `/var/lib/containerd` | SSD; observed containerd overlayfs snapshot storage |
|
||||
| `/data/portainer` | HDD array; deployed [Portainer](portainer.md) state; backed up |
|
||||
| `/data/authelia` | Configuration, users, SQLite, notifications |
|
||||
| `/data/traefik/{acme,dynamic,certs}` | ACME state, dynamic configuration, certificates |
|
||||
|
||||
Cache placement is per mount, not globally SSD-backed. `/data` backups do not
|
||||
implicitly include SSD-backed Docker volumes.
|
||||
|
||||
## Named-volume snapshot
|
||||
|
||||
Inspected volumes below use driver `local`, no driver options, and
|
||||
`/var/lib/docker/volumes/<name>/_data` on SSD.
|
||||
|
||||
| Volume | Running consumer / container path |
|
||||
| --- | --- |
|
||||
| `audiobookshelf_config` | Audiobookshelf `/config` |
|
||||
| `jellyfin_config` | Jellyfin `/config` |
|
||||
| `notes_static` | Notes web `/usr/share/nginx/html`; builder `/site-output` |
|
||||
| `dozzle_dozzle_data` | Dozzle `/data` |
|
||||
| `ctq_ctq_postgres_data` | CTQ PostgreSQL `/var/lib/postgresql/data` |
|
||||
| `havox_src` | Havox web/sync `/git` |
|
||||
|
||||
Other volumes are present without a running consumer shown, including
|
||||
`jellyfin_cache`, `notes_notes_static`, and monitoring volumes. Presence does
|
||||
not prove use or justify deletion. Named volumes are outside the supplied
|
||||
four-directory backup plan.
|
||||
@@ -0,0 +1,48 @@
|
||||
:root {
|
||||
--infra-panel: #f8fafc;
|
||||
--infra-panel-glow: #eef2ff;
|
||||
--infra-border: #cbd5e1;
|
||||
--infra-zone: #ffffff;
|
||||
--infra-text: #1e293b;
|
||||
--infra-line: #64748b;
|
||||
--infra-client-fill: #f1f5f9;
|
||||
--infra-client-stroke: #94a3b8;
|
||||
--infra-edge-fill: #eef2ff;
|
||||
--infra-edge-stroke: #818cf8;
|
||||
--infra-service-fill: #ecfdf5;
|
||||
--infra-service-stroke: #34d399;
|
||||
--infra-cloud-fill: #fff7ed;
|
||||
--infra-cloud-stroke: #fb923c;
|
||||
--infra-storage-fill: #eff6ff;
|
||||
--infra-storage-stroke: #60a5fa;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] {
|
||||
--infra-panel: #141b2b;
|
||||
--infra-panel-glow: #202944;
|
||||
--infra-border: #475569;
|
||||
--infra-zone: #1e293b;
|
||||
--infra-text: #e2e8f0;
|
||||
--infra-line: #94a3b8;
|
||||
--infra-client-fill: #293548;
|
||||
--infra-client-stroke: #94a3b8;
|
||||
--infra-edge-fill: #30335c;
|
||||
--infra-edge-stroke: #a5b4fc;
|
||||
--infra-service-fill: #153c35;
|
||||
--infra-service-stroke: #6ee7b7;
|
||||
--infra-cloud-fill: #493324;
|
||||
--infra-cloud-stroke: #fdba74;
|
||||
--infra-storage-fill: #203654;
|
||||
--infra-storage-stroke: #93c5fd;
|
||||
}
|
||||
|
||||
.md-typeset .mermaid {
|
||||
--md-mermaid-label-fg-color: var(--infra-text);
|
||||
--md-mermaid-label-bg-color: var(--infra-zone);
|
||||
margin: 1.25rem 0;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--infra-border);
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, var(--infra-panel), var(--infra-panel-glow));
|
||||
}
|
||||
Reference in new issue
Block a user