#!/usr/bin/env python3 # This script gets all the branches of the repo (set belwo) with a certain prefix # and copies them to the webroot as separate directories. # Not very secure ofcourse so only run the webhook when the workshop is ongoing # and remove the files afterwards. import os import shutil import subprocess import tempfile import fcntl from pathlib import Path REPO = Path("/srv/git/braids.git") # bare/mirror repo WEBROOT = Path("/var/www/html/") # public root PREFIX = "braids" # this sets both the prefix for the branches as the directory the sites will be published in. PEOPLE_DIR = WEBROOT / PREFIX # Had to experiment with a few different location for this. /tmp and /run did not work. LOCKFILE = Path("/var/lock/braids-deploy.lock") REF_PREFIX = "refs/heads/" + PREFIX + "/" ALLOWED_SLUG_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-") def run(cmd, cwd=None, input_bytes=None): result = subprocess.run( cmd, cwd=str(cwd) if cwd else None, input=input_bytes, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True, ) return result.stdout def is_valid_slug(slug): if not slug: return False for ch in slug: if ch not in ALLOWED_SLUG_CHARS: return False return True # Gets all branches for the prefix. def get_people_refs(): out = run([ "git", "-C", str(REPO), "for-each-ref", "--format=%(refname)", "refs/heads/" + PREFIX +"/*", ]) refs = [] for line in out.decode("utf-8", errors="replace").splitlines(): line = line.strip() if not line.startswith(REF_PREFIX): continue slug = line[len(REF_PREFIX):] if is_valid_slug(slug): refs.append((line, slug)) refs.sort(key=lambda item: item[1].lower()) return refs # create the directories if necessary def ensure_dirs(): WEBROOT.mkdir(parents=True, exist_ok=True) PEOPLE_DIR.mkdir(parents=True, exist_ok=True) # update the local bare repo def fetch_mirror(): run(["git", "-C", str(REPO), "fetch", "--prune", "origin", "+refs/heads/*:refs/heads/*"]) def publish_one(ref, slug): tmpdir = Path(tempfile.mkdtemp(prefix="braids-" + slug + "-")) try: # Export branch root archive = run(["git", "-C", str(REPO), "archive", ref]) run([ "tar", "-x", "-C", str(tmpdir), "--no-same-owner", "--no-same-permissions", ], input_bytes=archive) dest = PEOPLE_DIR / slug if dest.exists(): shutil.rmtree(dest) dest.parent.mkdir(parents=True, exist_ok=True) shutil.move(str(tmpdir), str(dest)) make_world_readable(dest) except subprocess.CalledProcessError: # If branch doesn't contain that folder, just skip it. pass finally: shutil.rmtree(tmpdir, ignore_errors=True) def make_world_readable(path): for root, dirs, files in os.walk(path): root_path = Path(root) for d in dirs: os.chmod(root_path / d, 0o755) for f in files: os.chmod(root_path / f, 0o644) os.chmod(path, 0o755) # create a simple html file with links to the documents. iframes because it looks nice. def rebuild_index(refs): ensure_dirs() parts = [ "", "Braids", "", "

Braids

", "
", ] for _, slug in refs: parts.append("
") parts.append(f"
{slug}
") parts.append(f"") parts.append("
") parts.append("
") (WEBROOT / "index.html").write_text("\n".join(parts), encoding="utf-8") def main(): # Lock (avoid concurrent deploys) LOCKFILE.parent.mkdir(parents=True, exist_ok=True) with open(LOCKFILE, "w") as lf: fcntl.flock(lf, fcntl.LOCK_EX) ensure_dirs() fetch_mirror() refs = get_people_refs() for ref, slug in refs: publish_one(ref, slug) rebuild_index(refs) if __name__ == "__main__": main()