Skip to content

Instantly share code, notes, and snippets.

@cs224
Created January 14, 2026 15:14
Show Gist options
  • Select an option

  • Save cs224/3af61447aa358fceb77985c01e87a4e9 to your computer and use it in GitHub Desktop.

Select an option

Save cs224/3af61447aa358fceb77985c01e87a4e9 to your computer and use it in GitHub Desktop.
Nym Mixnet & dVPN: A Node Operator's Guide (2026) - Backup
#!/usr/bin/env bash
set -euo pipefail
NODE_ID="${NODE_ID:-default-nym-node}"
SRC_BASE="/root/.nym"
SRC_NODE="${SRC_BASE}/nym-nodes/${NODE_ID}"
BASE="/root/nym-backup"
REPO="${BASE}/dotnym-repo"
if [ ! -d "$SRC_NODE" ]; then
echo "ERROR: Source node directory not found: $SRC_NODE" >&2
exit 1
fi
if [ ! -d "${REPO}/.git" ]; then
echo "ERROR: Backup repo not initialized at ${REPO}. Run backup-init.sh first." >&2
exit 1
fi
mkdir -p "${REPO}/nym-nodes/${NODE_ID}"
start_node() {
systemctl start nym-node || true
}
trap start_node EXIT
systemctl stop nym-node
# Make sure SQLite DBs are self-contained (no WAL dependency)
shopt -s nullglob
for db in "${SRC_NODE}/data/"*.sqlite; do
sqlite3 "$db" 'PRAGMA wal_checkpoint(TRUNCATE);' >/dev/null 2>&1 || true
done
shopt -u nullglob
# Update target marker (optional but useful)
mkdir -p "${REPO}/meta"
{
echo "hostname: $(hostname -f 2>/dev/null || hostname)"
echo "node_id: ${NODE_ID}"
echo "snapshot_utc: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
} > "${REPO}/meta/current_target.txt"
rsync -a --delete \
--exclude 'data/replay-detection/' \
--exclude 'data/*.sqlite-wal' \
--exclude 'data/*.sqlite-shm' \
--exclude 'data/*~' \
--exclude 'data/*.bak*' \
"${SRC_NODE}/" "${REPO}/nym-nodes/${NODE_ID}/"
cd "$REPO"
git add -A
if git diff --cached --quiet; then
echo "No changes detected; no commit created."
else
TS="$(date -u +%Y-%m-%d-%H%M%S)"
git -c user.name="Anonymous" -c user.email="anon@example.com" commit -m "Snapshot ${TS}"
git gc --auto || true
echo "Committed snapshot ${TS}"
fi
#!/usr/bin/env bash
set -euo pipefail
BASE="/root/nym-backup"
REPO="${BASE}/dotnym-repo"
BUNDLE_DIR="${BASE}/dotnym-bundles"
if [ ! -d "${REPO}/.git" ]; then
echo "ERROR: Backup repo not initialized at ${REPO}. Run backup-init.sh first." >&2
exit 1
fi
mkdir -p "$BUNDLE_DIR"
chmod 700 "$BUNDLE_DIR"
cd "$REPO"
LAST_TS="$(git log -1 --date=format:'%Y-%m-%d-%H%M%S' --format='%cd')"
BUNDLE="${BUNDLE_DIR}/dotnym.${LAST_TS}.bundle"
CHECKSUM="${BUNDLE}.sha256"
# If bundle exists and verifies, treat as no-op.
if [ -f "$BUNDLE" ]; then
if git bundle verify "$BUNDLE" >/dev/null 2>&1; then
echo "Bundle already exists and verifies: $BUNDLE"
exit 0
else
echo "Existing bundle failed verification; recreating: $BUNDLE" >&2
rm -f "$BUNDLE" "$CHECKSUM"
fi
fi
git bundle create "$BUNDLE" --all
git bundle verify "$BUNDLE"
sha256sum "$BUNDLE" > "$CHECKSUM"
chmod 600 "$BUNDLE" "$CHECKSUM"
echo "Created and verified bundle: $BUNDLE"
#!/usr/bin/env bash
set -euo pipefail
BASE="/root/nym-backup"
REPO="${BASE}/dotnym-repo"
BUNDLE_DIR="${BASE}/dotnym-bundles"
mkdir -p "$REPO" "$BUNDLE_DIR"
chmod 700 "$BASE" "$REPO" "$BUNDLE_DIR"
cd "$REPO"
if [ ! -d .git ]; then
git init -b main
fi
cat > "${REPO}/.gitignore" <<'EOF'
# Exclude large runtime anti-replay state
nym-nodes/*/data/replay-detection/
# Exclude SQLite runtime sidecars (we snapshot DB files; WAL/SHM are noise)
nym-nodes/*/data/*.sqlite-wal
nym-nodes/*/data/*.sqlite-shm
# Editor/backup noise
**/*~
**/*.bak*
EOF
# Optional: store a per-snapshot "target marker" for audit/history
mkdir -p "${REPO}/meta"
if [ ! -f "${REPO}/meta/current_target.txt" ]; then
{
echo "hostname: $(hostname -f 2>/dev/null || hostname)"
echo "created_utc: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
} > "${REPO}/meta/current_target.txt"
fi
git add .gitignore meta/current_target.txt || true
# Only commit if this is a new repo or files changed
if ! git rev-parse --verify HEAD >/dev/null 2>&1; then
git -c user.name="Anonymous" -c user.email="anon@example.com" commit -m "Initialize backup repository"
else
if ! git diff --cached --quiet; then
git -c user.name="Anonymous" -c user.email="anon@example.com" commit -m "Update backup repo metadata"
fi
fi
echo "Init complete:"
echo " Repo: $REPO"
echo " Bundles: $BUNDLE_DIR"
#!/usr/bin/env bash
set -euo pipefail
# Usage:
# backup-restore-bundle.sh /path/to/dotnym.YYYY-MM-DD-HHMMSS.bundle
#
# Optional environment variables:
# NODE_ID=default-nym-node
# SRC_BASE=/root/.nym
# BACKUP_BASE=/root/nym-backup
# START_SERVICE=true|false
if [ $# -lt 1 ]; then
echo "Usage: $0 /path/to/dotnym.<TIMESTAMP>.bundle" >&2
exit 2
fi
# Resolve bundle to absolute path so later chdir/-C does not break it
BUNDLE_PATH="$(readlink -f "$1" 2>/dev/null || true)"
NODE_ID="${NODE_ID:-default-nym-node}"
SRC_BASE="${SRC_BASE:-/root/.nym}"
BACKUP_BASE="${BACKUP_BASE:-/root/nym-backup}"
REPO="${BACKUP_BASE}/dotnym-repo"
START_SERVICE="${START_SERVICE:-true}"
TS="$(date -u +%Y-%m-%d-%H%M%S)"
OLD_RUNTIME="${SRC_BASE}.old.${TS}"
OLD_REPO="${REPO}.old.${TS}"
COMPLETE="false"
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "ERROR: Required command not found: $1" >&2
exit 1
}
}
rollback() {
if [ "$COMPLETE" = "true" ]; then
return 0
fi
echo "ERROR occurred; attempting rollback..." >&2
# Restore repo
if [ -d "$OLD_REPO" ]; then
rm -rf "$REPO" 2>/dev/null || true
mv "$OLD_REPO" "$REPO" 2>/dev/null || true
fi
# Restore runtime
if [ -d "$OLD_RUNTIME" ]; then
rm -rf "$SRC_BASE" 2>/dev/null || true
mv "$OLD_RUNTIME" "$SRC_BASE" 2>/dev/null || true
fi
if [ "$START_SERVICE" = "true" ]; then
systemctl start nym-node 2>/dev/null || true
fi
}
trap rollback ERR
if [ -z "$BUNDLE_PATH" ] || [ ! -f "$BUNDLE_PATH" ]; then
echo "ERROR: Bundle not found: $1" >&2
exit 1
fi
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: Must be run as root." >&2
exit 1
fi
require_cmd git
require_cmd rsync
mkdir -p "$BACKUP_BASE"
chmod 700 "$BACKUP_BASE"
echo "Restoring from bundle: $BUNDLE_PATH"
echo "Node ID: $NODE_ID"
echo "Runtime dir: $SRC_BASE"
echo "Backup repo target: $REPO"
echo "Timestamp: $TS"
echo
echo "[1/6] Stopping nym-node (best-effort)..."
systemctl stop nym-node 2>/dev/null || true
echo "[2/6] Backing up existing runtime dir (if any)..."
if [ -d "$SRC_BASE" ]; then
mv "$SRC_BASE" "$OLD_RUNTIME"
else
echo "No existing runtime dir."
fi
echo "[3/6] Backing up existing backup repo (if any)..."
if [ -d "$REPO" ]; then
mv "$REPO" "$OLD_REPO"
else
echo "No existing backup repo."
fi
echo "[4/6] Cloning bundle into backup repo..."
# Note: this is the "integrity check" now; if the bundle is invalid, clone will fail.
git clone "$BUNDLE_PATH" "$REPO"
SNAPSHOT_NODE="${REPO}/nym-nodes/${NODE_ID}"
if [ ! -d "$SNAPSHOT_NODE" ]; then
echo "ERROR: Snapshot directory not found in bundle repo: $SNAPSHOT_NODE" >&2
echo "Available node IDs in bundle repo:" >&2
ls -1 "${REPO}/nym-nodes" 2>/dev/null || true
exit 1
fi
echo "[5/6] Restoring node snapshot into runtime..."
mkdir -p "${SRC_BASE}/nym-nodes/${NODE_ID}"
rsync -a --delete "${SNAPSHOT_NODE}/" "${SRC_BASE}/nym-nodes/${NODE_ID}/"
# Restore optional /root/.nym/backup directory if present in snapshot repo
if [ -d "${REPO}/backup" ]; then
mkdir -p "${SRC_BASE}/backup"
rsync -a --delete "${REPO}/backup/" "${SRC_BASE}/backup/"
fi
# Lock down perms (keys + mnemonic are sensitive)
chown -R root:root "$SRC_BASE"
chmod -R go-rwx "$SRC_BASE"
echo "[6/6] Starting nym-node (if enabled, best-effort)..."
if [ "$START_SERVICE" = "true" ]; then
systemctl start nym-node 2>/dev/null || true
systemctl status nym-node --no-pager 2>/dev/null || true
else
echo "START_SERVICE=false; leaving nym-node stopped."
fi
COMPLETE="true"
echo
echo "Restore complete."
echo "Backups kept at (if they existed):"
echo " $OLD_RUNTIME"
echo " $OLD_REPO"
alias git-ls='( cd /root/nym-backup/dotnym-repo && \
git log --pretty=format:%H | while read -r c; do \
date=$(git show --no-patch --format="%cd" --date=format:"%Y-%m-%d-%H%M" "$c"); \
subj=$(git show --no-patch --format="%s" "$c"); \
target=$(git show "$c:meta/current_target.txt" 2>/dev/null | tr "\n" " "); \
[ -z "$target" ] && target="N/A"; \
echo "$date $c $subj $target"; \
done )'
alias nym-ls='curl --silent "https://api.github.com/repos/nymtech/nym/releases" | jq -r "(\"prerelease,draft,updated_at,url\"), (.[] | . as \$release | .assets[] | select(.name == \"nym-node\") | [\$release.prerelease,\$release.draft,.updated_at,.browser_download_url] | @csv)"'
alias nym-dl='f(){ \
local tag="${1:-}"; \
if [ -z "$tag" ]; then \
echo "usage: nym-dl <nym-binaries-tag>" >&2; \
echo "example: nym-dl nym-binaries-v2025.21-mozzarella" >&2; \
return 2; \
fi; \
local dir="/root/nym-binaries"; \
local ver="$(printf "%s" "$tag" | sed -E "s/^nym-binaries-//")"; \
local url="https://github.com/nymtech/nym/releases/download/${tag}/nym-node"; \
local out="${dir}/nym-node-${ver}"; \
mkdir -p "$dir" || return 1; \
if ! curl --fail --location --show-error --silent "$url" -o "$out"; then \
echo "ERROR: download failed for tag: $tag" >&2; \
echo "URL: $url" >&2; \
rm -f "$out" 2>/dev/null || true; \
return 1; \
fi; \
chmod +x "$out" || return 1; \
echo "Downloaded: $out"; \
}; f'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment