Netgrimoire/Pocket-Grimoire/Sync/Deployment-Guide.md
2026-04-12 09:53:51 -05:00

91 KiB
Raw Blame History

title description published date tags editor dateCreated
Pocket Grimoire true 2026-02-26T12:42:50.676Z markdown 2026-02-20T04:41:35.122Z

Pocket Grimoire - Complete Deployment Guide

Portable, Encrypted, Offline-Capable Media Server and Documentation Reference


Overview

Pocket Grimoire is a portable companion to the Netgrimoire homelab, providing offline access to:

  • Documentation and reference material (Wiki.js)
  • Personal and family media libraries (Jellyfin)
  • Photos, documents, and backups (encrypted vault)
  • Automatic synchronization with Netgrimoire when connected

Design Philosophy:

  • Calm and predictable
  • Encrypted at rest
  • Offline-first operation
  • Automatic synchronization
  • One wall plug
  • No cloud dependencies
  • Minimal services (media + docs only, no gaming)

Hardware Inventory

Core Compute

  • Raspberry Pi 4 (8GB)
  • Passive heatsink case or low-noise fan case
  • Official Raspberry Pi 4 Power Supply OR quality 3A USB-A to USB-C cable
  • Spare MicroSD card (32GB+, for OS recovery)
  • USB card reader (for flashing Pi images)

Storage (3 SSDs, 2 Active at a Time)

  • SSD #1 VAULT (1-2TB, encrypted, always connected)

    • Git repository mirrors (from Forgejo)
    • Wiki.js content backups
    • Kopia repository (backup data)
    • Photos and documents
    • System backups and configs
    • SSH keys
    • Does NOT contain media, Stash data, or VeraCrypt containers
  • SSD #2 GREEN (2TB+, encrypted, for personal trips)

    • Personal media library (H.264/AAC movies and TV)
    • Stash-Pocket data (database, previews, blobs)
    • VeraCrypt containers (for ultra-sensitive files)
    • Personal content organized under /Green/ structure
    • Connected during personal/solo trips
    • Syncs from /export/vault/Green/ on Netgrimoire
  • SSD #3 MEDIA-FAMILY (2TB+, unencrypted, for family trips)

    • Family-friendly movies and TV shows (H.264/AAC)
    • Simple /library/movies/ and /library/tv/ structure
    • Connected during family visits/trips
    • Unencrypted for easy sharing with relatives
    • Can be used on other devices without Pocket Grimoire
  • USB drive ISO/Rebuild (64GB+, labeled, write-protected)

  • USB drive Data Transfer (128GB+, labeled)

Networking

  • GL.iNet Beryl AX (GL-MT3000) travel router
  • Short CAT5/6 Ethernet cable (6-12 inch, Pi ↔ Router)
  • USB Ethernet adapter (backup/emergency)

Power

  • Anker Prime 200W 6-Port GaN Charging Station (Model A2683)
  • Short USB-A to USB-C cable (3A-rated, 6-12 inch, for Pi)
  • Short USB-A to USB-A cable (6-12 inch, for Vault SSD)
  • 2× USB-C to USB-C cables (6ft, 100W with E-Marker chip, for laptop/phone)

Media Players

  • 2× Onn 4K streaming boxes with power supplies
  • 2× HDMI cables
  • Mini wireless keyboard (for Onn boxes and emergency Pi access)

Cables & Accessories

  • Micro-HDMI to HDMI cable (Pi emergency console access)
  • HDMI extender (if hotel TV ports are difficult to reach)

Organization & Emergency

  • Carry case for complete kit
  • Cable organizer pouch (separate from main case)
  • Velcro cable ties (pack of 20)
  • Labels for SSDs (VAULT, MEDIA-PERSONAL, MEDIA-FAMILY)
  • Small flashlight or headlamp
  • Small screwdriver or multitool (accessing hotel TV ports)

Power Configuration

Anker Prime A2683 Port Assignments

USB-C1 (retractable) → GL.iNet Beryl AX (12W)
USB-C2 (100W PD)     → Laptop charging (65-90W)
USB-C3               → Phone charging (20-30W)
USB-C4               → Tablet/spare (optional)
USB-A1 (5V/3A)       → Raspberry Pi 4 (15W)
USB-A2 (5V/3A)       → Vault SSD (always connected, 5W)

AC Outlet 1          → Spare
AC Outlet 2          → Spare

Raspberry Pi USB Ports

USB 3.0 Port 1 → Media SSD (personal or family, rotated)
USB 3.0 Port 2 → Spare (emergency USB, data transfer)
USB 2.0 Port 1 → Spare (wireless keyboard dongle if needed)
USB 2.0 Port 2 → Spare

Power Budget

Component             Power Draw    Running Total
─────────────────────────────────────────────────
Raspberry Pi 4        15W           15W
Beryl AX              12W           27W
Vault SSD             5W            32W
Media SSD (via Pi)    5W            37W
Laptop (charging)     65W           102W
Phone (charging)      20W           122W
─────────────────────────────────────────────────
Total                               122W / 200W
Headroom: 78W

Software Stack

Host OS & Services (Native)

Operating System:

  • Raspberry Pi OS Lite 64-bit (headless, no desktop environment)
  • Alternative: Ubuntu Server 22.04 LTS ARM64

Storage & Filesystems:

  • ZFS (OpenZFS) - Encrypted pools
    • vaultpg pool (Vault SSD, always connected)
    • mediapg pool (Media SSDs, rotated personal/family)
    • Native ZFS encryption with passphrase unlock
    • ARC memory capped (512MB-1GB maximum)

File Sharing:

  • NFS server (host-level, not containerized)
    • Exports /srv/mediapg to LAN (read-only)
    • Laptop and Onn boxes mount for media access

Sync & Automation:

  • systemd timers (scheduled jobs every 6 hours)
    • ZFS replication from Netgrimoire via syncoid
    • Git pulls for wiki/docs repositories
    • ntfy failure notifications

Networking:

  • Standard Linux networking
  • Docker and Docker Compose

Docker Containers

Required Stack:

  1. Wiki.js - Documentation mirror

    • Read-only wiki pulling from Forgejo
    • Git backend with SSH deploy key (read-only)
    • Works fully offline after sync
    • Port: 3000
  2. PostgreSQL - Wiki.js database backend

    • Stored on Vault SSD
    • Tuned for 16K recordsize (Postgres optimal)
  3. Jellyfin - Media server

    • Direct play ONLY (transcoding disabled)
    • Serves H.264/AAC pre-encoded media
    • Accessible from Onn boxes and laptop
    • Port: 8096

Optional Containers:

  1. File Browser - Read-only web UI

    • Quick LAN access to vault/media without SSH
    • Port: 8080
  2. Dozzle - Container log viewer

    • Simple Docker log viewer for debugging
    • Port: 9999

Network Architecture

Operating Modes

Home Base (Netgrimoire LAN):

  • Direct LAN connectivity
  • VPN not required
  • Fast local synchronization
  • All services accessible

Travel (Online):

  • All traffic routed via WireGuard VPN to Netgrimoire
  • DNS and ad blocking handled by Beryl AX router
  • Primary DNS: Netgrimoire (via VPN)
  • Fallback DNS: Public resolvers

Travel (Offline):

  • Full local access to all services
  • Wiki, files, and media available
  • No synchronization until connectivity returns
  • DNS handled locally by router

Router Configuration (Beryl AX)

DNS & Ad Blocking:

  • AdGuard Home enabled on router
  • Acts as primary DNS for all clients
  • Blocks ads and trackers network-wide

DNS Behavior:

  • Primary DNS: Netgrimoire (via VPN when available)
  • Fallback DNS: Public resolvers (1.1.1.1, 9.9.9.9)
  • Local DNS entries:
    • pocket-grimoire.local
    • wiki.pocket-grimoire.local
    • media.pocket-grimoire.local

VPN Behavior:

  • WireGuard client configured
  • When VPN available: All traffic tunneled to Netgrimoire
  • When VPN unavailable: Normal WAN routing

Directory Structure

/srv/pocket-grimoire/              # Main application root (on VAULT SSD)
├── stacks/                        # Docker Compose files
│   ├── wikijs/
│   │   ├── docker-compose.yml
│   │   └── .env
│   ├── jellyfin/
│   │   ├── docker-compose.yml
│   │   └── .env
│   ├── stash/
│   │   ├── docker-compose.yml
│   │   └── .env
│   └── filebrowser/               # Optional
│       └── docker-compose.yml
├── data/                          # Persistent container data
│   ├── postgres/                  # PostgreSQL data
│   ├── wikijs/                    # Wiki.js data
│   ├── jellyfin/                  # Jellyfin metadata/config
│   └── filebrowser/               # File browser config
├── repos/                         # Git repository mirrors
│   └── wiki/                      # Wiki content from Forgejo
└── keys/                          # SSH keys
    ├── forgejo_wiki_ro            # Read-only wiki deploy key
    └── zfs_pull_ro                # ZFS replication key

/srv/vaultpg/                      # VAULT SSD (always connected)
├── kopia/                         # Kopia backup repository
├── backups/                       # System backups
│   ├── wiki/                      # Wiki.js backups
│   ├── photos/                    # Photo backups
│   └── documents/                 # Document backups
└── repos/                         # Git repository mirrors

/srv/greenpg/                      # GREEN SSD (personal, rotated)
└── Pocket/                        # Dataset received from vault/Green/Pocket
    ├── media/library/             # Personal media files
    │   ├── movies/
    │   └── tv/
    ├── stash/                     # Stash-Pocket data
    │   ├── config/                # Stash database
    │   ├── generated/             # Previews
    │   └── blobs/                 # Scene markers
    └── veracrypt/                 # VeraCrypt containers
        └── sensitive.vc           # Encrypted container files

/srv/mediapg/                      # MEDIA-FAMILY SSD (family, rotated)
└── library/                       # Family media files
    ├── movies/
    └── tv/

/mnt/veracrypt/                    # VeraCrypt mount points (optional)
├── vault1/                        # Mounted container 1
└── vault2/                        # Mounted container 2 (if needed)

/usr/local/sbin/                   # System scripts
├── pocketgrimoire-sync.sh         # Main sync script
├── pocketgrimoire-zfs-pull.sh     # ZFS replication script
├── unlock-pocket-grimoire.sh      # Headless unlock script
└── mount-veracrypt-vault.sh       # VeraCrypt mount script (optional)

/etc/                              # Config files
├── pocketgrimoire-sync.env        # Secrets (ntfy tokens)
├── exports                        # NFS exports
└── systemd/system/
    ├── pocketgrimoire-sync.service
    └── pocketgrimoire-sync.timer

Installation Instructions

1. Base OS Installation

Download Raspberry Pi OS:

# On your laptop
# Download Raspberry Pi OS Lite (64-bit) from raspberrypi.com
# Use Raspberry Pi Imager to flash to MicroSD card

# Configure:
# - Hostname: pocket-grimoire
# - Enable SSH
# - Set username/password
# - Configure WiFi (for initial setup only)

First Boot:

# SSH into Pi
ssh user@pocket-grimoire.local

# Update system
sudo apt update && sudo apt upgrade -y

# Set timezone
sudo timedatectl set-timezone America/Chicago

# Configure locale
sudo raspi-config
# System Options → Locale → en_US.UTF-8

⚠️ Important: Ubuntu Pi Boot Configuration Note

Ubuntu on Raspberry Pi uses a different boot config location than Raspberry Pi OS.

The active kernel command line is in:

/boot/firmware/current/cmdline.txt

Do NOT edit /boot/firmware/cmdline.txt for kernel parameters — that file is only read during tryboot scenarios and is ignored on normal boot.

Any kernel parameters (including USB quirks for drives) must go in /boot/firmware/current/cmdline.txt as a single unbroken line.

This is critical for applying USB storage quirks (see Troubleshooting section if you experience drive issues).


2. Install VeraCrypt (Optional - For Encrypted Container Files)

VeraCrypt allows you to mount encrypted container files as virtual drives. This is useful for:

  • Encrypted file containers for ultra-sensitive data
  • Portable encrypted volumes that can be moved between systems
  • Additional layer of encryption beyond ZFS (nested encryption)
  • Cross-platform compatibility (Windows, Mac, Linux)

Installation:

# Add VeraCrypt PPA repository
sudo add-apt-repository ppa:unit193/encryption -y

# Update package lists
sudo apt update

# Install VeraCrypt
sudo apt install veracrypt -y

# Verify installation
veracrypt --text --version

Create Mount Point:

# Create directory for VeraCrypt volumes
sudo mkdir -p /mnt/veracrypt
sudo mkdir -p /mnt/veracrypt/vault1
sudo mkdir -p /mnt/veracrypt/vault2

Mount VeraCrypt Container:

# Mount a VeraCrypt container file
sudo veracrypt --text \
  --mount /path/to/container.vc \
  /mnt/veracrypt/vault1

# You will be prompted for:
# - Container password
# - PIM (leave blank if not used)
# - Keyfiles (if any)

# Verify mounted
mount | grep veracrypt
df -h /mnt/veracrypt/vault1

Auto-Mount on Boot (Optional):

Create systemd service to mount VeraCrypt on boot with manual password entry:

sudo nano /etc/systemd/system/veracrypt-vault.service
[Unit]
Description=Mount VeraCrypt vault container
After=local-fs.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/veracrypt --text --non-interactive \
  --password-stdin \
  --mount /srv/vaultpg/containers/vault.vc \
  /mnt/veracrypt/vault1
ExecStop=/usr/bin/veracrypt --text --dismount /mnt/veracrypt/vault1

[Install]
WantedBy=multi-user.target

Note: For security, password should be entered manually at boot, not stored in files.

Better Approach - Manual Mount Script:

sudo nano /usr/local/sbin/mount-veracrypt-vault.sh
#!/bin/bash
# Mount VeraCrypt container from GREEN drive

CONTAINER="/srv/greenpg/Pocket/veracrypt/sensitive.vc"
MOUNT_POINT="/mnt/veracrypt/vault1"

if mount | grep -q "$MOUNT_POINT"; then
    echo "VeraCrypt volume already mounted at $MOUNT_POINT"
    exit 0
fi

# Check if GREEN drive is mounted
if [ ! -f "$CONTAINER" ]; then
    echo "Error: VeraCrypt container not found at $CONTAINER"
    echo "Is GREEN drive mounted?"
    exit 1
fi

echo "Mounting VeraCrypt container from GREEN drive..."
sudo veracrypt --text --mount "$CONTAINER" "$MOUNT_POINT"

if [ $? -eq 0 ]; then
    echo "Successfully mounted: $MOUNT_POINT"
    df -h "$MOUNT_POINT"
else
    echo "Failed to mount VeraCrypt container"
    exit 1
fi
sudo chmod +x /usr/local/sbin/mount-veracrypt-vault.sh

Usage:

# Mount manually after boot
sudo /usr/local/sbin/mount-veracrypt-vault.sh

# Unmount
sudo veracrypt --text --dismount /mnt/veracrypt/vault1

# List mounted volumes
veracrypt --text --list

VeraCrypt Container Creation (Do this on Netgrimoire first):

# Create directory in your existing vault/Green/Pocket dataset
sudo mkdir -p /export/Green/Pocket/veracrypt

# Create a new VeraCrypt container (example: 10GB)
veracrypt --text --create /export/Green/Pocket/veracrypt/sensitive.vc

# Follow prompts:
# - Volume type: Normal
# - Encryption algorithm: AES
# - Hash algorithm: SHA-512
# - Filesystem: Linux Ext4
# - Size: 10GB (or desired size)
# - Password: (enter strong password)
# - Format volume: Yes

Sync VeraCrypt Container via ZFS:

# VeraCrypt containers are stored in vault/Green/Pocket/veracrypt/
# They automatically sync to GREEN drive with the rest of the Pocket dataset

# On Netgrimoire:
# /export/Green/Pocket/veracrypt/sensitive.vc

# After sync to GREEN drive:
# /mnt/pocket-green/Pocket/veracrypt/sensitive.vc

# On Pocket Grimoire after import:
# /srv/greenpg/Pocket/veracrypt/sensitive.vc

# The container syncs automatically when you sync the Green/Pocket dataset

When to Use VeraCrypt vs ZFS Encryption:

Use VeraCrypt when:

  • Need portable encrypted containers (can move to other systems)
  • Want different passwords for different data sets
  • Need compatibility with Windows/Mac (VeraCrypt is cross-platform)
  • Want nested encryption (VeraCrypt inside ZFS)

Use ZFS encryption when:

  • Encrypting entire drives/pools
  • Want transparent encryption (no manual mounting)
  • Need better performance (native filesystem encryption)
  • Don't need to move encrypted data to non-Linux systems

For Pocket Grimoire, recommended approach:

  • ZFS encryption for VAULT and GREEN SSDs (always)
  • VeraCrypt for ultra-sensitive files on GREEN drive (optional)
  • Example: Tax documents, financial records, personal files
  • VeraCrypt containers stored in /export/Green/Pocket/veracrypt/
  • Syncs to GREEN drive automatically with other Pocket data

3. Install ZFS

# Install ZFS utilities
sudo apt install -y zfsutils-linux

# Verify ZFS is working
sudo zpool list

4. Initial Drive Setup on Netgrimoire (Before Moving to Pocket)

IMPORTANT: Build drives on Netgrimoire first, then move to Pocket Grimoire.

This approach allows you to:

  • Create encrypted pools with proper passphrases
  • Perform initial ZFS sync while drives are fast-connected (SATA/USB 3.0)
  • Verify data integrity before moving drives
  • Test encryption/unlock on powerful hardware first

Drive Configuration Overview

Drive #1: VAULT (1-2TB, encrypted, always connected)

  • Purpose: Backups and system data ONLY
  • Contains: Git repos, Wiki backups, Kopia repository, photos, documents
  • Does NOT contain media, Stash data, or VeraCrypt containers

Drive #2: GREEN (2TB+, encrypted, rotated for personal trips)

  • Purpose: Personal media, Stash-Pocket data, and VeraCrypt containers
  • Contains: Personal media library, Stash database/previews/blobs, VeraCrypt files
  • Syncs from /export/vault/Green/ on Netgrimoire

Drive #3: MEDIA-FAMILY (2TB+, unencrypted, rotated for family trips)

  • Purpose: Family-friendly shareable content
  • Contains: Simple library structure with movies and TV
  • Unencrypted for easy sharing with relatives

On Netgrimoire: Create and Populate Drives

Connect drives to Netgrimoire:

  • VAULT SSD (1-2TB) via USB 3.0 or SATA
  • GREEN SSD (2TB+) via USB 3.0 or SATA
  • MEDIA-FAMILY SSD (2TB+) via USB 3.0 or SATA (optional, can be created later)

Identify drives:

# On Netgrimoire
lsblk
# Note device names: /dev/sdX, /dev/sdY, /dev/sdZ

Create VAULT pool (encrypted - backups only):

# On Netgrimoire
sudo zpool create -o ashift=12 \
  -O encryption=on \
  -O keylocation=prompt \
  -O keyformat=passphrase \
  -O compression=lz4 \
  -O atime=off \
  -O recordsize=1M \
  -m /mnt/pocket-vault \
  pocket-vault /dev/sdX

# Enter STRONG passphrase when prompted
# Write down this passphrase - you'll need it on Pocket Grimoire

# Create datasets for backups and system data
sudo zfs create -o recordsize=16K pocket-vault/wiki-pg      # PostgreSQL backups
sudo zfs create pocket-vault/repos                           # Git repository mirrors
sudo zfs create pocket-vault/kopia                           # Kopia backup repository
sudo zfs create pocket-vault/backups                         # General backups
sudo zfs create pocket-vault/backups/wiki                    # Wiki.js backups
sudo zfs create pocket-vault/backups/photos                  # Photo backups
sudo zfs create pocket-vault/backups/documents               # Document backups

# Set ownership
sudo chown -R 1000:1000 /mnt/pocket-vault

GREEN pool - Use Existing vault/Green/Pocket Dataset:

IMPORTANT: You already have an encrypted dataset vault/Green/Pocket on Netgrimoire with your personal media and Stash data. Do NOT create a new pool from scratch. Instead, you'll use ZFS send/receive to replicate this existing dataset to the GREEN drive.

# On Netgrimoire
# Verify your existing dataset
zfs list vault/Green/Pocket
# Should show: vault/Green/Pocket  5.01T  2.49T  5.01T  /export/Green/Pocket

# Check what's in it
ls /export/Green/Pocket/
# Should show: media/ and stash/ directories

# This dataset will be sent to the GREEN drive in the next step
# No need to create pocket-green datasets manually

Create empty GREEN pool (will receive data via ZFS send):

# On Netgrimoire with GREEN SSD connected
sudo zpool create -o ashift=12 \
  -O encryption=on \
  -O keylocation=prompt \
  -O keyformat=passphrase \
  -O compression=lz4 \
  -O atime=off \
  -O recordsize=1M \
  -m /mnt/pocket-green \
  pocket-green /dev/sdY

# Enter STRONG passphrase (can be different from VAULT)
# Write down this passphrase

# Don't create datasets manually - they'll be created by zfs receive
# The pool is now ready to receive vault/Green/Pocket dataset

Create MEDIA-FAMILY pool (unencrypted - family content):

# On Netgrimoire
sudo zpool create -o ashift=12 \
  -O compression=lz4 \
  -O atime=off \
  -O recordsize=1M \
  -m /mnt/pocket-media \
  pocket-media /dev/sdZ

# No encryption - family can use this drive on any system

# Create simple library structure
sudo zfs create pocket-media/library
sudo zfs create pocket-media/library/movies
sudo zfs create pocket-media/library/tv

# Set ownership
sudo chown -R 1000:1000 /mnt/pocket-media

Perform initial sync to VAULT:

# On Netgrimoire
# Sync backups and system data to VAULT drive

# Sync Wiki backups
sudo rsync -avP \
  /export/vault/wiki-backups/ \
  /mnt/pocket-vault/backups/wiki/

# Sync Git repositories
sudo rsync -avP \
  /export/vault/repos/ \
  /mnt/pocket-vault/repos/

# Sync Kopia repository (if exists)
sudo rsync -avP \
  /export/vault/kopia/ \
  /mnt/pocket-vault/kopia/

# Sync photos and documents
sudo rsync -avP \
  /export/vault/photos/ \
  /mnt/pocket-vault/backups/photos/

sudo rsync -avP \
  /export/vault/documents/ \
  /mnt/pocket-vault/backups/documents/

# Verify data
ls -lh /mnt/pocket-vault/
du -sh /mnt/pocket-vault/

Perform initial sync to GREEN:

You have two options for syncing your existing vault/Green/Pocket dataset to the GREEN drive:

Option A: Using Syncoid (Recommended - Easier)

# On Netgrimoire with GREEN drive connected
# Syncoid handles snapshots and incremental transfers automatically

sudo syncoid vault/Green/Pocket pocket-green/Pocket

# Syncoid will:
# - Create snapshot automatically
# - Send data to pocket-green/Pocket
# - Show progress bar
# - Handle all ZFS send/receive details

# Verify received
zfs list pocket-green/Pocket
ls -lh /mnt/pocket-green/Pocket/
du -sh /mnt/pocket-green/Pocket/

Important Note on Pool Naming:

  • On Netgrimoire during initial build: Pool is called pocket-green
  • After moving to Pocket Grimoire: Pool is renamed to greenpg during import
  • If you've already moved the drive to Pocket and back, use: sudo syncoid vault/Green/Pocket greenpg/Pocket

Option B: Manual ZFS Send (Advanced)

# On Netgrimoire
# You have an existing encrypted dataset: vault/Green/Pocket
# This contains your personal media and will include Stash data

# First, verify the dataset exists and its size
zfs list vault/Green/Pocket
# Should show: vault/Green/Pocket  5.01T  2.49T  5.01T  /export/Green/Pocket

# Create snapshot for initial send
sudo zfs snapshot vault/Green/Pocket@initial

# Send to pocket-green pool, creating pocket-green/Pocket dataset
# IMPORTANT: Must specify destination dataset name, not just pool name
sudo zfs send vault/Green/Pocket@initial | \
  sudo zfs receive pocket-green/Pocket

# Or if pool was already renamed to greenpg:
# sudo zfs send vault/Green/Pocket@initial | \
#   sudo zfs receive greenpg/Pocket

# This creates: pocket-green/Pocket (or greenpg/Pocket)
# NOT just "pocket-green" (which is the pool name)

# Verify received
zfs list pocket-green/Pocket  # or greenpg/Pocket
ls -lh /mnt/pocket-green/Pocket/  # or /srv/greenpg/Pocket

# Verify data integrity
du -sh /mnt/pocket-green/Pocket/  # or /srv/greenpg/Pocket

Both options create the same result:

# The data structure will be:
# /mnt/pocket-green/Pocket/ (or /srv/greenpg/Pocket if already renamed)
# ├── media/library/
# │   ├── movies/
# │   └── tv/
# └── stash/
#     ├── config/
#     ├── generated/
#     └── blobs/

Important notes:

  • The vault/Green/Pocket dataset is encrypted on Netgrimoire
  • zfs send transfers the data (decrypted during send)
  • pocket-green (or greenpg) pool has its own encryption (encrypts during receive)
  • Result: Data is encrypted at rest on both systems with different keys
  • The dataset name becomes pocket-green/Pocket initially, or greenpg/Pocket if pool was already renamed
  • Recommended: Use syncoid (Option A) - it's simpler and handles everything automatically

Populate MEDIA-FAMILY (optional - curate family content):

# On Netgrimoire
# Copy family-friendly media to MEDIA-FAMILY drive

# Example: Copy family movies
sudo cp /export/vault/media/family-movies/*.mp4 \
   /mnt/pocket-media/library/movies/

# Or use rsync for large transfers
sudo rsync -avP \
  /export/vault/media/family-shows/ \
  /mnt/pocket-media/library/tv/

# Verify
du -sh /mnt/pocket-media/library/

Export pools before disconnecting:

# On Netgrimoire
# CRITICAL: Export pools before physically disconnecting drives

sudo zpool export pocket-vault

# For GREEN drive - check which name it has
zpool list | grep -E "pocket-green|greenpg"

# If it shows "pocket-green":
sudo zpool export pocket-green

# If it shows "greenpg" (already renamed from previous import):
sudo zpool export greenpg

# For MEDIA-FAMILY (if created):
sudo zpool export pocket-media  # or mediapg if renamed

# Verify exported
zpool list
# Should NOT show pocket-* or *pg pools

Physically disconnect drives from Netgrimoire.

5. Configure ZFS Pools on Pocket Grimoire

Now connect drives to Pocket Grimoire:

  • VAULT → Anker USB-A port #2 (always connected)
  • GREEN (for personal trips) → Raspberry Pi USB 3.0 port OR
  • MEDIA-FAMILY (for family trips) → Raspberry Pi USB 3.0 port

Import and rename pools:

# On Pocket Grimoire (SSH into Pi)
ssh user@pocket-grimoire.local

# Import VAULT pool with new name
sudo zpool import pocket-vault vaultpg

# Import GREEN pool with new name (for personal trips)
sudo zpool import pocket-green greenpg

# OR import MEDIA-FAMILY pool (for family trips)
# sudo zpool import pocket-media mediapg

# Verify pools imported
zpool list
# Should show: vaultpg, greenpg (or mediapg for family)

Set mount points for Pocket Grimoire:

# Set proper mount points
sudo zfs set mountpoint=/srv/vaultpg vaultpg
sudo zfs set mountpoint=/srv/greenpg greenpg

# For the Pocket dataset (received from vault/Green/Pocket)
sudo zfs set mountpoint=/srv/greenpg/Pocket greenpg/Pocket

# Or for family drive (when you swap):
# sudo zfs set mountpoint=/srv/mediapg mediapg

# Create mount points
sudo mkdir -p /srv/vaultpg
sudo mkdir -p /srv/greenpg
sudo mkdir -p /srv/mediapg  # Create both, use as needed

# Unmount and remount with new paths
sudo zfs unmount -a
sudo zfs mount -a

# Verify mounted
df -h | grep srv
# Should show:
# vaultpg          mounted on /srv/vaultpg
# greenpg          mounted on /srv/greenpg
# greenpg/Pocket   mounted on /srv/greenpg/Pocket

# Verify data
ls /srv/vaultpg/
ls /srv/greenpg/Pocket/media/library/
ls /srv/greenpg/Pocket/stash/
# Or for family:
# ls /srv/mediapg/library/

Configure for headless unlock:

# Set pools to NOT auto-mount on boot
# This prevents boot hanging waiting for passphrase

sudo zfs set canmount=noauto vaultpg
sudo zfs set canmount=noauto greenpg
sudo zfs set canmount=noauto greenpg/Pocket
sudo zfs set canmount=noauto mediapg  # For when you swap to family drive

# Pools will need manual unlock via SSH after boot

Cap ZFS ARC Memory:

# Create /etc/modprobe.d/zfs.conf
sudo nano /etc/modprobe.d/zfs.conf

# Add this line (for 8GB Pi, cap at 1GB):
options zfs zfs_arc_max=1073741824

# Save and apply
sudo update-initramfs -u
sudo reboot

6. Create Headless Unlock Script

After reboot, SSH back in and create unlock script:

sudo nano /usr/local/sbin/unlock-pocket-grimoire.sh
#!/bin/bash
# Unlock Pocket Grimoire encrypted ZFS pools (headless operation)

set -e

echo "=========================================="
echo "  Pocket Grimoire ZFS Unlock (Headless)"
echo "=========================================="
echo

# Check if VAULT pool is already unlocked
if zfs list vaultpg &>/dev/null && mount | grep -q /srv/vaultpg; then
    echo "✓ vaultpg (VAULT) already unlocked and mounted"
else
    # Import pool if needed
    if ! zpool list vaultpg &>/dev/null; then
        echo "Importing vaultpg pool..."
        sudo zpool import vaultpg
    fi
    
    # Unlock VAULT pool
    echo "Unlocking vaultpg (VAULT - backups and system data)..."
    sudo zfs load-key vaultpg
    
    # Mount all vaultpg datasets
    sudo zfs mount vaultpg
    sudo zfs mount -a
    
    if mount | grep -q /srv/vaultpg; then
        echo "✓ vaultpg unlocked and mounted at /srv/vaultpg"
    else
        echo "✗ Failed to mount vaultpg"
        exit 1
    fi
fi

echo

# Check for GREEN pool (personal media + Stash)
if zpool list greenpg &>/dev/null; then
    if zfs list greenpg &>/dev/null && mount | grep -q /srv/greenpg; then
        echo "✓ greenpg (GREEN - personal media + Stash) already unlocked"
    else
        echo "Unlocking greenpg (GREEN - personal media + Stash)..."
        sudo zfs load-key greenpg
        sudo zfs mount greenpg
        sudo zfs mount -a
        
        if mount | grep -q /srv/greenpg; then
            echo "✓ greenpg unlocked and mounted at /srv/greenpg"
        else
            echo "✗ Failed to mount greenpg"
            exit 1
        fi
    fi
else
    echo "  greenpg pool not found (GREEN drive not connected)"
fi

echo

# Check for MEDIA-FAMILY pool (family content)
if zpool list mediapg &>/dev/null; then
    if zfs list mediapg &>/dev/null && mount | grep -q /srv/mediapg; then
        echo "✓ mediapg (MEDIA-FAMILY) already unlocked"
    else
        echo "Unlocking mediapg (MEDIA-FAMILY - family content)..."
        
        # Check if encrypted (shouldn't be, but check anyway)
        if zfs get encryption mediapg | grep -q "encryption.*on"; then
            sudo zfs load-key mediapg
        fi
        
        sudo zfs mount mediapg
        sudo zfs mount -a
        
        if mount | grep -q /srv/mediapg; then
            echo "✓ mediapg unlocked and mounted at /srv/mediapg"
        else
            echo "✗ Failed to mount mediapg"
            exit 1
        fi
    fi
else
    echo "  mediapg pool not found (MEDIA-FAMILY drive not connected)"
fi

echo

# Optional: Mount VeraCrypt containers
if [ -f /usr/local/sbin/mount-veracrypt-vault.sh ]; then
    echo "VeraCrypt container found. Mount now? (y/n)"
    read -r response
    if [[ "$response" == "y" ]]; then
        /usr/local/sbin/mount-veracrypt-vault.sh
    fi
fi

echo
echo "=========================================="
echo "  Starting Docker Services"
echo "=========================================="
echo

# Start Docker service
if ! systemctl is-active --quiet docker; then
    echo "Starting Docker..."
    sudo systemctl start docker
    sleep 3
fi

# Start containers
echo "Starting Wiki.js stack..."
cd /srv/pocket-grimoire/stacks/wikijs && docker compose up -d

echo "Starting Jellyfin stack..."
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose up -d

echo "Starting Stash stack..."
if [ -d /srv/pocket-grimoire/stacks/stash ]; then
    cd /srv/pocket-grimoire/stacks/stash && docker compose up -d
fi

# Optional containers
if [ -d /srv/pocket-grimoire/stacks/filebrowser ]; then
    echo "Starting File Browser..."
    cd /srv/pocket-grimoire/stacks/filebrowser && docker compose up -d
fi

echo
echo "=========================================="
echo "  Pocket Grimoire Ready!"
echo "=========================================="
echo
echo "Drives mounted:"
if mount | grep -q /srv/vaultpg; then
    echo "  ✓ VAULT (vaultpg) at /srv/vaultpg"
fi
if mount | grep -q /srv/greenpg; then
    echo "  ✓ GREEN (greenpg) at /srv/greenpg - Personal media + Stash"
fi
if mount | grep -q /srv/mediapg; then
    echo "  ✓ MEDIA-FAMILY (mediapg) at /srv/mediapg - Family content"
fi
echo
echo "Services available at:"
echo "  Wiki.js:      http://pocket-grimoire.local:3000"
echo "  Jellyfin:     http://pocket-grimoire.local:8096"
echo "  Stash:        http://pocket-grimoire.local:9999"
echo "  File Browser: http://pocket-grimoire.local:8080"
echo
echo "Total unlock time: $(($SECONDS / 60)) minutes $(($SECONDS % 60)) seconds"
echo
sudo chmod +x /usr/local/sbin/unlock-pocket-grimoire.sh

7. Disable Docker Auto-Start (Headless Configuration)

Prevent Docker from starting before ZFS pools are unlocked:

# Disable Docker auto-start on boot
sudo systemctl disable docker

# Docker will be started manually by unlock script

Or, configure Docker to wait for ZFS (if you prefer):

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo nano /etc/systemd/system/docker.service.d/wait-for-zfs.conf
[Unit]
# Don't start Docker until after manual ZFS unlock
After=zfs-mount.service
Wants=zfs-mount.service

[Service]
# Restart Docker if it fails (ZFS not ready)
Restart=on-failure
RestartSec=10
sudo systemctl daemon-reload

Recommended: Just disable auto-start and use unlock script.

8. Test Headless Unlock Procedure

Test at home before traveling:

# 1. Reboot Pi
sudo reboot

# 2. Wait 2-3 minutes for boot (don't connect monitor/keyboard)

# 3. SSH from laptop
ssh user@pocket-grimoire.local

# 4. Run unlock script
/usr/local/sbin/unlock-pocket-grimoire.sh

# Enter passphrases when prompted:
# - VAULT passphrase
# - MEDIA-PERSONAL passphrase (if encrypted)
# - VeraCrypt password (if using)

# 5. Wait for Docker containers to start

# 6. Verify services running
docker ps

# 7. Access from browser
# http://pocket-grimoire.local:3000
# http://pocket-grimoire.local:8096  
# http://pocket-grimoire.local:9999

# 8. Verify data accessible
ls /srv/vaultpg/Green/Pocket/
ls /srv/mediapg/library/

If everything works, you're ready for travel!

9. Quick Manual Unlock (If Script Fails)

# SSH into Pocket Grimoire
ssh user@pocket-grimoire.local

# Import pools if needed
sudo zpool import vaultpg
sudo zpool import greenpg      # For GREEN (personal)
# Or:
# sudo zpool import mediapg    # For MEDIA-FAMILY

# Load encryption keys
sudo zfs load-key vaultpg      # VAULT (always encrypted)
sudo zfs load-key greenpg      # GREEN (encrypted)
# mediapg is unencrypted (MEDIA-FAMILY) - no key needed

# Mount all datasets
sudo zfs mount -a

# Verify mounted
df -h | grep srv
# Should show vaultpg and either greenpg or mediapg

# Start Docker
sudo systemctl start docker

# Start containers manually
cd /srv/pocket-grimoire/stacks/wikijs && docker compose up -d
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose up -d
cd /srv/pocket-grimoire/stacks/stash && docker compose up -d

Configure ZFS to Wait for Passphrase on Boot:

# Edit /etc/systemd/system/zfs-load-key.service
sudo nano /etc/systemd/system/zfs-load-key.service

Add:

[Unit]
Description=Load ZFS encryption keys
Before=zfs-mount.service
After=zfs-import.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/zfs load-key -a

[Install]
WantedBy=zfs-mount.service

Enable:

sudo systemctl daemon-reload
sudo systemctl enable zfs-load-key.service

4. Install Docker

# Install Docker
sudo apt install -y docker.io docker-compose

# Add user to docker group
sudo usermod -aG docker $USER

# Enable Docker service
sudo systemctl enable docker
sudo systemctl start docker

# Log out and back in for group changes
exit
# SSH back in

5. Install NFS Server

# Install NFS server
sudo apt install -y nfs-kernel-server

# Configure exports
sudo nano /etc/exports

Add:

/srv/mediapg  10.0.0.0/24(ro,fsid=10,async,no_subtree_check)

Apply:

sudo exportfs -ra
sudo systemctl restart nfs-server
sudo systemctl enable nfs-server

# Verify
sudo exportfs -v

6. Install Syncoid (ZFS Replication Tool)

Syncoid is a ZFS replication tool that makes syncing datasets much easier than manual ZFS send/receive.

# Install Sanoid (includes syncoid)
sudo apt update
sudo apt install -y sanoid

# Verify installation
which syncoid
syncoid --version

# Should show: syncoid version X.X.X

What syncoid does:

  • Automatically creates snapshots
  • Handles incremental ZFS send/receive
  • Manages snapshot cleanup
  • Shows progress bars
  • Works over SSH
  • Resumes interrupted transfers

Example usage:

# Local sync (same machine)
sudo syncoid source/dataset destination/dataset

# Remote sync over SSH
sudo syncoid --sshkey /path/to/key \
  root@remote-host:source/dataset \
  local/dataset

You'll use this for:

  • Initial GREEN drive sync on Netgrimoire
  • Ongoing syncs from Netgrimoire to Pocket Grimoire over network
  • Much simpler than manual zfs send commands

7. Install System Packages

# Core utilities
sudo apt install -y \
  curl \
  git \
  htop \
  ncdu \
  smartmontools \
  sanoid

# For ntfy notifications
sudo apt install -y curl

Docker Configuration

Wiki.js Stack

Create directory structure:

mkdir -p /srv/pocket-grimoire/stacks/wikijs
mkdir -p /srv/pocket-grimoire/data/postgres
mkdir -p /srv/pocket-grimoire/data/wikijs
mkdir -p /srv/pocket-grimoire/repos/wiki
mkdir -p /srv/pocket-grimoire/keys

Create environment file:

nano /srv/pocket-grimoire/stacks/wikijs/.env
TZ=America/Chicago

PUID=1000
PGID=1000

POSTGRES_DB=wikijs
POSTGRES_USER=wikijs
POSTGRES_PASSWORD=CHANGE_ME_LONG_RANDOM_PASSWORD

WIKI_PORT=3000
COMPOSE_PROJECT_NAME=pocketgrimoire_wikijs

Create Docker Compose file:

nano /srv/pocket-grimoire/stacks/wikijs/docker-compose.yml
services:
  db:
    image: postgres:16-alpine
    container_name: pocketgrimoire_db
    environment:
      TZ: ${TZ}
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - /srv/pocket-grimoire/data/postgres:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10

  wikijs:
    image: requarks/wiki:2
    container_name: pocketgrimoire_wikijs
    depends_on:
      db:
        condition: service_healthy
    environment:
      TZ: ${TZ}
      DB_TYPE: postgres
      DB_HOST: db
      DB_PORT: 5432
      DB_USER: ${POSTGRES_USER}
      DB_PASS: ${POSTGRES_PASSWORD}
      DB_NAME: ${POSTGRES_DB}
    ports:
      - "${WIKI_PORT}:3000"
    volumes:
      - /srv/pocket-grimoire/repos:/repos
    restart: unless-stopped

Start Wiki.js:

cd /srv/pocket-grimoire/stacks/wikijs
docker compose up -d

Access Wiki.js:

  • Open browser: http://pocket-grimoire.local:3000
  • Complete initial setup
  • Configure as read-only (see Wiki.js Configuration section below)

Jellyfin Stack

Create directory structure:

mkdir -p /srv/pocket-grimoire/stacks/jellyfin
mkdir -p /srv/pocket-grimoire/data/jellyfin/config
mkdir -p /srv/pocket-grimoire/data/jellyfin/cache

Create environment file:

nano /srv/pocket-grimoire/stacks/jellyfin/.env
TZ=America/Chicago
PUID=1000
PGID=1000
JELLYFIN_PORT=8096
COMPOSE_PROJECT_NAME=pocketgrimoire_jellyfin

Create Docker Compose file:

nano /srv/pocket-grimoire/stacks/jellyfin/docker-compose.yml
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: pocketgrimoire_jellyfin
    user: "${PUID}:${PGID}"
    environment:
      - TZ=${TZ}
    volumes:
      - /srv/pocket-grimoire/data/jellyfin/config:/config
      - /srv/pocket-grimoire/data/jellyfin/cache:/cache
      - /srv/mediapg:/media:ro
    ports:
      - "${JELLYFIN_PORT}:8096"
    restart: unless-stopped

Start Jellyfin:

cd /srv/pocket-grimoire/stacks/jellyfin
docker compose up -d

Access Jellyfin:

  • Open browser: http://pocket-grimoire.local:8096
  • Complete initial setup
  • Add media library: /media/library
  • Configure for direct play only (see Jellyfin Configuration section below)

Optional: File Browser

Create directory structure:

mkdir -p /srv/pocket-grimoire/stacks/filebrowser
mkdir -p /srv/pocket-grimoire/data/filebrowser

Create Docker Compose file:

nano /srv/pocket-grimoire/stacks/filebrowser/docker-compose.yml
services:
  filebrowser:
    image: filebrowser/filebrowser:s6
    container_name: pocketgrimoire_filebrowser
    ports:
      - "8080:80"
    volumes:
      - /srv/pocket-grimoire/data/filebrowser:/database
      - /srv/pocket-grimoire/data/filebrowser:/config
      - /srv/vaultpg:/vault:ro
      - /srv/mediapg:/media:ro
    restart: unless-stopped

Start File Browser:

cd /srv/pocket-grimoire/stacks/filebrowser
docker compose up -d

Access File Browser:

  • Open browser: http://pocket-grimoire.local:8080
  • Default login: admin / admin
  • Change password immediately
  • Configure as read-only in settings

Optional: Dozzle (Container Logs)

Create Docker Compose file:

mkdir -p /srv/pocket-grimoire/stacks/dozzle
nano /srv/pocket-grimoire/stacks/dozzle/docker-compose.yml
services:
  dozzle:
    image: amir20/dozzle:latest
    container_name: pocketgrimoire_dozzle
    ports:
      - "9999:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped

Start Dozzle:

cd /srv/pocket-grimoire/stacks/dozzle
docker compose up -d

Service Configuration

Wiki.js Configuration

After initial setup, configure read-only mode:

  1. Disable User Registration:

    • Administration → Users → Settings
    • Disable "Allow self-registration"
  2. Configure Read-Only Permissions:

    • Administration → Groups
    • Edit "Guests" group (or create "Readers" group)
    • Permissions:
      • ✓ Read pages
      • ✗ Create pages
      • ✗ Edit pages
      • ✗ Delete pages
      • ✗ Upload files
  3. Configure Git Storage:

    • Administration → Storage → Git
    • Setup:
      • Remote: git@your-forgejo-host:username/wiki-content.git
      • Authentication: SSH (deploy key)
      • Sync Direction: Pull/Import only
      • Branch: main
  4. Generate SSH Deploy Key:

    mkdir -p /srv/pocket-grimoire/keys
    ssh-keygen -t ed25519 -f /srv/pocket-grimoire/keys/forgejo_wiki_ro -N ""
    chmod 600 /srv/pocket-grimoire/keys/forgejo_wiki_ro
    cat /srv/pocket-grimoire/keys/forgejo_wiki_ro.pub
    
  5. Add Deploy Key to Forgejo:

    • Copy public key
    • Forgejo → Repository → Settings → Deploy Keys
    • Add key (read-only access)
  6. Import Content:

    • Administration → Storage → Git
    • Click "Import Content" or "Force Sync"
    • Verify pages appear

Jellyfin Configuration

Critical: Disable All Transcoding

  1. Dashboard → Playback:

    • ✓ Prefer Direct Play
    • ✓ Prefer Direct Stream
    • ✗ Allow video transcoding (DISABLE THIS)
    • ✗ Allow audio transcoding when supported (DISABLE THIS)
    • ✗ Hardware acceleration: None (not needed)
  2. Dashboard → Libraries:

    • Add Library: /media/library/movies
    • Content type: Movies
    • Add Library: /media/library/tv
    • Content type: TV Shows
  3. Dashboard → Networking:

    • Published Server URL: http://pocket-grimoire.local:8096
    • Enable automatic port mapping: No
  4. Dashboard → Scheduled Tasks:

    • Disable aggressive scanning
    • Scan library: Manual only (or daily at most)

Verify Direct Play:

  • Play a movie
  • During playback: Click info icon
  • Verify: "Direct Play" (NOT "Transcoding" or "Direct Stream with Transcode")
  • If transcoding appears: Media is not properly encoded

Synchronization Configuration

Create ntfy Environment File

sudo nano /etc/pocketgrimoire-sync.env
NTFY_URL="https://ntfy.YOUR_DOMAIN/pocket-grimoire"
NTFY_TOKEN="YOUR_NTFY_TOKEN_HERE"  # Optional
HOSTNAME_TAG="$(hostname -s)"
sudo chmod 600 /etc/pocketgrimoire-sync.env

Create Main Sync Script

sudo nano /usr/local/sbin/pocketgrimoire-sync.sh
#!/usr/bin/env bash
set -euo pipefail

ENV_FILE="/etc/pocketgrimoire-sync.env"
LOG="/var/log/pocketgrimoire-sync.log"
LOCK="/run/pocketgrimoire-sync.lock"
STATE_DIR="/var/lib/pocketgrimoire"
FAIL_FLAG="${STATE_DIR}/last_run_failed"

mkdir -p "$STATE_DIR"
touch "$LOG"
chmod 640 "$LOG"

# shellcheck disable=SC1090
source "$ENV_FILE"

notify_ntfy() {
  local title="$1"
  local msg="$2"
  local priority="${3:-default}"
  local tags="${4:-warning}"

  local auth=()
  if [[ -n "${NTFY_TOKEN:-}" ]]; then
    auth=(-H "Authorization: Bearer ${NTFY_TOKEN}")
  fi

  curl -fsS -X POST "${NTFY_URL}" \
    "${auth[@]}" \
    -H "Title: ${title}" \
    -H "Priority: ${priority}" \
    -H "Tags: ${tags}" \
    -d "${msg}" >/dev/null 2>&1 || true
}

# Prevent overlapping runs
exec 9>"$LOCK"
if ! flock -n 9; then
  echo "$(date -Is) sync already running, exiting" >> "$LOG"
  exit 0
fi

run_or_fail() {
  local label="$1"; shift
  echo "$(date -Is) --- ${label} START ---" >> "$LOG"
  if "$@" >> "$LOG" 2>&1; then
    echo "$(date -Is) --- ${label} OK ---" >> "$LOG"
    return 0
  else
    local rc=$?
    echo "$(date -Is) --- ${label} FAIL (rc=${rc}) ---" >> "$LOG"
    return $rc
  fi
}

main() {
  echo "$(date -Is) ===== Pocket Grimoire sync start =====" >> "$LOG"

  # 1) ZFS replication pull
  # Placeholder - configure after setting up ZFS replication
  # run_or_fail "ZFS pull" /usr/local/sbin/pocketgrimoire-zfs-pull.sh
  echo "$(date -Is) ZFS pull: placeholder (configure syncoid)" >> "$LOG"

  # 2) Git pull for wiki content
  REPO_DIR="/srv/pocket-grimoire/repos/wiki"
  BRANCH="main"

  if [[ -d "${REPO_DIR}/.git" ]]; then
    run_or_fail "Git fetch (wiki)" git -C "$REPO_DIR" fetch --all --prune
    run_or_fail "Git reset (wiki)" git -C "$REPO_DIR" reset --hard "origin/${BRANCH}"
  else
    echo "$(date -Is) WARNING: ${REPO_DIR} is not a git repo" >> "$LOG"
  fi

  echo "$(date -Is) ===== Pocket Grimoire sync end =====" >> "$LOG"

  # If previously failing, send recovery notice
  if [[ -f "$FAIL_FLAG" ]]; then
    rm -f "$FAIL_FLAG"
    notify_ntfy \
      "Pocket Grimoire sync recovered (${HOSTNAME_TAG})" \
      "Sync is healthy again. Last run succeeded at $(date -Is)." \
      "low" \
      "white_check_mark"
  fi
}

# Trap errors to notify
on_error() {
  local rc=$?
  touch "$FAIL_FLAG"

  local tail_txt
  tail_txt="$(tail -n 60 "$LOG" 2>/dev/null || true)"

  notify_ntfy \
    "Pocket Grimoire sync FAILED (${HOSTNAME_TAG})" \
    "Return code: ${rc}\nTime: $(date -Is)\n\nLast log lines:\n${tail_txt}" \
    "high" \
    "rotating_light"

  exit $rc
}
trap on_error ERR

main
sudo chmod +x /usr/local/sbin/pocketgrimoire-sync.sh
sudo mkdir -p /var/lib/pocketgrimoire

Create ZFS Replication Script

sudo nano /usr/local/sbin/pocketgrimoire-zfs-pull.sh
#!/usr/bin/env bash
set -euo pipefail

# Configuration - UPDATE THESE
SRC_HOST="netgrimoire.example.lan"
SRC_DATASET="vault/source_dataset"
DST_DATASET="vaultpg/mirror_dataset"
SSH_KEY="/srv/pocket-grimoire/keys/zfs_pull_ro"

# Run syncoid for incremental replication
syncoid --no-sync-snap --recursive \
  --sshkey "${SSH_KEY}" \
  "root@${SRC_HOST}:${SRC_DATASET}" \
  "${DST_DATASET}"
sudo chmod +x /usr/local/sbin/pocketgrimoire-zfs-pull.sh

Note: Configure SSH keys and ZFS send/receive permissions on Netgrimoire before enabling this script.

Create systemd Service

sudo nano /etc/systemd/system/pocketgrimoire-sync.service
[Unit]
Description=Pocket Grimoire periodic sync (ZFS + Git) with ntfy alerts
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/pocketgrimoire-sync.sh

Create systemd Timer

sudo nano /etc/systemd/system/pocketgrimoire-sync.timer
[Unit]
Description=Run Pocket Grimoire sync every 6 hours

[Timer]
OnBootSec=10min
OnUnitActiveSec=6h
Persistent=true

[Install]
WantedBy=timers.target

Enable and Start Timer

sudo systemctl daemon-reload
sudo systemctl enable pocketgrimoire-sync.timer
sudo systemctl start pocketgrimoire-sync.timer

# Verify timer is active
systemctl list-timers | grep pocketgrimoire

# Check timer status
systemctl status pocketgrimoire-sync.timer

# View sync logs
tail -f /var/log/pocketgrimoire-sync.log

# Manually trigger sync (for testing)
sudo systemctl start pocketgrimoire-sync.service

Media Encoding Requirements

All media MUST be encoded to these specifications for direct play:

Video Codec

  • Codec: H.264 (AVC)
  • Profile: High
  • Level: 4.1
  • Bit depth: 8-bit
  • Pixel format: yuv420p
  • Container: MKV or MP4

Audio Codec

  • Primary: AAC 2.0 (stereo, 192 kbps)
  • Optional: AC3 5.1 (surround, if needed)
  • Avoid: DTS, DTS-HD, TrueHD (these force audio transcoding)

Subtitles

  • Format: SRT (SubRip Text) only
  • Avoid: PGS/VobSub (image-based subs force video transcoding)
  • Location: External .srt files or embedded in MKV

FFmpeg Encoding Command

Single file:

ffmpeg -i input.mkv \
  -map 0:v:0 -map 0:a:0 -map 0:s? \
  -c:v libx264 -preset slow -crf 20 \
  -profile:v high -level 4.1 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  -c:s srt \
  output.mkv

CRF Quality Guide:

  • 18 - Near-lossless (large files, ~8-12 GB per movie)
  • 20 - Excellent quality (recommended, ~4-6 GB per movie)
  • 22 - Good quality (smaller files, ~3-4 GB per movie)

Batch encode directory:

#!/bin/bash
for f in *.mkv; do
  ffmpeg -i "$f" \
    -c:v libx264 -preset slow -crf 20 \
    -profile:v high -level 4.1 -pix_fmt yuv420p \
    -c:a aac -b:a 192k \
    -c:s srt \
    "${f%.mkv}.h264.mkv"
done

Check existing media codec:

ffprobe input.mkv 2>&1 | grep -E "Video:|Audio:"

Verify direct play compatibility:

# After encoding, verify:
ffprobe output.mkv 2>&1 | grep "h264" # Should show h264
ffprobe output.mkv 2>&1 | grep "aac"  # Should show aac

Pre-Trip Checklist

Complete these tasks before traveling:

1. System Health Check

# Check ZFS pool health
sudo zpool status

# Check disk space
df -h /srv/vaultpg /srv/mediapg

# Check SSD health
sudo smartctl -a /dev/sdX  # Replace with actual device

# Verify Docker containers running
docker ps

2. Sync Everything

# Manually trigger sync
sudo systemctl start pocketgrimoire-sync.service

# Wait for completion and verify
journalctl -u pocketgrimoire-sync.service -n 100 --no-pager
tail -n 200 /var/log/pocketgrimoire-sync.log

3. Test Media Playback

# Access Jellyfin
# Open: http://pocket-grimoire.local:8096
# Play a movie
# Verify: Direct Play (check info during playback)
# No transcoding icon should appear

4. Test Offline Mode

# Disconnect from internet
# Verify services accessible:
# - http://pocket-grimoire.local:3000 (Wiki.js)
# - http://pocket-grimoire.local:8096 (Jellyfin)
# - http://pocket-grimoire.local:8080 (File Browser)

# Test media playback offline
# Test wiki page browsing offline

5. Verify NFS Export

# On laptop
sudo mkdir -p /mnt/pocket-media
sudo mount -t nfs pocket-grimoire.local:/srv/mediapg /mnt/pocket-media
ls /mnt/pocket-media/library
sudo umount /mnt/pocket-media

6. Label Hardware

# Ensure all SSDs are labeled:
# - VAULT (always stays connected)
# - MEDIA-PERSONAL (for personal trips)
# - MEDIA-FAMILY (for family visits)

7. Pack Emergency Items

  • Spare MicroSD card (Pi recovery)
  • USB card reader
  • Micro-HDMI to HDMI cable
  • USB Ethernet adapter
  • Extra cables (USB-C, HDMI)
  • Flashlight
  • Small screwdriver

8. Document Passphrases

  • ZFS encryption passphrases (written down, secured)
    • VAULT (vaultpg): [write passphrase on paper]
    • GREEN (greenpg): [write passphrase on paper]
    • MEDIA-FAMILY (mediapg): N/A (unencrypted)
  • VeraCrypt container passwords (if using, written down, secured)
  • WiFi credentials for travel router (portapotty network)
  • Jellyfin admin password
  • Wiki.js admin password
  • Stash admin password
  • Keep all passphrases in secure location separate from device

9. Test Headless Unlock Procedure (CRITICAL)

# At home, test the exact hotel deployment workflow

# 1. Reboot Pi without monitor/keyboard attached
sudo reboot

# 2. Wait 2-3 minutes for boot

# 3. SSH from laptop
ssh user@pocket-grimoire.local

# 4. Run unlock script
/usr/local/sbin/unlock-pocket-grimoire.sh

# 5. Enter passphrases when prompted
# - VAULT passphrase (always)
# - GREEN passphrase (if GREEN drive connected for personal trip)
# - MEDIA-FAMILY has no passphrase (unencrypted)
# - VeraCrypt password (if applicable)

# 6. Wait for Docker containers to start (~30 seconds)

# 7. Verify all services running
docker ps

# 8. Test access from browser
# http://pocket-grimoire.local:3000 (Wiki.js)
# http://pocket-grimoire.local:8096 (Jellyfin)
# http://pocket-grimoire.local:9999 (Stash)

# 9. Test media playback in Jellyfin
# 10. Test Stash preview playback
# 11. Test NFS mount from laptop (optional)

If anything fails during this test, debug at home before traveling!

10. Resync GREEN Drive When Connected to Netgrimoire

If you've physically moved the GREEN drive back to Netgrimoire for fast resyncing:

# On Netgrimoire with GREEN SSD connected

# 1. Check which name the pool has
zpool list | grep -E "pocket-green|greenpg"

# 2. Import if not already imported
# If pool is named "pocket-green":
sudo zpool import pocket-green

# If pool is named "greenpg" (already renamed from Pocket):
sudo zpool import greenpg

# 3. Load encryption key
sudo zfs load-key pocket-green  # or greenpg
# Enter GREEN drive passphrase

# 4. Mount datasets
sudo zfs mount -a

# 5. Verify mounted
zfs list | grep -E "pocket-green|greenpg"
# Should show the Pocket dataset mounted

# 6. Resync using syncoid
# If pool is named "pocket-green":
sudo syncoid vault/Green/Pocket pocket-green/Pocket

# If pool is named "greenpg":
sudo syncoid vault/Green/Pocket greenpg/Pocket

# Syncoid shows progress:
# Sending incremental vault/Green/Pocket@... 
# 2.3GB 0:01:23 [28.4MB/s] [===============>] 100%

# 7. Verify sync completed
zfs list pocket-green/Pocket  # or greenpg/Pocket
du -sh /mnt/pocket-green/Pocket/  # or /srv/greenpg/Pocket

# 8. Export pool before disconnecting
sudo zfs unmount -a
sudo zpool export pocket-green  # or greenpg

# 9. Safe to physically disconnect GREEN SSD

Quick Commands Based on Pool Name:

# Check pool name first
POOL_NAME=$(zpool list | grep -oE "pocket-green|greenpg")
echo "Pool name: $POOL_NAME"

# Then use appropriate commands
sudo zpool import $POOL_NAME
sudo zfs load-key $POOL_NAME
sudo zfs mount -a
sudo syncoid vault/Green/Pocket ${POOL_NAME}/Pocket
sudo zpool export $POOL_NAME

11. Configure Ongoing ZFS Sync (After Initial Setup)

Once drives are on Pocket Grimoire, set up ongoing sync from Netgrimoire:

# On Pocket Grimoire
# Create ZFS replication script

sudo nano /usr/local/sbin/pocketgrimoire-zfs-pull.sh
#!/usr/bin/env bash
set -euo pipefail

SRC_HOST="netgrimoire.local"
SSH_KEY="/srv/pocket-grimoire/keys/zfs_pull_ro"

# Sync GREEN/Pocket dataset (personal media + Stash)
# This pulls incremental changes from vault/Green/Pocket to greenpg/Pocket
syncoid --no-sync-snap \
  --sshkey "${SSH_KEY}" \
  "root@${SRC_HOST}:vault/Green/Pocket" \
  "greenpg/Pocket"

# Note: VAULT and MEDIA-FAMILY don't sync ongoing
# VAULT: Contains backups only, managed separately
# MEDIA-FAMILY: Manually updated when needed
sudo chmod +x /usr/local/sbin/pocketgrimoire-zfs-pull.sh

This sync runs every 6 hours automatically and:

  • Syncs /export/Green/Pocket/ from Netgrimoire
  • Includes personal media updates
  • Includes Stash database changes
  • Includes new previews/blobs
  • Only transfers incremental changes (fast)

Test sync manually:

# Unlock pools first
/usr/local/sbin/unlock-pocket-grimoire.sh

# Run sync
sudo /usr/local/sbin/pocketgrimoire-zfs-pull.sh

# Check for new data
zfs list greenpg/Pocket
du -sh /srv/greenpg/Pocket/

12. Verify Data Synced from Netgrimoire

# Check VAULT data present (backups only)
ls /srv/vaultpg/kopia/
ls /srv/vaultpg/backups/
ls /srv/vaultpg/repos/
du -sh /srv/vaultpg/

# Check GREEN data (personal media + Stash)
ls /srv/greenpg/Pocket/media/library/
ls /srv/greenpg/Pocket/stash/
du -sh /srv/greenpg/Pocket/

# Or check MEDIA-FAMILY data (if that drive is connected)
ls /srv/mediapg/library/
du -sh /srv/mediapg/

# Verify Stash database and previews (only on GREEN)
ls -lh /srv/greenpg/Pocket/stash/config/
# Should show: stash-go.sqlite

ls /srv/greenpg/Pocket/stash/generated/ | wc -l
# Should show: hundreds of preview files

Drive Movement Workflow (VAULT, GREEN, MEDIA-FAMILY)

This section covers moving SSDs between Netgrimoire and Pocket Grimoire for syncing and swapping.


VAULT Drive Movement

VAULT is normally ALWAYS CONNECTED to Pocket Grimoire, but you may need to move it back to Netgrimoire for:

  • Initial population with backup data
  • Major updates to backup repositories
  • Troubleshooting or recovery

Connect VAULT to Netgrimoire

# On Netgrimoire

# Physical: Connect VAULT SSD via USB 3.0 or SATA

# Import pool
sudo zpool import pocket-vault

# Load encryption key
sudo zfs load-key pocket-vault
# Enter VAULT passphrase

# Mount datasets
sudo zfs mount -a

# Verify mounted
zfs list | grep pocket-vault
df -h | grep pocket-vault

Update VAULT on Netgrimoire

# On Netgrimoire

# Update Kopia repository
sudo kopia repository connect filesystem --path=/mnt/pocket-vault/kopia
sudo kopia snapshot create /path/to/backup/source

# Sync Git repositories
sudo rsync -avP /export/vault/repos/ /mnt/pocket-vault/repos/

# Sync Wiki backups
sudo rsync -avP /srv/wikijs/backups/ /mnt/pocket-vault/backups/wiki/

# Sync photo/document backups
sudo rsync -avP /export/vault/photos/ /mnt/pocket-vault/backups/photos/
sudo rsync -avP /export/vault/documents/ /mnt/pocket-vault/backups/documents/

# Verify updates
du -sh /mnt/pocket-vault/

Disconnect VAULT from Netgrimoire

# On Netgrimoire

# Unmount datasets
sudo zfs unmount pocket-vault
# Or unmount all at once:
# sudo zfs unmount -a (be careful with this!)

# Export pool
sudo zpool export pocket-vault

# Verify exported
zpool list | grep pocket-vault
# Should show nothing

# Physical: Disconnect VAULT SSD

Connect VAULT to Pocket Grimoire

# Physical: Connect to Anker USB-A port #2

# On Pocket Grimoire (SSH)
ssh user@pocket-grimoire.local

# Use unlock script (recommended)
/usr/local/sbin/unlock-pocket-grimoire.sh
# Enter VAULT passphrase when prompted

# Or manual:
sudo zpool import pocket-vault vaultpg
sudo zfs load-key vaultpg
sudo zfs set mountpoint=/srv/vaultpg vaultpg
sudo zfs mount -a

# Verify mounted
df -h | grep vaultpg
zfs list | grep vaultpg

GREEN Drive Movement (Personal Media + Stash)

GREEN is rotated - connected during personal trips, synced on Netgrimoire when updates needed.

Connect GREEN to Netgrimoire

# On Netgrimoire

# Physical: Connect GREEN SSD via USB 3.0 or SATA

# Check if drive detected
lsblk

# Import pool
sudo zpool import greenpg

# Load encryption key
sudo zfs load-key greenpg
# Enter GREEN passphrase

# Mount datasets
sudo zfs mount -a

# Verify mounted
zfs list | grep greenpg
df -h | grep greenpg

# Should show:
# greenpg          5.00T  2.14T   280K  /srv/greenpg
# greenpg/Pocket   5.00T  2.14T  5.00T  /srv/greenpg/Pocket

Update GREEN on Netgrimoire

Using Syncoid (Recommended):

# On Netgrimoire

# Sync from vault/Green/Pocket to GREEN drive
sudo syncoid vault/Green/Pocket greenpg/Pocket

# Progress shown:
# Sending incremental vault/Green/Pocket@... 
# 2.3GB 0:01:23 [28.4MB/s] [===============>] 100%

# Verify sync completed
zfs list greenpg/Pocket
du -sh /srv/greenpg/Pocket/

# Check latest snapshot
zfs list -t snapshot greenpg/Pocket | tail -3

Manual file updates (if needed):

# On Netgrimoire

# Add new media
sudo cp /path/to/new/movie.mp4 /srv/greenpg/Pocket/media/library/movies/

# Add new TV episodes
sudo cp -r /path/to/show/Season02 /srv/greenpg/Pocket/media/library/tv/ShowName/

# Add VeraCrypt containers
sudo cp /path/to/sensitive.vc /srv/greenpg/Pocket/veracrypt/

# Update Stash data (usually automatic via syncoid)
# Stash database, previews, and blobs sync automatically

Disconnect GREEN from Netgrimoire

# On Netgrimoire

# Stop any processes using the drive
sudo lsof | grep greenpg
# Kill processes if needed

# Unmount datasets
sudo zfs unmount greenpg/Pocket
sudo zfs unmount greenpg

# Export pool (CRITICAL!)
sudo zpool export greenpg

# Verify exported
zpool list | grep greenpg
# Should show nothing

# Verify ready for import elsewhere
sudo zpool import | grep greenpg
# Should show pool available

# Physical: Disconnect GREEN SSD

Connect GREEN to Pocket Grimoire

# Physical: Connect to Raspberry Pi USB 3.0 port (blue port)

# On Pocket Grimoire (SSH)
ssh user@pocket-grimoire.local

# Use unlock script (recommended)
/usr/local/sbin/unlock-pocket-grimoire.sh
# Enter passphrases when prompted

# Or manual:
sudo zpool import greenpg
sudo zfs load-key greenpg
sudo zfs set mountpoint=/srv/greenpg greenpg
sudo zfs set mountpoint=/srv/greenpg/Pocket greenpg/Pocket
sudo zfs mount -a

# Start Docker containers
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose up -d
cd /srv/pocket-grimoire/stacks/stash && docker compose up -d

# Verify services
docker ps

Test services:

# From browser (on portapotty WiFi)
http://pocket-grimoire.local:8096  # Jellyfin
http://pocket-grimoire.local:9999  # Stash

Disconnect GREEN from Pocket Grimoire

# On Pocket Grimoire (SSH)
ssh user@pocket-grimoire.local

# Stop Docker containers using GREEN
cd /srv/pocket-grimoire/stacks/jellyfin
docker compose down

cd /srv/pocket-grimoire/stacks/stash
docker compose down

# Unmount VeraCrypt (if using)
sudo veracrypt --text --dismount-all

# Unmount datasets
sudo zfs unmount greenpg/Pocket
sudo zfs unmount greenpg

# Export pool
sudo zpool export greenpg

# Verify exported
zpool list | grep greenpg
# Should show nothing

# Physical: Disconnect GREEN SSD

MEDIA-FAMILY Drive Movement (Family Content)

MEDIA-FAMILY is rotated - connected during family trips, manually updated as needed.

Connect MEDIA-FAMILY to Netgrimoire

# On Netgrimoire

# Physical: Connect MEDIA-FAMILY SSD via USB 3.0 or SATA

# Import pool (no encryption key needed - unencrypted)
sudo zpool import pocket-media

# Mount datasets
sudo zfs mount -a

# Verify mounted
zfs list | grep pocket-media
df -h | grep pocket-media

Update MEDIA-FAMILY on Netgrimoire

# On Netgrimoire

# Add new family movies
sudo cp /export/vault/media/family-movies/*.mp4 \
   /mnt/pocket-media/library/movies/

# Add new family TV shows
sudo rsync -avP \
   /export/vault/media/family-shows/NewShow/ \
   /mnt/pocket-media/library/tv/NewShow/

# Remove old content to free space
sudo rm -rf /mnt/pocket-media/library/movies/OldMovie/
sudo rm -rf /mnt/pocket-media/library/tv/OldShow/

# Verify space usage
du -sh /mnt/pocket-media/library/
df -h /mnt/pocket-media

Disconnect MEDIA-FAMILY from Netgrimoire

# On Netgrimoire

# Unmount datasets
sudo zfs unmount pocket-media

# Export pool
sudo zpool export pocket-media

# Verify exported
zpool list | grep pocket-media
# Should show nothing

# Physical: Disconnect MEDIA-FAMILY SSD

Connect MEDIA-FAMILY to Pocket Grimoire

# Physical: Connect to Raspberry Pi USB 3.0 port (blue port)
# Note: Only connect ONE media drive at a time (GREEN or MEDIA-FAMILY)

# On Pocket Grimoire (SSH)
ssh user@pocket-grimoire.local

# Import pool and rename
sudo zpool import pocket-media mediapg

# Set mount point
sudo zfs set mountpoint=/srv/mediapg mediapg

# Mount datasets (no encryption key needed)
sudo zfs mount -a

# Verify mounted
df -h | grep mediapg
zfs list | grep mediapg

# Start Jellyfin
cd /srv/pocket-grimoire/stacks/jellyfin
docker compose up -d

# Verify
docker ps | grep jellyfin

Test Jellyfin:

# From browser
http://pocket-grimoire.local:8096
# Should show family-friendly media

Disconnect MEDIA-FAMILY from Pocket Grimoire

# On Pocket Grimoire (SSH)
ssh user@pocket-grimoire.local

# Stop Jellyfin
cd /srv/pocket-grimoire/stacks/jellyfin
docker compose down

# Unmount datasets
sudo zfs unmount mediapg

# Export pool
sudo zpool export mediapg

# Verify exported
zpool list | grep mediapg
# Should show nothing

# Physical: Disconnect MEDIA-FAMILY SSD

Swapping Media Drives (GREEN ↔ MEDIA-FAMILY)

To swap from GREEN to MEDIA-FAMILY on Pocket Grimoire:

# On Pocket Grimoire

# 1. Disconnect GREEN (see above)
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose down
cd /srv/pocket-grimoire/stacks/stash && docker compose down
sudo zfs unmount greenpg/Pocket
sudo zfs unmount greenpg
sudo zpool export greenpg
# Physically disconnect GREEN SSD

# 2. Connect MEDIA-FAMILY (see above)
# Physically connect MEDIA-FAMILY SSD
sudo zpool import pocket-media mediapg
sudo zfs set mountpoint=/srv/mediapg mediapg
sudo zfs mount -a
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose up -d

# Note: Stash won't work with MEDIA-FAMILY (no Stash data on that drive)
# Only Jellyfin uses MEDIA-FAMILY

To swap from MEDIA-FAMILY to GREEN:

# On Pocket Grimoire

# 1. Disconnect MEDIA-FAMILY
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose down
sudo zfs unmount mediapg
sudo zpool export mediapg
# Physically disconnect MEDIA-FAMILY SSD

# 2. Connect GREEN
# Physically connect GREEN SSD
sudo zpool import greenpg
sudo zfs load-key greenpg
sudo zfs mount -a
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose up -d
cd /srv/pocket-grimoire/stacks/stash && docker compose up -d

# Both Jellyfin and Stash work with GREEN

Quick Reference: Drive Movement Commands

VAULT:

# To Netgrimoire:
sudo zpool import pocket-vault
sudo zfs load-key pocket-vault
sudo zfs mount -a

# From Netgrimoire:
sudo zfs unmount pocket-vault
sudo zpool export pocket-vault

# To Pocket:
sudo zpool import pocket-vault vaultpg
sudo zfs load-key vaultpg
sudo zfs mount -a

GREEN:

# To Netgrimoire:
sudo zpool import greenpg
sudo zfs load-key greenpg
sudo zfs mount -a
sudo syncoid vault/Green/Pocket greenpg/Pocket  # Update

# From Netgrimoire:
sudo zfs unmount greenpg/Pocket && sudo zfs unmount greenpg
sudo zpool export greenpg

# To Pocket:
/usr/local/sbin/unlock-pocket-grimoire.sh  # Easy way
# Or manual: import → load-key → mount → start containers

# From Pocket:
docker compose down  # Stop jellyfin & stash
sudo zfs unmount greenpg/Pocket && sudo zfs unmount greenpg
sudo zpool export greenpg

MEDIA-FAMILY:

# To Netgrimoire:
sudo zpool import pocket-media
sudo zfs mount -a
# Add/remove content

# From Netgrimoire:
sudo zfs unmount pocket-media
sudo zpool export pocket-media

# To Pocket:
sudo zpool import pocket-media mediapg
sudo zfs set mountpoint=/srv/mediapg mediapg
sudo zfs mount -a
docker compose up -d  # Start jellyfin

# From Pocket:
docker compose down  # Stop jellyfin
sudo zfs unmount mediapg
sudo zpool export mediapg

Typical Use Cases

Before Personal Trip:

  1. Connect GREEN to Netgrimoire
  2. Update: sudo syncoid vault/Green/Pocket greenpg/Pocket
  3. Disconnect from Netgrimoire
  4. Connect GREEN to Pocket Grimoire
  5. Test Jellyfin and Stash

Before Family Visit:

  1. Disconnect GREEN from Pocket (if connected)
  2. Connect MEDIA-FAMILY to Netgrimoire
  3. Add/update family content
  4. Disconnect from Netgrimoire
  5. Connect MEDIA-FAMILY to Pocket Grimoire
  6. Test Jellyfin

Weekly at Home (No Drive Movement):

  • Leave drives in Pocket Grimoire
  • Use network sync: /usr/local/sbin/pocketgrimoire-zfs-pull.sh
  • Automatic every 6 hours

Deployment Procedure

Hotel/Travel Location Setup:

Physical Setup (5 minutes)

  1. Unpack Pocket Grimoire enclosure
  2. Connect Beryl AX to hotel WiFi (configure via phone app or admin panel)
  3. Connect Pi to Beryl AX via Ethernet (CAT5 cable)
  4. Plug Anker Prime into wall outlet
  5. Connect all USB devices to Anker Prime:
    • VAULT SSD → Anker USB-A port #2
    • Media SSD (PERSONAL or FAMILY) → Pi USB 3.0 port
    • Beryl AX → Anker USB-C retractable port
    • Pi → Anker USB-A port #1
  6. Power on (wait 2-3 minutes for boot)

SSH Connection (1 minute)

# From laptop (connected to portapotty WiFi)
ssh user@pocket-grimoire.local

# If .local doesn't work, find Pi's IP:
# - Check Beryl AX admin: http://192.168.8.1
# - Look for "pocket-grimoire" in client list
# - SSH via IP: ssh user@192.168.8.50

ZFS Unlock (2-3 minutes)

# Run unlock script
/usr/local/sbin/unlock-pocket-grimoire.sh

# Script will prompt for passphrases:
# Enter passphrase for 'vaultpg': [type VAULT passphrase]
# Enter passphrase for 'mediapg': [type MEDIA-PERSONAL passphrase]
# (MEDIA-FAMILY is unencrypted, no passphrase needed)

# Script automatically:
# - Unlocks ZFS pools
# - Mounts all datasets
# - Starts Docker service
# - Starts all containers (Wiki.js, Jellyfin, Stash)
# - Displays service URLs

# Total unlock time: ~2-3 minutes

Verify Services (1 minute)

Verify Services (1 minute)

# Check Docker containers running
docker ps

# Should show:
# pocketgrimoire_wikijs
# pocketgrimoire_db
# pocketgrimoire_jellyfin
# pocketgrimoire_stash

# Check ZFS pools mounted
df -h | grep srv

# Should show:
# vaultpg mounted on /srv/vaultpg
# mediapg mounted on /srv/mediapg

Access Services (1 minute)

From laptop browser (connected to portapotty WiFi):

  • Wiki.js: http://pocket-grimoire.local:3000
  • Jellyfin: http://pocket-grimoire.local:8096
  • Stash: http://pocket-grimoire.local:9999
  • File Browser: http://pocket-grimoire.local:8080 (if enabled)

From Onn Streaming Boxes:

  • Configure Jellyfin app: Server http://pocket-grimoire.local:8096
  • Configure StashApp: Server http://pocket-grimoire.local:9999

Total setup time: ~10-12 minutes

If Unlock Script Fails

Manual unlock procedure:

# SSH into Pi
ssh user@pocket-grimoire.local

# Import pools
sudo zpool import vaultpg
sudo zpool import mediapg

# Load encryption keys
sudo zfs load-key vaultpg
sudo zfs load-key mediapg  # Only if MEDIA-PERSONAL (encrypted)

# Mount datasets
sudo zfs mount -a

# Verify
df -h | grep srv

# Start Docker
sudo systemctl start docker

# Start containers
cd /srv/pocket-grimoire/stacks/wikijs && docker compose up -d
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose up -d
cd /srv/pocket-grimoire/stacks/stash && docker compose up -d
# Check Docker containers
docker ps

# Should see:
# - pocketgrimoire_db (PostgreSQL)
# - pocketgrimoire_wikijs (Wiki.js)
# - pocketgrimoire_jellyfin (Jellyfin)
# - pocketgrimoire_filebrowser (File Browser, if enabled)

Connect Onn Boxes (5 minutes)

  1. Power on Onn streaming box
  2. Connect to hotel TV via HDMI
  3. Configure Onn to connect to Beryl AX WiFi network
  4. Install Jellyfin app on Onn (if not already installed)
  5. Open Jellyfin app
  6. Add server: http://pocket-grimoire.local:8096
  7. Login and browse library

Laptop Setup (2 minutes)

# Mount NFS share (optional, for Jellyfin client on laptop)
sudo mkdir -p /mnt/pocket-media
sudo mount -t nfs pocket-grimoire.local:/srv/mediapg /mnt/pocket-media

# Or configure in /etc/fstab for persistence:
pocket-grimoire.local:/srv/mediapg  /mnt/pocket-media  nfs  defaults,_netdev  0  0

Total setup time: ~15 minutes


Troubleshooting

Pi Won't Boot

  1. Check power LED on Pi (should be solid red)
  2. Check ACT LED (should blink green during boot)
  3. If no LEDs: Check USB-C cable and Anker USB-A port
  4. If ACT LED doesn't blink: MicroSD card issue
    • Use spare MicroSD card
    • Reflash OS with USB card reader

ZFS Pools Won't Mount

# Check pool status
sudo zpool status

# Import pool manually
sudo zpool import -a

# Load encryption keys
sudo zfs load-key vaultpg
sudo zfs load-key greenpg  # GREEN drive

# Mount all
sudo zfs mount -a

# If corruption detected
sudo zpool scrub vaultpg
sudo zpool scrub greenpg

Pool Name Confusion (pocket-green vs greenpg)

Problem: You're not sure if your GREEN pool is named pocket-green or greenpg

Solution:

# Check which name the pool has
zpool list | grep -E "pocket-green|greenpg"

# If on Netgrimoire (initial build): Usually "pocket-green"
# If on Pocket Grimoire: Always "greenpg" (renamed during import)
# If moved back to Netgrimoire: Keeps "greenpg" name from Pocket

# Import using the correct name
sudo zpool import pocket-green  # if shows pocket-green
# OR
sudo zpool import greenpg       # if shows greenpg

# For syncoid, use whichever name it has:
sudo syncoid vault/Green/Pocket pocket-green/Pocket  # if pocket-green
# OR
sudo syncoid vault/Green/Pocket greenpg/Pocket       # if greenpg

Why the name changes:

  • Created on Netgrimoire: pocket-green (temporary name for building)
  • Imported to Pocket: Renamed to greenpg (permanent name for travel)
  • Moved back to Netgrimoire: Keeps greenpg name (doesn't revert)

Best practice: After first import to Pocket, the pool is permanently greenpg

Kanguru UltraLock UAS Errors / Pool Suspended

Symptoms:

  • ZFS pool repeatedly suspending with error=5 (EIO)
  • dmesg showing uas_eh_abort_handler every ~30 seconds
  • Pool status shows SUSPENDED
  • Drive resets cycling: uas_eh_device_reset_handler start/success repeating
sd 0:0:0:0: [sda] tag#8 uas_eh_abort_handler 0 uas-tag 3 inflight: CMD IN
scsi host0: uas_eh_device_reset_handler start
scsi host0: uas_eh_device_reset_handler success
WARNING: Pool 'greenpg' has encountered an uncorrectable I/O failure and has been suspended.

Root Cause:

The Kanguru UltraLock (idVendor=1e1d, idProduct=2001) uses the UAS driver by default. The Raspberry Pi 4's xhci USB controller has a known incompatibility with UAS on certain drives. The fix is to force the drive to use the usb-storage driver instead via a kernel quirk parameter.

Fix (Ubuntu Pi — permanent):

# Edit the correct cmdline file (NOT /boot/firmware/cmdline.txt)
sudo nano /boot/firmware/current/cmdline.txt

Add usb-storage.quirks=1e1d:2001:u to the end of the existing single line:

console=serial0,115200 multipath=off dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 panic=10 rootwait fixrtc usb-storage.quirks=1e1d:2001:u
# Verify: should show ONE $ at end, no blank lines
cat -A /boot/firmware/current/cmdline.txt

# Reboot
sudo reboot

Verify fix after reboot:

sudo dmesg | grep -i "kanguru\|uas\|usb-storage" | head -10

Confirmed working output:

usb 2-2: UAS is ignored for this device, using usb-storage instead
usb-storage 2-2:1.0: USB Mass Storage device detected
usb-storage 2-2:1.0: Quirks match for vid 1e1d pid 2001: 800000
scsi host0: usb-storage 2-2:1.0

Recover suspended pool after applying fix:

sudo zpool clear greenpg
sudo zfs load-key greenpg/Pocket
sudo zfs mount -a

If pool has data errors from before the fix:

sudo zpool status -v greenpg
sudo zpool scrub greenpg
# If metadata errors remain and can't be repaired, destroy and resync from Netgrimoire

Why /boot/firmware/cmdline.txt doesn't work:

On Ubuntu Pi, /boot/firmware/config.txt only reads cmdline=cmdline.txt under the [tryboot] section. The active boot uses /boot/firmware/current/cmdline.txt instead. This differs from Raspberry Pi OS where /boot/firmware/cmdline.txt is the correct file.

Hardware reference:

  • Kanguru UltraLock USB ID: 1e1d:2001
  • Pi 4 USB controller: xhci_hcd (Broadcom BCM2711)
  • Issue: xhci + UAS incompatibility on large USB drives

Fix discovered and documented during greenpg pool troubleshooting, February 2026

Docker Containers Not Starting

# Check if ZFS pools are mounted first
zfs list

# Check Docker service
sudo systemctl status docker

# View container logs
docker logs pocketgrimoire_wikijs
docker logs pocketgrimoire_jellyfin

# Restart containers
cd /srv/pocket-grimoire/stacks/wikijs
docker compose restart

cd /srv/pocket-grimoire/stacks/jellyfin
docker compose restart

Jellyfin Shows Transcoding

This should never happen - all media must be direct play only

  1. During playback, click info icon

  2. If "Transcoding" appears:

    • Media is not H.264/AAC
    • Re-encode media before next trip
    • Do NOT allow transcoding on Pi (will overheat/crash)
  3. Verify media codec:

    ffprobe /srv/mediapg/library/movies/example.mkv
    
  4. If incorrect codec, re-encode:

    ffmpeg -i input.mkv -c:v libx264 -preset slow -crf 20 \
      -profile:v high -level 4.1 -pix_fmt yuv420p \
      -c:a aac -b:a 192k -c:s srt output.mkv
    

NFS Mount Fails on Laptop

# Check if NFS is running on Pi
ssh user@pocket-grimoire.local
sudo systemctl status nfs-server

# Check exports
sudo exportfs -v

# Try manual mount with verbose
sudo mount -v -t nfs pocket-grimoire.local:/srv/mediapg /mnt/pocket-media

# Check firewall (if enabled)
sudo ufw status

Wiki.js Not Loading

# Check container status
docker ps | grep wikijs

# Check logs
docker logs pocketgrimoire_wikijs
docker logs pocketgrimoire_db

# Restart Wiki.js stack
cd /srv/pocket-grimoire/stacks/wikijs
docker compose restart

# Check database
docker exec -it pocketgrimoire_db psql -U wikijs -d wikijs -c "\dt"

VeraCrypt Container Won't Mount

Check container exists:

ls -lh /srv/vaultpg/veracrypt-containers/
# Should show vault.vc file

Verify VeraCrypt is installed:

veracrypt --text --version
# Should show version number

Try mounting with verbose output:

sudo veracrypt --text --verbose \
  --mount /srv/vaultpg/veracrypt-containers/vault.vc \
  /mnt/veracrypt/vault1

Common issues:

  • Wrong password: Re-enter carefully (passwords are case-sensitive)
  • Container corrupted: Try mounting read-only:
    sudo veracrypt --text --mount --protect-hidden=no \
      /srv/vaultpg/veracrypt-containers/vault.vc \
      /mnt/veracrypt/vault1
    
  • Already mounted elsewhere: Unmount first:
    sudo veracrypt --text --dismount /mnt/veracrypt/vault1
    
  • FUSE not available:
    sudo apt install -y fuse libfuse2
    sudo modprobe fuse
    

Check what's mounted:

veracrypt --text --list
mount | grep veracrypt

Force unmount (if stuck):

sudo veracrypt --text --force --dismount /mnt/veracrypt/vault1
# Or:
sudo umount -f /mnt/veracrypt/vault1

Verify container integrity:

# Test mount without password (will fail but shows if container is valid)
sudo veracrypt --test /srv/vaultpg/veracrypt-containers/vault.vc

cd /srv/pocket-grimoire/stacks/wikijs docker compose restart

Check database

docker exec -it pocketgrimoire_db psql -U wikijs -d wikijs -c "\dt"


### Sync Failures
```bash
# Check sync log
tail -n 200 /var/log/pocketgrimoire-sync.log

# Check ntfy notifications (should have received failure alert)

# Manually run sync
sudo /usr/local/sbin/pocketgrimoire-sync.sh

# Check timer status
systemctl status pocketgrimoire-sync.timer
systemctl list-timers | grep pocketgrimoire

# Reset timer
sudo systemctl restart pocketgrimoire-sync.timer

Beryl AX Won't Connect to Hotel WiFi

  1. Access Beryl AX admin panel: http://192.168.8.1
  2. Navigate to: Internet → Repeater
  3. Scan for hotel WiFi networks
  4. Connect (may require captive portal login)
  5. If captive portal required:
    • Connect phone to Beryl AX WiFi
    • Open browser, complete hotel WiFi login
    • Beryl AX will inherit connection

Pi Overheating

Should not happen with media-only stack

# Check temperature
vcgencmd measure_temp

# Normal: <60°C idle, <70°C under load
# Warning: >70°C
# Critical: >80°C

# If overheating:
# 1. Ensure passive heatsink case is properly installed
# 2. Verify Pi is not in enclosed space (needs airflow)
# 3. Check if transcoding is occurring (should never happen)
# 4. Check for runaway processes
htop

Shutdown Procedure

Proper shutdown to protect encrypted ZFS pools (headless operation):

# SSH into Pi from laptop
ssh user@pocket-grimoire.local

# Stop Docker containers
cd /srv/pocket-grimoire/stacks/wikijs
docker compose down

cd /srv/pocket-grimoire/stacks/jellyfin
docker compose down

cd /srv/pocket-grimoire/stacks/stash
docker compose down

# Optional: Stop other containers
cd /srv/pocket-grimoire/stacks/filebrowser
docker compose down

# Unmount VeraCrypt containers (if using)
sudo veracrypt --text --dismount /mnt/veracrypt/vault1
# Or dismount all:
sudo veracrypt --text --dismount-all

# Verify unmounted
veracrypt --text --list
# Should show "No volumes mounted"

# Unmount and export ZFS pools
sudo zfs unmount -a

# Export VAULT (always present)
sudo zpool export vaultpg

# Export GREEN (if connected for personal trip)
if zpool list greenpg &>/dev/null; then
    sudo zpool export greenpg
fi

# Export MEDIA-FAMILY (if connected for family trip)
if zpool list mediapg &>/dev/null; then
    sudo zpool export mediapg
fi

# Verify pools exported
zpool list
# Should NOT show vaultpg, greenpg, or mediapg

# Shutdown Pi
sudo shutdown -h now

# Wait 30 seconds for complete shutdown
# Pi's green ACT LED will stop blinking
# Red power LED will turn off
# Safe to unplug power

Total shutdown time: ~2-3 minutes

Emergency Shutdown

If SSH is unavailable or Pi is unresponsive:

  1. Stop all network activity:

    • Unplug Ethernet cable from Pi
    • Wait 10 seconds
  2. Power off:

    • Unplug power from Anker Prime (pulls power from everything)
    • Wait 10 seconds
  3. Consequences:

    • ZFS pools may need recovery on next boot (usually auto-repairs)
    • VeraCrypt containers are generally safe with sudden unmount
    • Docker containers will need restart
    • No data loss expected (ZFS is resilient)

Note: ZFS and VeraCrypt are resilient to sudden power loss, but proper shutdown is always better for data integrity.

Shutdown Checklist

Before leaving hotel:

  • SSH into Pocket Grimoire
  • Stop all Docker containers
  • Unmount VeraCrypt (if using)
  • Export ZFS pools (vaultpg, mediapg)
  • Shutdown Pi (sudo shutdown -h now)
  • Wait for Pi LEDs to turn off (30 seconds)
  • Unplug power from Anker Prime
  • Disconnect and pack all equipment

Never skip the ZFS export step! Exporting pools ensures:

  • All data is flushed to disk
  • Filesystem is marked clean
  • Prevents corruption
  • Allows pools to be imported cleanly on next boot

Maintenance

Weekly (While at Home)

# Check ZFS pool health
sudo zpool status

# Check for errors
sudo zpool status -v | grep -i error

# Verify sync is working
tail -n 50 /var/log/pocketgrimoire-sync.log

# Check Docker disk usage
docker system df

Monthly

# Run ZFS scrub (verify data integrity)
sudo zpool scrub vaultpg
sudo zpool scrub mediapg

# Check scrub results (after completion, usually 1-2 hours)
sudo zpool status

# Update system packages
sudo apt update && sudo apt upgrade -y

# Update Docker images
cd /srv/pocket-grimoire/stacks/wikijs
docker compose pull
docker compose up -d

cd /srv/pocket-grimoire/stacks/jellyfin
docker compose pull
docker compose up -d

# Prune unused Docker images
docker system prune -a

Before Each Trip

  • Run pre-trip checklist (see section above)
  • Verify all media plays directly (no transcoding)
  • Test offline mode
  • Check battery/charge status of all devices
  • Update any documentation that changed

After Each Trip

# Check for any errors in logs
journalctl -p err -b
tail -n 500 /var/log/pocketgrimoire-sync.log

# Verify ZFS pool health
sudo zpool status

# Check SSD health
sudo smartctl -a /dev/sdX

# Review and clear old sync logs if needed
sudo truncate -s 0 /var/log/pocketgrimoire-sync.log

Service Access Summary

When connected to Pocket Grimoire network:

Wiki.js:         http://pocket-grimoire.local:3000
Jellyfin:        http://pocket-grimoire.local:8096
File Browser:    http://pocket-grimoire.local:8080
Dozzle:          http://pocket-grimoire.local:9999
SSH:             ssh user@pocket-grimoire.local
NFS Media:       nfs://pocket-grimoire.local/srv/mediapg
Router Admin:    http://192.168.8.1

Resource Profile

Idle (At Home, Syncing)

Wiki.js + PostgreSQL:   ~250MB RAM
Jellyfin (idle):        ~150MB RAM
ZFS ARC (capped):       ~512MB RAM
System overhead:        ~200MB RAM
─────────────────────────────────
Total:                  ~1.1GB / 8GB RAM
CPU: <5%
Temperature: Cool (<60°C)

Media Playback (Direct Play)

Jellyfin (serving):     ~200MB RAM
NFS:                    ~50MB RAM
No transcoding:         0 CPU spike
─────────────────────────────────
Total:                  ~1.4GB / 8GB RAM
CPU: <10%
Temperature: Cool (<65°C)

The Pi should remain cool and quiet during all operations.


Security Notes

Encryption

  • ZFS Encryption: Both SSDs use native ZFS encryption
    • Passphrases required on boot (manual unlock)
    • Family media SSD is unencrypted (for portability/sharing)
    • SSH keys are stored on encrypted Vault SSD
  • VeraCrypt Containers (Optional): Additional encryption layer
    • Encrypted file containers within ZFS-encrypted drives (nested encryption)
    • Separate passwords for different data sets
    • Portable containers can be moved to other systems
    • Cross-platform compatibility (Windows, Mac, Linux)

Network Security

  • All services bound to LAN only (not exposed to WAN)
  • Beryl AX handles firewall and VPN routing
  • No services accept connections from internet directly
  • WireGuard VPN to Netgrimoire when online

Physical Security

  • Pocket Grimoire is a physical device - keep secure
  • Encrypted SSDs protect data at rest
  • ZFS and/or VeraCrypt passphrases required on boot (prevents unauthorized access)
  • Keep all encryption passphrases separate from device
  • Consider: Write passphrases on paper, store in secure location

Backup Strategy

  • Pocket Grimoire is a mirror, not primary storage
  • All data originates from Netgrimoire (source of truth)
  • ZFS replication provides redundancy
  • VeraCrypt containers sync like any other file
  • Can rebuild Pocket Grimoire from Netgrimoire if needed

Encryption Best Practices

  • Use strong passphrases: 20+ characters, mix of types
  • Don't reuse passwords: ZFS ≠ VeraCrypt ≠ services
  • Document recovery: Write down passphrases (paper, not digital)
  • Test recovery: Verify you can unlock before traveling
  • Secure storage: Keep passphrase backup separate from device

Appendix A: System Specifications

Raspberry Pi 4 (8GB)

  • CPU: Broadcom BCM2711, Quad-core Cortex-A72 @ 1.5GHz
  • RAM: 8GB LPDDR4-3200
  • Storage: MicroSD (OS) + 2× USB 3.0 SSDs (data)
  • Network: Gigabit Ethernet + WiFi 5 (802.11ac)
  • Power: 5V/3A via USB-C (15W)

GL.iNet Beryl AX (GL-MT3000)

  • CPU: MediaTek MT7981B, Dual-core ARM Cortex-A53 @ 1.3GHz
  • RAM: 512MB DDR4
  • WiFi: WiFi 6 (802.11ax) dual-band
  • VPN: WireGuard, OpenVPN
  • Ports: 1× WAN, 1× LAN, 1× USB 3.0
  • Power: USB-C, 12W max

Anker Prime 200W (Model A2683)

  • Total Output: 200W
  • USB-C Ports: 4× (100W max each)
  • USB-A Ports: 2× (5V/3A, 15W max each)
  • AC Outlets: 2×
  • Surge Protection: Yes

Storage Configuration

  • SSD #1 (Vault): 1-2TB, encrypted ZFS
  • SSD #2 (Personal Media): 2TB+, encrypted ZFS
  • SSD #3 (Family Media): 2TB+, unencrypted ZFS
  • Total capacity: 5-6TB (2 active at a time)

Appendix B: Quick Reference Commands

System Status

# Check ZFS pools
sudo zpool status

# Check mounted filesystems
df -h

# Check memory usage
free -h

# Check temperature
vcgencmd measure_temp

# Check Docker containers
docker ps

# Check system load
htop

VeraCrypt Operations

# Mount VeraCrypt container
sudo veracrypt --text --mount \
  /srv/vaultpg/veracrypt-containers/vault.vc \
  /mnt/veracrypt/vault1

# Or use helper script
sudo /usr/local/sbin/mount-veracrypt-vault.sh

# List mounted volumes
veracrypt --text --list

# Check what's in mounted container
ls -lh /mnt/veracrypt/vault1

# Unmount specific volume
sudo veracrypt --text --dismount /mnt/veracrypt/vault1

# Unmount all VeraCrypt volumes
sudo veracrypt --text --dismount-all

# Force unmount (if stuck)
sudo veracrypt --text --force --dismount /mnt/veracrypt/vault1

# Check VeraCrypt version
veracrypt --text --version

sudo zpool status

Check mounted filesystems

df -h

Check memory usage

free -h

Check temperature

vcgencmd measure_temp

Check Docker containers

docker ps

Check system load

htop


### Service Management
```bash
# Restart Wiki.js
cd /srv/pocket-grimoire/stacks/wikijs && docker compose restart

# Restart Jellyfin
cd /srv/pocket-grimoire/stacks/jellyfin && docker compose restart

# View Wiki.js logs
docker logs -f pocketgrimoire_wikijs

# View Jellyfin logs
docker logs -f pocketgrimoire_jellyfin

# Restart NFS
sudo systemctl restart nfs-server

Sync Management

# Check sync timer status
systemctl status pocketgrimoire-sync.timer

# View recent sync logs
tail -n 200 /var/log/pocketgrimoire-sync.log

# Manually trigger sync
sudo systemctl start pocketgrimoire-sync.service

# Watch sync in real-time
tail -f /var/log/pocketgrimoire-sync.log

ZFS Operations

# List all pools and datasets
zfs list

# Check pool health
sudo zpool status

# Load encryption keys
sudo zfs load-key vaultpg
sudo zfs load-key mediapg

# Mount all datasets
sudo zfs mount -a

# Unmount all datasets
sudo zfs unmount -a

# Export pools (before shutdown)
sudo zpool export vaultpg
sudo zpool export mediapg

# Import pools
sudo zpool import vaultpg
sudo zpool import mediapg

# Start scrub (data verification)
sudo zpool scrub vaultpg

# Check scrub progress
sudo zpool status -v

Network Diagnostics

# Check network interfaces
ip addr

# Test connectivity to router
ping 192.168.8.1

# Test DNS resolution
nslookup google.com

# Check NFS exports
sudo exportfs -v

# Test NFS mount (from laptop)
sudo mount -t nfs pocket-grimoire.local:/srv/mediapg /mnt/test

Official Documentation

Netgrimoire Resources

  • Main documentation: (link to your Netgrimoire Wiki)
  • Forgejo instance: (link to your Forgejo)
  • ntfy instance: (link to your ntfy server)

Tools & Utilities


Version History

v1.0 - Initial Release

  • Basic media server + documentation setup
  • ZFS encrypted storage
  • Automatic sync with Netgrimoire
  • Gaming components removed for simplicity

Support & Feedback

For issues or improvements to this documentation:

  • Update this Wiki page directly
  • Or submit changes to Forgejo repository
  • Test all changes on non-production system first

This guide was created for Pocket Grimoire deployment and maintenance. Keep this documentation updated as the system evolves.