365 lines
12 KiB
Bash
Executable File
365 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
DEPLOY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
cd "$DEPLOY_ROOT"
|
|
declare -a COMPOSE_FILES=(-f compose.yaml)
|
|
|
|
fail() {
|
|
printf 'ERROR: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
read_env_setting() {
|
|
local key="$1"
|
|
local value
|
|
value="$(
|
|
sed -n "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*//p" .env |
|
|
tail -n 1
|
|
)"
|
|
value="${value#"${value%%[![:space:]]*}"}"
|
|
value="${value%"${value##*[![:space:]]}"}"
|
|
value="${value#\"}"
|
|
value="${value%\"}"
|
|
value="${value#\'}"
|
|
value="${value%\'}"
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
set_env_setting() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local temporary_file
|
|
|
|
temporary_file="$(mktemp "${DEPLOY_ROOT}/.env.tmp.XXXXXX")" ||
|
|
fail "unable to create temporary environment file"
|
|
awk -v key="$key" -v value="$value" '
|
|
BEGIN { replaced = 0 }
|
|
$0 ~ "^[[:space:]]*" key "[[:space:]]*=" {
|
|
if (!replaced) {
|
|
print key "=" value
|
|
replaced = 1
|
|
}
|
|
next
|
|
}
|
|
{ print }
|
|
END {
|
|
if (!replaced) print key "=" value
|
|
}
|
|
' .env >"$temporary_file"
|
|
chmod 600 "$temporary_file"
|
|
mv -f "$temporary_file" .env
|
|
}
|
|
|
|
select_compose_stack() {
|
|
COMPOSE_FILES=(-f compose.yaml)
|
|
if [[ "$SSL_MODE" == "none" ]]; then
|
|
COMPOSE_FILES+=(-f compose.http.yaml)
|
|
else
|
|
COMPOSE_FILES+=(-f compose.tls.yaml)
|
|
fi
|
|
}
|
|
|
|
compose_stack() {
|
|
docker compose "${COMPOSE_FILES[@]}" "$@"
|
|
}
|
|
|
|
broker_config_ready() {
|
|
local key
|
|
local value
|
|
local -a required=(
|
|
TAPM_GITEA_CLIENT_ID
|
|
TAPM_GITEA_CLIENT_SECRET
|
|
TAPM_GITEA_PACKAGE_USERNAME
|
|
TAPM_GITEA_PACKAGE_TOKEN
|
|
TAPM_GITEA_PACKAGE_WRITE_USERNAME
|
|
TAPM_GITEA_PACKAGE_WRITE_TOKEN
|
|
TAPM_COOKIE_SECRET
|
|
)
|
|
|
|
for key in "${required[@]}"; do
|
|
value="$(read_env_setting "$key")"
|
|
if [[ -z "$value" || "$value" == replace-* ]]; then
|
|
printf 'Missing broker setting: %s\n' "$key" >&2
|
|
return 1
|
|
fi
|
|
done
|
|
value="$(read_env_setting TAPM_COOKIE_SECRET)"
|
|
if (( ${#value} < 32 )); then
|
|
printf 'TAPM_COOKIE_SECRET must contain at least 32 characters.\n' >&2
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
require_env() {
|
|
[[ -f .env ]] || fail "copy .env.example to .env and configure it first"
|
|
BROKER_DOMAIN="$(read_env_setting BROKER_DOMAIN)"
|
|
GITEA_DOMAIN="$(read_env_setting GITEA_DOMAIN)"
|
|
GITEA_PUBLIC_URL="$(read_env_setting GITEA_PUBLIC_URL)"
|
|
LETSENCRYPT_EMAIL="$(read_env_setting LETSENCRYPT_EMAIL)"
|
|
SSL_MODE="$(read_env_setting SSL_MODE)"
|
|
SSL_MODE="${SSL_MODE:-letsencrypt}"
|
|
SSL_CERTIFICATE_DIR="$(read_env_setting SSL_CERTIFICATE_DIR)"
|
|
SSL_CERTIFICATE_FILE="$(read_env_setting SSL_CERTIFICATE_FILE)"
|
|
SSL_CERTIFICATE_KEY_FILE="$(read_env_setting SSL_CERTIFICATE_KEY_FILE)"
|
|
SSL_CERTIFICATE_FILE="${SSL_CERTIFICATE_FILE:-fullchain.pem}"
|
|
SSL_CERTIFICATE_KEY_FILE="${SSL_CERTIFICATE_KEY_FILE:-privkey.pem}"
|
|
: "${BROKER_DOMAIN:?BROKER_DOMAIN is required}"
|
|
: "${GITEA_DOMAIN:?GITEA_DOMAIN is required}"
|
|
[[ "$BROKER_DOMAIN" != "$GITEA_DOMAIN" ]] ||
|
|
fail "BROKER_DOMAIN and GITEA_DOMAIN must be different hostnames"
|
|
if [[ -z "$GITEA_PUBLIC_URL" ]]; then
|
|
if [[ "$SSL_MODE" == "none" ]]; then
|
|
http_port="$(read_env_setting HTTP_PORT)"
|
|
http_port="${http_port:-8680}"
|
|
GITEA_PUBLIC_URL="http://${GITEA_DOMAIN}:${http_port}/"
|
|
else
|
|
GITEA_PUBLIC_URL="https://${GITEA_DOMAIN}/"
|
|
fi
|
|
fi
|
|
case "$SSL_MODE" in
|
|
none)
|
|
[[ "$(read_env_setting TAPM_PUBLIC_URL)" == http://* ]] ||
|
|
fail "TAPM_PUBLIC_URL must use http:// when SSL_MODE=none"
|
|
[[ "$(read_env_setting TAPM_GITEA_URL)" == http://* ]] ||
|
|
fail "TAPM_GITEA_URL must use http:// when SSL_MODE=none"
|
|
[[ "$GITEA_PUBLIC_URL" == http://* ]] ||
|
|
fail "GITEA_PUBLIC_URL must use http:// when SSL_MODE=none"
|
|
;;
|
|
letsencrypt)
|
|
: "${LETSENCRYPT_EMAIL:?LETSENCRYPT_EMAIL is required for SSL_MODE=letsencrypt}"
|
|
SSL_CERTIFICATE_DIR="${SSL_CERTIFICATE_DIR:-./config/letsencrypt/live/${BROKER_DOMAIN}}"
|
|
[[ "$(read_env_setting TAPM_PUBLIC_URL)" == https://* ]] ||
|
|
fail "TAPM_PUBLIC_URL must use https:// when TLS is enabled"
|
|
[[ "$(read_env_setting TAPM_GITEA_URL)" == https://* ]] ||
|
|
fail "TAPM_GITEA_URL must use https:// when TLS is enabled"
|
|
[[ "$GITEA_PUBLIC_URL" == https://* ]] ||
|
|
fail "GITEA_PUBLIC_URL must use https:// when TLS is enabled"
|
|
;;
|
|
custom)
|
|
SSL_CERTIFICATE_DIR="${SSL_CERTIFICATE_DIR:-./config/ssl}"
|
|
[[ -f "${SSL_CERTIFICATE_DIR}/${SSL_CERTIFICATE_FILE}" ]] ||
|
|
fail "custom certificate was not found at ${SSL_CERTIFICATE_DIR}/${SSL_CERTIFICATE_FILE}"
|
|
[[ -f "${SSL_CERTIFICATE_DIR}/${SSL_CERTIFICATE_KEY_FILE}" ]] ||
|
|
fail "custom certificate key was not found at ${SSL_CERTIFICATE_DIR}/${SSL_CERTIFICATE_KEY_FILE}"
|
|
[[ "$(read_env_setting TAPM_PUBLIC_URL)" == https://* ]] ||
|
|
fail "TAPM_PUBLIC_URL must use https:// when TLS is enabled"
|
|
[[ "$(read_env_setting TAPM_GITEA_URL)" == https://* ]] ||
|
|
fail "TAPM_GITEA_URL must use https:// when TLS is enabled"
|
|
[[ "$GITEA_PUBLIC_URL" == https://* ]] ||
|
|
fail "GITEA_PUBLIC_URL must use https:// when TLS is enabled"
|
|
;;
|
|
*)
|
|
fail "SSL_MODE must be none, letsencrypt, or custom"
|
|
;;
|
|
esac
|
|
export GITEA_PUBLIC_URL SSL_CERTIFICATE_DIR SSL_CERTIFICATE_FILE SSL_CERTIFICATE_KEY_FILE
|
|
select_compose_stack
|
|
}
|
|
|
|
prepare() {
|
|
mkdir -p \
|
|
config/broker \
|
|
config/gitea/data \
|
|
config/gitea/config \
|
|
config/letsencrypt \
|
|
config/ssl \
|
|
config/certbot-webroot \
|
|
config/backups
|
|
chmod 700 config/broker config/gitea/data config/gitea/config config/backups
|
|
if [[ "$(id -u)" == 0 ]]; then
|
|
chown -R 1000:1000 config/broker config/gitea
|
|
fi
|
|
docker network inspect tapm-edge >/dev/null 2>&1 ||
|
|
docker network create tapm-edge >/dev/null
|
|
}
|
|
|
|
case "${1:-}" in
|
|
prepare)
|
|
require_env
|
|
prepare
|
|
;;
|
|
bootstrap)
|
|
require_env
|
|
prepare
|
|
if [[ "$SSL_MODE" == "none" ]]; then
|
|
compose_stack up -d gitea nginx
|
|
elif [[ "$SSL_MODE" == "letsencrypt" ]]; then
|
|
docker compose up -d gitea nginx
|
|
docker compose --profile tools run --rm certbot certonly \
|
|
--webroot --webroot-path /var/www/certbot \
|
|
--non-interactive --agree-tos \
|
|
--email "$LETSENCRYPT_EMAIL" \
|
|
--cert-name "$BROKER_DOMAIN" \
|
|
-d "$BROKER_DOMAIN" -d "$GITEA_DOMAIN"
|
|
compose_stack up -d gitea nginx
|
|
else
|
|
compose_stack up -d gitea nginx
|
|
fi
|
|
;;
|
|
start)
|
|
require_env
|
|
prepare
|
|
broker_config_ready ||
|
|
fail "broker configuration is incomplete; run ./manage.sh configure-broker"
|
|
compose_stack up -d --build
|
|
;;
|
|
start-direct)
|
|
require_env
|
|
prepare
|
|
broker_config_ready ||
|
|
fail "broker configuration is incomplete; run ./manage.sh configure-broker"
|
|
docker compose \
|
|
"${COMPOSE_FILES[@]}" \
|
|
-f compose.direct.yaml \
|
|
up -d --build
|
|
;;
|
|
create-admin)
|
|
require_env
|
|
command -v openssl >/dev/null 2>&1 ||
|
|
fail "openssl is required to generate the temporary password"
|
|
printf 'Gitea administrator username [taiadmin]: '
|
|
read -r admin_username
|
|
admin_username="${admin_username:-taiadmin}"
|
|
printf 'Gitea administrator email: '
|
|
read -r admin_email
|
|
[[ -n "$admin_email" ]] || fail "an administrator email is required"
|
|
temporary_password="$(openssl rand -base64 24)"
|
|
docker compose exec -T gitea gitea admin user create \
|
|
--admin \
|
|
--username "$admin_username" \
|
|
--email "$admin_email" \
|
|
--password "$temporary_password" \
|
|
--must-change-password
|
|
printf '\nGitea administrator created.\n'
|
|
printf 'Username: %s\n' "$admin_username"
|
|
printf 'Temporary password: %s\n' "$temporary_password"
|
|
printf 'Sign in at %s and change the password immediately.\n' \
|
|
"$GITEA_PUBLIC_URL"
|
|
;;
|
|
configure-broker)
|
|
require_env
|
|
command -v openssl >/dev/null 2>&1 ||
|
|
fail "openssl is required to generate the cookie secret"
|
|
printf 'Create the Gitea OAuth application with this callback URL:\n'
|
|
printf ' %s/auth/callback\n\n' "$(read_env_setting TAPM_PUBLIC_URL)"
|
|
printf 'Gitea OAuth client ID: '
|
|
read -r oauth_client_id
|
|
[[ -n "$oauth_client_id" ]] || fail "OAuth client ID is required"
|
|
printf 'Gitea OAuth client secret: '
|
|
read -r -s oauth_client_secret
|
|
printf '\n'
|
|
[[ -n "$oauth_client_secret" ]] || fail "OAuth client secret is required"
|
|
printf 'Read-only package username [tapm-packages]: '
|
|
read -r package_username
|
|
package_username="${package_username:-tapm-packages}"
|
|
printf 'Read-only package token: '
|
|
read -r -s package_token
|
|
printf '\n'
|
|
[[ -n "$package_token" ]] || fail "read-only package token is required"
|
|
printf 'Package publisher username [tapm-publisher]: '
|
|
read -r publisher_username
|
|
publisher_username="${publisher_username:-tapm-publisher}"
|
|
printf 'Package publisher token: '
|
|
read -r -s publisher_token
|
|
printf '\n'
|
|
[[ -n "$publisher_token" ]] || fail "publisher token is required"
|
|
printf 'Allowed Gitea users [taiadmin]: '
|
|
read -r allowed_users
|
|
allowed_users="${allowed_users:-taiadmin}"
|
|
cookie_secret="$(read_env_setting TAPM_COOKIE_SECRET)"
|
|
if (( ${#cookie_secret} < 32 )); then
|
|
cookie_secret="$(openssl rand -base64 48)"
|
|
fi
|
|
set_env_setting TAPM_GITEA_CLIENT_ID "$oauth_client_id"
|
|
set_env_setting TAPM_GITEA_CLIENT_SECRET "$oauth_client_secret"
|
|
set_env_setting TAPM_GITEA_PACKAGE_USERNAME "$package_username"
|
|
set_env_setting TAPM_GITEA_PACKAGE_TOKEN "$package_token"
|
|
set_env_setting TAPM_GITEA_PACKAGE_WRITE_USERNAME "$publisher_username"
|
|
set_env_setting TAPM_GITEA_PACKAGE_WRITE_TOKEN "$publisher_token"
|
|
set_env_setting TAPM_ALLOWED_GITEA_USERS "$allowed_users"
|
|
set_env_setting TAPM_COOKIE_SECRET "$cookie_secret"
|
|
printf 'Broker credentials saved to .env with mode 0600.\n'
|
|
printf 'Start the complete stack with ./manage.sh start\n'
|
|
;;
|
|
set-ssl)
|
|
require_env
|
|
target_mode="${2:-}"
|
|
case "$target_mode" in
|
|
none)
|
|
http_port="$(read_env_setting HTTP_PORT)"
|
|
http_port="${http_port:-8680}"
|
|
set_env_setting SSL_MODE none
|
|
set_env_setting TAPM_PUBLIC_URL "http://${BROKER_DOMAIN}:${http_port}"
|
|
set_env_setting TAPM_GITEA_URL "http://${GITEA_DOMAIN}:${http_port}"
|
|
set_env_setting GITEA_PUBLIC_URL "http://${GITEA_DOMAIN}:${http_port}/"
|
|
printf 'HTTP testing mode configured. Apply it with ./manage.sh start\n'
|
|
;;
|
|
letsencrypt)
|
|
email="$(read_env_setting LETSENCRYPT_EMAIL)"
|
|
if [[ -z "$email" ]]; then
|
|
printf 'Let'\''s Encrypt account email: '
|
|
read -r email
|
|
[[ -n "$email" ]] || fail "an email is required for Let's Encrypt"
|
|
set_env_setting LETSENCRYPT_EMAIL "$email"
|
|
fi
|
|
set_env_setting SSL_MODE letsencrypt
|
|
set_env_setting SSL_CERTIFICATE_DIR "./config/letsencrypt/live/${BROKER_DOMAIN}"
|
|
set_env_setting TAPM_PUBLIC_URL "https://${BROKER_DOMAIN}"
|
|
set_env_setting TAPM_GITEA_URL "https://${GITEA_DOMAIN}"
|
|
set_env_setting GITEA_PUBLIC_URL "https://${GITEA_DOMAIN}/"
|
|
printf 'TLS URLs configured. Obtain the certificate with ./manage.sh bootstrap\n'
|
|
;;
|
|
custom)
|
|
set_env_setting SSL_MODE custom
|
|
set_env_setting SSL_CERTIFICATE_DIR ./config/ssl
|
|
set_env_setting TAPM_PUBLIC_URL "https://${BROKER_DOMAIN}"
|
|
set_env_setting TAPM_GITEA_URL "https://${GITEA_DOMAIN}"
|
|
set_env_setting GITEA_PUBLIC_URL "https://${GITEA_DOMAIN}/"
|
|
printf 'Custom TLS mode configured. Place the certificate files, then run ./manage.sh bootstrap\n'
|
|
;;
|
|
*)
|
|
fail "usage: ./manage.sh set-ssl {none|letsencrypt|custom}"
|
|
;;
|
|
esac
|
|
;;
|
|
renew)
|
|
require_env
|
|
[[ "$SSL_MODE" == "letsencrypt" ]] ||
|
|
fail "automatic renewal is only available with SSL_MODE=letsencrypt"
|
|
docker compose --profile tools run --rm certbot renew --quiet
|
|
compose_stack exec nginx nginx -s reload
|
|
;;
|
|
backup)
|
|
require_env
|
|
prepare
|
|
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
destination="config/backups/${stamp}"
|
|
mkdir -p "$destination"
|
|
docker compose stop broker
|
|
docker compose stop gitea
|
|
cp -a config/broker "$destination/"
|
|
cp -a config/gitea "$destination/"
|
|
compose_stack start gitea broker
|
|
printf 'Backup created at %s\n' "$destination"
|
|
;;
|
|
status)
|
|
require_env
|
|
compose_stack ps
|
|
;;
|
|
broker-container-id)
|
|
require_env
|
|
compose_stack ps -q broker
|
|
;;
|
|
broker-logs)
|
|
require_env
|
|
compose_stack logs --tail=100 broker
|
|
;;
|
|
*)
|
|
printf 'Usage: %s {prepare|bootstrap|configure-broker|set-ssl|start|start-direct|create-admin|renew|backup|status}\n' "$0"
|
|
exit 2
|
|
;;
|
|
esac
|