Auto-discover active Pangolin directory from Docker inspect and backup Docker volumes
This commit is contained in:
1 parent
f74a3e09f5
commit
5c44884c5c
2 files changed
+70
-13
No files matched your search
+41
-7
@@ -87,17 +87,51 @@ async def run_backup_task(
|
|||||||
remote_commands = [
|
remote_commands = [
|
||||||
f'echo "=== STEP 1: Creating Hot-Snapshot on Remote VPS ({vps_host}) ==="',
|
f'echo "=== STEP 1: Creating Hot-Snapshot on Remote VPS ({vps_host}) ==="',
|
||||||
f'mkdir -p "{remote_stage}/pangolin"',
|
f'mkdir -p "{remote_stage}/pangolin"',
|
||||||
f'echo "--- Staging Pangolin files from {p_dir} ---"',
|
f'''
|
||||||
f'if [ -d "{p_dir}" ]; then rsync -av --exclude="*.db" --exclude="*.db-wal" --exclude="*.db-shm" "{p_dir}/" "{remote_stage}/pangolin/"; fi',
|
P_DIR="{p_dir}"
|
||||||
f'echo "--- Performing zero-lock hot-backup of SQLite databases on VPS ---"',
|
if [ ! -d "$P_DIR" ] || [ -z "$(ls -A "$P_DIR" 2>/dev/null)" ]; then
|
||||||
f"""
|
echo "Notice: Configured path $P_DIR is empty or does not exist."
|
||||||
for db in $(find "{p_dir}" -name "*.db" -o -name "*.sqlite" 2>/dev/null); do
|
echo "Searching VPS for Pangolin Docker compose stack..."
|
||||||
rel=$(realpath --relative-to="{p_dir}" "$db")
|
DETECTED=$(docker inspect --format '{{{{index .Config.Labels "com.docker.compose.project.working_dir"}}}}' $(docker ps -aq --filter "name=pangolin" 2>/dev/null) 2>/dev/null | head -n 1)
|
||||||
|
if [ -z "$DETECTED" ] || [ ! -d "$DETECTED" ]; then
|
||||||
|
for candidate in /root/pangolin /opt/pangolin /home/*/pangolin /var/lib/pangolin /srv/pangolin /etc/pangolin; do
|
||||||
|
if [ -d "$candidate" ] && [ -n "$(ls -A "$candidate" 2>/dev/null)" ]; then
|
||||||
|
DETECTED="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ -n "$DETECTED" ] && [ -d "$DETECTED" ] && [ -n "$(ls -A "$DETECTED" 2>/dev/null)" ]; then
|
||||||
|
echo "--> Auto-discovered active Pangolin directory at: $DETECTED"
|
||||||
|
P_DIR="$DETECTED"
|
||||||
|
else
|
||||||
|
echo "--> Warning: Could not locate alternate Pangolin directory. Staging from $P_DIR"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "--- Staging Pangolin files from $P_DIR ---"
|
||||||
|
if [ -d "$P_DIR" ]; then
|
||||||
|
rsync -av --exclude="*.db" --exclude="*.db-wal" --exclude="*.db-shm" "$P_DIR/" "{remote_stage}/pangolin/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "--- Performing zero-lock hot-backup of SQLite databases on VPS ---"
|
||||||
|
for db in $(find "$P_DIR" -name "*.db" -o -name "*.sqlite" 2>/dev/null); do
|
||||||
|
rel=$(realpath --relative-to="$P_DIR" "$db")
|
||||||
mkdir -p "{remote_stage}/pangolin/$(dirname "$rel")"
|
mkdir -p "{remote_stage}/pangolin/$(dirname "$rel")"
|
||||||
echo "Hot-backing up $rel..."
|
echo "Hot-backing up $rel..."
|
||||||
sqlite3 "$db" ".backup '{remote_stage}/pangolin/$rel'" 2>/dev/null || cp -v "$db" "{remote_stage}/pangolin/$rel"
|
sqlite3 "$db" ".backup '{remote_stage}/pangolin/$rel'" 2>/dev/null || cp -v "$db" "{remote_stage}/pangolin/$rel"
|
||||||
done
|
done
|
||||||
"""
|
|
||||||
|
# Backup any named Docker volumes attached to pangolin containers
|
||||||
|
for vol in $(docker inspect --format '{{{{range .Mounts}}}}{{{{if eq .Type "volume"}}}}{{{{.Source}}}}{{{{"\\n"}}}}{{{{end}}}}{{{{end}}}}' $(docker ps -aq --filter "name=pangolin" 2>/dev/null) 2>/dev/null); do
|
||||||
|
if [ -d "$vol" ] && [ -n "$(ls -A "$vol" 2>/dev/null)" ]; then
|
||||||
|
vname=$(basename "$vol")
|
||||||
|
echo "--- Staging Docker Volume: $vname from $vol ---"
|
||||||
|
mkdir -p "{remote_stage}/pangolin/volumes/$vname"
|
||||||
|
cp -a "$vol/." "{remote_stage}/pangolin/volumes/$vname/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'''
|
||||||
]
|
]
|
||||||
|
|
||||||
if scope in ["vps_config", "full"]:
|
if scope in ["vps_config", "full"]:
|
||||||
|
|||||||
+29
-6
@@ -31,11 +31,28 @@ async def get_pangolin_stack_status() -> Dict[str, Any]:
|
|||||||
|
|
||||||
# Query Remote VPS for files, DB, and Docker status
|
# Query Remote VPS for files, DB, and Docker status
|
||||||
check_script = f"""
|
check_script = f"""
|
||||||
[ -f "{p_dir}/docker-compose.yml" ] || [ -f "{p_dir}/compose.yaml" ] && echo "COMPOSE_EXISTS" || echo "COMPOSE_NO"
|
P_DIR="{p_dir}"
|
||||||
[ -f "{p_dir}/config/config.yml" ] || [ -f "{p_dir}/config.yml" ] && echo "CONFIG_EXISTS" || echo "CONFIG_NO"
|
if [ ! -d "$P_DIR" ] || [ -z "$(ls -A "$P_DIR" 2>/dev/null)" ]; then
|
||||||
|
DETECTED=$(docker inspect --format '{{{{index .Config.Labels \"com.docker.compose.project.working_dir\"}}}}' $(docker ps -aq --filter "name=pangolin" 2>/dev/null) 2>/dev/null | head -n 1)
|
||||||
|
if [ -z "$DETECTED" ] || [ ! -d "$DETECTED" ]; then
|
||||||
|
for candidate in /root/pangolin /opt/pangolin /home/*/pangolin /var/lib/pangolin /srv/pangolin /etc/pangolin; do
|
||||||
|
if [ -d "$candidate" ] && [ -n "$(ls -A "$candidate" 2>/dev/null)" ]; then
|
||||||
|
DETECTED="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ -n "$DETECTED" ] && [ -d "$DETECTED" ]; then
|
||||||
|
P_DIR="$DETECTED"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "RESOLVED_DIR:$P_DIR"
|
||||||
|
|
||||||
|
[ -f "$P_DIR/docker-compose.yml" ] || [ -f "$P_DIR/compose.yaml" ] && echo "COMPOSE_EXISTS" || echo "COMPOSE_NO"
|
||||||
|
[ -f "$P_DIR/config/config.yml" ] || [ -f "$P_DIR/config.yml" ] && echo "CONFIG_EXISTS" || echo "CONFIG_NO"
|
||||||
|
|
||||||
# Check DB
|
# Check DB
|
||||||
db=$(find "{p_dir}" -name "*.db" -o -name "*.sqlite" 2>/dev/null | head -n 1)
|
db=$(find "$P_DIR" -name "*.db" -o -name "*.sqlite" 2>/dev/null | head -n 1)
|
||||||
if [ -n "$db" ]; then
|
if [ -n "$db" ]; then
|
||||||
echo "DB_PATH:$db"
|
echo "DB_PATH:$db"
|
||||||
wc -c "$db" 2>/dev/null | awk '{{print $1}}'
|
wc -c "$db" 2>/dev/null | awk '{{print $1}}'
|
||||||
@@ -45,7 +62,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Docker containers
|
# Docker containers
|
||||||
docker ps -a --format '{{json .}}' 2>/dev/null || true
|
docker ps -a --format '{{{{json .}}}}' 2>/dev/null || true
|
||||||
"""
|
"""
|
||||||
ret, stdout, stderr = await run_remote_quick(check_script, settings)
|
ret, stdout, stderr = await run_remote_quick(check_script, settings)
|
||||||
|
|
||||||
@@ -54,7 +71,7 @@ docker ps -a --format '{{json .}}' 2>/dev/null || true
|
|||||||
"vps_configured": True,
|
"vps_configured": True,
|
||||||
"connected": False,
|
"connected": False,
|
||||||
"installed": False,
|
"installed": False,
|
||||||
"path": p_dir,
|
"path": resolved_path,
|
||||||
"compose_exists": False,
|
"compose_exists": False,
|
||||||
"config_exists": False,
|
"config_exists": False,
|
||||||
"database": {"found": False, "path": None, "size_bytes": 0},
|
"database": {"found": False, "path": None, "size_bytes": 0},
|
||||||
@@ -66,6 +83,12 @@ docker ps -a --format '{{json .}}' 2>/dev/null || true
|
|||||||
}
|
}
|
||||||
|
|
||||||
lines = stdout.splitlines()
|
lines = stdout.splitlines()
|
||||||
|
resolved_path = p_dir
|
||||||
|
for l in lines:
|
||||||
|
if l.startswith("RESOLVED_DIR:"):
|
||||||
|
resolved_path = l.split(":", 1)[1].strip()
|
||||||
|
break
|
||||||
|
|
||||||
compose_exists = any("COMPOSE_EXISTS" in l for l in lines)
|
compose_exists = any("COMPOSE_EXISTS" in l for l in lines)
|
||||||
config_exists = any("CONFIG_EXISTS" in l for l in lines)
|
config_exists = any("CONFIG_EXISTS" in l for l in lines)
|
||||||
|
|
||||||
@@ -107,7 +130,7 @@ docker ps -a --format '{{json .}}' 2>/dev/null || true
|
|||||||
"vps_configured": True,
|
"vps_configured": True,
|
||||||
"connected": True,
|
"connected": True,
|
||||||
"installed": compose_exists,
|
"installed": compose_exists,
|
||||||
"path": p_dir,
|
"path": resolved_path,
|
||||||
"compose_exists": compose_exists,
|
"compose_exists": compose_exists,
|
||||||
"config_exists": config_exists,
|
"config_exists": config_exists,
|
||||||
"database": {
|
"database": {
|
||||||
|
|||||||
Reference in new issue
Block a user