update installation
This commit is contained in:
+15
-11
@@ -1,6 +1,7 @@
|
|||||||
BROKER_DOMAIN=broker.example.com
|
BROKER_DOMAIN=broker.example.com
|
||||||
GITEA_DOMAIN=git.example.com
|
GITEA_DOMAIN=git.example.com
|
||||||
LETSENCRYPT_EMAIL=admin@example.com
|
GITEA_PUBLIC_URL=http://git.example.com:8680/
|
||||||
|
LETSENCRYPT_EMAIL=
|
||||||
HTTP_PORT=8680
|
HTTP_PORT=8680
|
||||||
HTTPS_PORT=8643
|
HTTPS_PORT=8643
|
||||||
|
|
||||||
@@ -9,25 +10,28 @@ BROKER_DIRECT_PORT=8080
|
|||||||
GITEA_DIRECT_PORT=3000
|
GITEA_DIRECT_PORT=3000
|
||||||
GITEA_DIRECT_ROOT_URL=http://localhost:3000/
|
GITEA_DIRECT_ROOT_URL=http://localhost:3000/
|
||||||
|
|
||||||
|
# none: HTTP-only testing before DNS and port forwarding are ready.
|
||||||
# letsencrypt: manage a certificate automatically with Certbot.
|
# letsencrypt: manage a certificate automatically with Certbot.
|
||||||
# custom: read the named certificate files from ./config/ssl.
|
# custom: read the named certificate files from ./config/ssl.
|
||||||
SSL_MODE=letsencrypt
|
SSL_MODE=none
|
||||||
SSL_CERTIFICATE_DIR=./config/letsencrypt/live/broker.example.com
|
SSL_CERTIFICATE_DIR=./config/letsencrypt/live/broker.example.com
|
||||||
SSL_CERTIFICATE_FILE=fullchain.pem
|
SSL_CERTIFICATE_FILE=fullchain.pem
|
||||||
SSL_CERTIFICATE_KEY_FILE=privkey.pem
|
SSL_CERTIFICATE_KEY_FILE=privkey.pem
|
||||||
|
|
||||||
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=http://broker.example.com:8680
|
||||||
TAPM_GITEA_URL=https://git.example.com
|
TAPM_GITEA_URL=http://git.example.com:8680
|
||||||
TAPM_GITEA_CLIENT_ID=replace-after-creating-the-gitea-oauth-app
|
# These values are intentionally blank during initial Gitea bootstrap.
|
||||||
TAPM_GITEA_CLIENT_SECRET=replace-after-creating-the-gitea-oauth-app
|
# Run ./manage.sh configure-broker before starting the broker.
|
||||||
|
TAPM_GITEA_CLIENT_ID=
|
||||||
|
TAPM_GITEA_CLIENT_SECRET=
|
||||||
TAPM_GITEA_PACKAGE_OWNER=TAI
|
TAPM_GITEA_PACKAGE_OWNER=TAI
|
||||||
TAPM_GITEA_PACKAGE_USERNAME=tapm-packages
|
TAPM_GITEA_PACKAGE_USERNAME=
|
||||||
TAPM_GITEA_PACKAGE_TOKEN=replace-with-read-package-token
|
TAPM_GITEA_PACKAGE_TOKEN=
|
||||||
TAPM_GITEA_PACKAGE_WRITE_USERNAME=tapm-publisher
|
TAPM_GITEA_PACKAGE_WRITE_USERNAME=
|
||||||
TAPM_GITEA_PACKAGE_WRITE_TOKEN=replace-with-write-package-token
|
TAPM_GITEA_PACKAGE_WRITE_TOKEN=
|
||||||
TAPM_ALLOWED_GITEA_USERS=taiadmin
|
TAPM_ALLOWED_GITEA_USERS=taiadmin
|
||||||
TAPM_COOKIE_SECRET=replace-with-output-of-openssl-rand-base64-48
|
TAPM_COOKIE_SECRET=
|
||||||
TAPM_DEFAULT_DURATION=3h
|
TAPM_DEFAULT_DURATION=3h
|
||||||
TAPM_DEFAULT_HOST_LIMIT=3
|
TAPM_DEFAULT_HOST_LIMIT=3
|
||||||
TAPM_MAX_HOST_LIMIT=25
|
TAPM_MAX_HOST_LIMIT=25
|
||||||
|
|||||||
@@ -23,11 +23,36 @@ See [docs/deployment.md](docs/deployment.md) for the installation, Gitea setup,
|
|||||||
TLS bootstrap, repository move, renewal, backup, and update procedures. The
|
TLS bootstrap, repository move, renewal, backup, and update procedures. The
|
||||||
ProxMenu integration contract is in [docs/client-api.md](docs/client-api.md).
|
ProxMenu integration contract is in [docs/client-api.md](docs/client-api.md).
|
||||||
|
|
||||||
|
## Guided installation
|
||||||
|
|
||||||
|
After cloning the repository, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer defaults to `SSL_MODE=none`, writes a mode-`0600` `.env`, creates
|
||||||
|
the runtime directories and Docker network, and starts Gitea behind the
|
||||||
|
HTTP-only Nginx test configuration. It does not invent Gitea OAuth or package
|
||||||
|
credentials. After creating those resources in Gitea, run
|
||||||
|
`./manage.sh configure-broker` and `./manage.sh start`.
|
||||||
|
|
||||||
|
Once public DNS and TCP 80/443 forwarding are ready, switch the same persistent
|
||||||
|
deployment to Let's Encrypt:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./manage.sh set-ssl letsencrypt
|
||||||
|
./manage.sh bootstrap
|
||||||
|
./manage.sh start
|
||||||
|
```
|
||||||
|
|
||||||
## Local checks
|
## Local checks
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
GOCACHE=/tmp/tapm-go-cache go test ./...
|
GOCACHE=/tmp/tapm-go-cache go test ./...
|
||||||
docker compose --env-file .env.example config
|
docker compose --env-file .env.example config
|
||||||
|
docker compose --env-file .env.example \
|
||||||
|
-f compose.yaml -f compose.http.yaml config
|
||||||
docker compose --env-file .env.example \
|
docker compose --env-file .env.example \
|
||||||
-f compose.yaml -f compose.tls.yaml -f compose.direct.yaml config
|
-f compose.yaml -f compose.tls.yaml -f compose.direct.yaml config
|
||||||
docker build -t tai/tapm-deployment-broker:local .
|
docker build -t tai/tapm-deployment-broker:local .
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Plain-HTTP testing mode. Do not expose this mode to the public Internet.
|
||||||
|
services:
|
||||||
|
broker:
|
||||||
|
environment:
|
||||||
|
TAPM_ALLOW_INSECURE_HTTP: "true"
|
||||||
|
|
||||||
|
gitea:
|
||||||
|
environment:
|
||||||
|
GITEA__server__ROOT_URL: ${GITEA_PUBLIC_URL}
|
||||||
|
GITEA__session__COOKIE_SECURE: "false"
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
volumes:
|
||||||
|
- ./deploy/nginx/templates/http.conf.template:/etc/nginx/templates/default.conf.template:ro
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
|
ports:
|
||||||
|
- "${HTTPS_PORT:-8643}:443"
|
||||||
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
|
- ${SSL_CERTIFICATE_DIR:-./config/ssl}:/etc/nginx/ssl:ro
|
||||||
|
|||||||
+1
-2
@@ -51,7 +51,7 @@ services:
|
|||||||
GITEA__database__DB_TYPE: sqlite3
|
GITEA__database__DB_TYPE: sqlite3
|
||||||
GITEA__database__PATH: /var/lib/gitea/data/gitea.db
|
GITEA__database__PATH: /var/lib/gitea/data/gitea.db
|
||||||
GITEA__server__DOMAIN: ${GITEA_DOMAIN}
|
GITEA__server__DOMAIN: ${GITEA_DOMAIN}
|
||||||
GITEA__server__ROOT_URL: https://${GITEA_DOMAIN}/
|
GITEA__server__ROOT_URL: ${GITEA_PUBLIC_URL}
|
||||||
GITEA__server__HTTP_ADDR: 0.0.0.0
|
GITEA__server__HTTP_ADDR: 0.0.0.0
|
||||||
GITEA__server__HTTP_PORT: "3000"
|
GITEA__server__HTTP_PORT: "3000"
|
||||||
GITEA__server__DISABLE_SSH: "true"
|
GITEA__server__DISABLE_SSH: "true"
|
||||||
@@ -78,7 +78,6 @@ services:
|
|||||||
- edge
|
- edge
|
||||||
ports:
|
ports:
|
||||||
- "${HTTP_PORT:-8680}:80"
|
- "${HTTP_PORT:-8680}:80"
|
||||||
- "${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/certbot-webroot:/var/www/certbot:ro
|
- ./config/certbot-webroot:/var/www/certbot:ro
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Testing-only HTTP reverse proxy. Production should use tls.conf.template.
|
||||||
|
server_tokens off;
|
||||||
|
reset_timedout_connection on;
|
||||||
|
client_header_timeout 15s;
|
||||||
|
client_body_timeout 60s;
|
||||||
|
keepalive_timeout 30s;
|
||||||
|
send_timeout 60s;
|
||||||
|
|
||||||
|
limit_conn_zone $binary_remote_addr zone=per_address_connections:10m;
|
||||||
|
limit_req_zone $binary_remote_addr zone=broker_requests:10m rate=20r/s;
|
||||||
|
map $uri $gitea_login_client {
|
||||||
|
default "";
|
||||||
|
"/user/login" $binary_remote_addr;
|
||||||
|
}
|
||||||
|
limit_req_zone $gitea_login_client zone=gitea_login_requests:10m rate=10r/m;
|
||||||
|
|
||||||
|
resolver 127.0.0.11 valid=30s;
|
||||||
|
|
||||||
|
upstream gitea_backend {
|
||||||
|
zone gitea_backend 64k;
|
||||||
|
server gitea:3000 resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ${BROKER_DOMAIN};
|
||||||
|
|
||||||
|
client_max_body_size 1100m;
|
||||||
|
limit_conn per_address_connections 30;
|
||||||
|
limit_req zone=broker_requests burst=40 nodelay;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header Referrer-Policy "no-referrer" always;
|
||||||
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Resolve at request time so Gitea-only bootstrap works before the
|
||||||
|
# broker container exists. Requests return 502 until broker setup ends.
|
||||||
|
set $broker_backend_url http://broker:8080;
|
||||||
|
proxy_pass $broker_backend_url;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto http;
|
||||||
|
proxy_connect_timeout 30s;
|
||||||
|
proxy_send_timeout 30m;
|
||||||
|
proxy_read_timeout 30m;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_buffering off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ${GITEA_DOMAIN};
|
||||||
|
|
||||||
|
client_max_body_size 1100m;
|
||||||
|
limit_conn per_address_connections 50;
|
||||||
|
limit_req zone=gitea_login_requests burst=10 nodelay;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://gitea_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto http;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
-43
@@ -11,6 +11,11 @@ One Compose project manages:
|
|||||||
- Nginx, terminating TLS for the broker and Gitea
|
- Nginx, terminating TLS for the broker and Gitea
|
||||||
- Certbot, as an on-demand utility for certificate issuance and renewal
|
- Certbot, as an on-demand utility for certificate issuance and renewal
|
||||||
|
|
||||||
|
The same deployment supports an explicitly insecure HTTP testing phase before
|
||||||
|
public DNS and port forwarding are ready. HTTP mode changes only routing, URL
|
||||||
|
schemes, and cookie security; it does not create separate databases or move
|
||||||
|
runtime data.
|
||||||
|
|
||||||
In production, only the configured Nginx HTTP and HTTPS ports are published.
|
In production, only the configured Nginx HTTP and HTTPS ports are published.
|
||||||
They default to 8680 and 8643. Inside Docker, Nginx listens on ports 80 and 443,
|
They default to 8680 and 8643. Inside Docker, Nginx listens on ports 80 and 443,
|
||||||
the broker on 8080, and Gitea is explicitly pinned to `0.0.0.0:3000`. Gitea
|
the broker on 8080, and Gitea is explicitly pinned to `0.0.0.0:3000`. Gitea
|
||||||
@@ -48,20 +53,29 @@ sudo mkdir -p /YOUR/INSTALL/PARENT
|
|||||||
sudo git clone GITEA-REPOSITORY-URL /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
sudo git clone GITEA-REPOSITORY-URL /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
||||||
sudo chown -R 1000:1000 /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
sudo chown -R 1000:1000 /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
||||||
cd /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
cd /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
||||||
cp .env.example .env
|
./install.sh
|
||||||
chmod 600 .env
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Set `BROKER_DOMAIN`, `GITEA_DOMAIN`, and `LETSENCRYPT_EMAIL` first. `HTTP_PORT`
|
The guided installer validates both hostnames and Docker ports, generates the
|
||||||
and `HTTPS_PORT` control the VM-side Docker bindings and default to 8680 and
|
broker cookie secret, writes `.env` with mode `0600`, prepares persistent
|
||||||
8643. The datacenter edge should therefore forward public ports 80 and 443 to
|
storage, and starts only Gitea and Nginx. It defaults to `SSL_MODE=none`, so no
|
||||||
VM ports 8680 and 8643. Replace all example domains in the TAPM variables.
|
certificate or public port forwarding is required.
|
||||||
Generate the cookie secret with:
|
|
||||||
|
With the default `HTTP_PORT=8680`, test URLs include that port:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
openssl rand -base64 48
|
http://BROKER_DOMAIN:8680
|
||||||
|
http://GITEA_DOMAIN:8680
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Both names must resolve to the Docker VM from the test workstation. Temporary
|
||||||
|
hosts-file records are sufficient. Do not forward this HTTP test listener to
|
||||||
|
the public Internet.
|
||||||
|
|
||||||
|
For a manual installation instead, copy `.env.example` to `.env`, replace its
|
||||||
|
example hostnames, generate `TAPM_COOKIE_SECRET` with
|
||||||
|
`openssl rand -base64 48`, and run `./manage.sh bootstrap`.
|
||||||
|
|
||||||
Runtime state is deliberately visible below the checkout:
|
Runtime state is deliberately visible below the checkout:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -78,15 +92,49 @@ config/
|
|||||||
|
|
||||||
The contents are ignored by Git. They must never be committed.
|
The contents are ignored by Git. They must never be committed.
|
||||||
|
|
||||||
## 3. Bootstrap Gitea and TLS
|
## 3. Configure Gitea and the broker
|
||||||
|
|
||||||
|
The installer has already bootstrapped Gitea. Create the initial administrator:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./manage.sh bootstrap
|
./manage.sh create-admin
|
||||||
```
|
```
|
||||||
|
|
||||||
This prepares the runtime directories, creates the private Docker network,
|
Sign in at the Gitea URL printed by the command and change the temporary
|
||||||
starts Gitea and the HTTP-only Nginx configuration, obtains a certificate when
|
password. Create the `TAI` organization, then create:
|
||||||
using Let's Encrypt, and switches Nginx to TLS.
|
|
||||||
|
1. `tapm-packages`, with a read-only package token.
|
||||||
|
2. `tapm-publisher`, with a write package token.
|
||||||
|
3. An OAuth2 application. In HTTP testing mode its callback is
|
||||||
|
`http://BROKER_DOMAIN:HTTP_PORT/auth/callback`.
|
||||||
|
|
||||||
|
Save the resulting values without echoing secrets to the terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./manage.sh configure-broker
|
||||||
|
./manage.sh start
|
||||||
|
```
|
||||||
|
|
||||||
|
The broker is deliberately not started during the first bootstrap. Blank
|
||||||
|
OAuth/package values are valid for Gitea-only setup, while `start` refuses to
|
||||||
|
launch the broker until all required credentials are present.
|
||||||
|
|
||||||
|
## 4. Enable TLS when forwarding is ready
|
||||||
|
|
||||||
|
After public DNS resolves and the datacenter edge forwards public TCP 80 and
|
||||||
|
443 to the configured VM ports, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./manage.sh set-ssl letsencrypt
|
||||||
|
./manage.sh bootstrap
|
||||||
|
./manage.sh start
|
||||||
|
```
|
||||||
|
|
||||||
|
`set-ssl` changes all public URLs to HTTPS and removes the temporary `:8680`
|
||||||
|
test port. `bootstrap` obtains the certificate and switches Nginx to TLS.
|
||||||
|
`start` recreates Gitea and the broker with secure cookies. Existing SQLite
|
||||||
|
databases, repositories, packages, installation identities, and secrets remain
|
||||||
|
in place.
|
||||||
|
|
||||||
For an administrator-provided certificate instead, place the certificate and
|
For an administrator-provided certificate instead, place the certificate and
|
||||||
key in `config/ssl/` and set:
|
key in `config/ssl/` and set:
|
||||||
@@ -98,46 +146,27 @@ SSL_CERTIFICATE_FILE=fullchain.pem
|
|||||||
SSL_CERTIFICATE_KEY_FILE=privkey.pem
|
SSL_CERTIFICATE_KEY_FILE=privkey.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
The certificate must cover both `BROKER_DOMAIN` and `GITEA_DOMAIN`. With custom
|
The certificate must cover both `BROKER_DOMAIN` and `GITEA_DOMAIN`. Run
|
||||||
|
`./manage.sh set-ssl custom` before bootstrap. With custom
|
||||||
mode, `bootstrap` skips Certbot and `renew` is intentionally unavailable;
|
mode, `bootstrap` skips Certbot and `renew` is intentionally unavailable;
|
||||||
replace the files through the certificate provider's process and restart or
|
replace the files through the certificate provider's process and restart or
|
||||||
reload Nginx. `SSL_CERTIFICATE_DIR` can point at another directory relative to
|
reload Nginx. `SSL_CERTIFICATE_DIR` can point at another directory relative to
|
||||||
the repository or at an absolute host path.
|
the repository or at an absolute host path.
|
||||||
|
|
||||||
Create the initial Gitea administrator:
|
## 5. Start and inspect the complete deployment
|
||||||
|
|
||||||
```sh
|
|
||||||
./manage.sh create-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
No Gitea login is hard-coded or stored in `.env`. The command prompts for the
|
|
||||||
administrator username and email, generates a temporary password, and requires
|
|
||||||
it to be changed at first login. The initial web installer is intentionally
|
|
||||||
locked and public registration is disabled.
|
|
||||||
|
|
||||||
Sign in to Gitea at `https://GITEA_DOMAIN`, create the `TAI` organization, and
|
|
||||||
create:
|
|
||||||
|
|
||||||
1. `tapm-packages`, with a read-only package token.
|
|
||||||
2. `tapm-publisher`, with a write package token.
|
|
||||||
3. An OAuth2 application whose callback is
|
|
||||||
`https://BROKER_DOMAIN/auth/callback`.
|
|
||||||
|
|
||||||
Place those token and OAuth values in `.env`. The broker cannot start until all
|
|
||||||
required credentials are present.
|
|
||||||
|
|
||||||
## 4. Start the complete deployment
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./manage.sh start
|
./manage.sh start
|
||||||
./manage.sh status
|
./manage.sh status
|
||||||
curl --fail "https://BROKER_DOMAIN/health/ready"
|
curl --fail "http://BROKER_DOMAIN:HTTP_PORT/health/ready" # SSL_MODE=none
|
||||||
curl --fail "https://GITEA_DOMAIN/api/healthz"
|
curl --fail "http://GITEA_DOMAIN:HTTP_PORT/api/healthz" # SSL_MODE=none
|
||||||
```
|
```
|
||||||
|
|
||||||
The broker automatically applies SQLite schema migrations before starting.
|
The broker automatically applies SQLite schema migrations before starting.
|
||||||
Gitea uses its own SQLite database at
|
Gitea uses its own SQLite database at
|
||||||
`config/gitea/data/data/gitea.db`.
|
`config/gitea/data/data/gitea.db`.
|
||||||
|
After TLS is enabled, use the equivalent `https://` URLs without the temporary
|
||||||
|
HTTP test port.
|
||||||
|
|
||||||
For local testing that bypasses Nginx, set `BROKER_DIRECT_PORT` and
|
For local testing that bypasses Nginx, set `BROKER_DIRECT_PORT` and
|
||||||
`GITEA_DIRECT_PORT` in `.env`. Set `GITEA_DIRECT_ROOT_URL` to the exact URL the
|
`GITEA_DIRECT_PORT` in `.env`. Set `GITEA_DIRECT_ROOT_URL` to the exact URL the
|
||||||
@@ -164,10 +193,9 @@ production URL and secure-cookie policy with:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
./manage.sh start
|
./manage.sh start
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml up -d --force-recreate broker gitea
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Move repositories
|
## 6. Move repositories
|
||||||
|
|
||||||
For each existing repository, create an empty matching repository in the new
|
For each existing repository, create an empty matching repository in the new
|
||||||
Gitea and mirror all refs:
|
Gitea and mirror all refs:
|
||||||
@@ -183,7 +211,7 @@ module path only if the hostname embedded in the module path is also changing.
|
|||||||
Move the deployment repository last so this checkout remains updateable during
|
Move the deployment repository last so this checkout remains updateable during
|
||||||
the transition.
|
the transition.
|
||||||
|
|
||||||
## 6. Renewal and backups
|
## 7. Renewal and backups
|
||||||
|
|
||||||
When `SSL_MODE=letsencrypt`, run renewal twice daily from root's crontab:
|
When `SSL_MODE=letsencrypt`, run renewal twice daily from root's crontab:
|
||||||
|
|
||||||
@@ -201,7 +229,7 @@ The backup briefly stops both SQLite writers. Copy `config/backups/` to storage
|
|||||||
outside the VM. A backup left only on this VM does not protect against VM or
|
outside the VM. A backup left only on this VM does not protect against VM or
|
||||||
datacenter loss. Also back up `.env` through a secrets-aware system.
|
datacenter loss. Also back up `.env` through a secrets-aware system.
|
||||||
|
|
||||||
## 7. Updates
|
## 8. Updates
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
cd /YOUR/INSTALL/PARENT/TA-Deployment-Access
|
||||||
|
|||||||
Executable
+151
@@ -0,0 +1,151 @@
|
|||||||
|
#!/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 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 Gitea behind Nginx.\n'
|
||||||
|
printf 'The broker starts only after its Gitea OAuth and package credentials are configured.\n\n'
|
||||||
|
|
||||||
|
broker_domain="$(prompt_default 'Broker hostname' 'tapm.example.com')"
|
||||||
|
gitea_domain="$(prompt_default 'Gitea hostname' 'git.example.com')"
|
||||||
|
valid_hostname "$broker_domain" || fail "invalid broker hostname"
|
||||||
|
valid_hostname "$gitea_domain" || fail "invalid Gitea hostname"
|
||||||
|
[[ "$broker_domain" != "$gitea_domain" ]] ||
|
||||||
|
fail "broker and Gitea 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_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=3h\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 'Gitea has not been started yet.\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
"${DEPLOY_ROOT}/manage.sh" bootstrap
|
||||||
|
|
||||||
|
printf '\nInitial Gitea deployment is running.\n'
|
||||||
|
printf 'Gitea 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 Gitea 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
|
||||||
@@ -39,7 +39,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
Path: "/auth",
|
Path: "/auth",
|
||||||
MaxAge: 600,
|
MaxAge: 600,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: true,
|
Secure: s.cfg.CookieSecure,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ func (s *Server) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
Path: "/auth",
|
Path: "/auth",
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: true,
|
Secure: s.cfg.CookieSecure,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ func (s *Server) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
Expires: expiresAt,
|
Expires: expiresAt,
|
||||||
MaxAge: int(time.Until(expiresAt).Seconds()),
|
MaxAge: int(time.Until(expiresAt).Seconds()),
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: true,
|
Secure: s.cfg.CookieSecure,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
_ = s.audit(r.Context(), "login_succeeded", user.Login, nil, "", "", s.clientIP(r), "")
|
_ = s.audit(r.Context(), "login_succeeded", user.Login, nil, "", "", s.clientIP(r), "")
|
||||||
@@ -280,7 +280,7 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: true,
|
Secure: s.cfg.CookieSecure,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
|||||||
+19
-4
@@ -24,6 +24,7 @@ type Config struct {
|
|||||||
GiteaWriteToken string
|
GiteaWriteToken string
|
||||||
AllowedGiteaUsers map[string]struct{}
|
AllowedGiteaUsers map[string]struct{}
|
||||||
CookieSecret []byte
|
CookieSecret []byte
|
||||||
|
CookieSecure bool
|
||||||
DefaultDuration time.Duration
|
DefaultDuration time.Duration
|
||||||
DefaultHostLimit int
|
DefaultHostLimit int
|
||||||
MaxHostLimit int
|
MaxHostLimit int
|
||||||
@@ -50,14 +51,21 @@ func LoadConfig() (Config, error) {
|
|||||||
cfg.GiteaWriteToken = os.Getenv("TAPM_GITEA_PACKAGE_WRITE_TOKEN")
|
cfg.GiteaWriteToken = os.Getenv("TAPM_GITEA_PACKAGE_WRITE_TOKEN")
|
||||||
cfg.CookieSecret = []byte(os.Getenv("TAPM_COOKIE_SECRET"))
|
cfg.CookieSecret = []byte(os.Getenv("TAPM_COOKIE_SECRET"))
|
||||||
|
|
||||||
cfg.PublicURL, err = parseAbsoluteURL("TAPM_PUBLIC_URL")
|
allowInsecureHTTP, err := strconv.ParseBool(
|
||||||
|
envDefault("TAPM_ALLOW_INSECURE_HTTP", "false"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, fmt.Errorf("TAPM_ALLOW_INSECURE_HTTP: %w", err)
|
||||||
|
}
|
||||||
|
cfg.PublicURL, err = parseAbsoluteURL("TAPM_PUBLIC_URL", allowInsecureHTTP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
cfg.GiteaURL, err = parseAbsoluteURL("TAPM_GITEA_URL")
|
cfg.GiteaURL, err = parseAbsoluteURL("TAPM_GITEA_URL", allowInsecureHTTP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
cfg.CookieSecure = cfg.PublicURL.Scheme == "https"
|
||||||
|
|
||||||
cfg.DefaultDuration, err = time.ParseDuration(
|
cfg.DefaultDuration, err = time.ParseDuration(
|
||||||
envDefault("TAPM_DEFAULT_DURATION", "3h"),
|
envDefault("TAPM_DEFAULT_DURATION", "3h"),
|
||||||
@@ -128,10 +136,17 @@ func LoadConfig() (Config, error) {
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAbsoluteURL(name string) (*url.URL, error) {
|
func parseAbsoluteURL(name string, allowInsecureHTTP bool) (*url.URL, error) {
|
||||||
raw := os.Getenv(name)
|
raw := os.Getenv(name)
|
||||||
parsed, err := url.Parse(raw)
|
parsed, err := url.Parse(raw)
|
||||||
if err != nil || parsed.Scheme != "https" || parsed.Host == "" {
|
validScheme := parsed != nil && parsed.Scheme == "https"
|
||||||
|
if allowInsecureHTTP && parsed != nil && parsed.Scheme == "http" {
|
||||||
|
validScheme = true
|
||||||
|
}
|
||||||
|
if err != nil || !validScheme || parsed.Host == "" {
|
||||||
|
if allowInsecureHTTP {
|
||||||
|
return nil, fmt.Errorf("%s must be an absolute HTTP or HTTPS URL", name)
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("%s must be an absolute HTTPS URL", name)
|
return nil, fmt.Errorf("%s must be an absolute HTTPS URL", name)
|
||||||
}
|
}
|
||||||
return parsed, nil
|
return parsed, nil
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setRequiredConfigEnvironment(t *testing.T, scheme string) {
|
||||||
|
t.Helper()
|
||||||
|
t.Setenv("TAPM_PUBLIC_URL", scheme+"://broker.example.com")
|
||||||
|
t.Setenv("TAPM_GITEA_URL", scheme+"://git.example.com")
|
||||||
|
t.Setenv("TAPM_GITEA_CLIENT_ID", "client-id")
|
||||||
|
t.Setenv("TAPM_GITEA_CLIENT_SECRET", "client-secret")
|
||||||
|
t.Setenv("TAPM_GITEA_PACKAGE_USERNAME", "reader")
|
||||||
|
t.Setenv("TAPM_GITEA_PACKAGE_TOKEN", "reader-token")
|
||||||
|
t.Setenv("TAPM_GITEA_PACKAGE_WRITE_USERNAME", "writer")
|
||||||
|
t.Setenv("TAPM_GITEA_PACKAGE_WRITE_TOKEN", "writer-token")
|
||||||
|
t.Setenv("TAPM_COOKIE_SECRET", "0123456789abcdef0123456789abcdef")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigRejectsHTTPByDefault(t *testing.T) {
|
||||||
|
setRequiredConfigEnvironment(t, "http")
|
||||||
|
_, err := LoadConfig()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "absolute HTTPS URL") {
|
||||||
|
t.Fatalf("LoadConfig() error = %v, want HTTPS requirement", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigAllowsExplicitHTTPTestingMode(t *testing.T) {
|
||||||
|
setRequiredConfigEnvironment(t, "http")
|
||||||
|
t.Setenv("TAPM_ALLOW_INSECURE_HTTP", "true")
|
||||||
|
cfg, err := LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg.CookieSecure {
|
||||||
|
t.Fatal("HTTP testing mode unexpectedly enabled secure cookies")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPSConfigUsesSecureCookies(t *testing.T) {
|
||||||
|
setRequiredConfigEnvironment(t, "https")
|
||||||
|
cfg, err := LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !cfg.CookieSecure {
|
||||||
|
t.Fatal("HTTPS mode did not enable secure cookies")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
DEPLOY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
DEPLOY_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||||
cd "$DEPLOY_ROOT"
|
cd "$DEPLOY_ROOT"
|
||||||
|
declare -a COMPOSE_FILES=(-f compose.yaml)
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
printf 'ERROR: %s\n' "$*" >&2
|
printf 'ERROR: %s\n' "$*" >&2
|
||||||
@@ -26,10 +27,77 @@ read_env_setting() {
|
|||||||
printf '%s' "$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() {
|
require_env() {
|
||||||
[[ -f .env ]] || fail "copy .env.example to .env and configure it first"
|
[[ -f .env ]] || fail "copy .env.example to .env and configure it first"
|
||||||
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)"
|
||||||
|
GITEA_PUBLIC_URL="$(read_env_setting GITEA_PUBLIC_URL)"
|
||||||
LETSENCRYPT_EMAIL="$(read_env_setting LETSENCRYPT_EMAIL)"
|
LETSENCRYPT_EMAIL="$(read_env_setting LETSENCRYPT_EMAIL)"
|
||||||
SSL_MODE="$(read_env_setting SSL_MODE)"
|
SSL_MODE="$(read_env_setting SSL_MODE)"
|
||||||
SSL_MODE="${SSL_MODE:-letsencrypt}"
|
SSL_MODE="${SSL_MODE:-letsencrypt}"
|
||||||
@@ -40,10 +108,35 @@ require_env() {
|
|||||||
SSL_CERTIFICATE_KEY_FILE="${SSL_CERTIFICATE_KEY_FILE:-privkey.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}"
|
||||||
|
[[ "$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
|
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)
|
||||||
: "${LETSENCRYPT_EMAIL:?LETSENCRYPT_EMAIL is required for SSL_MODE=letsencrypt}"
|
: "${LETSENCRYPT_EMAIL:?LETSENCRYPT_EMAIL is required for SSL_MODE=letsencrypt}"
|
||||||
SSL_CERTIFICATE_DIR="${SSL_CERTIFICATE_DIR:-./config/letsencrypt/live/${BROKER_DOMAIN}}"
|
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)
|
custom)
|
||||||
SSL_CERTIFICATE_DIR="${SSL_CERTIFICATE_DIR:-./config/ssl}"
|
SSL_CERTIFICATE_DIR="${SSL_CERTIFICATE_DIR:-./config/ssl}"
|
||||||
@@ -51,12 +144,19 @@ require_env() {
|
|||||||
fail "custom certificate was not found at ${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}" ]] ||
|
[[ -f "${SSL_CERTIFICATE_DIR}/${SSL_CERTIFICATE_KEY_FILE}" ]] ||
|
||||||
fail "custom certificate key was not found at ${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 letsencrypt or custom"
|
fail "SSL_MODE must be none, letsencrypt, or custom"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
export SSL_CERTIFICATE_DIR SSL_CERTIFICATE_FILE SSL_CERTIFICATE_KEY_FILE
|
export GITEA_PUBLIC_URL SSL_CERTIFICATE_DIR SSL_CERTIFICATE_FILE SSL_CERTIFICATE_KEY_FILE
|
||||||
|
select_compose_stack
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
prepare() {
|
||||||
@@ -84,28 +184,35 @@ prepare)
|
|||||||
bootstrap)
|
bootstrap)
|
||||||
require_env
|
require_env
|
||||||
prepare
|
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 up -d gitea nginx
|
||||||
if [[ "$SSL_MODE" == "letsencrypt" ]]; then
|
|
||||||
docker compose --profile tools run --rm certbot certonly \
|
docker compose --profile tools run --rm certbot certonly \
|
||||||
--webroot --webroot-path /var/www/certbot \
|
--webroot --webroot-path /var/www/certbot \
|
||||||
--non-interactive --agree-tos \
|
--non-interactive --agree-tos \
|
||||||
--email "$LETSENCRYPT_EMAIL" \
|
--email "$LETSENCRYPT_EMAIL" \
|
||||||
--cert-name "$BROKER_DOMAIN" \
|
--cert-name "$BROKER_DOMAIN" \
|
||||||
-d "$BROKER_DOMAIN" -d "$GITEA_DOMAIN"
|
-d "$BROKER_DOMAIN" -d "$GITEA_DOMAIN"
|
||||||
|
compose_stack up -d gitea nginx
|
||||||
|
else
|
||||||
|
compose_stack up -d gitea nginx
|
||||||
fi
|
fi
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml up -d nginx
|
|
||||||
;;
|
;;
|
||||||
start)
|
start)
|
||||||
require_env
|
require_env
|
||||||
prepare
|
prepare
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml up -d --build
|
broker_config_ready ||
|
||||||
|
fail "broker configuration is incomplete; run ./manage.sh configure-broker"
|
||||||
|
compose_stack up -d --build
|
||||||
;;
|
;;
|
||||||
start-direct)
|
start-direct)
|
||||||
require_env
|
require_env
|
||||||
prepare
|
prepare
|
||||||
|
broker_config_ready ||
|
||||||
|
fail "broker configuration is incomplete; run ./manage.sh configure-broker"
|
||||||
docker compose \
|
docker compose \
|
||||||
-f compose.yaml \
|
"${COMPOSE_FILES[@]}" \
|
||||||
-f compose.tls.yaml \
|
|
||||||
-f compose.direct.yaml \
|
-f compose.direct.yaml \
|
||||||
up -d --build
|
up -d --build
|
||||||
;;
|
;;
|
||||||
@@ -129,15 +236,101 @@ create-admin)
|
|||||||
printf '\nGitea administrator created.\n'
|
printf '\nGitea administrator created.\n'
|
||||||
printf 'Username: %s\n' "$admin_username"
|
printf 'Username: %s\n' "$admin_username"
|
||||||
printf 'Temporary password: %s\n' "$temporary_password"
|
printf 'Temporary password: %s\n' "$temporary_password"
|
||||||
printf 'Sign in at https://%s/ and change the password immediately.\n' \
|
printf 'Sign in at %s and change the password immediately.\n' \
|
||||||
"$GITEA_DOMAIN"
|
"$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)
|
renew)
|
||||||
require_env
|
require_env
|
||||||
[[ "$SSL_MODE" == "letsencrypt" ]] ||
|
[[ "$SSL_MODE" == "letsencrypt" ]] ||
|
||||||
fail "automatic renewal is only available with 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
|
compose_stack exec nginx nginx -s reload
|
||||||
;;
|
;;
|
||||||
backup)
|
backup)
|
||||||
require_env
|
require_env
|
||||||
@@ -149,14 +342,23 @@ backup)
|
|||||||
docker compose stop gitea
|
docker compose stop gitea
|
||||||
cp -a config/broker "$destination/"
|
cp -a config/broker "$destination/"
|
||||||
cp -a config/gitea "$destination/"
|
cp -a config/gitea "$destination/"
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml start gitea broker
|
compose_stack start gitea broker
|
||||||
printf 'Backup created at %s\n' "$destination"
|
printf 'Backup created at %s\n' "$destination"
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml ps
|
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|start|start-direct|create-admin|renew|backup|status}\n' "$0"
|
printf 'Usage: %s {prepare|bootstrap|configure-broker|set-ssl|start|start-direct|create-admin|renew|backup|status}\n' "$0"
|
||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
Executable
+85
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
TEST_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
||||||
|
test_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$test_dir"' EXIT
|
||||||
|
deployment="${test_dir}/deployment"
|
||||||
|
mock_bin="${test_dir}/bin"
|
||||||
|
mkdir -p "$deployment" "$mock_bin"
|
||||||
|
cp -R \
|
||||||
|
"${TEST_ROOT}/install.sh" \
|
||||||
|
"${TEST_ROOT}/manage.sh" \
|
||||||
|
"${TEST_ROOT}/compose.yaml" \
|
||||||
|
"${TEST_ROOT}/compose.http.yaml" \
|
||||||
|
"${TEST_ROOT}/compose.tls.yaml" \
|
||||||
|
"${TEST_ROOT}/compose.direct.yaml" \
|
||||||
|
"${TEST_ROOT}/deploy" \
|
||||||
|
"$deployment/"
|
||||||
|
|
||||||
|
cat >"${mock_bin}/docker" <<'MOCK'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
printf '%s\n' "$*" >>"$MOCK_DOCKER_LOG"
|
||||||
|
case "$*" in
|
||||||
|
"compose version" | "info" | "network inspect tapm-edge")
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
MOCK
|
||||||
|
chmod +x "${mock_bin}/docker"
|
||||||
|
export MOCK_DOCKER_LOG="${test_dir}/docker.log"
|
||||||
|
export PATH="${mock_bin}:${PATH}"
|
||||||
|
|
||||||
|
printf '%s\n' \
|
||||||
|
'broker.test' \
|
||||||
|
'git.test' \
|
||||||
|
'8780' \
|
||||||
|
'8743' \
|
||||||
|
'' |
|
||||||
|
"${deployment}/install.sh" >/dev/null
|
||||||
|
|
||||||
|
assert_env() {
|
||||||
|
local expected="$1"
|
||||||
|
grep -Fx "$expected" "${deployment}/.env" >/dev/null || {
|
||||||
|
printf 'FAIL: missing environment line: %s\n' "$expected" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_env 'SSL_MODE=none'
|
||||||
|
assert_env 'TAPM_PUBLIC_URL=http://broker.test:8780'
|
||||||
|
assert_env 'TAPM_GITEA_URL=http://git.test:8780'
|
||||||
|
assert_env 'GITEA_PUBLIC_URL=http://git.test:8780/'
|
||||||
|
grep -F -- '-f compose.yaml -f compose.http.yaml up -d gitea nginx' \
|
||||||
|
"$MOCK_DOCKER_LOG" >/dev/null || {
|
||||||
|
printf 'FAIL: HTTP bootstrap did not select compose.http.yaml\n' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if stat -c '%a' "${deployment}/.env" >/dev/null 2>&1; then
|
||||||
|
env_mode="$(stat -c '%a' "${deployment}/.env")"
|
||||||
|
else
|
||||||
|
env_mode="$(stat -f '%Lp' "${deployment}/.env")"
|
||||||
|
fi
|
||||||
|
[[ "$env_mode" == 600 ]] || {
|
||||||
|
printf 'FAIL: .env mode is %s, want 600\n' "$env_mode" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if "${deployment}/manage.sh" start >/dev/null 2>&1; then
|
||||||
|
printf 'FAIL: broker started without Gitea credentials\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' 'admin@example.test' |
|
||||||
|
"${deployment}/manage.sh" set-ssl letsencrypt >/dev/null
|
||||||
|
assert_env 'SSL_MODE=letsencrypt'
|
||||||
|
assert_env 'TAPM_PUBLIC_URL=https://broker.test'
|
||||||
|
assert_env 'GITEA_PUBLIC_URL=https://git.test/'
|
||||||
|
|
||||||
|
"${deployment}/manage.sh" set-ssl none >/dev/null
|
||||||
|
assert_env 'SSL_MODE=none'
|
||||||
|
assert_env 'TAPM_PUBLIC_URL=http://broker.test:8780'
|
||||||
|
|
||||||
|
printf 'PASS: guided HTTP deployment and TLS transition\n'
|
||||||
@@ -57,7 +57,7 @@ printf 'Updating the local Gitea image and deployment stack...\n'
|
|||||||
docker compose --env-file .env pull gitea nginx
|
docker compose --env-file .env pull gitea nginx
|
||||||
"${DEPLOY_ROOT}/manage.sh" start
|
"${DEPLOY_ROOT}/manage.sh" start
|
||||||
|
|
||||||
container_id="$(docker compose -f compose.yaml -f compose.tls.yaml ps -q broker)"
|
container_id="$("${DEPLOY_ROOT}/manage.sh" broker-container-id)"
|
||||||
[[ -n "$container_id" ]] ||
|
[[ -n "$container_id" ]] ||
|
||||||
fail "Docker Compose did not return the local broker container"
|
fail "Docker Compose did not return the local broker container"
|
||||||
|
|
||||||
@@ -71,13 +71,13 @@ for ((attempt = 1; attempt <= 75; attempt++)); do
|
|||||||
case "$status" in
|
case "$status" in
|
||||||
healthy)
|
healthy)
|
||||||
printf ' healthy.\n'
|
printf ' healthy.\n'
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml ps
|
"${DEPLOY_ROOT}/manage.sh" status
|
||||||
printf 'Local deployment updated successfully.\n'
|
printf 'Local deployment updated successfully.\n'
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
exited | dead | unhealthy)
|
exited | dead | unhealthy)
|
||||||
printf ' %s.\n' "$status"
|
printf ' %s.\n' "$status"
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml logs --tail=100 broker
|
"${DEPLOY_ROOT}/manage.sh" broker-logs
|
||||||
fail "the local broker failed its health check"
|
fail "the local broker failed its health check"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -86,5 +86,5 @@ for ((attempt = 1; attempt <= 75; attempt++)); do
|
|||||||
done
|
done
|
||||||
|
|
||||||
printf ' timed out.\n'
|
printf ' timed out.\n'
|
||||||
docker compose -f compose.yaml -f compose.tls.yaml logs --tail=100 broker
|
"${DEPLOY_ROOT}/manage.sh" broker-logs
|
||||||
fail "the local broker did not become healthy within 150 seconds"
|
fail "the local broker did not become healthy within 150 seconds"
|
||||||
|
|||||||
Reference in New Issue
Block a user