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

gitpages.sh (4458B)


      1 #!/bin/sh
      2 #
      3 # Copyright (c) 2026 Fred Großkopf
      4 #
      5 # Permission to use, copy, modify, and/or distribute this software for any
      6 # purpose with or without fee is hereby granted, provided that the above
      7 # copyright notice and this permission notice appear in all copies.
      8 #
      9 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16 
     17 set -eu
     18 
     19 DEFAULT_CONFIG="/etc/gitpages.conf"
     20 
     21 # --- Secure Config Parser ---
     22 get_config_val() {
     23   awk -F '=' -v key="$1" '
     24     $1 ~ /^[[:space:]]*[a-zA-Z0-9_]+[[:space:]]*$/ && $1 == key {
     25       gsub(/[[:space:]]+$/, "", $2);
     26       gsub(/^[[:space:]]+/, "", $2);
     27       print $2;
     28       exit;
     29     }
     30   ' "$CONFIG"
     31 }
     32 
     33 # --- Utility Functions ---
     34 error() { printf 'error: %s\n' "$1" >&2; exit 1; }
     35 info() { printf 'info: %s\n' "$1"; }
     36 sed_escape() { printf '%s\n' "$1" | sed 's/[\\/&]/\\&/g'; }
     37 
     38 # --- Validation ---
     39 check_deps() {
     40   command -v stagit > /dev/null 2>&1 || error "stagit not found"
     41   command -v stagit-index > /dev/null 2>&1 || error "stagit-index not found"
     42   command -v git > /dev/null 2>&1 || error "git not found"
     43 }
     44 
     45 # --- Core Logic ---
     46 generate_repo_html() {
     47   rm -rf "$STAGING_DIR"
     48   mkdir -p "$STAGING_DIR"
     49   cd "$STAGING_DIR"
     50   stagit "$REPO_SRC" >/dev/null 2>&1
     51 }
     52 
     53 process_repo_titles() {
     54   [ -n "${REPO_TITLE_DESC:-}" ] || return 0
     55   esc_desc=$(sed_escape "$REPO_TITLE_DESC")
     56   find "$STAGING_DIR" -name "*.html" -type f -exec sed -i \
     57     "s| - $REPO_BASE - [^<]*| - $REPO_BASE$esc_desc|g" {} +
     58 }
     59 
     60 generate_index() {
     61   # 1. Verify destination path exists
     62   [ -d "$WEB_ROOT/$GIT_HTML" ] || { printf 'Error: %s/html not found\n' "$WEB_ROOT"; return 1; }
     63 
     64   # 2. Use a subshell to avoid persistent cd and ensure we run from the right place
     65   (
     66     cd "$WEB_ROOT/$GIT_HTML" || exit 1
     67     
     68     # 3. Check if we have repositories before running the command
     69     # Use 'find' to safely handle the list of repos
     70     repos=$(find "$SRC_DIR" -maxdepth 1 -name "*.git")
     71     
     72     if [ -n "$repos" ]; then
     73       stagit-index $repos > index.html
     74     else
     75       printf 'No repositories found in %s\n' "$SRC_DIR"
     76     fi
     77   )
     78 }
     79 
     80 process_index() {
     81   [ -f "$WEB_ROOT/$GIT_HTML/index.html" ] || return 0
     82   [ -n "${INDEX_TITLE:-}" ] && {
     83     esc_title=$(sed_escape "$INDEX_TITLE")
     84     sed -i "s|<title>Repositories</title>|<title>$esc_title</title>|g" "$WEB_ROOT/$GIT_HTML/index.html"
     85   }
     86   [ -n "${INDEX_DESC:-}" ] && {
     87     esc_desc=$(sed_escape "$INDEX_DESC")
     88     sed -i "s|<span class=\"desc\">Repositories</span>|<span class=\"desc\">$esc_desc</span>|g" "$WEB_ROOT/$GIT_HTML/index.html"
     89   }
     90 }
     91 
     92 deploy_assets() {
     93   [ -d "${ASSETS_DIR:-}" ] || return 0
     94   mkdir -p "$STAGING_DIR"
     95   for f in "$ASSETS_DIR"/*; do
     96     [ -f "$f" ] && cp -L "$f" "$STAGING_DIR/$(basename "$f")"
     97   done
     98 }
     99 
    100 swap_target_dir() {
    101   rm -rf "$TARGET_DIR" 2>/dev/null || true
    102   mv "$STAGING_DIR" "$TARGET_DIR" || error "could not move staging into place"
    103 }
    104 
    105 main() {
    106   CONFIG="$DEFAULT_CONFIG"
    107   while getopts ":c:h" opt; do
    108     case "$opt" in
    109       c) CONFIG="$OPTARG" ;;
    110       h) printf 'usage: gitpages.sh [-c config] SRC\n'; exit 0 ;;
    111       *) exit 1 ;;
    112     esac
    113   done
    114   shift $((OPTIND - 1))
    115   [ $# -eq 1 ] || error "usage: gitpages.sh [-c config] SRC"
    116   
    117   # Load configuration
    118   [ -r "$CONFIG" ] || error "config $CONFIG not readable"
    119   WEB_ROOT=$(get_config_val "WEB_ROOT")
    120   GIT_HTML=$(get_config_val "GIT_HTML")
    121   GIT_RAW=$(get_config_val "GIT_RAW")
    122   INDEX_TITLE=$(get_config_val "INDEX_TITLE")
    123   INDEX_DESC=$(get_config_val "INDEX_DESCRIPTION")
    124   REPO_TITLE_DESC=$(get_config_val "REPO_TITLE_DESCRIPTION")
    125   ASSETS_DIR=$(get_config_val "ASSETS_DIR")
    126 
    127   # Setup environment context
    128   check_deps
    129   REPO_SRC="$1"
    130   REPO_BASE=$(basename "$REPO_SRC" ".git")
    131   SRC_DIR="$WEB_ROOT/$GIT_RAW"
    132   VAR_DIR="${GITPAGES_VAR_DIR:-/var/gitpages}"
    133   TARGET_DIR="$WEB_ROOT/$GIT_HTML/$REPO_BASE"
    134   STAGING_DIR="$VAR_DIR/staging/$REPO_BASE"
    135 
    136   # Execution
    137   generate_repo_html
    138   deploy_assets
    139   process_repo_titles
    140   swap_target_dir
    141   generate_index
    142   process_index
    143   
    144   info "Generation complete for $REPO_BASE"
    145 }
    146 
    147 main "$@"