sync: auto-sync from HOWARD-HOME at 2026-07-05 15:29:59
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-07-05 15:29:59
This commit is contained in:
169
.claude/skills/owncloud/scripts/owncloud.py
Normal file
169
.claude/skills/owncloud/scripts/owncloud.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CLI for the `owncloud` skill - read-only ownCloud 10.16 inventory for the GPS
|
||||
backup audit (server: cloud.acghosting.com / 172.16.3.22, driven via occ over SSH).
|
||||
|
||||
Commands:
|
||||
status SSH ok + occ version / edition
|
||||
users [--json] all users + display name, enabled, quota, used, last login
|
||||
usage [--json] per-user rollup: used GB (desc) + quota + last login (audit view)
|
||||
audit [--client NAME] [--rmm] [--json]
|
||||
active users holding data; with --rmm cross-check GuruRMM
|
||||
endpoints for the ownCloud desktop client (scope with --client)
|
||||
|
||||
Server-side (occ) is the source of "who has an account + how much data"; --rmm adds
|
||||
the "which enrolled machine actually runs the ownCloud client" half.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from owncloud_client import OwncloudClient # noqa: E402
|
||||
import backup_common as bc # noqa: E402
|
||||
|
||||
GB = 1_000_000_000
|
||||
|
||||
|
||||
def _gb(n) -> float:
|
||||
try:
|
||||
return round(int(n) / GB, 2)
|
||||
except (TypeError, ValueError):
|
||||
return 0.0
|
||||
|
||||
|
||||
def cmd_status(c: OwncloudClient, args) -> int:
|
||||
s = c.server_status()
|
||||
print(f"[OK] {c.host}")
|
||||
print(json.dumps(s, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_users(c: OwncloudClient, args) -> int:
|
||||
users = c.list_users()
|
||||
users.sort(key=lambda u: int(u.get("used_bytes") or 0), reverse=True)
|
||||
if args.json:
|
||||
print(json.dumps(users, indent=2))
|
||||
return 0
|
||||
print(f"{'UID':20} {'NAME':22} {'EN':3} {'USED':>10} {'QUOTA':>12} {'LAST LOGIN':20}")
|
||||
for u in users:
|
||||
print(f"{(u.get('uid') or '')[:20]:20} "
|
||||
f"{(u.get('display_name') or '')[:22]:22} "
|
||||
f"{'yes' if u.get('enabled') else 'NO':3} "
|
||||
f"{_gb(u.get('used_bytes')):>7} GB "
|
||||
f"{str(u.get('quota') or '-')[:12]:>12} "
|
||||
f"{str(u.get('last_login') or '-')[:19]:20}")
|
||||
print(f"\n{len(users)} users; total used "
|
||||
f"{round(sum(_gb(u.get('used_bytes')) for u in users), 1)} GB")
|
||||
return 0
|
||||
|
||||
|
||||
def _usage_rollup(c: OwncloudClient) -> list[dict]:
|
||||
users = c.list_users()
|
||||
rollup = []
|
||||
for u in users:
|
||||
rollup.append({
|
||||
"uid": u.get("uid"),
|
||||
"display_name": u.get("display_name"),
|
||||
"email": u.get("email"),
|
||||
"enabled": bool(u.get("enabled")),
|
||||
"used_bytes": int(u.get("used_bytes") or 0),
|
||||
"used_gb": _gb(u.get("used_bytes")),
|
||||
"used_source": u.get("used_source"),
|
||||
"quota": u.get("quota"),
|
||||
"last_login": u.get("last_login"),
|
||||
})
|
||||
rollup.sort(key=lambda x: x["used_bytes"], reverse=True)
|
||||
return rollup
|
||||
|
||||
|
||||
def cmd_usage(c: OwncloudClient, args) -> int:
|
||||
rollup = _usage_rollup(c)
|
||||
if args.json:
|
||||
print(json.dumps(rollup, indent=2))
|
||||
return 0
|
||||
print(f"{'UID':20} {'EN':3} {'USED':>10} {'QUOTA':>12} {'LAST LOGIN':20}")
|
||||
for r in rollup:
|
||||
print(f"{(r['uid'] or '')[:20]:20} "
|
||||
f"{'yes' if r['enabled'] else 'NO':3} "
|
||||
f"{r['used_gb']:>7} GB "
|
||||
f"{str(r['quota'] or '-')[:12]:>12} "
|
||||
f"{str(r['last_login'] or '-')[:19]:20}")
|
||||
print(f"\n{len(rollup)} users; total used "
|
||||
f"{round(sum(r['used_gb'] for r in rollup), 1)} GB")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_audit(c: OwncloudClient, args) -> int:
|
||||
rollup = _usage_rollup(c)
|
||||
# enabled users holding data are the ones actually backing up here
|
||||
active = [r for r in rollup if r["enabled"] and r["used_bytes"] > 0]
|
||||
result = {"backend": "owncloud", "server": c.host, "accounts": active}
|
||||
|
||||
if args.rmm:
|
||||
rmm = bc.RmmClient()
|
||||
rmm.login()
|
||||
checks = []
|
||||
agents = rmm.find_agents(client_substr=args.client) if args.client else rmm.list_agents()
|
||||
for a in agents:
|
||||
det = rmm.detect_client(a, ["ownCloud"])
|
||||
if det.get("detected"):
|
||||
checks.append(det)
|
||||
result["rmm_endpoints_with_client"] = checks
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2))
|
||||
return 0
|
||||
|
||||
print(f"=== ownCloud backup accounts - {c.host} ===")
|
||||
print(f"{'UID':20} {'USED':>10} {'QUOTA':>12} {'LAST LOGIN':20}")
|
||||
for r in active:
|
||||
print(f"{(r['uid'] or '')[:20]:20} {r['used_gb']:>7} GB "
|
||||
f"{str(r['quota'] or '-')[:12]:>12} {str(r['last_login'] or '-')[:19]:20}")
|
||||
print(f"\n{len(active)} active accounts with data; "
|
||||
f"{round(sum(r['used_gb'] for r in active), 1)} GB total")
|
||||
if args.rmm:
|
||||
eps = result.get("rmm_endpoints_with_client", [])
|
||||
print(f"\n--- GuruRMM endpoints running an ownCloud client ({len(eps)}) ---")
|
||||
for e in eps:
|
||||
print(f" {e['hostname']:25} client={e.get('client_name')}")
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser(prog="owncloud", description="ownCloud backup inventory (GPS audit)")
|
||||
p.add_argument("--host", help="override server host/IP (default 172.16.3.22)")
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
sub.add_parser("status")
|
||||
u = sub.add_parser("users"); u.add_argument("--json", action="store_true")
|
||||
us = sub.add_parser("usage"); us.add_argument("--json", action="store_true")
|
||||
a = sub.add_parser("audit"); a.add_argument("--client"); a.add_argument("--rmm", action="store_true"); a.add_argument("--json", action="store_true")
|
||||
return p
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
c = OwncloudClient(args.host)
|
||||
handlers = {"status": cmd_status, "users": cmd_users,
|
||||
"usage": cmd_usage, "audit": cmd_audit}
|
||||
try:
|
||||
return handlers[args.cmd](c, args)
|
||||
except bc.BackupError as exc:
|
||||
print(f"[ERROR] {exc}", file=sys.stderr)
|
||||
# best-effort error logging per house rule
|
||||
try:
|
||||
root = bc.resolve_root()
|
||||
bc.subprocess.run(["bash", str(root / ".claude/scripts/log-skill-error.sh"),
|
||||
"owncloud", str(exc)[:200]], timeout=15)
|
||||
except Exception:
|
||||
pass
|
||||
return 1
|
||||
finally:
|
||||
c.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user