Skip to content

Instantly share code, notes, and snippets.

@stone
Created November 10, 2025 15:59
Show Gist options
  • Select an option

  • Save stone/e4d0611068c1a6adff2db6fa16743748 to your computer and use it in GitHub Desktop.

Select an option

Save stone/e4d0611068c1a6adff2db6fa16743748 to your computer and use it in GitHub Desktop.
Did you delete your proxmox /etc/pve/nodes directory? Script to recreate pmxcfs database from a file backup (like etckeeper/restic..)
#!/bin/bash
# 2025-11-09 - Initial version
# Restore pmxcfs Proxmox config.db
# (C) Frdrik Steen <fredrik@tty.se>
set -euo pipefail
# Configuration
DB="${DB:-config.db}"
# Place your node files in the BACKUP_DIR to restore
BACKUP_DIR="${1:?Usage: $0 <backup_dir> [node_name]}"
NODE="${2:-$(basename "$BACKUP_DIR")}"
WRITER=0
VERSION=1
[[ -d "$BACKUP_DIR" ]] || { echo "Error: Directory $BACKUP_DIR not found"; exit 1; }
echo "Restoring from: $BACKUP_DIR"
echo "Node name: $NODE"
echo "Database: $DB"
echo
# Get next available inode
get_next_inode() {
sqlite3 "$DB" "SELECT COALESCE(MAX(inode), 0) + 1 FROM tree;"
}
# Create directory entry in database
create_dir() {
local parent_inode="$1"
local basename="$2"
local inode
local now
now=$(date +%s)
inode=$(get_next_inode)
>&2 echo "Creating directory: $basename (inode=$inode, parent=$parent_inode)"
sqlite3 "$DB" <<EOF
INSERT INTO tree (inode, parent, version, writer, mtime, type, name, data)
VALUES ($inode, $parent_inode, $VERSION, $WRITER, $now, 4, '$basename', NULL);
EOF
echo "$inode"
}
# Insert file into database
insert_file() {
local inode
local now
local parent_inode="$1"
local basename="$2"
local file_path="$3"
if [[ ! -f "$file_path" ]]; then
echo "Warning: $file_path not found, skipping"
return
fi
inode=$(get_next_inode)
now=$(date +%s)
echo "Inserting file: $basename (inode=$inode, parent=$parent_inode)"
sqlite3 "$DB" <<EOF
INSERT INTO tree (inode, parent, version, writer, mtime, type, name, data)
VALUES ($inode, $parent_inode, $VERSION, $WRITER, $now, 8, '$basename', readfile('$file_path'));
EOF
}
# Recursively process directory
process_directory() {
local basename
local subdir_inode
local parent_inode="$1"
local dir_path="$2"
# Process files in current directory
while IFS= read -r -d '' file; do
basename=$(basename "$file")
insert_file "$parent_inode" "$basename" "$file"
done < <(find "$dir_path" -maxdepth 1 -type f -print0 | sort -z)
# Process subdirectories
while IFS= read -r -d '' subdir; do
basename=$(basename "$subdir")
subdir_inode=$(create_dir "$parent_inode" "$basename")
process_directory "$subdir_inode" "$subdir"
done < <(find "$dir_path" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
}
echo "Creating nodes/$NODE directory structure..."
# Create nodes/ directory if it doesn't exist
NODES_INODE=$(sqlite3 "$DB" "SELECT inode FROM tree WHERE parent=0 AND name='nodes' LIMIT 1;")
if [[ -z "$NODES_INODE" ]]; then
NODES_INODE=$(create_dir 0 "nodes")
fi
# Create node directory under nodes/
NODE_INODE=$(create_dir "$NODES_INODE" "$NODE")
echo ""
echo "Processing files and directories..."
# Process all content from backup directory
process_directory "$NODE_INODE" "$BACKUP_DIR"
echo ""
echo "Restore complete!"
echo ""
echo "Database verification:"
sqlite3 "$DB" "SELECT inode, parent, type,
CASE type WHEN 4 THEN 'DIR' WHEN 8 THEN 'FILE' ELSE type END as type_name,
name
FROM tree
WHERE inode >= $NODES_INODE
ORDER BY inode;"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment