FossilRepo

fossilrepo / docker / fossil-shell
Source Blame History 65 lines
c588255… ragelink 1 #!/bin/bash
c588255… ragelink 2 # fossil-shell — Forced command for SSH-based Fossil clone/push/pull.
c588255… ragelink 3 #
c588255… ragelink 4 # Each authorized_keys entry uses:
c588255… ragelink 5 # command="/usr/local/bin/fossil-shell <username>",no-port-forwarding,...
c588255… ragelink 6 #
c588255… ragelink 7 # When a Fossil client connects via SSH, it sends a command like:
c588255… ragelink 8 # fossil http /path/to/repo.fossil
c588255… ragelink 9 # which arrives in $SSH_ORIGINAL_COMMAND.
c588255… ragelink 10 #
c588255… ragelink 11 # This script:
c588255… ragelink 12 # 1. Extracts the repo name from the SSH command
c588255… ragelink 13 # 2. Maps it to the on-disk .fossil file
c588255… ragelink 14 # 3. Runs fossil http in CGI mode with --localauth
c588255… ragelink 15 #
c588255… ragelink 16 # Auth is already handled by the SSH key → user mapping in authorized_keys.
c588255… ragelink 17
c588255… ragelink 18 set -euo pipefail
c588255… ragelink 19
c588255… ragelink 20 FOSSIL_USER="${1:-anonymous}"
c588255… ragelink 21 REPO_DIR="${FOSSIL_DATA_DIR:-/data/repos}"
c588255… ragelink 22
c588255… ragelink 23 # Validate SSH_ORIGINAL_COMMAND
c588255… ragelink 24 if [ -z "${SSH_ORIGINAL_COMMAND:-}" ]; then
c588255… ragelink 25 echo "Error: Interactive SSH sessions are not supported." >&2
c588255… ragelink 26 echo "Use: fossil clone ssh://fossil@<host>/<project-slug> local.fossil" >&2
c588255… ragelink 27 exit 1
c588255… ragelink 28 fi
c588255… ragelink 29
c588255… ragelink 30 # Fossil SSH sends: fossil http <repo-path> --args...
c588255… ragelink 31 # We only allow "fossil http" commands.
c588255… ragelink 32 if ! echo "$SSH_ORIGINAL_COMMAND" | grep -qE '^fossil\s+http\s+'; then
c588255… ragelink 33 echo "Error: Only fossil http commands are allowed." >&2
c588255… ragelink 34 exit 1
c588255… ragelink 35 fi
c588255… ragelink 36
c588255… ragelink 37 # Extract the repo identifier (second argument after "fossil http")
c588255… ragelink 38 REPO_ARG=$(echo "$SSH_ORIGINAL_COMMAND" | awk '{print $3}')
c588255… ragelink 39
c588255… ragelink 40 if [ -z "$REPO_ARG" ]; then
c588255… ragelink 41 echo "Error: No repository specified." >&2
c588255… ragelink 42 exit 1
c588255… ragelink 43 fi
c588255… ragelink 44
c588255… ragelink 45 # Strip any path components — only allow bare slugs or slug.fossil
c588255… ragelink 46 REPO_NAME=$(basename "$REPO_ARG" .fossil)
c588255… ragelink 47
c588255… ragelink 48 # Sanitize: only allow alphanumeric, hyphens, underscores
c588255… ragelink 49 if ! echo "$REPO_NAME" | grep -qE '^[a-zA-Z0-9_-]+$'; then
c588255… ragelink 50 echo "Error: Invalid repository name." >&2
c588255… ragelink 51 exit 1
c588255… ragelink 52 fi
c588255… ragelink 53
c588255… ragelink 54 REPO_PATH="${REPO_DIR}/${REPO_NAME}.fossil"
c588255… ragelink 55
c588255… ragelink 56 if [ ! -f "$REPO_PATH" ]; then
c588255… ragelink 57 echo "Error: Repository '${REPO_NAME}' not found." >&2
c588255… ragelink 58 exit 1
c588255… ragelink 59 fi
c588255… ragelink 60
c588255… ragelink 61 # Log the access
c588255… ragelink 62 logger -t fossil-shell "user=${FOSSIL_USER} repo=${REPO_NAME} action=ssh-sync"
c588255… ragelink 63
c588255… ragelink 64 # Run fossil http in CGI mode
c588255… ragelink 65 exec fossil http "$REPO_PATH" --localauth

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button