163 lines
4.6 KiB
Python
Executable File
163 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Recupere la liste des apps installees sur le serveur YunoHost et ajoute
|
|
celles qui manquent dans src/config.json (dans "ungrouped"), sans toucher a
|
|
celles deja placees dans un groupe, dans "pinned" ou deja presentes.
|
|
|
|
Usage: python3 scripts/sync-apps.py [utilisateur@hote] [domaine]
|
|
|
|
Toute la configuration vient de .env (SYNC_SSH_USER, SYNC_SSH_HOST,
|
|
PORTAL_DOMAIN, REMOTE_USER) ; les arguments en ligne de commande, s'ils sont
|
|
fournis, ont priorite sur .env.
|
|
"""
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
PROJECT_ROOT = SCRIPT_DIR.parent
|
|
CONFIG_PATH = PROJECT_ROOT / "src" / "config.json"
|
|
ENV_PATH = PROJECT_ROOT / ".env"
|
|
|
|
|
|
def load_env():
|
|
env = {}
|
|
if not ENV_PATH.exists():
|
|
return env
|
|
for line in ENV_PATH.read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
env[key.strip()] = value.strip()
|
|
return env
|
|
|
|
|
|
ENV = load_env()
|
|
|
|
SYNC_SSH_USER = ENV.get("SYNC_SSH_USER", "root")
|
|
SYNC_SSH_HOST = ENV.get("SYNC_SSH_HOST") or ENV.get("REMOTE_HOST")
|
|
PORTAL_DOMAIN = ENV.get("PORTAL_DOMAIN") or ENV.get("REMOTE_HOST")
|
|
SELF_APP_ID = ENV.get("REMOTE_USER", "") # cette page elle-meme, jamais ajoutee
|
|
|
|
if len(sys.argv) > 1:
|
|
SSH_TARGET = sys.argv[1]
|
|
elif SYNC_SSH_HOST:
|
|
SSH_TARGET = f"{SYNC_SSH_USER}@{SYNC_SSH_HOST}"
|
|
else:
|
|
SSH_TARGET = None
|
|
|
|
DOMAIN = sys.argv[2] if len(sys.argv) > 2 else PORTAL_DOMAIN
|
|
|
|
if not SSH_TARGET or not DOMAIN:
|
|
print(
|
|
"Configuration manquante : renseigne SYNC_SSH_HOST (ou REMOTE_HOST) "
|
|
"et PORTAL_DOMAIN dans .env (lance 'make config' si .env n'existe pas "
|
|
"encore), ou passe-les en argument :\n"
|
|
" python3 scripts/sync-apps.py utilisateur@hote domaine",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
PALETTE = [
|
|
"#1db9c3", "#ff5a1f", "#2094f3", "#ffc230", "#00a651", "#e6721a",
|
|
"#b71c1c", "#f39c12", "#0d3b66", "#3b8a8c", "#568d34", "#609926",
|
|
"#d24939", "#0066cc", "#8e44ad", "#16a085", "#c0392b", "#2c3e50",
|
|
]
|
|
|
|
|
|
def color_of(name):
|
|
h = 0
|
|
for ch in name:
|
|
h = (h * 31 + ord(ch)) & 0xFFFFFFFF
|
|
return PALETTE[h % len(PALETTE)]
|
|
|
|
|
|
def initials_of(name):
|
|
words = name.strip().split()
|
|
if len(words) == 1:
|
|
return words[0][:2].upper()
|
|
return (words[0][0] + words[1][0]).upper()
|
|
|
|
|
|
def fetch_remote_apps():
|
|
cmd = ["ssh", "-o", "BatchMode=yes", SSH_TARGET, f"cat /etc/yunohost/portal/{DOMAIN}.json"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
print(f"Erreur SSH: {result.stderr}", file=sys.stderr)
|
|
sys.exit(1)
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def normalize_url(url):
|
|
return url.replace("https://", "").replace("http://", "").rstrip("/").lower()
|
|
|
|
|
|
def known_urls(config):
|
|
urls = set()
|
|
for app in config.get("pinned", []):
|
|
urls.add(normalize_url(app["url"]))
|
|
for group in config.get("groups", []):
|
|
for app in group["apps"]:
|
|
urls.add(normalize_url(app["url"]))
|
|
for app in config.get("ungrouped", []):
|
|
urls.add(normalize_url(app["url"]))
|
|
return urls
|
|
|
|
|
|
def main():
|
|
with open(CONFIG_PATH, encoding="utf-8") as f:
|
|
config = json.load(f)
|
|
|
|
remote = fetch_remote_apps()
|
|
remote_apps = remote.get("apps", {})
|
|
existing_urls = known_urls(config)
|
|
|
|
added = []
|
|
for perm_key, info in sorted(remote_apps.items()):
|
|
if not perm_key.endswith(".main"):
|
|
continue
|
|
app_id = perm_key[: -len(".main")]
|
|
if app_id == SELF_APP_ID:
|
|
continue
|
|
|
|
url = info["url"]
|
|
if not url.startswith("http"):
|
|
url = "https://" + url
|
|
if not url.endswith("/"):
|
|
url += "/"
|
|
|
|
if normalize_url(url) in existing_urls:
|
|
continue
|
|
|
|
name = info.get("label", app_id)
|
|
app = {
|
|
"id": app_id,
|
|
"name": name,
|
|
"url": url,
|
|
"color": color_of(name),
|
|
"initials": initials_of(name),
|
|
}
|
|
logo = info.get("logo")
|
|
if logo:
|
|
app["icon"] = f"https://{DOMAIN}{logo}"
|
|
|
|
config.setdefault("ungrouped", []).append(app)
|
|
added.append(name)
|
|
|
|
if not added:
|
|
print("Rien a ajouter, config.json est deja a jour.")
|
|
return
|
|
|
|
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
|
|
json.dump(config, f, ensure_ascii=False, indent=2)
|
|
f.write("\n")
|
|
|
|
print(f"{len(added)} application(s) ajoutee(s) dans \"ungrouped\" :")
|
|
for name in added:
|
|
print(f" - {name}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|