#!/usr/bin/env bash set -Eeuo pipefail DEPLOY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" cd "$DEPLOY_ROOT" fail() { printf 'ERROR: %s\n' "$*" >&2 exit 1 } prompt_default() { local prompt="$1" local fallback="$2" local value printf '%s [%s]: ' "$prompt" "$fallback" >&2 read -r value printf '%s' "${value:-$fallback}" } valid_hostname() { [[ "$1" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$ && "$1" != *..* ]] } valid_port() { [[ "$1" =~ ^[0-9]+$ ]] && (( 10#$1 >= 1 && 10#$1 <= 65535 )) } [[ $# -eq 0 ]] || fail "usage: ./install.sh" [[ ! -e .env ]] || fail ".env already exists; use ./manage.sh instead of overwriting this deployment" for required_file in \ compose.yaml \ compose.http.yaml \ compose.tls.yaml \ deploy/nginx/templates/http.conf.template \ deploy/nginx/templates/tls.conf.template; do [[ -f "$required_file" ]] || fail "repository file is missing or not a regular file: ${required_file}" done for command_name in docker openssl; do command -v "$command_name" >/dev/null 2>&1 || fail "$command_name is required" done docker compose version >/dev/null 2>&1 || fail "Docker Compose v2 is required" docker info >/dev/null 2>&1 || fail "the Docker daemon is unavailable or this user cannot access it" printf '\nTAPM Deployment Access installer\n' printf 'This creates the initial environment and starts Git behind Nginx.\n' printf 'The broker starts only after its Git OAuth and package credentials are configured.\n\n' broker_domain="$(prompt_default 'Broker hostname' 'tapm.example.com')" gitea_domain="$(prompt_default 'Git hostname' 'git.example.com')" valid_hostname "$broker_domain" || fail "invalid broker hostname" valid_hostname "$gitea_domain" || fail "invalid Git hostname" [[ "$broker_domain" != "$gitea_domain" ]] || fail "broker and Git hostnames must be different" http_port="$(prompt_default 'VM port mapped to Nginx HTTP port 80' '8680')" https_port="$(prompt_default 'VM port mapped to Nginx HTTPS port 443' '8643')" valid_port "$http_port" || fail "invalid HTTP port" valid_port "$https_port" || fail "invalid HTTPS port" [[ "$http_port" != "$https_port" ]] || fail "HTTP and HTTPS ports must differ" ssl_mode="$(prompt_default 'SSL mode (none, letsencrypt, or custom)' 'none')" case "$ssl_mode" in none) public_scheme=http public_port_suffix=":${http_port}" letsencrypt_email='' certificate_dir="./config/letsencrypt/live/${broker_domain}" ;; letsencrypt) public_scheme=https public_port_suffix='' printf 'Let'\''s Encrypt account email: ' read -r letsencrypt_email [[ "$letsencrypt_email" == *@* ]] || fail "a valid email is required" certificate_dir="./config/letsencrypt/live/${broker_domain}" ;; custom) public_scheme=https public_port_suffix='' letsencrypt_email='' certificate_dir='./config/ssl' ;; *) fail "SSL mode must be none, letsencrypt, or custom" ;; esac cookie_secret="$(openssl rand -base64 48)" umask 077 { printf 'BROKER_DOMAIN=%s\n' "$broker_domain" printf 'GITEA_DOMAIN=%s\n' "$gitea_domain" printf 'GITEA_PUBLIC_URL=%s://%s%s/\n' "$public_scheme" "$gitea_domain" "$public_port_suffix" printf 'LETSENCRYPT_EMAIL=%s\n' "$letsencrypt_email" printf 'HTTP_PORT=%s\n' "$http_port" printf 'HTTPS_PORT=%s\n\n' "$https_port" printf 'BROKER_DIRECT_PORT=8080\n' printf 'GITEA_DIRECT_PORT=3000\n' printf 'GITEA_DIRECT_ROOT_URL=http://localhost:3000/\n\n' printf 'SSL_MODE=%s\n' "$ssl_mode" printf 'SSL_CERTIFICATE_DIR=%s\n' "$certificate_dir" printf 'SSL_CERTIFICATE_FILE=fullchain.pem\n' printf 'SSL_CERTIFICATE_KEY_FILE=privkey.pem\n\n' printf 'TAPM_DATABASE_DSN=file:/data/tapm.db?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)\n' printf 'TAPM_PUBLIC_URL=%s://%s%s\n' "$public_scheme" "$broker_domain" "$public_port_suffix" printf 'TAPM_GITEA_URL=%s://%s%s\n' "$public_scheme" "$gitea_domain" "$public_port_suffix" printf 'TAPM_GITEA_INTERNAL_URL=http://gitea:3000\n' printf 'TAPM_GITEA_CLIENT_ID=\n' printf 'TAPM_GITEA_CLIENT_SECRET=\n' printf 'TAPM_GITEA_PACKAGE_OWNER=TAI\n' printf 'TAPM_GITEA_PACKAGE_USERNAME=\n' printf 'TAPM_GITEA_PACKAGE_TOKEN=\n' printf 'TAPM_GITEA_PACKAGE_WRITE_USERNAME=\n' printf 'TAPM_GITEA_PACKAGE_WRITE_TOKEN=\n' printf 'TAPM_ALLOWED_GITEA_USERS=taiadmin\n' printf 'TAPM_COOKIE_SECRET=%s\n' "$cookie_secret" printf 'TAPM_DEFAULT_DURATION=7d\n' printf 'TAPM_DEFAULT_HOST_LIMIT=3\n' printf 'TAPM_MAX_HOST_LIMIT=25\n' printf 'TAPM_MAX_UPLOAD_BYTES=1073741824\n' printf 'TAPM_TRUST_PROXY_HEADERS=true\n' printf 'TAPM_DISPLAY_TIME_ZONE=America/Chicago\n' } >.env chmod 600 .env printf '\nInitial configuration saved to %s/.env.\n' "$DEPLOY_ROOT" if [[ "$ssl_mode" == "custom" ]]; then printf 'Place the certificate and key in config/ssl, then run ./manage.sh bootstrap.\n' printf 'Git has not been started yet.\n' exit 0 fi "${DEPLOY_ROOT}/manage.sh" bootstrap printf '\nInitial Git deployment is running.\n' printf 'Git URL: %s://%s%s\n' "$public_scheme" "$gitea_domain" "$public_port_suffix" if [[ "$ssl_mode" == "none" ]]; then printf 'This is HTTP-only testing mode. Do not expose it publicly.\n' printf 'Make both hostnames resolve to this VM or its test-forwarding address.\n' fi printf '\nNext steps:\n' printf ' 1. Run ./manage.sh create-admin\n' printf ' 2. Sign in to Git and create the organization, package users/tokens,\n' printf ' and OAuth application documented in docs/deployment.md.\n' printf ' 3. Run ./manage.sh configure-broker\n' printf ' 4. Run ./manage.sh start\n' if [[ "$ssl_mode" == "none" ]]; then printf ' 5. Later, after DNS and public 80/443 forwarding are ready, run:\n' printf ' ./manage.sh set-ssl letsencrypt\n' printf ' ./manage.sh bootstrap\n' printf ' ./manage.sh start\n' fi