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

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
fly.toml

17
Caddyfile Normal file
View file

@ -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
}
}

61
Dockerfile Normal file
View file

@ -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"]

3
Procfile Normal file
View file

@ -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

76
README.md Normal file
View file

@ -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://<appname>.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
```

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

49
fly.toml.tmpl Normal file
View file

@ -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

58
forgejo-app.ini.tmpl Normal file
View file

@ -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

129
setup.sh Executable file
View file

@ -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