Netgrimoire/False Grimoire/Netgrimoire/Backup/Wiki_Backup.md
2026-04-12 09:39:57 -05:00

16 KiB

title description published date tags editor dateCreated
Wikijs Backup Backup Wikijs true 2026-02-23T04:35:32.870Z markdown 2026-02-23T04:35:24.121Z

Wiki.js Backup & Recovery

Service: Wiki.js (Netgrimoire) Stack: Docker Compose — Wiki.js + PostgreSQL Backup Targets: PostgreSQL database dump, Git content repository, Docker Compose config Backup Destinations: Local vault path → Kopia → offsite vaults


Overview

Wiki.js data lives in two separate places that must be backed up independently:

PostgreSQL database — stores page metadata, navigation, user accounts, permissions, page history, assets, and all configuration. This is the critical component for a portable restore. Without it, a new instance has no knowledge of your wiki structure.

Git content repository — stores the actual page content in markdown files, synced from Forgejo. This is already mirrored on the VAULT SSD at /vault/repos/wiki/. It is inherently redundant as long as Forgejo is healthy, but is included in backups for completeness and offline portability.

Docker Compose config — the docker-compose.yml and .env files needed to recreate the stack.


What Gets Backed Up

Component Location Method Critical?
PostgreSQL database Docker volume pg_dump → SQL file Yes — primary restore target
Git content repo /vault/repos/wiki/ Already on VAULT SSD Yes — page content
Docker Compose files /opt/stacks/wikijs/ rsync copy Yes — stack config
Wiki.js data volume Docker volume Optional rsync No — DB + Git covers this

Backup Strategy

Tier 1 — Daily Dump to Vault Path

A script runs daily via systemd timer. It produces a portable pg_dump SQL file written to /vault/backups/wiki/. These local dumps are retained for 14 days.

Key choices:

  • --format=plain — plain SQL, portable to any PostgreSQL version and any host
  • --no-owner — strips role ownership, so the dump restores cleanly on a new instance with a different postgres user (critical for Pocket Grimoire restores)
  • --no-acl — strips GRANT/REVOKE statements for the same reason
  • No application downtime required — PostgreSQL handles consistent dumps natively

Tier 2 — Kopia Snapshot to Offsite Vaults

After the daily dump completes, Kopia snapshots the entire /vault/backups/wiki/ directory and replicates to your offsite vaults. Kopia deduplication means only changed blocks are transferred after the first run.


Setup

Step 0 — Confirm Kopia Repository Exists

If Kopia is not yet initialized on this host, initialize it first. If you already initialized Kopia for Mailcow or another service, skip this step — all services share the same Kopia repository.

# Check if repository already exists
kopia repository status

# If not initialized, create it against your vault path
kopia repository create filesystem --path=/vault/kopia

# Connect on subsequent logins if disconnected
kopia repository connect filesystem --path=/vault/kopia

Step 1 — Create Backup Directories

sudo mkdir -p /vault/backups/wiki
sudo chown $(whoami):$(whoami) /vault/backups/wiki

Step 2 — Create the Backup Script

sudo nano /usr/local/sbin/wikijs-backup.sh
#!/usr/bin/env bash
# wikijs-backup.sh — Daily Wiki.js backup: pg_dump + git repo + config
# Writes to /vault/backups/wiki/, then snapshots with Kopia

set -euo pipefail

# ── Configuration ─────────────────────────────────────────────────────────────
BACKUP_DIR="/vault/backups/wiki"
DATE=$(date +%Y%m%d_%H%M%S)
CONTAINER_DB="wikijs_db"            # Adjust to your actual container name
PG_USER="wikijs"
PG_DB="wikijs"
WIKI_STACK_DIR="/opt/stacks/wikijs" # Location of docker-compose.yml and .env
GIT_REPO_DIR="/vault/repos/wiki"    # Git content mirror (already on vault SSD)
RETAIN_DAYS=14                      # Local dump retention

LOG="/var/log/wikijs-backup.log"
touch "$LOG"

log() { echo "$(date -Is) $*" | tee -a "$LOG"; }

# ── Step 1: PostgreSQL dump ────────────────────────────────────────────────────
log "Starting Wiki.js PostgreSQL dump..."

docker exec "$CONTAINER_DB" pg_dump \
  -U "$PG_USER" \
  "$PG_DB" \
  --format=plain \
  --no-owner \
  --no-acl \
  > "${BACKUP_DIR}/wikijs-db-${DATE}.sql"

gzip "${BACKUP_DIR}/wikijs-db-${DATE}.sql"

log "PostgreSQL dump complete: wikijs-db-${DATE}.sql.gz"

# ── Step 2: Docker Compose config backup ──────────────────────────────────────
log "Backing up Docker Compose config..."

CONFIG_BACKUP="${BACKUP_DIR}/wikijs-config-${DATE}.tar.gz"

tar -czf "$CONFIG_BACKUP" \
  -C "$(dirname "$WIKI_STACK_DIR")" \
  "$(basename "$WIKI_STACK_DIR")"

log "Config backup complete: wikijs-config-${DATE}.tar.gz"

# ── Step 3: Git repo snapshot (content mirror) ────────────────────────────────
# The git repo lives on the VAULT SSD and is already versioned.
# We record the current HEAD commit for reference.

if [ -d "${GIT_REPO_DIR}/.git" ]; then
  GIT_HEAD=$(git -C "$GIT_REPO_DIR" rev-parse HEAD 2>/dev/null || echo "unknown")
  echo "Git HEAD at backup time: ${GIT_HEAD}" \
    > "${BACKUP_DIR}/wikijs-git-ref-${DATE}.txt"
  log "Git content repo HEAD: ${GIT_HEAD}"
else
  log "WARNING: Git repo not found at ${GIT_REPO_DIR} — skipping git ref"
fi

# ── Step 4: Cleanup old local dumps ───────────────────────────────────────────
log "Cleaning up dumps older than ${RETAIN_DAYS} days..."

find "$BACKUP_DIR" -name "wikijs-db-*.sql.gz" -mtime +"$RETAIN_DAYS" -delete
find "$BACKUP_DIR" -name "wikijs-config-*.tar.gz" -mtime +"$RETAIN_DAYS" -delete
find "$BACKUP_DIR" -name "wikijs-git-ref-*.txt" -mtime +"$RETAIN_DAYS" -delete

# ── Step 5: Kopia snapshot ────────────────────────────────────────────────────
log "Running Kopia snapshot of /vault/backups/wiki/..."

kopia snapshot create "$BACKUP_DIR" \
  --tags "service:wikijs,host:$(hostname -s)"

log "Kopia snapshot complete."

# ── Done ──────────────────────────────────────────────────────────────────────
log "Wiki.js backup finished successfully."
sudo chmod +x /usr/local/sbin/wikijs-backup.sh

Step 3 — Create systemd Service and Timer

sudo nano /etc/systemd/system/wikijs-backup.service
[Unit]
Description=Wiki.js daily backup (pg_dump + config + Kopia snapshot)
After=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/wikijs-backup.sh
sudo nano /etc/systemd/system/wikijs-backup.timer
[Unit]
Description=Run Wiki.js backup daily at 02:00

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable wikijs-backup.timer
sudo systemctl start wikijs-backup.timer

# Verify
systemctl list-timers | grep wikijs

Step 4 — Configure Kopia Retention Policy

# Set retention policy for wiki backups
kopia policy set /vault/backups/wiki \
  --keep-daily 14 \
  --keep-weekly 8 \
  --keep-monthly 12 \
  --compression zstd

# Verify policy
kopia policy show /vault/backups/wiki

Step 5 — Test the Backup

# Run manually first time
sudo /usr/local/sbin/wikijs-backup.sh

# Verify output
ls -lh /vault/backups/wiki/
# Should show: wikijs-db-YYYYMMDD_HHMMSS.sql.gz
#              wikijs-config-YYYYMMDD_HHMMSS.tar.gz
#              wikijs-git-ref-YYYYMMDD_HHMMSS.txt

# Verify Kopia snapshot was created
kopia snapshot list /vault/backups/wiki

# Check backup log
tail -n 30 /var/log/wikijs-backup.log

Verifying Backups

Check dump is readable

# Inspect the SQL dump without extracting
zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | head -50

# Should show PostgreSQL header, version info, and CREATE TABLE statements

Verify Kopia snapshots

# List recent snapshots
kopia snapshot list /vault/backups/wiki

# Show snapshot details
kopia snapshot list /vault/backups/wiki --all

# Verify snapshot integrity
kopia snapshot verify

Test restore to a temporary database (non-destructive)

# Start a temporary Postgres container
docker run --rm -d \
  --name wikijs-restore-test \
  -e POSTGRES_USER=wikijs \
  -e POSTGRES_PASSWORD=testpassword \
  -e POSTGRES_DB=wikijs_test \
  postgres:16-alpine

# Wait for Postgres to be ready
sleep 5

# Restore dump into test container
zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \
  docker exec -i wikijs-restore-test psql -U wikijs -d wikijs_test

# Verify tables exist
docker exec wikijs-restore-test psql -U wikijs -d wikijs_test -c "\dt"

# Expected output: List of tables (pages, users, pageHistory, assets, etc.)

# Cleanup test container
docker stop wikijs-restore-test

Recovery Procedures

Scenario A — Restore to a New Wiki.js Instance (Any Host)

This covers full disaster recovery to a fresh server, including Pocket Grimoire.

Requirements on the destination host:

  • Docker and Docker Compose installed
  • A docker-compose.yml and .env ready (from backup or Pocket Grimoire stack)
  • Sufficient disk space

Step 1: Locate the backup

# On Netgrimoire, find the dump to restore
ls -lh /vault/backups/wiki/

# Or restore from Kopia
kopia snapshot list /vault/backups/wiki
kopia restore SNAPSHOT_ID /tmp/wiki-restore/
ls /tmp/wiki-restore/

Step 2: Copy dump to the destination host

# From Netgrimoire, copy to the destination server
scp /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz \
  user@destination-host:/tmp/

# Or to Pocket Grimoire
scp /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz \
  user@pocket-grimoire.local:/tmp/

Step 3: Start the database container only

On the destination host, start just the database — do not start Wiki.js yet:

cd /srv/pocket-grimoire/stacks/wikijs   # Adjust path as needed

# Start only the database container
docker compose up -d db

# Wait for healthy status
docker compose ps
# db should show: healthy

Step 4: Restore the dump

# Restore the dump into the running database container
zcat /tmp/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \
  docker exec -i pocketgrimoire_db psql \
  -U wikijs \
  -d wikijs

# Verify tables restored
docker exec pocketgrimoire_db psql -U wikijs -d wikijs -c "\dt"

Step 5: Start Wiki.js

docker compose up -d

# Watch startup logs
docker logs -f pocketgrimoire_wikijs
# Wait for: "HTTP Server started successfully"

Step 6: Verify

Open http://pocket-grimoire.local:3000 and confirm:

  • Pages load correctly
  • Navigation structure is intact
  • User accounts are present (if you had multiple users)

Step 7: Re-sync Git content (if needed)

The database knows the page structure, but if the Git content repo isn't present on the new host, import it:

# In Wiki.js admin panel:
# Administration → Storage → Git
# Click "Force Sync" or "Import Content"

# Or copy the repo from VAULT SSD
rsync -avP /vault/repos/wiki/ /srv/pocket-grimoire/repos/wiki/

Scenario B — Restore on Existing Netgrimoire Instance

Use this when the Wiki.js database is corrupted but the host is otherwise healthy.

Step 1: Stop Wiki.js (leave database running)

cd /opt/stacks/wikijs
docker compose stop wikijs

Step 2: Drop and recreate the database

docker exec -it wikijs_db psql -U postgres -c "DROP DATABASE wikijs;"
docker exec -it wikijs_db psql -U postgres -c "CREATE DATABASE wikijs OWNER wikijs;"

Step 3: Restore

zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \
  docker exec -i wikijs_db psql -U wikijs -d wikijs

Step 4: Restart Wiki.js

docker compose start wikijs
docker logs -f wikijs

Scenario C — Restore Config Only

If the stack config was lost but the database volume is intact:

# Extract config from backup
tar -xzf /vault/backups/wiki/wikijs-config-YYYYMMDD_HHMMSS.tar.gz \
  -C /opt/stacks/

# Verify
ls /opt/stacks/wikijs/
# Should show: docker-compose.yml  .env

# Restart stack
cd /opt/stacks/wikijs
docker compose up -d

Restore from Kopia (Offsite)

When local vault files are unavailable, restore the backup directory from Kopia first:

# List available snapshots
kopia snapshot list /vault/backups/wiki

# Restore snapshot to temp directory
kopia restore SNAPSHOT_ID /tmp/wiki-restore/

# Then proceed with the appropriate scenario above
# using files from /tmp/wiki-restore/ instead of /vault/backups/wiki/

Pocket Grimoire Specifics

When restoring to Pocket Grimoire, note the following differences from a full Netgrimoire instance:

Container names differ — use pocketgrimoire_db instead of wikijs_db.

Stack path is /srv/pocket-grimoire/stacks/wikijs/ instead of /opt/stacks/wikijs/.

The database is already initialized when Pocket Grimoire is first set up. Restoring a Netgrimoire dump overwrites it entirely, which is the intended behavior — Pocket Grimoire becomes a mirror of Netgrimoire's wiki state.

Git content repo is located at /srv/pocket-grimoire/repos/wiki/ and is populated via the sync script (pocketgrimoire-sync.sh). A database restore alone is sufficient if the Git repo is already in place.

Recommended restore workflow for Pocket Grimoire:

# 1. Copy dump from VAULT SSD (already available on Pocket Grimoire)
ls /srv/vaultpg/backups/wiki/

# 2. Start db container only
cd /srv/pocket-grimoire/stacks/wikijs && docker compose up -d db

# 3. Restore
zcat /srv/vaultpg/backups/wiki/wikijs-db-LATEST.sql.gz | \
  docker exec -i pocketgrimoire_db psql -U wikijs -d wikijs

# 4. Start full stack
docker compose up -d

Because the VAULT SSD is always connected to Pocket Grimoire, no file transfer is needed — the dumps are already there.


Monitoring & Alerts

Add the following to your existing ntfy/monitoring setup to alert on backup failures. Wrap the backup script call in an error trap:

# Add to wikijs-backup.sh after set -euo pipefail:

NTFY_URL="https://ntfy.YOUR_DOMAIN/wikijs-backup"

on_error() {
  curl -fsS -X POST "$NTFY_URL" \
    -H "Title: Wiki.js backup FAILED ($(hostname -s))" \
    -H "Priority: high" \
    -H "Tags: rotating_light" \
    -d "Backup failed at $(date -Is). Check /var/log/wikijs-backup.log"
}
trap on_error ERR

Check backup age manually

# Find most recent dump
ls -lt /vault/backups/wiki/wikijs-db-*.sql.gz | head -3

# Check Kopia last snapshot time
kopia snapshot list /vault/backups/wiki | tail -5

Quick Reference

# Run backup manually
sudo /usr/local/sbin/wikijs-backup.sh

# Watch backup log
tail -f /var/log/wikijs-backup.log

# Check timer status
systemctl status wikijs-backup.timer

# List local dumps
ls -lh /vault/backups/wiki/

# List Kopia snapshots
kopia snapshot list /vault/backups/wiki

# Restore dump (generic)
zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | \
  docker exec -i CONTAINER_NAME psql -U wikijs -d wikijs

# Test dump is readable
zcat /vault/backups/wiki/wikijs-db-YYYYMMDD_HHMMSS.sql.gz | head -50

Revision History

Version Date Notes
1.0 2026-02-22 Initial release — pg_dump + Kopia + Pocket Grimoire restore procedures