hmm
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
sydney
|
sydney2
|
||||||
|
|||||||
@@ -71,4 +71,3 @@ rex doas mkdir -p /etc/doas.d
|
|||||||
|
|
||||||
rex doas rc-service sshd restart
|
rex doas rc-service sshd restart
|
||||||
rex doas rc-service nebula restart
|
rex doas rc-service nebula restart
|
||||||
rex doas reboot
|
|
||||||
|
|||||||
@@ -396,6 +396,15 @@ def server_audit():
|
|||||||
return {"raw_output": output}
|
return {"raw_output": output}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/server/stats", dependencies=[Depends(verify_api_key)])
|
||||||
|
def server_stats():
|
||||||
|
output = run_command([f"{BIN_PATH}/stats"])
|
||||||
|
try:
|
||||||
|
return json.loads(output)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {"raw_output": output}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/system/pull", dependencies=[Depends(verify_api_key)])
|
@app.post("/system/pull", dependencies=[Depends(verify_api_key)])
|
||||||
def pull_all_images():
|
def pull_all_images():
|
||||||
return {"message": run_command([f"{BIN_PATH}/pullAllContainers"])}
|
return {"message": run_command([f"{BIN_PATH}/pullAllContainers"])}
|
||||||
@@ -550,6 +559,11 @@ def backup_check(request: BackupCheckRequest):
|
|||||||
return {"message": run_command([f"{BIN_PATH}/checkBackup", request.uuid])}
|
return {"message": run_command([f"{BIN_PATH}/checkBackup", request.uuid])}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/backup/all", dependencies=[Depends(verify_api_key)])
|
||||||
|
def backup_all():
|
||||||
|
return {"message": run_command([f"{BIN_PATH}/backupAll"])}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/backup/borgpush", dependencies=[Depends(verify_api_key)])
|
@app.get("/backup/borgpush", dependencies=[Depends(verify_api_key)])
|
||||||
def backup_borgpush():
|
def backup_borgpush():
|
||||||
return {"message": run_command(["doas", "-u", "4server", f"{BIN_PATH}/borgpush"])}
|
return {"message": run_command(["doas", "-u", "4server", f"{BIN_PATH}/borgpush"])}
|
||||||
|
|||||||
@@ -132,16 +132,6 @@ def main():
|
|||||||
# Users
|
# Users
|
||||||
audit["passwd"] = read_file("/etc/passwd")
|
audit["passwd"] = read_file("/etc/passwd")
|
||||||
|
|
||||||
# Runtime info
|
|
||||||
audit["uptime"] = cmd(["uptime"])
|
|
||||||
audit["top"] = cmd(["top", "-b", "-n", "1"]).splitlines()
|
|
||||||
|
|
||||||
# cgroups (useful in Docker)
|
|
||||||
audit["cgroup"] = read_file("/proc/self/cgroup")
|
|
||||||
|
|
||||||
# Mounted filesystems
|
|
||||||
audit["mounts"] = cmd(["mount"]).splitlines()
|
|
||||||
|
|
||||||
print(json.dumps(audit, indent=2))
|
print(json.dumps(audit, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+46
-8
@@ -13,7 +13,7 @@ source /4server/sbin/helpers
|
|||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
log "UUID: $UUID"
|
log "UUID: $UUID"
|
||||||
log "Backup slots: $BACKUP_SLOTS"
|
log "Retention: daily 14d, weekly 12w"
|
||||||
log "DB host: $POSTGRES_HOST"
|
log "DB host: $POSTGRES_HOST"
|
||||||
|
|
||||||
# Build paths
|
# Build paths
|
||||||
@@ -48,13 +48,51 @@ doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
|
|||||||
doas ln -sf "../$FILENAME" "$BB_DIR/current.zip"
|
doas ln -sf "../$FILENAME" "$BB_DIR/current.zip"
|
||||||
log "bb/current.zip -> ../$FILENAME"
|
log "bb/current.zip -> ../$FILENAME"
|
||||||
|
|
||||||
# Remove old backups beyond the configured slot count
|
# --- Backup retention ---
|
||||||
# Process substitution (< <(...)) keeps the loop in the main shell so
|
# Policy:
|
||||||
# set -e / exit codes propagate correctly — unlike a pipe subshell.
|
# - Keep all backups from the last 14 days (daily tier)
|
||||||
while IFS= read -r old_file; do
|
# - For backups older than 14 days but within 3 months (12 weeks),
|
||||||
log "Deleting old backup: $old_file"
|
# keep one per ISO week (the newest in each week)
|
||||||
doas rm -f "$old_file"
|
# - Delete everything else
|
||||||
done < <(ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 3)))
|
BACKUP_DAYS_DAILY=14
|
||||||
|
BACKUP_WEEKS_WEEKLY=12 # ~3 months
|
||||||
|
|
||||||
|
# Map of kept files (absolute path -> 1)
|
||||||
|
declare -A kept_files
|
||||||
|
# Track which ISO weeks already have a kept weekly backup
|
||||||
|
declare -A weekly_kept
|
||||||
|
|
||||||
|
now_ts=$(date +%s)
|
||||||
|
|
||||||
|
while IFS= read -r file; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
filedate="${filename:0:8}" # YYYYMMDD from filename
|
||||||
|
# BusyBox date (Alpine) requires YYYY-MM-DD; compact YYYYMMDD is rejected
|
||||||
|
filedate_fmt="${filedate:0:4}-${filedate:4:2}-${filedate:6:2}"
|
||||||
|
file_ts=$(date -d "$filedate_fmt" +%s 2>/dev/null) || continue
|
||||||
|
age_days=$(( (now_ts - file_ts) / 86400 ))
|
||||||
|
|
||||||
|
if [ "$age_days" -le "$BACKUP_DAYS_DAILY" ]; then
|
||||||
|
# --- Daily tier: keep everything within the last 14 days ---
|
||||||
|
kept_files["$file"]=1
|
||||||
|
elif [ "$age_days" -le $((BACKUP_DAYS_DAILY + BACKUP_WEEKS_WEEKLY * 7)) ]; then
|
||||||
|
# --- Weekly tier: keep 1 per ISO week ---
|
||||||
|
week_key=$(date -d "$filedate_fmt" +%G%V) # ISO year + week number
|
||||||
|
if [ -z "${weekly_kept[$week_key]-}" ]; then
|
||||||
|
weekly_kept[$week_key]=1
|
||||||
|
kept_files["$file"]=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null)
|
||||||
|
|
||||||
|
# Delete any backup not in the keep map
|
||||||
|
for file in "$BACKUP_DIR"/[0-9]*.zip; do
|
||||||
|
[ -f "$file" ] || continue
|
||||||
|
if [ -z "${kept_files[$file]-}" ]; then
|
||||||
|
log "Deleting old backup: $file"
|
||||||
|
doas rm -f "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
log "Backup finished successfully ."
|
log "Backup finished successfully ."
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ if not directory.is_dir():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
latest_date = None
|
latest_date = None
|
||||||
|
latest_file = None
|
||||||
|
|
||||||
for zip_file in directory.rglob("*.zip"):
|
for zip_file in directory.rglob("*.zip"):
|
||||||
name = zip_file.name
|
name = zip_file.name
|
||||||
@@ -29,10 +30,15 @@ for zip_file in directory.rglob("*.zip"):
|
|||||||
|
|
||||||
if latest_date is None or date_part > latest_date:
|
if latest_date is None or date_part > latest_date:
|
||||||
latest_date = date_part
|
latest_date = date_part
|
||||||
|
latest_file = zip_file
|
||||||
|
|
||||||
if latest_date is None:
|
if latest_date is None:
|
||||||
print("NO BACKUPS")
|
print("NO BACKUPS")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if latest_file.stat().st_size < 1000:
|
||||||
|
print("01-01-2000")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Format as yyyy-mm-dd
|
# Format as yyyy-mm-dd
|
||||||
print(f"{latest_date[:4]}-{latest_date[4:6]}-{latest_date[6:8]}")
|
print(f"{latest_date[:4]}-{latest_date[4:6]}-{latest_date[6:8]}")
|
||||||
|
|||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Collect server runtime stats (uptime, top, cgroup, mounts) and return as JSON."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def cmd(args, splitlines=False):
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
args,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
out = (result.stdout + result.stderr).strip()
|
||||||
|
|
||||||
|
if splitlines:
|
||||||
|
return out.splitlines()
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"ERROR: {e}"
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(path):
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
return f.read().strip().splitlines()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
stats = {}
|
||||||
|
|
||||||
|
stats["uptime"] = cmd(["uptime"])
|
||||||
|
stats["top"] = cmd(["sh", "-c", "top -b -n 1 | head -n 3"], splitlines=True)
|
||||||
|
stats["cgroup"] = read_file("/proc/self/cgroup")
|
||||||
|
stats["mounts"] = cmd(["mount"], splitlines=True)
|
||||||
|
|
||||||
|
print(json.dumps(stats, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
Reference in New Issue
Block a user