This commit is contained in:
ryuuseijin 2026-08-17 22:47:22 +08:00
commit 27c4191be8
17 changed files with 730 additions and 0 deletions

16
bin/backup-init.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
set -euo pipefail
: "${AWS_ACCESS_KEY_ID:?AWS_ACCESS_KEY_ID must be set}"
: "${AWS_SECRET_ACCESS_KEY:?AWS_SECRET_ACCESS_KEY must be set}"
: "${RESTIC_REPOSITORY:?RESTIC_REPOSITORY must be set}"
: "${RESTIC_PASSWORD:?RESTIC_PASSWORD must be set}"
if restic snapshots >/dev/null 2>&1; then
echo "Restic repository is already initialized."
exit 0
fi
echo "Initializing restic repository at $RESTIC_REPOSITORY..."
restic init
echo "Restic repository initialized successfully."

54
bin/backup-restore.sh Executable file
View file

@ -0,0 +1,54 @@
#!/bin/bash
set -euo pipefail
usage() {
cat >&2 <<'EOF'
Usage:
backup-restore.sh [snapshot]
Environment:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
RESTIC_REPOSITORY
RESTIC_PASSWORD
EOF
exit 1
}
if [ $# -gt 1 ]; then
usage
fi
: "${AWS_ACCESS_KEY_ID:?AWS_ACCESS_KEY_ID must be set}"
: "${AWS_SECRET_ACCESS_KEY:?AWS_SECRET_ACCESS_KEY must be set}"
: "${RESTIC_REPOSITORY:?RESTIC_REPOSITORY must be set}"
: "${RESTIC_PASSWORD:?RESTIC_PASSWORD must be set}"
snapshot="${1:-latest}"
restore_path='/data/forgejo'
clear_directory() {
local dir=$1
mkdir -p "$dir"
find "$dir" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
}
staging_dir="$(mktemp -d)"
cleanup() {
rm -rf "$staging_dir"
}
trap cleanup EXIT INT TERM
echo "Restoring snapshot '$snapshot' from $RESTIC_REPOSITORY into $restore_path ..."
restic restore "$snapshot" --target "$staging_dir" --include "$restore_path"
restored_path="$staging_dir$restore_path"
if [ ! -e "$restored_path" ]; then
echo "Snapshot '$snapshot' did not contain $restore_path" >&2
exit 1
fi
clear_directory "$restore_path"
cp -a "$restored_path"/. "$restore_path"/
echo "Restore completed at $restore_path."

27
bin/debug-deploy.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/sh
set -eu
usage() {
printf 'Usage: %s <start|stop>\n' "$0" >&2
exit 1
}
script_dir="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
repo_root="$(dirname "$script_dir")"
config_path="$repo_root/fly.toml"
[ $# -eq 1 ] || usage
case "$1" in
start)
printf 'Enabling DEBUGDEPLOY using %s\n' "$config_path"
exec fly secrets set -c "$config_path" DEBUGDEPLOY=1
;;
stop)
printf 'Disabling DEBUGDEPLOY using %s\n' "$config_path"
exec fly secrets unset -c "$config_path" DEBUGDEPLOY
;;
*)
usage
;;
esac

44
bin/initialize-forgejo.sh Executable file
View file

@ -0,0 +1,44 @@
#!/bin/sh
set -eu
: "${FORGEJO_ADMIN_USERNAME:?FORGEJO_ADMIN_USERNAME must be set}"
: "${FORGEJO_ADMIN_EMAIL:?FORGEJO_ADMIN_EMAIL must be set}"
: "${FORGEJO_ADMIN_PASSWORD:?FORGEJO_ADMIN_PASSWORD must be set}"
shell_quote() {
printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
}
run_forgejo() {
command="exec /usr/local/bin/forgejo"
for argument in "$@"; do
command="$command $(shell_quote "$argument")"
done
su forgejo -s /bin/sh -c "$command"
}
if ! run_forgejo -c "$GITEA_APP_INI" migrate >/tmp/forgejo-migrate.log 2>&1; then
cat /tmp/forgejo-migrate.log >&2
exit 1
fi
if run_forgejo -c "$GITEA_APP_INI" admin user create \
--username "$FORGEJO_ADMIN_USERNAME" \
--password "$FORGEJO_ADMIN_PASSWORD" \
--email "$FORGEJO_ADMIN_EMAIL" \
--admin \
--must-change-password=false >/tmp/forgejo-admin-create.log 2>&1; then
printf 'Created Forgejo admin user %s.\n' "$FORGEJO_ADMIN_USERNAME"
exit 0
fi
run_forgejo -c "$GITEA_APP_INI" admin user list >/tmp/forgejo-admin-list.log 2>/dev/null || true
while read -r _id username _rest; do
if [ "$username" = "$FORGEJO_ADMIN_USERNAME" ]; then
printf 'Forgejo admin user %s already exists; leaving it unchanged.\n' "$FORGEJO_ADMIN_USERNAME"
exit 0
fi
done < /tmp/forgejo-admin-list.log
cat /tmp/forgejo-admin-create.log >&2
exit 1

35
bin/prepare-caddy.sh Executable file
View file

@ -0,0 +1,35 @@
#!/bin/sh
set -eu
auth_file=/etc/caddy/auth.caddy
username="${CADDY_BASIC_AUTH_USERNAME:-}"
password="${CADDY_BASIC_AUTH_PASSWORD:-}"
if [ -z "$username" ] && [ -z "$password" ]; then
: > "$auth_file"
exit 0
fi
if [ -z "$username" ] || [ -z "$password" ]; then
printf 'CADDY_BASIC_AUTH_USERNAME and CADDY_BASIC_AUTH_PASSWORD must both be set.\n' >&2
exit 1
fi
case "$username" in
*[!A-Za-z0-9._@-]*)
printf 'CADDY_BASIC_AUTH_USERNAME contains unsupported characters.\n' >&2
exit 1
;;
esac
password_hash="$(/usr/local/bin/caddy hash-password --plaintext "$password")"
cat > "$auth_file" <<EOF
@protected {
not path /healthz
not path_regexp public ^(/[^/]+/[^/]+\.git/.*|/api/packages(/.*)?)$
}
basic_auth @protected {
$username $password_hash
}
EOF

85
bin/prepare-forgejo.sh Executable file
View file

@ -0,0 +1,85 @@
#!/bin/sh
set -eu
: "${GITEA_WORK_DIR:=/data/forgejo}"
: "${GITEA_CUSTOM:=$GITEA_WORK_DIR/custom}"
: "${GITEA_TEMP:=/tmp/gitea}"
: "${GITEA_APP_INI:=$GITEA_CUSTOM/conf/app.ini}"
: "${FORGEJO_HOME:=/data/forgejo/git}"
sed_escape() {
printf '%s' "$1" | sed 's/[\\/&]/\\&/g'
}
set_top_level_key() {
key="$1"
value="$2"
escaped_value="$(sed_escape "$value")"
if grep -q "^$key[[:space:]]*=" "$GITEA_APP_INI"; then
sed -i "s/^$key[[:space:]]*=.*/$key = $escaped_value/" "$GITEA_APP_INI"
return
fi
tmp_file="$(mktemp)"
{
printf '%s = %s\n' "$key" "$value"
cat "$GITEA_APP_INI"
} > "$tmp_file"
mv "$tmp_file" "$GITEA_APP_INI"
}
mkdir -p \
"$FORGEJO_HOME" \
"$GITEA_CUSTOM" \
"$GITEA_TEMP" \
"$GITEA_WORK_DIR/data" \
"$GITEA_WORK_DIR/git/repositories" \
"$GITEA_WORK_DIR/git/lfs" \
"$(dirname "$GITEA_APP_INI")"
chown -R forgejo:forgejo "$GITEA_WORK_DIR" "$GITEA_TEMP"
chmod 700 "$FORGEJO_HOME" "$GITEA_CUSTOM" "$GITEA_TEMP" "$(dirname "$GITEA_APP_INI")"
APP_NAME="${APP_NAME:-Forgejo: Beyond coding. We forge.}"
RUN_MODE="${RUN_MODE:-prod}"
RUN_USER="${RUN_USER:-forgejo}"
if [ ! -f "$GITEA_APP_INI" ]; then
SSH_DOMAIN="${SSH_DOMAIN:-localhost}"
HTTP_PORT="${HTTP_PORT:-3000}"
ROOT_URL="${ROOT_URL:-${GITEA__server__ROOT_URL:-}}"
DISABLE_SSH="${DISABLE_SSH:-false}"
SSH_PORT="${SSH_PORT:-2222}"
SSH_LISTEN_PORT="${SSH_LISTEN_PORT:-$SSH_PORT}"
LFS_START_SERVER="${LFS_START_SERVER:-true}"
DB_TYPE="${DB_TYPE:-sqlite3}"
DB_HOST="${DB_HOST:-localhost:3306}"
DB_NAME="${DB_NAME:-gitea}"
DB_USER="${DB_USER:-root}"
DB_PASSWD="${DB_PASSWD:-}"
INSTALL_LOCK="${INSTALL_LOCK:-false}"
DISABLE_REGISTRATION="${DISABLE_REGISTRATION:-false}"
REQUIRE_SIGNIN_VIEW="${REQUIRE_SIGNIN_VIEW:-false}"
SECRET_KEY="${SECRET_KEY:-}"
export APP_NAME RUN_MODE RUN_USER SSH_DOMAIN HTTP_PORT ROOT_URL DISABLE_SSH SSH_PORT SSH_LISTEN_PORT LFS_START_SERVER DB_TYPE DB_HOST DB_NAME DB_USER DB_PASSWD INSTALL_LOCK DISABLE_REGISTRATION REQUIRE_SIGNIN_VIEW SECRET_KEY
envsubst < /etc/templates/forgejo-app.ini.tmpl > "$GITEA_APP_INI"
fi
set_top_level_key APP_NAME "$APP_NAME"
set_top_level_key RUN_USER "$RUN_USER"
set_top_level_key RUN_MODE "$RUN_MODE"
crudini --set "$GITEA_APP_INI" server BUILTIN_SSH_SERVER_USER "$RUN_USER"
printenv | while IFS='=' read -r name value; do
case "$name" in
GITEA__*__*)
rest="${name#GITEA__}"
section="${rest%%__*}"
key="${rest#*__}"
crudini --set "$GITEA_APP_INI" "$section" "$key" "$value"
;;
esac
done
chown forgejo:forgejo "$GITEA_APP_INI"

5
bin/run-forgejo.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
set -eu
/usr/local/bin/prepare-forgejo.sh
exec su forgejo -s /bin/sh -c "exec /usr/local/bin/forgejo -c \"$GITEA_APP_INI\" web"

57
bin/run-restic-backups.sh Executable file
View file

@ -0,0 +1,57 @@
#!/bin/bash
set -uo pipefail
required_vars=(
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
RESTIC_REPOSITORY
RESTIC_PASSWORD
)
missing_vars=()
for var_name in "${required_vars[@]}"; do
if [ -z "${!var_name:-}" ]; then
missing_vars+=("$var_name")
fi
done
if [ "${#missing_vars[@]}" -gt 0 ]; then
echo "Restic backups disabled; missing ${missing_vars[*]}." >&2
exec sleep infinity
fi
BACKUP_INTERVAL_SECONDS="${RESTIC_BACKUP_INTERVAL_SECONDS:-3600}"
BACKUP_PATHS=("/data")
KEEP_HOURLY=24
KEEP_DAILY=7
KEEP_WEEKLY=4
KEEP_MONTHLY=6
run_backup() {
echo "Starting restic backup..."
restic backup "${BACKUP_PATHS[@]}" || return $?
echo "Pruning old backups..."
restic forget \
--keep-hourly "$KEEP_HOURLY" \
--keep-daily "$KEEP_DAILY" \
--keep-weekly "$KEEP_WEEKLY" \
--keep-monthly "$KEEP_MONTHLY" \
--prune || return $?
echo "Checking repository..."
restic check || return $?
echo "Backup completed successfully."
}
while true; do
if run_backup; then
:
else
status=$?
echo "Restic backup failed with status $status." >&2
fi
sleep "$BACKUP_INTERVAL_SECONDS"
done

13
bin/start-services.sh Executable file
View file

@ -0,0 +1,13 @@
#!/bin/sh
set -eu
if [ -n "${DEBUGDEPLOY:-}" ]; then
printf 'DEBUGDEPLOY is set; skipping startup and waiting indefinitely.\n' >&2
exec sleep infinity
fi
/usr/local/bin/prepare-caddy.sh
/usr/local/bin/prepare-forgejo.sh
/usr/local/bin/initialize-forgejo.sh
exec /usr/local/bin/hivemind /etc/Procfile