update
This commit is contained in:
@@ -5,6 +5,13 @@ HTTP_PORT=8680
|
|||||||
HTTPS_PORT=8643
|
HTTPS_PORT=8643
|
||||||
GITEA_SSH_PORT=2222
|
GITEA_SSH_PORT=2222
|
||||||
|
|
||||||
|
# letsencrypt: manage a certificate automatically with Certbot.
|
||||||
|
# custom: read the named certificate files from ./config/ssl.
|
||||||
|
SSL_MODE=letsencrypt
|
||||||
|
SSL_CERTIFICATE_DIR=./config/letsencrypt/live/broker.example.com
|
||||||
|
SSL_CERTIFICATE_FILE=fullchain.pem
|
||||||
|
SSL_CERTIFICATE_KEY_FILE=privkey.pem
|
||||||
|
|
||||||
TAPM_LISTEN_ADDR=:8080
|
TAPM_LISTEN_ADDR=:8080
|
||||||
TAPM_DATABASE_DSN=file:/data/tapm.db?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)
|
TAPM_DATABASE_DSN=file:/data/tapm.db?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)
|
||||||
TAPM_PUBLIC_URL=https://broker.example.com
|
TAPM_PUBLIC_URL=https://broker.example.com
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ services:
|
|||||||
nginx:
|
nginx:
|
||||||
volumes:
|
volumes:
|
||||||
- ./deploy/nginx/templates/tls.conf.template:/etc/nginx/templates/default.conf.template:ro
|
- ./deploy/nginx/templates/tls.conf.template:/etc/nginx/templates/default.conf.template:ro
|
||||||
|
- ${SSL_CERTIFICATE_DIR:-./config/ssl}:/etc/nginx/ssl:ro
|
||||||
|
|||||||
+2
-1
@@ -37,11 +37,12 @@ services:
|
|||||||
- "${HTTPS_PORT:-8643}:443"
|
- "${HTTPS_PORT:-8643}:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./deploy/nginx/templates/bootstrap.conf.template:/etc/nginx/templates/default.conf.template:ro
|
- ./deploy/nginx/templates/bootstrap.conf.template:/etc/nginx/templates/default.conf.template:ro
|
||||||
- ./config/letsencrypt:/etc/letsencrypt:ro
|
|
||||||
- ./config/certbot-webroot:/var/www/certbot:ro
|
- ./config/certbot-webroot:/var/www/certbot:ro
|
||||||
environment:
|
environment:
|
||||||
BROKER_DOMAIN: ${BROKER_DOMAIN}
|
BROKER_DOMAIN: ${BROKER_DOMAIN}
|
||||||
GITEA_DOMAIN: ${GITEA_DOMAIN}
|
GITEA_DOMAIN: ${GITEA_DOMAIN}
|
||||||
|
SSL_CERTIFICATE_FILE: ${SSL_CERTIFICATE_FILE:-fullchain.pem}
|
||||||
|
SSL_CERTIFICATE_KEY_FILE: ${SSL_CERTIFICATE_KEY_FILE:-privkey.pem}
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
!gitea/config/.gitkeep
|
!gitea/config/.gitkeep
|
||||||
!letsencrypt/
|
!letsencrypt/
|
||||||
!letsencrypt/.gitkeep
|
!letsencrypt/.gitkeep
|
||||||
|
!ssl/
|
||||||
|
!ssl/.gitkeep
|
||||||
!certbot-webroot/
|
!certbot-webroot/
|
||||||
!certbot-webroot/.gitkeep
|
!certbot-webroot/.gitkeep
|
||||||
!backups/
|
!backups/
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -28,8 +28,8 @@ server {
|
|||||||
http2 on;
|
http2 on;
|
||||||
server_name ${BROKER_DOMAIN};
|
server_name ${BROKER_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/${BROKER_DOMAIN}/fullchain.pem;
|
ssl_certificate /etc/nginx/ssl/${SSL_CERTIFICATE_FILE};
|
||||||
ssl_certificate_key /etc/letsencrypt/live/${BROKER_DOMAIN}/privkey.pem;
|
ssl_certificate_key /etc/nginx/ssl/${SSL_CERTIFICATE_KEY_FILE};
|
||||||
ssl_session_cache shared:SSL:10m;
|
ssl_session_cache shared:SSL:10m;
|
||||||
ssl_session_timeout 1d;
|
ssl_session_timeout 1d;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
@@ -56,8 +56,8 @@ server {
|
|||||||
http2 on;
|
http2 on;
|
||||||
server_name ${GITEA_DOMAIN};
|
server_name ${GITEA_DOMAIN};
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/${BROKER_DOMAIN}/fullchain.pem;
|
ssl_certificate /etc/nginx/ssl/${SSL_CERTIFICATE_FILE};
|
||||||
ssl_certificate_key /etc/letsencrypt/live/${BROKER_DOMAIN}/privkey.pem;
|
ssl_certificate_key /etc/nginx/ssl/${SSL_CERTIFICATE_KEY_FILE};
|
||||||
ssl_session_cache shared:SSL:10m;
|
ssl_session_cache shared:SSL:10m;
|
||||||
ssl_session_timeout 1d;
|
ssl_session_timeout 1d;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|||||||
+26
-6
@@ -57,6 +57,7 @@ config/
|
|||||||
│ ├── config/ # app.ini and Gitea configuration
|
│ ├── config/ # app.ini and Gitea configuration
|
||||||
│ └── data/ # repositories, packages, and gitea.db
|
│ └── data/ # repositories, packages, and gitea.db
|
||||||
├── letsencrypt/ # account data, certificate, and private key
|
├── letsencrypt/ # account data, certificate, and private key
|
||||||
|
├── ssl/ # optional administrator-provided certificate
|
||||||
├── certbot-webroot/ # HTTP-01 challenge files
|
├── certbot-webroot/ # HTTP-01 challenge files
|
||||||
└── backups/ # local offline snapshots
|
└── backups/ # local offline snapshots
|
||||||
```
|
```
|
||||||
@@ -70,8 +71,24 @@ The contents are ignored by Git. They must never be committed.
|
|||||||
```
|
```
|
||||||
|
|
||||||
This prepares the runtime directories, creates the private Docker network,
|
This prepares the runtime directories, creates the private Docker network,
|
||||||
starts Gitea and the HTTP-only Nginx configuration, obtains one certificate
|
starts Gitea and the HTTP-only Nginx configuration, obtains a certificate when
|
||||||
covering both domains, and switches Nginx to TLS.
|
using Let's Encrypt, and switches Nginx to TLS.
|
||||||
|
|
||||||
|
For an administrator-provided certificate instead, place the certificate and
|
||||||
|
key in `config/ssl/` and set:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
SSL_MODE=custom
|
||||||
|
SSL_CERTIFICATE_DIR=./config/ssl
|
||||||
|
SSL_CERTIFICATE_FILE=fullchain.pem
|
||||||
|
SSL_CERTIFICATE_KEY_FILE=privkey.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
The certificate must cover both `BROKER_DOMAIN` and `GITEA_DOMAIN`. With custom
|
||||||
|
mode, `bootstrap` skips Certbot and `renew` is intentionally unavailable;
|
||||||
|
replace the files through the certificate provider's process and restart or
|
||||||
|
reload Nginx. `SSL_CERTIFICATE_DIR` can point at another directory relative to
|
||||||
|
the repository or at an absolute host path.
|
||||||
|
|
||||||
Create the initial Gitea administrator:
|
Create the initial Gitea administrator:
|
||||||
|
|
||||||
@@ -122,7 +139,7 @@ the transition.
|
|||||||
|
|
||||||
## 6. Renewal and backups
|
## 6. Renewal and backups
|
||||||
|
|
||||||
Run renewal twice daily from root's crontab:
|
When `SSL_MODE=letsencrypt`, run renewal twice daily from root's crontab:
|
||||||
|
|
||||||
```cron
|
```cron
|
||||||
17 3,15 * * * cd /YOUR/INSTALL/PARENT/TA-Deployment-Access && ./manage.sh renew
|
17 3,15 * * * cd /YOUR/INSTALL/PARENT/TA-Deployment-Access && ./manage.sh renew
|
||||||
@@ -142,10 +159,13 @@ datacenter loss. Also back up `.env` through a secrets-aware system.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
cd /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
||||||
git pull --ff-only
|
./update.sh
|
||||||
docker compose --env-file .env -f deploy/gitea/compose.yaml pull
|
|
||||||
./manage.sh start
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The updater operates only on the current VM. It fast-forwards the checked-out
|
||||||
|
branch, pulls the pinned Gitea image, rebuilds and restarts the local stack, and
|
||||||
|
waits for the local broker to become healthy. It has no peer discovery, SSH, or
|
||||||
|
multi-node update behavior.
|
||||||
|
|
||||||
Pin image versions as supplied and review release notes before changing them.
|
Pin image versions as supplied and review release notes before changing them.
|
||||||
Never change Gitea between rootless and rootful image families in place.
|
Never change Gitea between rootless and rootful image families in place.
|
||||||
|
|||||||
@@ -31,9 +31,32 @@ require_env() {
|
|||||||
BROKER_DOMAIN="$(read_env_setting BROKER_DOMAIN)"
|
BROKER_DOMAIN="$(read_env_setting BROKER_DOMAIN)"
|
||||||
GITEA_DOMAIN="$(read_env_setting GITEA_DOMAIN)"
|
GITEA_DOMAIN="$(read_env_setting GITEA_DOMAIN)"
|
||||||
LETSENCRYPT_EMAIL="$(read_env_setting LETSENCRYPT_EMAIL)"
|
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}"
|
: "${BROKER_DOMAIN:?BROKER_DOMAIN is required}"
|
||||||
: "${GITEA_DOMAIN:?GITEA_DOMAIN is required}"
|
: "${GITEA_DOMAIN:?GITEA_DOMAIN is required}"
|
||||||
: "${LETSENCRYPT_EMAIL:?LETSENCRYPT_EMAIL is required}"
|
case "$SSL_MODE" in
|
||||||
|
letsencrypt)
|
||||||
|
: "${LETSENCRYPT_EMAIL:?LETSENCRYPT_EMAIL is required for SSL_MODE=letsencrypt}"
|
||||||
|
SSL_CERTIFICATE_DIR="${SSL_CERTIFICATE_DIR:-./config/letsencrypt/live/${BROKER_DOMAIN}}"
|
||||||
|
;;
|
||||||
|
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}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "SSL_MODE must be letsencrypt or custom"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
export SSL_CERTIFICATE_DIR SSL_CERTIFICATE_FILE SSL_CERTIFICATE_KEY_FILE
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
prepare() {
|
||||||
@@ -42,6 +65,7 @@ prepare() {
|
|||||||
config/gitea/data \
|
config/gitea/data \
|
||||||
config/gitea/config \
|
config/gitea/config \
|
||||||
config/letsencrypt \
|
config/letsencrypt \
|
||||||
|
config/ssl \
|
||||||
config/certbot-webroot \
|
config/certbot-webroot \
|
||||||
config/backups
|
config/backups
|
||||||
chmod 700 config/broker config/gitea/data config/gitea/config config/backups
|
chmod 700 config/broker config/gitea/data config/gitea/config config/backups
|
||||||
@@ -66,12 +90,14 @@ bootstrap)
|
|||||||
prepare
|
prepare
|
||||||
gitea_compose up -d
|
gitea_compose up -d
|
||||||
docker compose up -d nginx
|
docker compose up -d nginx
|
||||||
docker compose --profile tools run --rm certbot certonly \
|
if [[ "$SSL_MODE" == "letsencrypt" ]]; then
|
||||||
--webroot --webroot-path /var/www/certbot \
|
docker compose --profile tools run --rm certbot certonly \
|
||||||
--non-interactive --agree-tos \
|
--webroot --webroot-path /var/www/certbot \
|
||||||
--email "$LETSENCRYPT_EMAIL" \
|
--non-interactive --agree-tos \
|
||||||
--cert-name "$BROKER_DOMAIN" \
|
--email "$LETSENCRYPT_EMAIL" \
|
||||||
-d "$BROKER_DOMAIN" -d "$GITEA_DOMAIN"
|
--cert-name "$BROKER_DOMAIN" \
|
||||||
|
-d "$BROKER_DOMAIN" -d "$GITEA_DOMAIN"
|
||||||
|
fi
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml up -d nginx
|
docker compose -f compose.yaml -f compose.tls.yaml up -d nginx
|
||||||
;;
|
;;
|
||||||
start)
|
start)
|
||||||
@@ -82,6 +108,8 @@ start)
|
|||||||
;;
|
;;
|
||||||
renew)
|
renew)
|
||||||
require_env
|
require_env
|
||||||
|
[[ "$SSL_MODE" == "letsencrypt" ]] ||
|
||||||
|
fail "automatic renewal is only available with SSL_MODE=letsencrypt"
|
||||||
docker compose --profile tools run --rm certbot renew --quiet
|
docker compose --profile tools run --rm certbot renew --quiet
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml exec nginx nginx -s reload
|
docker compose -f compose.yaml -f compose.tls.yaml exec nginx nginx -s reload
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
DEPLOY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||||
|
SCRIPT_PATH="${DEPLOY_ROOT}/$(basename -- "${BASH_SOURCE[0]}")"
|
||||||
|
cd "$DEPLOY_ROOT"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'ERROR: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
command -v git >/dev/null 2>&1 || fail "git is required"
|
||||||
|
command -v docker >/dev/null 2>&1 || fail "docker is required"
|
||||||
|
docker compose version >/dev/null 2>&1 ||
|
||||||
|
fail "Docker Compose v2 is required"
|
||||||
|
[[ -f .env ]] || fail ".env was not found in ${DEPLOY_ROOT}"
|
||||||
|
|
||||||
|
post_pull=0
|
||||||
|
if [[ "${1:-}" == "--post-pull" ]]; then
|
||||||
|
post_pull=1
|
||||||
|
elif [[ $# -ne 0 ]]; then
|
||||||
|
fail "usage: ${0} [--post-pull]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((post_pull == 0)); then
|
||||||
|
branch="$(git symbolic-ref --quiet --short HEAD)" ||
|
||||||
|
fail "the repository is in detached HEAD state"
|
||||||
|
upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null)" ||
|
||||||
|
fail "branch ${branch} does not have an upstream branch"
|
||||||
|
remote="$(git config --get "branch.${branch}.remote")" ||
|
||||||
|
fail "branch ${branch} does not have a configured remote"
|
||||||
|
|
||||||
|
git diff --quiet && git diff --cached --quiet ||
|
||||||
|
fail "tracked local changes must be committed or stashed before updating"
|
||||||
|
|
||||||
|
printf 'Checking %s against %s...\n' "$branch" "$upstream"
|
||||||
|
git fetch --quiet --prune "$remote"
|
||||||
|
|
||||||
|
local_commit="$(git rev-parse HEAD)"
|
||||||
|
upstream_commit="$(git rev-parse '@{upstream}')"
|
||||||
|
if [[ "$local_commit" == "$upstream_commit" ]]; then
|
||||||
|
printf 'Deployment is already up to date.\n'
|
||||||
|
elif git merge-base --is-ancestor "$local_commit" "$upstream_commit"; then
|
||||||
|
git merge --quiet --ff-only "$upstream_commit"
|
||||||
|
# Restart using the newly pulled copy so changes to this script apply.
|
||||||
|
exec "$SCRIPT_PATH" --post-pull
|
||||||
|
elif git merge-base --is-ancestor "$upstream_commit" "$local_commit"; then
|
||||||
|
fail "local branch ${branch} is ahead of ${upstream}; refusing to update"
|
||||||
|
else
|
||||||
|
fail "local branch ${branch} has diverged from ${upstream}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'Updating the local Gitea image and deployment stack...\n'
|
||||||
|
docker compose --env-file .env -f deploy/gitea/compose.yaml pull
|
||||||
|
"${DEPLOY_ROOT}/manage.sh" start
|
||||||
|
|
||||||
|
container_id="$(docker compose -f compose.yaml -f compose.tls.yaml ps -q broker)"
|
||||||
|
[[ -n "$container_id" ]] ||
|
||||||
|
fail "Docker Compose did not return the local broker container"
|
||||||
|
|
||||||
|
printf 'Waiting for the local broker health check'
|
||||||
|
for ((attempt = 1; attempt <= 75; attempt++)); do
|
||||||
|
status="$(
|
||||||
|
docker inspect \
|
||||||
|
--format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \
|
||||||
|
"$container_id"
|
||||||
|
)"
|
||||||
|
case "$status" in
|
||||||
|
healthy)
|
||||||
|
printf ' healthy.\n'
|
||||||
|
docker compose -f compose.yaml -f compose.tls.yaml ps
|
||||||
|
docker compose --env-file .env -f deploy/gitea/compose.yaml ps
|
||||||
|
printf 'Local deployment updated successfully.\n'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
exited | dead | unhealthy)
|
||||||
|
printf ' %s.\n' "$status"
|
||||||
|
docker compose -f compose.yaml -f compose.tls.yaml logs --tail=100 broker
|
||||||
|
fail "the local broker failed its health check"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
printf '.'
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
printf ' timed out.\n'
|
||||||
|
docker compose -f compose.yaml -f compose.tls.yaml logs --tail=100 broker
|
||||||
|
fail "the local broker did not become healthy within 150 seconds"
|
||||||
Reference in New Issue
Block a user