resume_page

Log | Files | Refs | README

commit 58ab4127ea864997b089fb8f14453d06345025f4
parent 24ad282a109f886f614e1bb8da5ac61c06194e73
Author: William Lindholm <william_lindholm@outlook.com>
Date:   Mon, 21 Sep 2026 20:16:47 +0200

Added stagit implementation

Diffstat:
A.gitattributes | 1+
MREADME.md | 30+++++++++++++++++++++++++++++-
Mcaddy/Caddyfile | 5+++++
Mcompose.yml | 14++++++++++++++
Dstagit/.gitkeep | 0
Astagit/Dockerfile | 5+++++
Astagit/favicon.png | 0
Astagit/logo.png | 0
Astagit/repos.txt | 2++
Astagit/style.css | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astagit/sync.sh | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 268 insertions(+), 1 deletion(-)

diff --git a/.gitattributes b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/README.md b/README.md @@ -14,6 +14,23 @@ To start with automatic application updates during development, run: docker compose watch ``` +# Git frontend + +Add one public HTTPS clone URL per line to `stagit/repos.txt`. Repository names +must be unique. Run `docker compose up -d --build`, then open <http://localhost>. +The same pages are served at <https://git.lindholmlabs.com> in production. + +The stagit container clones or fetches immediately, generates static pages, then +sleeps for 24 hours. Restart it with `docker compose restart stagit` to sync now. +Mirrors and generated pages persist in named volumes. Unavailable remotes keep +their last local copy; removing a URL does not delete its mirror. Caddy serves the +completed output read-only; stagit exposes no ports. + +The Git pages use local copies of Codemadness's +[stylesheet](https://codemadness.org/git/style.css), +[favicon](https://codemadness.org/git/favicon.png), and +[logo](https://codemadness.org/git/logo.png). + # Software Requirements Specification ## Personal Résumé Website @@ -42,8 +59,18 @@ Use this structure: ``` / ├── .gitignore +├── .gitattributes ├── README.md ├── compose.yml +├── caddy/ +│ └── Caddyfile +├── stagit/ +│ ├── Dockerfile +│ ├── sync.sh +│ ├── repos.txt +│ ├── style.css +│ ├── favicon.png +│ └── logo.png └── cv/ ├── main.py ├── Dockerfile @@ -58,7 +85,8 @@ Do not add directories. The README shall contain only: 1. Instructions to start the container. -2. This SRS. +2. Git frontend instructions. +3. This SRS. Place the start instructions first. diff --git a/caddy/Caddyfile b/caddy/Caddyfile @@ -1,3 +1,8 @@ cv.lindholmlabs.com { reverse_proxy cv:8000 } + +git.lindholmlabs.com, http://localhost { + root * /www/current + file_server +} diff --git a/compose.yml b/compose.yml @@ -7,6 +7,7 @@ services: - "443:443" volumes: - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - stagit_www:/www:ro depends_on: - cv @@ -30,3 +31,16 @@ services: path: ./cv/Dockerfile - action: rebuild path: ./cv/requirements.txt + + stagit: + build: ./stagit + restart: unless-stopped + init: true + volumes: + - ./stagit:/config:ro + - stagit_git:/git + - stagit_www:/www + +volumes: + stagit_git: + stagit_www: diff --git a/stagit/.gitkeep b/stagit/.gitkeep diff --git a/stagit/Dockerfile b/stagit/Dockerfile @@ -0,0 +1,5 @@ +FROM alpine:3.22 +RUN apk add --no-cache git ca-certificates stagit +COPY sync.sh /sync.sh +COPY style.css favicon.png logo.png /assets/ +CMD ["sh", "/sync.sh"] diff --git a/stagit/favicon.png b/stagit/favicon.png Binary files differ. diff --git a/stagit/logo.png b/stagit/logo.png Binary files differ. diff --git a/stagit/repos.txt b/stagit/repos.txt @@ -0,0 +1 @@ +https://github.com/LindholmLabs/DSA-Assignments.git +\ No newline at end of file diff --git a/stagit/style.css b/stagit/style.css @@ -0,0 +1,154 @@ +body { + color: #000; + background-color: #fff; + font-family: monospace; +} + +h1, h2, h3, h4, h5, h6 { + font-size: 1em; + margin: 0; +} + +img, h1, h2 { + vertical-align: middle; +} + +img { + border: 0; +} + +a:target { + background-color: #ccc; +} + +a.d, +a.h, +a.i, +a.line { + text-decoration: none; +} + +#blob a { + color: #555; +} + +#blob a:hover { + color: blue; + text-decoration: none; +} + +table thead td { + font-weight: bold; +} + +table td { + padding: 0 0.4em; +} + +#content table td { + vertical-align: top; + white-space: nowrap; +} + +#branches tr:hover td, +#tags tr:hover td, +#index tr:hover td, +#log tr:hover td, +#files tr:hover td { + background-color: #eee; +} + +#index tr td:nth-child(2), +#tags tr td:nth-child(3), +#branches tr td:nth-child(3), +#log tr td:nth-child(2) { + white-space: normal; +} + +td.num { + text-align: right; +} + +.desc { + color: #555; +} + +hr { + border: 0; + border-top: 1px solid #555; + height: 1px; +} + +pre { + font-family: monospace; +} + +pre a.h { + color: #00a; +} + +.A, +span.i, +pre a.i { + color: #070; +} + +.D, +span.d, +pre a.d { + color: #e00; +} + +pre a.h:hover, +pre a.i:hover, +pre a.d:hover { + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + body { + background-color: #000; + color: #bdbdbd; + } + hr { + border-color: #222; + } + a { + color: #56c8ff; + } + a:target { + background-color: #222; + } + .desc { + color: #aaa; + } + #blob a { + color: #555; + } + #blob a:target { + color: #eee; + } + #blob a:hover { + color: #56c8ff; + } + pre a.h { + color: #00cdcd; + } + .A, + span.i, + pre a.i { + color: #00cd00; + } + .D, + span.d, + pre a.d { + color: #cd0000; + } + #branches tr:hover td, + #tags tr:hover td, + #index tr:hover td, + #log tr:hover td, + #files tr:hover td { + background-color: #111; + } +} diff --git a/stagit/sync.sh b/stagit/sync.sh @@ -0,0 +1,58 @@ +#!/bin/sh +set -eu +export GIT_TERMINAL_PROMPT=0 +mkdir -p /git /www +rm -rf /git/.clone + +while :; do + echo 'Synchronizing repositories' + tr -d '\r' < /config/repos.txt > /tmp/repos.txt + while IFS= read -r url || [ -n "$url" ]; do + case "$url" in ''|'#'*) continue ;; esac + name=${url##*/} + name=${name%.git} + case "$name" in ''|.*|*[!a-zA-Z0-9._-]*) echo "Invalid name: $name"; continue ;; esac + mirror="/git/$name.git" + if [ -d "$mirror" ]; then + if git --git-dir="$mirror" fetch --atomic --prune origin; then + echo "Updated $name" + else + echo "Unavailable: $name; keeping local mirror" + fi + elif git clone --mirror "$url" /git/.clone; then + mv /git/.clone "$mirror" + : > "$mirror/description" + echo "Cloned $name" + else + rm -rf /git/.clone + echo "Could not clone $name; skipping" + fi + done < /tmp/repos.txt + + echo 'Generating stagit pages' + # Build in the inactive directory, then switch Caddy to the finished site. + build=/www/a + [ "$(readlink /www/current || true)" != a ] || build=/www/b + rm -rf "$build" + mkdir "$build" + cp /assets/* "$build/" + for mirror in /git/*.git; do + [ -d "$mirror" ] || continue + name=${mirror##*/} + mkdir "$build/${name%.git}" + (cd "$build/${name%.git}" && stagit "$mirror" && + ln -s log.html index.html && + ln -s ../style.css ../favicon.png ../logo.png .) + done + set -- /git/*.git + if [ -d "$1" ]; then + stagit-index "$@" > "$build/index.html" + else + echo '<!doctype html><title>Repositories</title><link rel="stylesheet" href="style.css"><link rel="icon" href="favicon.png"><img src="logo.png" alt=""><p>No repositories configured.</p>' > "$build/index.html" + fi + ln -sfn "${build##*/}" /www/current.new + mv -Tf /www/current.new /www/current + echo 'Site published; next synchronization in 24 hours' + sleep 86400 & + wait "$!" +done