From 27c4191be8a5d5da41e2cdbcad70bfbb066ecf32 Mon Sep 17 00:00:00 2001 From: ryuuseijin Date: Mon, 17 Aug 2026 22:47:22 +0800 Subject: [PATCH] init --- .gitignore | 1 + Caddyfile | 17 +++++ Dockerfile | 61 ++++++++++++++++++ Procfile | 3 + README.md | 76 ++++++++++++++++++++++ bin/backup-init.sh | 16 +++++ bin/backup-restore.sh | 54 ++++++++++++++++ bin/debug-deploy.sh | 27 ++++++++ bin/initialize-forgejo.sh | 44 +++++++++++++ bin/prepare-caddy.sh | 35 +++++++++++ bin/prepare-forgejo.sh | 85 +++++++++++++++++++++++++ bin/run-forgejo.sh | 5 ++ bin/run-restic-backups.sh | 57 +++++++++++++++++ bin/start-services.sh | 13 ++++ fly.toml.tmpl | 49 +++++++++++++++ forgejo-app.ini.tmpl | 58 +++++++++++++++++ setup.sh | 129 ++++++++++++++++++++++++++++++++++++++ 17 files changed, 730 insertions(+) create mode 100644 .gitignore create mode 100644 Caddyfile create mode 100644 Dockerfile create mode 100644 Procfile create mode 100644 README.md create mode 100755 bin/backup-init.sh create mode 100755 bin/backup-restore.sh create mode 100755 bin/debug-deploy.sh create mode 100755 bin/initialize-forgejo.sh create mode 100755 bin/prepare-caddy.sh create mode 100755 bin/prepare-forgejo.sh create mode 100755 bin/run-forgejo.sh create mode 100755 bin/run-restic-backups.sh create mode 100755 bin/start-services.sh create mode 100644 fly.toml.tmpl create mode 100644 forgejo-app.ini.tmpl create mode 100755 setup.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0501d09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +fly.toml diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..343939e --- /dev/null +++ b/Caddyfile @@ -0,0 +1,17 @@ +{ + auto_https off + admin off +} + +:8080 { + handle_path /healthz { + respond "ok" 200 + } + + import /etc/caddy/auth.caddy + + reverse_proxy 127.0.0.1:3000 { + lb_try_duration 30s + lb_try_interval 250ms + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b5de59c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +FROM golang:1.25-alpine AS tools + +RUN apk add --no-cache git +RUN GOBIN=/usr/local/bin go install github.com/caddyserver/caddy/v2/cmd/caddy@v2.10.2 +RUN GOBIN=/usr/local/bin go install github.com/DarthSim/hivemind@latest + +FROM debian:bookworm-slim + +ARG DEBIAN_FRONTEND=noninteractive +ARG TARGETARCH +ARG FORGEJO_VERSION=11.0.14 + +ENV LANG=C.UTF-8 +ENV GITEA_WORK_DIR=/data/forgejo +ENV GITEA_CUSTOM=/data/forgejo/custom +ENV GITEA_TEMP=/tmp/gitea +ENV GITEA_APP_INI=/data/forgejo/custom/conf/app.ini +ENV FORGEJO_HOME=/data/forgejo/git + +RUN apt-get -qq update && \ + apt-get -qq install --no-install-recommends -y \ + bash \ + ca-certificates \ + crudini \ + curl \ + gettext-base \ + git \ + locales \ + openssh-client \ + restic \ + sqlite3 \ + tzdata \ + xz-utils && \ + apt-get -qq clean && \ + rm -rf /var/lib/apt/lists/* +RUN printf 'C.UTF-8 UTF-8\n' > /etc/locale.gen && \ + locale-gen && \ + groupadd -g 1001 forgejo && \ + useradd -d /data/forgejo/git -M -u 1001 -g forgejo -s /bin/sh forgejo + +RUN arch="${TARGETARCH:-$(dpkg --print-architecture)}" && \ + case "$arch" in \ + amd64|arm64) forgejo_arch="$arch" ;; \ + *) echo "Unsupported Forgejo architecture: $arch" >&2; exit 1 ;; \ + esac && \ + curl -fLo /usr/local/bin/forgejo "https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${forgejo_arch}" && \ + chmod 755 /usr/local/bin/forgejo && \ + ln -sf /usr/local/bin/forgejo /usr/local/bin/gitea + +RUN mkdir -p /etc/caddy /etc/templates /etc/auth /tmp/gitea /data /data/forgejo + +COPY --from=tools /usr/local/bin/caddy /usr/local/bin/hivemind /usr/local/bin/ +COPY Caddyfile /etc/caddy/Caddyfile +COPY Procfile /etc/Procfile +COPY forgejo-app.ini.tmpl /etc/templates/forgejo-app.ini.tmpl +COPY bin/ /usr/local/bin/ + +RUN chmod 755 /usr/local/bin/* && \ + chown -R forgejo:forgejo /data/forgejo /tmp/gitea + +CMD ["/usr/local/bin/start-services.sh"] diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..739fd44 --- /dev/null +++ b/Procfile @@ -0,0 +1,3 @@ +caddy: /usr/local/bin/caddy run --config /etc/caddy/Caddyfile --adapter caddyfile +forgejo: /usr/local/bin/run-forgejo.sh +backup: /usr/local/bin/run-restic-backups.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a3af18 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# Forgejo on Fly.io + +Standalone Forgejo deployment for Fly.io with one initial admin, optional Caddy HTTP basic auth, and optional restic backups. + +## Setup + +Install and authenticate the [Fly CLI](https://fly.io/docs/flyctl/install/), then run: + +```bash +./setup.sh +``` + +The script prompts for: + +- A globally unique Fly app name +- A Fly region +- The initial Forgejo admin username and email +- Whether to enable Caddy HTTP basic auth and, if enabled, its single username and password + +It creates the Fly app, writes the generated `fly.toml`, stores credentials as Fly secrets, and deploys Forgejo with a 10 GB persistent volume. The Forgejo admin password is generated securely and printed before and after deployment. Save it when displayed. + +The app is available at `https://.fly.dev/`. Registration is disabled, so the generated admin is the only account on a new installation. Add any later users through Forgejo's admin interface. + +## Access Control + +Two independent settings restrict access by default: + +- Forgejo's `GITEA__service__REQUIRE_SIGNIN_VIEW` is set to `true`, so anonymous visitors cannot view Forgejo content. +- Caddy basic auth is enabled when both `CADDY_BASIC_AUTH_USERNAME` and `CADDY_BASIC_AUTH_PASSWORD` are set. The setup script can configure them for you. Git HTTP endpoints and the package API remain outside Caddy authentication so Git and package clients can authenticate directly with Forgejo. + +To make the instance publicly viewable, both controls must be disabled: leave the Caddy credentials unset and change `GITEA__service__REQUIRE_SIGNIN_VIEW` to `false` in the generated `fly.toml`, then run `fly deploy`. + +To enable or change Caddy authentication later: + +```bash +fly secrets set CADDY_BASIC_AUTH_USERNAME=forge CADDY_BASIC_AUTH_PASSWORD='your-password' +``` + +To disable it, unset both secrets: + +```bash +fly secrets unset CADDY_BASIC_AUTH_USERNAME CADDY_BASIC_AUTH_PASSWORD +``` + +## Backups + +Set all restic configuration as Fly secrets. Backups stay disabled until every value is present. + +```bash +fly secrets set \ + AWS_ACCESS_KEY_ID='...' \ + AWS_SECRET_ACCESS_KEY='...' \ + RESTIC_REPOSITORY='s3:https://.../forgejo' \ + RESTIC_PASSWORD='...' +``` + +Initialize the repository once: + +```bash +fly ssh console --command "/usr/local/bin/backup-init.sh" +``` + +The backup process snapshots `/data` hourly when all required variables are available. To restore a snapshot from inside a stopped or debug machine: + +```bash +backup-restore.sh latest +``` + +## Debug Startup + +Set `DEBUGDEPLOY` to keep a machine alive without starting services, or use: + +```bash +./bin/debug-deploy.sh start +./bin/debug-deploy.sh stop +``` diff --git a/bin/backup-init.sh b/bin/backup-init.sh new file mode 100755 index 0000000..3f36248 --- /dev/null +++ b/bin/backup-init.sh @@ -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." diff --git a/bin/backup-restore.sh b/bin/backup-restore.sh new file mode 100755 index 0000000..2257b7a --- /dev/null +++ b/bin/backup-restore.sh @@ -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." diff --git a/bin/debug-deploy.sh b/bin/debug-deploy.sh new file mode 100755 index 0000000..dc23d8c --- /dev/null +++ b/bin/debug-deploy.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -eu + +usage() { + printf 'Usage: %s \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 diff --git a/bin/initialize-forgejo.sh b/bin/initialize-forgejo.sh new file mode 100755 index 0000000..6d291ae --- /dev/null +++ b/bin/initialize-forgejo.sh @@ -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 diff --git a/bin/prepare-caddy.sh b/bin/prepare-caddy.sh new file mode 100755 index 0000000..2aff866 --- /dev/null +++ b/bin/prepare-caddy.sh @@ -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" < "$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" diff --git a/bin/run-forgejo.sh b/bin/run-forgejo.sh new file mode 100755 index 0000000..47eff15 --- /dev/null +++ b/bin/run-forgejo.sh @@ -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" diff --git a/bin/run-restic-backups.sh b/bin/run-restic-backups.sh new file mode 100755 index 0000000..bf7e3af --- /dev/null +++ b/bin/run-restic-backups.sh @@ -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 diff --git a/bin/start-services.sh b/bin/start-services.sh new file mode 100755 index 0000000..bb0117f --- /dev/null +++ b/bin/start-services.sh @@ -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 diff --git a/fly.toml.tmpl b/fly.toml.tmpl new file mode 100644 index 0000000..e341f99 --- /dev/null +++ b/fly.toml.tmpl @@ -0,0 +1,49 @@ +app = '__APP_NAME__' +primary_region = '__PRIMARY_REGION__' +swap_size_mb = 1024 + +[build] + dockerfile = 'Dockerfile' + +[env] + GITEA__default__APP_NAME = 'Forgejo' + GITEA__default__APP_SLOGAN = 'Self-hosted Git service' + GITEA__log__REQUEST_ID_HEADERS = 'Fly-Client-IP' + GITEA__other__SHOW_FOOTER_VERSION = 'false' + GITEA__packages__ENABLED = 'true' + GITEA__security__COOKIE_REMEMBER_NAME = 'forgejo_remember' + GITEA__security__INSTALL_LOCK = 'true' + GITEA__security__LOGIN_REMEMBER_DAYS = '10' + GITEA__security__REVERSE_PROXY_LIMIT = '2' + GITEA__server__DISABLE_SSH = 'true' + GITEA__server__HTTP_ADDR = '127.0.0.1' + GITEA__server__LANDING_PAGE = 'explore' + GITEA__server__OFFLINE_MODE = 'false' + GITEA__server__ROOT_URL = 'https://__APP_NAME__.fly.dev/' + GITEA__server__START_SSH_SERVER = 'false' + GITEA__service__DISABLE_REGISTRATION = 'true' + GITEA__service__REQUIRE_SIGNIN_VIEW = 'true' + GITEA__session__COOKIE_NAME = 'forgejo_session' + GITEA__session__SAME_SITE = 'strict' + +[[mounts]] + source = 'data' + destination = '/data' + initial_size = '10gb' + snapshot_retention = 20 + auto_extend_size_threshold = 80 + auto_extend_size_increment = '5gb' + auto_extend_size_limit = '50gb' + +[http_service] + internal_port = 8080 + force_https = true + auto_stop_machines = false + auto_start_machines = true + min_machines_running = 1 + processes = ['app'] + +[[vm]] + memory = '2048mb' + cpu_kind = 'shared' + cpus = 4 diff --git a/forgejo-app.ini.tmpl b/forgejo-app.ini.tmpl new file mode 100644 index 0000000..d15f510 --- /dev/null +++ b/forgejo-app.ini.tmpl @@ -0,0 +1,58 @@ +APP_NAME = $APP_NAME +RUN_USER = $RUN_USER +RUN_MODE = $RUN_MODE + +[repository] +ROOT = $GITEA_WORK_DIR/git/repositories + +[repository.local] +LOCAL_COPY_PATH = $GITEA_TEMP/local-repo + +[repository.upload] +TEMP_PATH = $GITEA_TEMP/uploads + +[server] +APP_DATA_PATH = $GITEA_WORK_DIR +SSH_DOMAIN = $SSH_DOMAIN +HTTP_PORT = $HTTP_PORT +ROOT_URL = $ROOT_URL +DISABLE_SSH = $DISABLE_SSH +START_SSH_SERVER = true +SSH_PORT = $SSH_PORT +SSH_LISTEN_PORT = $SSH_LISTEN_PORT +BUILTIN_SSH_SERVER_USER = $RUN_USER +LFS_START_SERVER = $LFS_START_SERVER + +[database] +PATH = $GITEA_WORK_DIR/data/gitea.db +DB_TYPE = $DB_TYPE +HOST = $DB_HOST +NAME = $DB_NAME +USER = $DB_USER +PASSWD = $DB_PASSWD + +[session] +PROVIDER_CONFIG = $GITEA_WORK_DIR/data/sessions + +[picture] +AVATAR_UPLOAD_PATH = $GITEA_WORK_DIR/data/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = $GITEA_WORK_DIR/data/repo-avatars + +[attachment] +PATH = $GITEA_WORK_DIR/data/attachments + +[log] +ROOT_PATH = $GITEA_WORK_DIR/data/log + +[security] +INSTALL_LOCK = $INSTALL_LOCK +SECRET_KEY = $SECRET_KEY +REVERSE_PROXY_LIMIT = 1 +REVERSE_PROXY_TRUSTED_PROXIES = * + +[service] +DISABLE_REGISTRATION = $DISABLE_REGISTRATION +REQUIRE_SIGNIN_VIEW = $REQUIRE_SIGNIN_VIEW + +[lfs] +PATH = $GITEA_WORK_DIR/git/lfs diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..6b81b6e --- /dev/null +++ b/setup.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(CDPATH= cd -- "$(dirname "$0")" && pwd)" +template="$repo_root/fly.toml.tmpl" +config="$repo_root/fly.toml" +cd "$repo_root" + +for command_name in fly openssl sed tr; do + if ! command -v "$command_name" >/dev/null 2>&1; then + printf 'Required command not found: %s\n' "$command_name" >&2 + exit 1 + fi +done + +prompt() { + local variable_name=$1 + local message=$2 + local default_value=${3:-} + local value + + if [ -n "$default_value" ]; then + read -r -p "$message [$default_value]: " value + value=${value:-$default_value} + else + while [ -z "${value:-}" ]; do + read -r -p "$message: " value + done + fi + printf -v "$variable_name" '%s' "$value" +} + +prompt app_name 'Fly app name' +case "$app_name" in + -*|*-|*[!a-z0-9-]*) + printf 'App names may only contain lowercase letters, numbers, and internal hyphens.\n' >&2 + exit 1 + ;; +esac + +prompt primary_region 'Fly primary region' 'nrt' +case "$primary_region" in + *[!a-z0-9]*) + printf 'Invalid Fly region: %s\n' "$primary_region" >&2 + exit 1 + ;; +esac + +prompt admin_username 'Forgejo admin username' 'admin' +case "$admin_username" in + -*|*-|*[!A-Za-z0-9._-]*) + printf 'The Forgejo username may only contain letters, numbers, ., _, and internal hyphens.\n' >&2 + exit 1 + ;; +esac +prompt admin_email 'Forgejo admin email' "admin@$app_name.fly.dev" +if [[ "$admin_email" != *@* ]]; then + printf 'Invalid email address: %s\n' "$admin_email" >&2 + exit 1 +fi + +caddy_username= +caddy_password= +read -r -p 'Enable Caddy HTTP basic auth? [y/N]: ' enable_caddy_auth +case "$enable_caddy_auth" in + y|Y|yes|YES) + prompt caddy_username 'Caddy basic auth username' "$admin_username" + case "$caddy_username" in + *[!A-Za-z0-9._@-]*) + printf 'The Caddy username may only contain letters, numbers, ., _, @, and -.\n' >&2 + exit 1 + ;; + esac + + while [ -z "$caddy_password" ]; do + read -r -s -p 'Caddy basic auth password: ' caddy_password + printf '\n' + done + read -r -s -p 'Confirm Caddy basic auth password: ' caddy_password_confirmation + printf '\n' + if [ "$caddy_password" != "$caddy_password_confirmation" ]; then + printf 'Caddy passwords do not match.\n' >&2 + exit 1 + fi + ;; + ''|n|N|no|NO) + ;; + *) + printf 'Please answer yes or no.\n' >&2 + exit 1 + ;; +esac + +admin_password="$(openssl rand -base64 24 | tr -d '\n')" + +sed \ + -e "s/__APP_NAME__/$app_name/g" \ + -e "s/__PRIMARY_REGION__/$primary_region/g" \ + "$template" > "$config" + +printf '\nGenerated Forgejo admin credentials:\n' +printf ' Username: %s\n' "$admin_username" +printf ' Password: %s\n' "$admin_password" + +printf '\nCreating Fly app %s...\n' "$app_name" +fly apps create "$app_name" + +secrets=( + "FORGEJO_ADMIN_USERNAME=$admin_username" + "FORGEJO_ADMIN_EMAIL=$admin_email" + "FORGEJO_ADMIN_PASSWORD=$admin_password" +) +if [ -n "$caddy_username" ]; then + secrets+=( + "CADDY_BASIC_AUTH_USERNAME=$caddy_username" + "CADDY_BASIC_AUTH_PASSWORD=$caddy_password" + ) +fi +fly secrets set --stage --app "$app_name" "${secrets[@]}" + +printf '\nDeploying Forgejo...\n' +fly deploy --config "$config" + +printf '\nForgejo is available at https://%s.fly.dev/\n' "$app_name" +printf 'Admin username: %s\n' "$admin_username" +printf 'Admin password: %s\n' "$admin_password" +if [ -n "$caddy_username" ]; then + printf 'Caddy basic auth username: %s\n' "$caddy_username" +fi