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