3703 lines
91 KiB
Markdown
3703 lines
91 KiB
Markdown
---
|
||
title: Pocket Grimoire
|
||
description:
|
||
published: true
|
||
date: 2026-02-26T12:42:50.676Z
|
||
tags:
|
||
editor: markdown
|
||
dateCreated: 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:**
|
||
|
||
4. **File Browser** - Read-only web UI
|
||
- Quick LAN access to vault/media without SSH
|
||
- Port: 8080
|
||
|
||
5. **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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
sudo nano /etc/systemd/system/veracrypt-vault.service
|
||
```
|
||
|
||
```ini
|
||
[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:**
|
||
|
||
```bash
|
||
sudo nano /usr/local/sbin/mount-veracrypt-vault.sh
|
||
```
|
||
|
||
```bash
|
||
#!/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
|
||
```
|
||
|
||
```bash
|
||
sudo chmod +x /usr/local/sbin/mount-veracrypt-vault.sh
|
||
```
|
||
|
||
**Usage:**
|
||
```bash
|
||
# 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):**
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# On Netgrimoire
|
||
lsblk
|
||
# Note device names: /dev/sdX, /dev/sdY, /dev/sdZ
|
||
```
|
||
|
||
**Create VAULT pool (encrypted - backups only):**
|
||
```bash
|
||
# 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.
|
||
|
||
```bash
|
||
# 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):**
|
||
```bash
|
||
# 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):**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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)**
|
||
|
||
```bash
|
||
# 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)**
|
||
|
||
```bash
|
||
# 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):**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
sudo nano /usr/local/sbin/unlock-pocket-grimoire.sh
|
||
```
|
||
|
||
```bash
|
||
#!/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
|
||
```
|
||
|
||
```bash
|
||
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:**
|
||
|
||
```bash
|
||
# 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):**
|
||
|
||
```bash
|
||
sudo mkdir -p /etc/systemd/system/docker.service.d
|
||
sudo nano /etc/systemd/system/docker.service.d/wait-for-zfs.conf
|
||
```
|
||
|
||
```ini
|
||
[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
|
||
```
|
||
|
||
```bash
|
||
sudo systemctl daemon-reload
|
||
```
|
||
|
||
**Recommended: Just disable auto-start and use unlock script.**
|
||
|
||
### 8. Test Headless Unlock Procedure
|
||
|
||
**Test at home before traveling:**
|
||
|
||
```bash
|
||
# 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)
|
||
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# Edit /etc/systemd/system/zfs-load-key.service
|
||
sudo nano /etc/systemd/system/zfs-load-key.service
|
||
```
|
||
|
||
Add:
|
||
```ini
|
||
[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:
|
||
```bash
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable zfs-load-key.service
|
||
```
|
||
|
||
### 4. Install Docker
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:
|
||
```bash
|
||
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.
|
||
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
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:**
|
||
```bash
|
||
nano /srv/pocket-grimoire/stacks/wikijs/.env
|
||
```
|
||
|
||
```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:**
|
||
```bash
|
||
nano /srv/pocket-grimoire/stacks/wikijs/docker-compose.yml
|
||
```
|
||
|
||
```yaml
|
||
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:**
|
||
```bash
|
||
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:**
|
||
```bash
|
||
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:**
|
||
```bash
|
||
nano /srv/pocket-grimoire/stacks/jellyfin/.env
|
||
```
|
||
|
||
```env
|
||
TZ=America/Chicago
|
||
PUID=1000
|
||
PGID=1000
|
||
JELLYFIN_PORT=8096
|
||
COMPOSE_PROJECT_NAME=pocketgrimoire_jellyfin
|
||
```
|
||
|
||
**Create Docker Compose file:**
|
||
```bash
|
||
nano /srv/pocket-grimoire/stacks/jellyfin/docker-compose.yml
|
||
```
|
||
|
||
```yaml
|
||
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:**
|
||
```bash
|
||
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:**
|
||
```bash
|
||
mkdir -p /srv/pocket-grimoire/stacks/filebrowser
|
||
mkdir -p /srv/pocket-grimoire/data/filebrowser
|
||
```
|
||
|
||
**Create Docker Compose file:**
|
||
```bash
|
||
nano /srv/pocket-grimoire/stacks/filebrowser/docker-compose.yml
|
||
```
|
||
|
||
```yaml
|
||
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:**
|
||
```bash
|
||
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:**
|
||
```bash
|
||
mkdir -p /srv/pocket-grimoire/stacks/dozzle
|
||
nano /srv/pocket-grimoire/stacks/dozzle/docker-compose.yml
|
||
```
|
||
|
||
```yaml
|
||
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:**
|
||
```bash
|
||
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:**
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
sudo nano /etc/pocketgrimoire-sync.env
|
||
```
|
||
|
||
```bash
|
||
NTFY_URL="https://ntfy.YOUR_DOMAIN/pocket-grimoire"
|
||
NTFY_TOKEN="YOUR_NTFY_TOKEN_HERE" # Optional
|
||
HOSTNAME_TAG="$(hostname -s)"
|
||
```
|
||
|
||
```bash
|
||
sudo chmod 600 /etc/pocketgrimoire-sync.env
|
||
```
|
||
|
||
### Create Main Sync Script
|
||
|
||
```bash
|
||
sudo nano /usr/local/sbin/pocketgrimoire-sync.sh
|
||
```
|
||
|
||
```bash
|
||
#!/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
|
||
```
|
||
|
||
```bash
|
||
sudo chmod +x /usr/local/sbin/pocketgrimoire-sync.sh
|
||
sudo mkdir -p /var/lib/pocketgrimoire
|
||
```
|
||
|
||
### Create ZFS Replication Script
|
||
|
||
```bash
|
||
sudo nano /usr/local/sbin/pocketgrimoire-zfs-pull.sh
|
||
```
|
||
|
||
```bash
|
||
#!/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}"
|
||
```
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
sudo nano /etc/systemd/system/pocketgrimoire-sync.service
|
||
```
|
||
|
||
```ini
|
||
[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
|
||
|
||
```bash
|
||
sudo nano /etc/systemd/system/pocketgrimoire-sync.timer
|
||
```
|
||
|
||
```ini
|
||
[Unit]
|
||
Description=Run Pocket Grimoire sync every 6 hours
|
||
|
||
[Timer]
|
||
OnBootSec=10min
|
||
OnUnitActiveSec=6h
|
||
Persistent=true
|
||
|
||
[Install]
|
||
WantedBy=timers.target
|
||
```
|
||
|
||
### Enable and Start Timer
|
||
|
||
```bash
|
||
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:**
|
||
```bash
|
||
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:**
|
||
```bash
|
||
#!/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:**
|
||
```bash
|
||
ffprobe input.mkv 2>&1 | grep -E "Video:|Audio:"
|
||
```
|
||
|
||
**Verify direct play compatibility:**
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# On Pocket Grimoire
|
||
# Create ZFS replication script
|
||
|
||
sudo nano /usr/local/sbin/pocketgrimoire-zfs-pull.sh
|
||
```
|
||
|
||
```bash
|
||
#!/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
|
||
```
|
||
|
||
```bash
|
||
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:**
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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):**
|
||
|
||
```bash
|
||
# 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):**
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# From browser (on portapotty WiFi)
|
||
http://pocket-grimoire.local:8096 # Jellyfin
|
||
http://pocket-grimoire.local:9999 # Stash
|
||
```
|
||
|
||
#### Disconnect GREEN from Pocket Grimoire
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# From browser
|
||
http://pocket-grimoire.local:8096
|
||
# Should show family-friendly media
|
||
```
|
||
|
||
#### Disconnect MEDIA-FAMILY from Pocket Grimoire
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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
|
||
```
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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):**
|
||
|
||
```bash
|
||
# 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
|
||
```
|
||
|
||
```bash
|
||
# Verify: should show ONE $ at end, no blank lines
|
||
cat -A /boot/firmware/current/cmdline.txt
|
||
|
||
# Reboot
|
||
sudo reboot
|
||
```
|
||
|
||
**Verify fix after reboot:**
|
||
|
||
```bash
|
||
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:**
|
||
|
||
```bash
|
||
sudo zpool clear greenpg
|
||
sudo zfs load-key greenpg/Pocket
|
||
sudo zfs mount -a
|
||
```
|
||
|
||
If pool has data errors from before the fix:
|
||
```bash
|
||
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
|
||
```bash
|
||
# 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:
|
||
```bash
|
||
ffprobe /srv/mediapg/library/movies/example.mkv
|
||
```
|
||
|
||
4. If incorrect codec, re-encode:
|
||
```bash
|
||
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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
ls -lh /srv/vaultpg/veracrypt-containers/
|
||
# Should show vault.vc file
|
||
```
|
||
|
||
**Verify VeraCrypt is installed:**
|
||
```bash
|
||
veracrypt --text --version
|
||
# Should show version number
|
||
```
|
||
|
||
**Try mounting with verbose output:**
|
||
```bash
|
||
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:
|
||
```bash
|
||
sudo veracrypt --text --mount --protect-hidden=no \
|
||
/srv/vaultpg/veracrypt-containers/vault.vc \
|
||
/mnt/veracrypt/vault1
|
||
```
|
||
- **Already mounted elsewhere:** Unmount first:
|
||
```bash
|
||
sudo veracrypt --text --dismount /mnt/veracrypt/vault1
|
||
```
|
||
- **FUSE not available:**
|
||
```bash
|
||
sudo apt install -y fuse libfuse2
|
||
sudo modprobe fuse
|
||
```
|
||
|
||
**Check what's mounted:**
|
||
```bash
|
||
veracrypt --text --list
|
||
mount | grep veracrypt
|
||
```
|
||
|
||
**Force unmount (if stuck):**
|
||
```bash
|
||
sudo veracrypt --text --force --dismount /mnt/veracrypt/vault1
|
||
# Or:
|
||
sudo umount -f /mnt/veracrypt/vault1
|
||
```
|
||
|
||
**Verify container integrity:**
|
||
```bash
|
||
# 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**
|
||
|
||
```bash
|
||
# 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):**
|
||
|
||
### From SSH (Recommended)
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```
|
||
|
||
---
|
||
|
||
## Appendix C: Useful Links
|
||
|
||
### Official Documentation
|
||
- Raspberry Pi OS: https://www.raspberrypi.com/documentation/
|
||
- OpenZFS: https://openzfs.github.io/openzfs-docs/
|
||
- Docker: https://docs.docker.com/
|
||
- Wiki.js: https://docs.requarks.io/
|
||
- Jellyfin: https://jellyfin.org/docs/
|
||
- GL.iNet: https://docs.gl-inet.com/
|
||
|
||
### Netgrimoire Resources
|
||
- Main documentation: (link to your Netgrimoire Wiki)
|
||
- Forgejo instance: (link to your Forgejo)
|
||
- ntfy instance: (link to your ntfy server)
|
||
|
||
### Tools & Utilities
|
||
- FFmpeg documentation: https://ffmpeg.org/documentation.html
|
||
- Syncoid (part of Sanoid): https://github.com/jimsalterjrs/sanoid
|
||
|
||
---
|
||
|
||
## 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.*
|