gitpages

A collection of scripts to securely webhost and publish git repositories.
git clone https://scm.kuandu.systems/git-raw/gitpages.git
Log | Files | Refs | README | LICENSE

commit 3d7562715a71f26f08996bbaef296e9303344142
parent 231ad162d26193546cc57594f7769e0e4729c1e0
Author: Fred Großkopf <fred@kuandu.systems>
Date:   Fri, 18 Sep 2026 00:17:15 +0200

dry: function naming et al

Diffstat:
Mgitpages.sh | 485++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mtest-gitpages-process-queue.sh | 1+
2 files changed, 237 insertions(+), 249 deletions(-)

diff --git a/gitpages.sh b/gitpages.sh @@ -69,6 +69,7 @@ error() { exit 1 } info() { printf 'info: %s\n' "$1"; } +warning() { printf 'warning: %s\n' "$1" >&2; } sed_escape() { printf '%s\n' "$1" | sed 's/[\\/&]/\\&/g'; } check_deps() { @@ -78,51 +79,57 @@ check_deps() { } is_bare_repo() { - git --git-dir="$1" rev-parse --is-bare-repository > /dev/null 2>&1 + git --git-dir="$1" rev-parse --is-bare-repository > /dev/null 2>&1 } has_repos() { - [ -d "$1" ] && - ls "$1"/*.git > /dev/null 2>&1 2> /dev/null + for repo in "$1"/*.git; do + [ -d "$repo" ] && return 0 + done + return 1 } - -# --- Core Logic --- - load_config() { - CONFIG="$1" - - [ -r "$CONFIG" ] || - error "config $CONFIG not readable" - - WEB_ROOT=$(get_config_val "WEB_ROOT") - GIT_SRC=$(get_config_val "GIT_SRC") - GIT_RAW=$(get_config_val "GIT_RAW") - GIT_HTML=$(get_config_val "GIT_HTML") - INDEX_TITLE=$(get_config_val "INDEX_TITLE") - INDEX_DESC=$(get_config_val "INDEX_DESCRIPTION") - REPO_TITLE_DESC=$(get_config_val "REPO_TITLE_DESCRIPTION") - ASSETS_DIR=$(get_config_val "ASSETS_DIR") + CONFIG="$1" + + [ -r "$CONFIG" ] \ + || error "config $CONFIG not readable" + + WEB_ROOT=$(get_config_val "WEB_ROOT") + GIT_SRC=$(get_config_val "GIT_SRC") + GIT_RAW=$(get_config_val "GIT_RAW") + GIT_HTML=$(get_config_val "GIT_HTML") + INDEX_TITLE=$(get_config_val "INDEX_TITLE") + INDEX_DESC=$(get_config_val "INDEX_DESCRIPTION") + REPO_TITLE_DESC=$(get_config_val "REPO_TITLE_DESCRIPTION") + ASSETS_DIR=$(get_config_val "ASSETS_DIR") + + [ -n "$WEB_ROOT" ] || error "WEB_ROOT missing in config" + [ -n "$GIT_SRC" ] || error "GIT_SRC missing in config" + [ -n "$GIT_RAW" ] || error "GIT_RAW missing in config" + [ -n "$GIT_HTML" ] || error "GIT_HTML missing in config" } -generate_repo_html() { +# --- Core Logic --- + +generate__run_stagit() { rm -rf "$STAGING_DIR" mkdir -p "$STAGING_DIR" cd "$STAGING_DIR" stagit "$REPO_SRC" > /dev/null 2>&1 } -process_repo_titles() { +generate__update_repo_metadata() { [ -n "${REPO_TITLE_DESC:-}" ] || return 0 esc_desc=$(sed_escape "$REPO_TITLE_DESC") find "$STAGING_DIR" -name "*.html" -type f -exec sed -i \ "s| - $REPO_BASE - [^<]*| - $REPO_BASE$esc_desc|g" {} + } -generate_index() { +generate__run_stagit_index() { # 1. Verify destination path exists [ -d "$WEB_ROOT/$GIT_HTML" ] || { - printf 'Error: %s/html not found\n' "$WEB_ROOT" + printf 'Error: %s/html not found\n' "$WEB_ROOT/$GIT_HTML" return 1 } @@ -142,7 +149,7 @@ generate_index() { ) } -process_index() { +generate__update_index_metadata() { [ -f "$WEB_ROOT/$GIT_HTML/index.html" ] || return 0 [ -n "${INDEX_TITLE:-}" ] && { esc_title=$(sed_escape "$INDEX_TITLE") @@ -154,7 +161,7 @@ process_index() { } } -deploy_assets() { +generate__copy_assets() { [ -d "${ASSETS_DIR:-}" ] || return 0 mkdir -p "$STAGING_DIR" for f in "$ASSETS_DIR"/*; do @@ -162,17 +169,15 @@ deploy_assets() { done } -swap_target_dir() { +generate__publish_repo() { rm -rf "$TARGET_DIR" 2> /dev/null || true mv "$STAGING_DIR" "$TARGET_DIR" || error "could not move staging into place" } generate_repo() { - load_config "$1" + load_config "$1" REPO_SRC="$2" - check_deps - REPO_BASE=$(basename "$REPO_SRC" ".git") SRC_DIR="$WEB_ROOT/$GIT_RAW" VAR_DIR="${GITPAGES_VAR_DIR:-/var/gitpages}" @@ -180,308 +185,290 @@ generate_repo() { STAGING_DIR="$VAR_DIR/staging/$REPO_BASE" # Execution - generate_repo_html - deploy_assets - process_repo_titles - swap_target_dir - generate_index - process_index + generate__run_stagit + generate__copy_assets + generate__update_repo_metadata + generate__publish_repo + generate__run_stagit_index + generate__update_index_metadata info "Generation complete for $REPO_BASE" } generate_all() { - load_config "$1" - - [ -n "$WEB_ROOT" ] || error "WEB_ROOT missing in config" - [ -n "$GIT_SRC" ] || error "GIT_SRC missing in config" - [ -n "$GIT_RAW" ] || error "GIT_RAW missing in config" + load_config "$1" - [ -d "$WEB_ROOT" ] || error "WEB_ROOT directory $WEB_ROOT does not exist" - [ -d "$GIT_SRC" ] || error "GIT_SRC directory $GIT_SRC does not exist" + [ -d "$WEB_ROOT" ] || error "WEB_ROOT directory $WEB_ROOT does not exist" + [ -d "$GIT_SRC" ] || error "GIT_SRC directory $GIT_SRC does not exist" - GIT_RAW_DIR="$WEB_ROOT/$GIT_RAW" + GIT_RAW_DIR="$WEB_ROOT/$GIT_RAW" - info "1/2 Mirroring repos $GIT_SRC → $GIT_RAW_DIR" - mirror "$GIT_SRC" "$GIT_RAW_DIR" + info "1/2 Mirroring repos $GIT_SRC → $GIT_RAW_DIR" + mirror "$GIT_SRC" "$GIT_RAW_DIR" - info "2/2 Generating HTML for all repos in $GIT_RAW_DIR" + info "2/2 Generating HTML for all repos in $GIT_RAW_DIR" - for repo in "$GIT_RAW_DIR"/*.git; do - [ -e "$repo" ] || continue + for repo in "$GIT_RAW_DIR"/*.git; do + [ -e "$repo" ] || continue - info "Processing: $(basename "$repo")" - generate_repo "$CONFIG" "$repo" - done + info "Processing: $(basename "$repo")" + generate_repo "$CONFIG" "$repo" + done } -setup_ask() { - prompt="$1" - current="${2:-}" +setup__ask() { + prompt="$1" + current="${2:-}" - if [ -t 0 ]; then - if [ -n "$current" ]; then - printf '%s [%s]: ' "$prompt" "$current" >&2 - else - printf '%s: ' "$prompt" >&2 - fi + if [ -t 0 ]; then + if [ -n "$current" ]; then + printf '%s [%s]: ' "$prompt" "$current" >&2 + else + printf '%s: ' "$prompt" >&2 fi + fi - read -r answer - case "$answer" in - "") printf '%s\n' "$current" ;; - *) printf '%s\n' "$answer" ;; - esac + read -r answer + case "$answer" in + "") printf '%s\n' "$current" ;; + *) printf '%s\n' "$answer" ;; + esac } -setup_install_hook() { - repo_dir="$1" +setup__install_hook() { + repo_dir="$1" - hook_dir="$repo_dir/hooks" - hook_file="$hook_dir/post-receive" + hook_dir="$repo_dir/hooks" + hook_file="$hook_dir/post-receive" - mkdir -p "$hook_dir" - cp "$POST_RECEIVE" "$hook_file" - chmod 755 "$hook_file" + mkdir -p "$hook_dir" + cp "$POST_RECEIVE" "$hook_file" + chmod 755 "$hook_file" - info "Installed post-receive hook in $repo_dir" + info "Installed post-receive hook in $repo_dir" } -setup_metadata() { - file="$1" - prompt="$2" - current="" +setup__edit_metadata() { + file="$1" + prompt="$2" + current="" - if [ -f "$file" ]; then - current=$(cat "$file") - fi + if [ -f "$file" ]; then + current=$(cat "$file") + fi - value=$(setup_ask "$prompt" "$current") + value=$(setup__ask "$prompt" "$current") - if [ -n "$value" ]; then - printf '%s\n' "$value" > "$file" - fi + if [ -n "$value" ]; then + printf '%s\n' "$value" > "$file" + fi } setup_repo() { - repo="$1" - repo_name=$(basename "$repo" .git) + repo="$1" + repo_name=$(basename "$repo" .git) - setup_metadata "$repo/description" "Description" - setup_metadata "$repo/owner" "Owner/Contact" - setup_metadata "$repo/url" "URL" + setup__edit_metadata "$repo/description" "Description" + setup__edit_metadata "$repo/owner" "Owner/Contact" + setup__edit_metadata "$repo/url" "URL" - printf '## Repository: %s ##\n' "$repo_name" + printf '## Repository: %s ##\n' "$repo_name" - setup_install_hook "$repo" + setup__install_hook "$repo" } setup_all() { - GIT_SRC="${1:-}" + GIT_SRC="${1:-}" - if [ -z "$GIT_SRC" ]; then - CONFIG="$DEFAULT_CONFIG" + if [ -z "$GIT_SRC" ]; then + CONFIG="$DEFAULT_CONFIG" - [ -r "$CONFIG" ] || - error "Config file $CONFIG not readable" + [ -r "$CONFIG" ] \ + || error "Config file $CONFIG not readable" - GIT_SRC=$(get_config_val "GIT_SRC") + GIT_SRC=$(get_config_val "GIT_SRC") - [ -n "$GIT_SRC" ] || - error "GIT_SRC not set in $CONFIG" - fi + [ -n "$GIT_SRC" ] \ + || error "GIT_SRC not set in $CONFIG" + fi - [ -d "$GIT_SRC" ] || - error "Directory '$GIT_SRC' not found" + [ -d "$GIT_SRC" ] \ + || error "Directory '$GIT_SRC' not found" - POST_RECEIVE="$SCRIPT_DIR/post-receive.hook" + POST_RECEIVE="$SCRIPT_DIR/post-receive.hook" - [ -f "$POST_RECEIVE" ] || - error "post-receive helper not found in $SCRIPT_DIR" + [ -f "$POST_RECEIVE" ] \ + || error "post-receive helper not found in $SCRIPT_DIR" - for repo in "$GIT_SRC"/*.git; do - [ -d "$repo" ] || continue - setup_repo "$repo" - done + for repo in "$GIT_SRC"/*.git; do + [ -d "$repo" ] || continue + setup_repo "$repo" + done - info "Setup complete for all repos in $GIT_SRC" + info "Setup complete for all repos in $GIT_SRC" } -mirror_repo() { - src="$1" - dst_dir="$2" - name=$(basename "$src") - dst="$dst_dir/$name" +mirror__copy_repo() { + src="$1" + dst_dir="$2" + name=$(basename "$src") + dst="$dst_dir/$name" - is_bare_repo "$src" || { - warning "skipping $name (not bare)" - return 0 - } + is_bare_repo "$src" || { + warning "skipping $name (not bare)" + return 0 + } - rm -rf "$dst" - cp -R "$src" "$dst" - git --git-dir="$dst" update-server-info > /dev/null 2>&1 + rm -rf "$dst" + cp -R "$src" "$dst" + git --git-dir="$dst" update-server-info > /dev/null 2>&1 } mirror() { - src="$1" - dst_dir="$2" + src="$1" + dst_dir="$2" - [ -d "$dst_dir" ] && [ -w "$dst_dir" ] || - error "DST_DIR '$dst_dir' not writable" + [ "$dst_dir" != "/" ] || + error "refusing to use / as destination" - if is_bare_repo "$src"; then - mirror_repo "$src" "$dst_dir" - info "synced: $src → $dst_dir" - elif has_repos "$src"; then - [ "$dst_dir" != "/" ] && rm -rf "$dst_dir"/* + [ -d "$dst_dir" ] && [ -w "$dst_dir" ] \ + || error "DST_DIR '$dst_dir' not writable" - for repo in "$src"/*.git; do - [ -d "$repo" ] || break - mirror_repo "$repo" "$dst_dir" - done + if is_bare_repo "$src"; then + mirror__copy_repo "$src" "$dst_dir" + info "synced: $src → $dst_dir" + elif has_repos "$src"; then + [ "$dst_dir" != "/" ] && rm -rf "$dst_dir"/* - info "synced all: $src → $dst_dir" - else - error "$src: neither bare repo nor *.git directory" - fi + for repo in "$src"/*.git; do + [ -d "$repo" ] || break + mirror__copy_repo "$repo" "$dst_dir" + done + + info "synced all: $src → $dst_dir" + else + error "$src: neither bare repo nor *.git directory" + fi } process_repo() { - CONFIG="$1" - repo_path="$2" + CONFIG="$1" + repo_path="$2" - repo_name=$(basename "$repo_path") + repo_name=$(basename "$repo_path") - [ -r "$CONFIG" ] || { - printf 'error: config %s not readable\n' "$CONFIG" >&2 - return 1 - } - - WEB_ROOT=$(get_config_val "WEB_ROOT") - GIT_RAW=$(get_config_val "GIT_RAW") - - [ -n "$WEB_ROOT" ] || { - printf 'error: WEB_ROOT missing in config\n' >&2 - return 1 - } - - [ -n "$GIT_RAW" ] || { - printf 'error: GIT_RAW missing in config\n' >&2 - return 1 - } + load_config "$CONFIG" - GIT_RAW_DIR="$WEB_ROOT/$GIT_RAW" + GIT_RAW_DIR="$WEB_ROOT/$GIT_RAW" - [ -d "$GIT_RAW_DIR" ] || { - printf 'error: GIT_RAW directory %s does not exist\n' "$GIT_RAW_DIR" >&2 - return 1 - } + [ -d "$GIT_RAW_DIR" ] || { + printf 'error: GIT_RAW directory %s does not exist\n' "$GIT_RAW_DIR" >&2 + return 1 + } - if ! mirror "$repo_path" "$GIT_RAW_DIR"; then - printf 'error: Failed to mirror %s\n' "$repo_name" >&2 - return 1 - fi + if ! mirror "$repo_path" "$GIT_RAW_DIR"; then + printf 'error: Failed to mirror %s\n' "$repo_name" >&2 + return 1 + fi - if ! generate_repo "$CONFIG" "$GIT_RAW_DIR/$repo_name"; then - printf 'error: Failed to generate pages for %s\n' "$repo_name" >&2 - return 1 - fi + if ! generate_repo "$CONFIG" "$GIT_RAW_DIR/$repo_name"; then + printf 'error: Failed to generate pages for %s\n' "$repo_name" >&2 + return 1 + fi } - cmd_setup() { - case "$#" in - 0) - setup_all - ;; - 1) - setup_all "$1" - ;; - *) - error "usage: gitpages setup [SRC]" - ;; - esac + case "$#" in + 0) + setup_all + ;; + 1) + setup_all "$1" + ;; + *) + error "usage: gitpages setup [SRC]" + ;; + esac } cmd_process_queue() { - CONFIG="$DEFAULT_CONFIG" - - while [ "$#" -gt 0 ]; do - case "$1" in - -h) - printf 'usage: gitpages process_queue [-c config]\n' - return 0 - ;; - -c) - [ "$#" -ge 2 ] || { - printf 'ERROR: -c requires a config file\n' >&2 - exit 1 - } - CONFIG="$2" - shift 2 - ;; - -*) - printf 'ERROR: unknown option: %s\n' "$1" >&2 - exit 1 - ;; - *) - printf 'ERROR: too many arguments\n' >&2 - exit 1 - ;; - esac - done + CONFIG="$DEFAULT_CONFIG" - [ -r "$CONFIG" ] || error "config $CONFIG not readable" - - if ! mkdir "$LOCK_DIR" 2>/dev/null; then - info "Update already in progress. Exiting." + while [ "$#" -gt 0 ]; do + case "$1" in + -h) + printf 'usage: gitpages process_queue [-c config]\n' return 0 - fi + ;; + -c) + [ "$#" -ge 2 ] || { + printf 'ERROR: -c requires a config file\n' >&2 + exit 1 + } + CONFIG="$2" + shift 2 + ;; + -*) + printf 'ERROR: unknown option: %s\n' "$1" >&2 + exit 1 + ;; + *) + printf 'ERROR: too many arguments\n' >&2 + exit 1 + ;; + esac + done - trap 'rmdir "$LOCK_DIR"' EXIT + [ -r "$CONFIG" ] || error "config $CONFIG not readable" - if [ ! -d "$QUEUE_DIR" ]; then - info "Queue directory not found: $QUEUE_DIR" - return 0 - fi + if ! mkdir "$LOCK_DIR" 2> /dev/null; then + info "Update already in progress. Exiting." + return 0 + fi - processed=0 + trap 'rmdir "$LOCK_DIR"' EXIT - for jobfile in "$QUEUE_DIR"/*; do - [ -f "$jobfile" ] || continue + if [ ! -d "$QUEUE_DIR" ]; then + info "Queue directory not found: $QUEUE_DIR" + return 0 + fi - repo_path=$(cat "$jobfile") + processed=0 - if [ -d "$repo_path" ]; then - info "Starting process_repo for: $repo_path (job: $jobfile)" + for jobfile in "$QUEUE_DIR"/*; do + [ -f "$jobfile" ] || continue - if process_repo "$CONFIG" "$repo_path"; then - processed=$((processed + 1)) - rm -f "$jobfile" - else - error "Failed to process_repo $repo_path (job: $jobfile)" - fi - else - printf 'error: Repository directory %s not found (job: %s)\n' \ - "$repo_path" "$jobfile" >&2 - fi - done + repo_path=$(cat "$jobfile") + + if [ -d "$repo_path" ]; then + info "Starting process_repo for: $repo_path (job: $jobfile)" - if [ "$processed" -gt 0 ]; then - info "process_queue completed." + if process_repo "$CONFIG" "$repo_path"; then + processed=$((processed + 1)) + rm -f "$jobfile" + else + printf 'error: failed to process %s (job: %s)\n' "$repo_path" "$jobfile" >&2 + fi + else + printf 'error: Repository directory %s not found (job: %s)\n' \ + "$repo_path" "$jobfile" >&2 fi + done + + if [ "$processed" -gt 0 ]; then + info "process_queue completed." + fi } cmd_mirror() { - case "$#" in - 2) - mirror "$1" "$2" - ;; - *) - error "usage: gitpages mirror SRC DST_DIR" - ;; - esac + case "$#" in + 2) + mirror "$1" "$2" + ;; + *) + error "usage: gitpages mirror SRC DST_DIR" + ;; + esac } cmd_generate() { @@ -519,6 +506,8 @@ cmd_generate() { [ -n "$config" ] || config="$DEFAULT_CONFIG" + check_deps + if [ -n "$repo" ]; then generate_repo "$config" "$repo" else @@ -526,8 +515,6 @@ cmd_generate() { fi } - - [ $# -gt 0 ] || usage command="$1" diff --git a/test-gitpages-process-queue.sh b/test-gitpages-process-queue.sh @@ -64,6 +64,7 @@ cat << EOF > "$CONFIG_FILE" WEB_ROOT=$TEST_DIR GIT_RAW=$GIT_RAW GIT_HTML=$GIT_HTML +GIT_SRC=$GIT_SRC EOF echo "Running test on gitpages.sh..."