Add self-contained automated server upgrade package
This commit is contained in:
1 parent
4d58fc7b66
commit
8e6c9a058f
14 files changed
+908
-26
No files matched your search
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read server metadata for migration without exposing config values or secrets."""
|
||||
import configparser
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def cron_units(nc_dir):
|
||||
"""Discover directly configured occ/cron.php writers, not arbitrary wrappers."""
|
||||
proc = subprocess.run(['systemctl','list-unit-files','--type=service','--no-legend','--no-pager'], check=True, text=True, capture_output=True)
|
||||
services = []
|
||||
for line in proc.stdout.splitlines():
|
||||
unit = line.split()[0]
|
||||
# Templates cannot be stopped without a concrete instance name.
|
||||
if '@.' in unit:
|
||||
continue
|
||||
command = subprocess.check_output(['systemctl','show',unit,'--property=ExecStart','--value'],text=True)
|
||||
if nc_dir in command and ('cron.php' in command or '/occ' in command):
|
||||
services.append(unit)
|
||||
timers = []
|
||||
for service in services:
|
||||
triggered = subprocess.check_output(['systemctl','show',service,'--property=TriggeredBy','--value'],text=True)
|
||||
timers.extend(x for x in triggered.split() if x.endswith(('.timer','.socket')))
|
||||
for unit in dict.fromkeys(timers + services):
|
||||
if not re.fullmatch(r'[A-Za-z0-9_.@:-]+\.(service|timer|socket)', unit):
|
||||
raise ValueError('Unexpected systemd unit name')
|
||||
print(unit)
|
||||
|
||||
|
||||
def check_crontabs(nc_dir, nc_user):
|
||||
"""Refuse to guess scheduling semantics for cron scripts; report locations only."""
|
||||
found = []
|
||||
paths = [Path('/etc/crontab')]
|
||||
for directory in ('/etc/cron.d','/etc/cron.hourly','/etc/cron.daily','/etc/cron.weekly','/etc/cron.monthly','/var/spool/cron'):
|
||||
root = Path(directory)
|
||||
if root.is_dir():
|
||||
paths.extend(p for p in root.rglob('*') if p.is_file())
|
||||
for path in paths:
|
||||
if not path.is_file():
|
||||
continue
|
||||
for line in path.read_text(errors='replace').splitlines():
|
||||
if not line.lstrip().startswith('#') and (nc_dir in line or 'cron.php' in line):
|
||||
found.append(str(path))
|
||||
break
|
||||
if found:
|
||||
print('Nextcloud cron entries: '+', '.join(sorted(set(found))))
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def gitea(path):
|
||||
parser = configparser.ConfigParser(interpolation=None, strict=False)
|
||||
parser.read_string('[DEFAULT]\n' + Path(path).read_text())
|
||||
result = {}
|
||||
# Only explicitly configured absolute filesystem paths are accepted here.
|
||||
for key, section, option in [('GITEA_DATA_DIR','DEFAULT','APP_DATA_PATH'),('GITEA_REPOS_DIR','repository','ROOT')]:
|
||||
value = parser[section].get(option,'') if section in parser else ''
|
||||
if value and os.path.isabs(value) and '%' not in value:
|
||||
result[key] = value
|
||||
if parser.get('database','DB_TYPE',fallback='') == 'mysql':
|
||||
result['GITEA_DB_NAME'] = parser.get('database','NAME',fallback='')
|
||||
for key,value in result.items():
|
||||
print(key+'='+shlex.quote(value))
|
||||
|
||||
|
||||
def environment_config():
|
||||
raw = subprocess.check_output(['systemctl','show','app-backup.service','--property=Environment','--value'],text=True)
|
||||
for item in shlex.split(raw):
|
||||
if item.startswith('RCLONE_CONFIG='):
|
||||
print(item.split('=',1)[1])
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
op,*args=sys.argv[1:]
|
||||
if op == 'cron-units': cron_units(*args)
|
||||
elif op == 'check-crontabs': check_crontabs(*args)
|
||||
elif op == 'gitea': gitea(*args)
|
||||
elif op == 'rclone-config': environment_config()
|
||||
else: raise ValueError('Unknown discovery operation')
|
||||
except (ValueError, OSError, configparser.Error, subprocess.CalledProcessError):
|
||||
# Parser errors can include secret config lines, so do not echo exceptions.
|
||||
print('ERROR: Discovery incomplete. Check Nextcloud cron scheduling or explicit Gitea/rclone settings; no secret values logged.',file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in new issue
Block a user