156 lines
4.5 KiB
Python
Executable file
156 lines
4.5 KiB
Python
Executable file
#!/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 <prefix/name> 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 = [
|
|
"<!doctype html><meta charset='utf-8'>",
|
|
"<title>Braids</title>",
|
|
"<style>",
|
|
"body{font-family:sans-serif;margin:20px}",
|
|
".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}",
|
|
".card{border:1px solid #ddd;padding:12px}",
|
|
"iframe{width:100%;height:260px;border:1px solid #eee}",
|
|
"</style>",
|
|
"<h1>Braids</h1>",
|
|
"<div class='grid'>",
|
|
]
|
|
for _, slug in refs:
|
|
parts.append("<div class='card'>")
|
|
parts.append(f"<div><a href='{PREFIX}/{slug}/' target='_blank'>{slug}</a></div>")
|
|
parts.append(f"<iframe src='{PREFIX}/{slug}/'></iframe>")
|
|
parts.append("</div>")
|
|
parts.append("</div>")
|
|
|
|
(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() |