VenomousViper Labs
Portfolio Infrastructure
← Back to Infrastructure

Primary storage and services host. Runs the container fleet, the AI/ML stack, the bulk storage array, and serves as the backup target for the environment.

Host

Hostnamehome-serv
OSUbuntu 22.04 LTS
CPUIntel i5-12600K (16 threads)
RAM32 GB
GPUIntel Arc A770 16 GB
LAN10.0.4.200
Root/dev/nvme1n1p2 — 1.9 TB (/boot on root, not a separate partition)
Bulk NVMe/dev/nvme0n1p1/mnt/storage, 1.9 TB
Container managementDockge, stacks under /home/jeremy/docker/stacks/

Kernel tuning

Set permanently via /etc/sysctl.d/:

Docker daemon

/etc/docker/daemon.json:

json
{
  "dns": ["1.1.1.1", "8.8.8.8"],
  "bip": "172.19.0.1/24",
  "default-address-pools": [
    { "base": "172.20.0.0/16", "size": 24 },
    { "base": "172.21.0.0/16", "size": 24 },
    { "base": "172.22.0.0/16", "size": 24 }
  ]
}

One external Docker network is created manually outside any stack:

bash
docker network create ai-net

ai-net carries qdrant, librechat-rag-api, and librechat.


Storage architecture

Backup array

Device/dev/md0 — RAID 1 mirror
Members2 × 6 TB WDC WD60EZRX, external USB dock
Mount/mnt/backup, ~5.2 TB usable
fstabUUID=<redacted> /mnt/backup ext4 defaults,nofail 0 2

Contents:

Bulk storage pool — MergerFS + SnapRAID

Three data disks and one parity disk, all 6 TB Seagate IronWolf, ext4. Pool mounts at /mnt/media, ~17 TB usable.

MountLabelRoleReserved blocks
/mnt/disk1media1data5%
/mnt/disk2media2data5%
/mnt/disk3media3data0%
/mnt/parity1parity5%

Device letters are not stable across reboots. Always resolve by UUID or label.

MergerFS fstab entry:

/mnt/disk1:/mnt/disk2:/mnt/disk3 /mnt/media mergerfs \
  defaults,allow_other,use_ino,category.create=mfs,minfreespace=50G,\
  x-systemd.requires-mounts-for=/mnt/disk1,\
  x-systemd.requires-mounts-for=/mnt/disk2,\
  x-systemd.requires-mounts-for=/mnt/disk3 0 0

x-systemd.requires-mounts-for guarantees every branch is mounted before MergerFS starts. Without it, a slow-enumerating USB drive produces a pool silently missing a disk.

All four pool entries carry nofail. Every disk on this host is USB-attached — without nofail, a single failed enumeration drops a headless machine to an emergency console.

Branch management. MergerFS 2.33.3. Branches change live through the xattr control interface; mount -o remount does not work for branch changes and fails with fuse: mountpoint is not empty.

bash
sudo getfattr -n user.mergerfs.branches /mnt/media/.mergerfs
sudo setfattr -n user.mergerfs.branches -v '+>/mnt/diskN=RW' /mnt/media/.mergerfs

SnapRAID. Config at /etc/snapraid.conf. Parity file at /mnt/parity1/snapraid.parity; data disks d1/d2/d3; content file mirrored to all three data drives. Single-drive failure is recoverable with snapraid fix. Two simultaneous drive failures exceed single-parity coverage.

Parity constraint. Parity must be greater than or equal to the largest data drive. All four disks are 6 TB, so the array sits at its limit — adding a data disk larger than 6 TB requires role-swapping parity onto the new disk and rebuilding parity first.

USB attachment

Both /mnt/backup RAID 1 members and all four pool drives sit behind ASMedia ASM1153E SATA bridges (174c:55aa) on Genesys Logic USB 3.1 hubs. There are no internally-attached spinning disks on this host. The four pool drives share a single hub at 4-1.4.

Stability mitigations live in the GRUB cmdline rather than a kernel package, so they survive kernel upgrades automatically:

usbcore.autosuspend=-1 usb-storage.quirks=174c:55aa:u

usb-storage.quirks matches by VID:PID, so any new drive on the same bridge chipset inherits the fix without a cmdline change.

Verify topology after adding a drive:

bash
for d in sdc sdd sde sdf; do
  echo -n "$d: "
  readlink -f /sys/block/$d | grep -oE 'usb[0-9]+(/[0-9.-]+)+' | head -1
done

Network

Traffic flow

mermaid (diagram source)
flowchart LR
    User[Browser or mobile]
    CF[Cloudflare edge]
    CFD[cloudflared daemon]
    SVC[home-serv services]

    User -->|HTTPS 443| CF
    CF -->|outbound QUIC tunnel| CFD
    CFD --> SVC

External access is inbound-free. Cloudflared holds an outbound QUIC tunnel to the Cloudflare edge; no ports are forwarded at the perimeter.

Mesh

home-serv is a member of a four-machine Tailscale mesh alongside sec-serv, jeremy-pc, and jeremy-lt. Other LAN hosts: 10.0.4.110 (sec-serv), 10.0.4.201 (cyberlab).

Exposure tiers

TierBindingReach
1127.0.0.1localhost only
2via tunnelpublic through Cloudflare Tunnel
30.0.0.0LAN and Tailscale reachable
4no host portDocker-internal only

Services

Notes, knowledge and reading

ServiceHostnameHost portContainer
BookStackwiki.lab.example0.0.0.0:3004bookstack + bookstack_db
SilverBulletsilverbullet.lab.example0.0.0.0:3005silverbullet
Blinkoblinko.lab.example0.0.0.0:1111blinko-website + blinko-postgres
Karakeepbookmarks.lab.example0.0.0.0:3008karakeep + karakeep-chrome + karakeep-meilisearch
FreshRSSrss.lab.example0.0.0.0:8088freshrss
Anki syncanki.lab.example127.0.0.1:27701anki-sync
InkSpirejournal.lab.example127.0.0.1:3009inkspire + inkspire-db
OpenBooksopenbooks.lab.example0.0.0.0:5079openbooks

Files and documents

ServiceHostnameHost portContainer
Paperless-ngxpaperless.lab.example0.0.0.0:8000paperless-ngx-webserver-1 + db + broker (TOTP MFA enabled)
Stirling-PDFpdf.lab.example0.0.0.0:8899stirling-pdf (started on demand)
ConvertXconvertx.lab.example0.0.0.0:8897convertx
Nextcloud AIOnextcloud.lab.example0.0.0.0:8082nextcloud-aio-apache + 9 sub-containers
Reactive Resumeresume.lab.example127.0.0.1:8106reactive-resume + db + printer

AI and ML

ServiceHostnameHost portType
Ollama (Intel Arc)LAN-only0.0.0.0:11434Docker — ollama-intel, KEEP_ALIVE=5m
Qdrantinternal127.0.0.1:6333/:6334Docker — v1.18.0, on ai-net
LibreChatchat.lab.example0.0.0.0:3080Docker — 5 containers
AnythingLLMllm.lab.example0.0.0.0:3001Docker — vector store is Qdrant
SD.NextLAN-only0.0.0.0:7860Native systemd — IPEX / Arc A770
RAG MCPLAN-only0.0.0.0:8765Native systemd — FastMCP SSE
RAG ingestNative systemd timer — nightly

LibreChat stack/home/jeremy/docker/stacks/librechat/

ContainerImagePort
librechatghcr.io/danny-avila/librechat:latest0.0.0.0:3080
librechat-mongodbmongo:7internal
librechat-meilisearchgetmeili/meilisearch:v1.11.1internal
librechat-pgvectorpgvector/pgvector:pg16internal
librechat-rag-apighcr.io/danny-avila/librechat-rag-api-dev:latestinternal :8000

Configured through librechat.yaml v1.3.9 with agents enabled, MCP domain allowlisting, and SearXNG-backed web search. Two MCP tools are exposed: search_knowledge_base against Qdrant, and web_search against the local SearXNG instance. Image generation routes through a Stable Diffusion agent to SD.Next.

RAG knowledge base/home/jeremy/docker/stacks/rag-ingest/

ComponentDetail
Collectionknowledge_base — 22,731 chunks
Embedding modelnomic-embed-text (768 dimensions) via Ollama
SourcesBookStack, Paperless-ngx, Gitea, Nextcloud
ScheduleNightly via rag-ingest.timer
Interfacerag-mcp.service on :8765

SD.Next — native systemd, install at /home/jeremy/docker/stacks/sdnext/. IPEX backend on the Arc A770.

Security

ServiceType
Wazuh agentNative systemd — reports to sec-serv, agent ID 001
MeshCentral agentNative systemd — enrolled with the server on sec-serv
Velociraptor clientNative systemd — reports to the server on sec-serv

Backup

ServiceHostnameHost portType
BBS (Borg Backup Server)backup.lab.example0.0.0.0:8081 web, 0.0.0.0:2222 SSHDocker
Healthcheckshc.lab.example0.0.0.0:8050Docker
ReaRnoneNative systemd timer

BBS is the primary backup system, providing a web UI, agent management, and Discord notifications through Apprise. Three clients are enrolled: home-serv, sec-serv, and jeremy-pc. All state lives on the /mnt/backup/bbs/ bind mount.

ReaR produces weekly bare-metal recovery images — a bootable rescue ISO plus a system tarball — written to /mnt/backup/rear/home-serv/. It runs entirely independently of BBS.

Healthchecks tracks run status for the ReaR jobs on both servers.

Finance, utilities and dashboards

ServiceHostnameHost portContainer
Actual Budgetactual.lab.example127.0.0.1:5006actual
Tamarimenu.lab.example0.0.0.0:4888tamari
Gamesgames.lab.example0.0.0.0:8108games
GameVaultgamevault.lab.example0.0.0.0:8080gamevault + gamevault-postgres
Questarrquestarr.lab.example0.0.0.0:5000questarr
Nexus Pulsenexus.lab.example127.0.0.1:3007nexus-pulse
ViperDashviperdash.lab.example0.0.0.0:3010viperdash — built locally
OmniToolsomnitools.lab.example0.0.0.0:8103omnitools
Changedetection.iochanges.lab.example127.0.0.1:8105changedetection
Dockgedockge.lab.example127.0.0.1:5001dockge-dockge-1
SearXNGsearch.lab.example0.0.0.0:8888searxng
Homarrhome.lab.example0.0.0.0:7575homarr
Giteagit.lab.example127.0.0.1:3020gitea
ntfyntfy.lab.example0.0.0.0:8181ntfy
Beszel agentinternalhost 45876beszel-agent — reports to the hub on sec-serv
Dozzle agentinternal0.0.0.0:7007dozzle-agent — reports to the hub on sec-serv

Cloudflare tunnel routes

Catch-all rule: http_status:404.

HostnameTarget
actual.lab.examplelocalhost:5006
anki.lab.examplelocalhost:27701
backup.lab.examplelocalhost:8081
blinko.lab.example10.0.4.200:1111
bookmarks.lab.examplelocalhost:3008
changes.lab.examplelocalhost:8105
chat.lab.examplelocalhost:3080
convertx.lab.examplelocalhost:8897
dockge.lab.examplelocalhost:5001
gamevault.lab.examplelocalhost:8080
games.lab.examplelocalhost:8108
git.lab.examplelocalhost:3020
hc.lab.examplelocalhost:8050
home.lab.example10.0.4.200:7575
journal.lab.examplelocalhost:3009
llm.lab.example10.0.4.200:3001
menu.lab.example10.0.4.200:4888
nexus.lab.examplelocalhost:3007
nextcloud.lab.examplelocalhost:8082
ntfy.lab.examplelocalhost:8181
omnitools.lab.examplelocalhost:8103
openbooks.lab.examplelocalhost:5079
paperless.lab.examplelocalhost:8000
pdf.lab.examplelocalhost:8899
questarr.lab.examplelocalhost:5000
resume.lab.examplelocalhost:8106
rss.lab.examplelocalhost:8088
search.lab.examplelocalhost:8888
silverbullet.lab.examplelocalhost:3005
viperdash.lab.examplelocalhost:3010
wiki.lab.examplelocalhost:3004

ntfy.lab.example requires disableChunkedEncoding: true.


Host port map

PortBindingService
220.0.0.0SSH
631127.0.0.1CUPS
11110.0.0.0blinko-website
22220.0.0.0BBS SSH — Borg agent connections
30010.0.0.0anythingllm
30040.0.0.0bookstack
30050.0.0.0silverbullet
3007127.0.0.1nexus-pulse
30080.0.0.0karakeep
3009127.0.0.1inkspire
30100.0.0.0viperdash
3020127.0.0.1gitea
30800.0.0.0librechat
3306127.0.0.1MariaDB (native)
34780.0.0.0 TCP+UDPnextcloud-aio-talk
48880.0.0.0tamari
50000.0.0.0questarr
5001127.0.0.1Dockge
5006127.0.0.1Actual Budget
50790.0.0.0openbooks
54350.0.0.0blinko-postgres
6333127.0.0.1Qdrant REST
6334127.0.0.1Qdrant gRPC
70070.0.0.0dozzle-agent
75750.0.0.0homarr
78600.0.0.0SD.Next (native)
80000.0.0.0paperless-ngx
80500.0.0.0Healthchecks
80800.0.0.0gamevault
80810.0.0.0BBS web UI
80820.0.0.0nextcloud-aio-apache
80880.0.0.0freshrss
80900.0.0.0nextcloud-aio-mastercontainer
81030.0.0.0omnitools
8105127.0.0.1Changedetection.io
8106127.0.0.1Reactive Resume
81080.0.0.0games
81810.0.0.0ntfy
87650.0.0.0rag-mcp (native)
88880.0.0.0searxng
88970.0.0.0convertx
88990.0.0.0stirling-pdf
114340.0.0.0ollama-intel
20241127.0.0.1cloudflared metrics
27701127.0.0.1anki-sync
416410.0.0.0 UDPtailscaled
458760.0.0.0beszel-agent (host network)

Compose projects

Stacks live under /home/jeremy/docker/stacks/, managed through Dockge.

actual/               anki/                 anythingllm/
bbs/                  beszel/               blinko/
bookstack/            changedetection/      convertx/
dockge/               freshrss/             games/
gitea/                healthchecks/         inkspire/
karakeep/             librechat/            nextcloud-aio/
nexus-pulse/          ntfy/                 ollama-intel/
omnitools/            paperless-ngx/        qdrant/
rag-ingest/           reactive-resume/      sdnext/
searxng/              silverbullet/         tamari/
viperdash/            watchtower/

sdnext/ and rag-ingest/ hold installs and scripts for services that run as native systemd units rather than compose stacks.


Native systemd services

ServiceRolePort
cloudflaredTunnel daemonoutbound + 127.0.0.1:20241
docker, containerdDocker Enginesockets
mariadbNative MariaDB127.0.0.1:3306
sdnextSD.Next image generation (IPEX / Arc A770)0.0.0.0:7860
rag-mcpKnowledge base MCP server0.0.0.0:8765
rag-ingest.timerNightly RAG ingestion
rear-backup.timerWeekly bare-metal system image
bbs-agentBorg Backup Server clientoutbound only
tailscaledTailscale meshUDP :41641
wazuh-agentSIEM agent → sec-servoutbound only
meshagentMeshCentral agent → sec-servoutbound only
velociraptor_clientVelociraptor DFIR client → sec-servoutbound only
smartmontoolsSMART disk monitoring
sshOpenSSH0.0.0.0:22
ufwFirewall
cupsPrinting127.0.0.1:631

Scheduled tasks

JobScheduleDefined in
SnapRAID sync02:00 nightlyjeremy crontab
SnapRAID scrub03:00 Sundaysjeremy crontab
RAG full ingest02:00 nightlyrag-ingest.timer
Obsidian → BookStack sync03:00 on the 1st and 15thjeremy crontab
Scrutiny SMART collector → sec-servmidnight nightlyroot crontab
BBS backup — home-serv01:00 dailyBBS server-side scheduler
BBS backup — sec-serv01:00 dailyBBS server-side scheduler
BBS backup — jeremy-pc02:00 dailyBBS server-side scheduler
ReaR system image03:00 Sundaysrear-backup.timer

rear-backup.timer sets Persistent=true, so a reboot inside the Sunday window triggers a catch-up run.