From: Shane Date: Sun, 11 Jan 2026 19:42:54 +0000 (+0000) Subject: works but klaus is basic/ugly, too, like gitweb X-Git-Url: https://git.nutra.tk/v1?a=commitdiff_plain;h=52df4afa50512e83f58b373a10bdc12f80aacdcb;p=nutratech%2Fvps-root.git works but klaus is basic/ugly, too, like gitweb --- diff --git a/Makefile b/Makefile index d91b17a..dfe33ae 100644 --- a/Makefile +++ b/Makefile @@ -65,17 +65,20 @@ test/nginx: ##H @Remote Test staged configuration without deploying .PHONY: deploy/klaus deploy/klaus: ##H @Remote Deploy Klaus (systemd + nginx) and install deps @echo "Uploading deployment bundle..." - tar cz -C etc/systemd/system klaus.service -C ../../nginx/conf.d klaus.conf | ssh $(VPS) "cat > /tmp/klaus-deploy.tgz" + tar cz -C etc/systemd/system klaus.service -C ../../nginx/conf.d klaus.conf -C ../../../scripts klaus_app.py | ssh $(VPS) "cat > /tmp/klaus-deploy.tgz" @echo "Installing on $(VPS_HOST)..." ssh -t $(VPS) "cd /tmp && tar xz -f klaus-deploy.tgz && \ - sudo pip3 install klaus gunicorn && \ - sudo mv klaus.service /etc/systemd/system/klaus.service && \ - sudo systemctl daemon-reload && \ - sudo systemctl enable --now klaus && \ - sudo mv /etc/nginx/conf.d/git-http.conf /etc/nginx/conf.d/git-http.conf.disabled 2>/dev/null || true && \ - sudo mv klaus.conf /etc/nginx/conf.d/klaus.conf && \ - sudo nginx -t && \ - sudo systemctl reload nginx && \ + sudo bash -c '# apt-get update && apt-get install -y universal-ctags && \ + pip3 install klaus gunicorn markdown && \ + mv klaus_app.py /usr/local/bin/klaus_app.py && \ + mv klaus.service /etc/systemd/system/klaus.service && \ + systemctl daemon-reload && \ + systemctl enable --now klaus && \ + systemctl restart klaus && \ + mv /etc/nginx/conf.d/git-http.conf /etc/nginx/conf.d/git-http.conf.disabled 2>/dev/null || true && \ + mv klaus.conf /etc/nginx/conf.d/klaus.conf && \ + nginx -t && \ + systemctl reload nginx' && \ rm klaus-deploy.tgz" @echo "Klaus deployed!" @@ -164,3 +167,7 @@ endif .PHONY: git/list git/list: ##H @Local List tracked repositories @python3 scripts/manage_repos.py list + +.PHONY: git/sync +git/sync: ##H @Local Sync remote repositories to local JSON + @python3 scripts/manage_repos.py --remote $(VPS) sync diff --git a/etc/systemd/system/klaus.service b/etc/systemd/system/klaus.service index 61c9a8c..e608798 100644 Binary files a/etc/systemd/system/klaus.service and b/etc/systemd/system/klaus.service differ diff --git a/scripts/klaus_app.py b/scripts/klaus_app.py new file mode 100644 index 0000000..6f3561a --- /dev/null +++ b/scripts/klaus_app.py @@ -0,0 +1,34 @@ +import os +import klaus +from klaus.contrib.wsgi import make_app + +# Root directory for repositories +REPO_ROOT = os.environ.get('KLAUS_REPOS_ROOT', '/srv/git') +SITE_NAME = os.environ.get('KLAUS_SITE_NAME', 'Git Repos') + +def find_git_repos(root_dir): + """ + Recursively find all git repositories (directories ending in .git) + """ + repos = [] + for root, dirs, files in os.walk(root_dir): + # Scan directories + for d in dirs: + if d.endswith('.git'): + full_path = os.path.join(root, d) + repos.append(full_path) + return sorted(repos) + +# Discover repositories +repositories = find_git_repos(REPO_ROOT) + +if not repositories: + print(f"Warning: No repositories found in {REPO_ROOT}") +else: + print(f"Found {len(repositories)} repositories: {repositories}") + +# Create the WSGI application +application = make_app( + repositories, + SITE_NAME, +) diff --git a/scripts/manage_repos.py b/scripts/manage_repos.py index 57cf1a5..22f371d 100755 --- a/scripts/manage_repos.py +++ b/scripts/manage_repos.py @@ -235,56 +235,76 @@ def cmd_sync(args, remote): print(f"Scanning {remote}:{GIT_ROOT}...") - # 1. Find all .git directories - # We look for something ending in .git. - # Use -maxdepth 3 to catch projects/foo.git but avoid deep nesting - find_cmd = ['ssh', remote, f'find {GIT_ROOT} -maxdepth 3 -name "*.git" -type d'] + # Remote python script to gather all metadata in one go + remote_script = f""" +import os, json, subprocess + +root = "{GIT_ROOT}" +results = {{}} + +for dirpath, dirnames, filenames in os.walk(root): + for d in dirnames: + if d.endswith('.git'): + full_path = os.path.join(dirpath, d) + rel_path = os.path.relpath(full_path, root) + + # Get description + desc = "" + desc_file = os.path.join(full_path, 'description') + if os.path.exists(desc_file): + try: + with open(desc_file, 'r') as f: + desc = f.read().strip() + if "Unnamed repository" in desc: desc = "" + except: pass + + # Get owner/origin via git config + owner = "" + origin = "" + try: + # We use git config to read keys. + # Note: 'git config' might fail if safe.directory issues, but usually fine for reading files directly if we parse? + # Safer to use git command. + # Only run if directory seems valid. + cmd = ['git', 'config', '--file', os.path.join(full_path, 'config'), '--list'] + out = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, universal_newlines=True) + for line in out.splitlines(): + if line.startswith('gitweb.owner='): + owner = line.split('=', 1)[1] + if line.startswith('remote.origin.url='): + origin = line.split('=', 1)[1] + except: + pass + + results[rel_path] = {{ + 'description': desc, + 'owner': owner, + 'remotes': {{'origin': origin}} if origin else {{}} + }} + +print(json.dumps(results)) +""" + + # Run the script remotely via SSH + cmd = ['ssh', remote, 'python3', '-c', shlex.quote(remote_script)] + try: - res = subprocess.check_output(find_cmd, universal_newlines=True) + output = subprocess.check_output(cmd, universal_newlines=True) + remote_data = json.loads(output) except subprocess.CalledProcessError as e: - print(f"Error scanning remote: {e}") - sys.exit(1) - - paths = [p.strip() for p in res.splitlines() if p.strip()] - + print(f"Error executing remote fetch: {e}") + return + except json.JSONDecodeError as e: + print(f"Error parsing remote response: {e}") + print("Raw output:", output) + return + + # Update local data data = load_repos() updated_count = 0 new_count = 0 - for full_path in paths: - if not full_path.startswith(GIT_ROOT): - continue - - rel_path = os.path.relpath(full_path, GIT_ROOT) - # normalize - if not rel_path.endswith('.git'): rel_path += '.git' - - # Check if we assume 'projects/' prefix if it's missing? - # The script generally enforces strict paths. - - print(f"Processing {rel_path}...") - - # Get Description - description = "" - try: - # cat description file. Silence stderr in case missing. - desc_cmd = ['ssh', remote, f'cat {shlex.quote(os.path.join(full_path, "description"))}'] - description = subprocess.check_output(desc_cmd, stderr=subprocess.DEVNULL, universal_newlines=True).strip() - # Default git description is usually "Unnamed repository..." - if "Unnamed repository" in description: - description = "" - except subprocess.CalledProcessError: - pass - - # Get Owner - owner = "" - try: - owner_cmd = ['ssh', remote, f'git config --file {shlex.quote(os.path.join(full_path, "config"))} gitweb.owner'] - owner = subprocess.check_output(owner_cmd, stderr=subprocess.DEVNULL, universal_newlines=True).strip() - except subprocess.CalledProcessError: - pass - - # Update Data + for rel_path, info in remote_data.items(): if rel_path not in data: data[rel_path] = {} new_count += 1 @@ -292,28 +312,14 @@ def cmd_sync(args, remote): else: updated_count += 1 - # Only overwrite if we found something useful, or if we want to sync truth? - # Let's trust remote as truth for now. - if description: - data[rel_path]['description'] = description - if owner: - data[rel_path]['owner'] = owner - - # We can't easily guess origin URL from the bare repo unless we look at the config - # but bare repos usually don't have 'origin' remotes that point upstream, - # unless they were cloned with --mirror. - # Let's check for 'remote.origin.url' - try: - origin_cmd = ['ssh', remote, f'git config --file {shlex.quote(os.path.join(full_path, "config"))} remote.origin.url'] - origin = subprocess.check_output(origin_cmd, stderr=subprocess.DEVNULL, universal_newlines=True).strip() - if origin: - if 'remotes' not in data[rel_path]: data[rel_path]['remotes'] = {} - data[rel_path]['remotes']['origin'] = origin - except: - pass - + data[rel_path]['description'] = info['description'] + data[rel_path]['owner'] = info['owner'] + if info.get('remotes'): + if 'remotes' not in data[rel_path]: data[rel_path]['remotes'] = {} + data[rel_path]['remotes'].update(info['remotes']) + save_repos(data) - print(f"\nSync complete. Added {new_count}, Scanned {updated_count}.") + print(f"\nSync complete. Added {new_count}, Scanned {updated_count}. (Single SSH connection used)") def cmd_list(args, remote): data = migrate_csv_if_needed() diff --git a/scripts/repos.json b/scripts/repos.json index 16698f4..0e07304 100644 --- a/scripts/repos.json +++ b/scripts/repos.json @@ -8,5 +8,37 @@ "owner": "Shane", "description": "SvelteKit fork with legacy browser support (IE11) and static builds", "remotes": {} + }, + "gamesguru/ffpass.git": { + "description": "CLI to export/manage Firefox & Quantum passwords.", + "owner": "Shane J" + }, + "gamesguru/vps-root.git": { + "description": "Configuration files for setting up nginx and more", + "owner": "Shane J" + }, + "gamesguru/git-remote-gcrypt.git": { + "description": "Progressive fork of `git-remote-gcrypt`", + "owner": "Shane J" + }, + "@nutratech/usda-sqlite.git": { + "description": "Portable USDA database, based on SR28", + "owner": "Shane J" + }, + "@nutratech/cli.git": { + "description": "CLI for SR28 nutrient DB, tracking/calculations", + "owner": "Shane J" + }, + "@nutratech/nt-sqlite.git": { + "description": "Portable USDA database, based on SR28", + "owner": "Shane J" + }, + "@tg-svelte/kit.git": { + "description": "SvelteKit fork with legacy browser support (IE11) and static builds", + "owner": "Shane J" + }, + "@tg-svelte/website-template-svkit-v2-legacy.git": { + "description": "SvelteKit v2 website sample/template with IE11 legacy support configuration.", + "owner": "Shane J" } } \ No newline at end of file