fix: make ops/deploy.sh rebuild, dry-run safely, and join Traefik at start
- Always build from the checkout (`up --build`, `pull_policy: build`) instead of reusing a stale local image or pulling an old registry one. - --render-only writes to rendered/ and no longer touches the live Traefik dynamic directory; the route is installed after health check. - Join the Traefik network via a generated docker-compose.override.yaml rather than `docker network connect` after startup, which raced the control-plane's startup call to wg-easy and was lost on recreate. - Add --non-interactive, check the public HTTPS endpoint, and point the closing instructions at Settings -> Gateway (the suggested `furtka gateway connect` command doesn't exist). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e240f071d5
commit
6b8cc6e006
5 changed files with 196 additions and 172 deletions
14
.env.example
14
.env.example
|
|
@ -58,12 +58,10 @@ GATEWAY_CERT_RESOLVER=
|
|||
GATEWAY_CONTROL_PLANE_HOST=gw.example.com
|
||||
|
||||
# Where the control-plane's own API is bound (docker-compose.yaml's port
|
||||
# publish) and where your Traefik reaches it, respectively. Same value
|
||||
# for the recommended same-docker-network setup (ops/deploy.sh sets both
|
||||
# to furtka-gateway-control-plane:8090 automatically, with the bind
|
||||
# staying loopback-only — see docker-compose.yaml's comment on why this
|
||||
# is more robust than the compose *service* name "control-plane" once the
|
||||
# network is joined after the fact via `docker network connect`).
|
||||
# publish) and where your Traefik reaches it, respectively. For the
|
||||
# recommended same-docker-network setup, ops/deploy.sh keeps the bind
|
||||
# loopback-only and sets the target to the fixed container name
|
||||
# furtka-gateway-control-plane:8090.
|
||||
# Different values only for a separate-host setup, where
|
||||
# GATEWAY_CONTROL_PLANE_BIND must be widened beyond 127.0.0.1 — put real
|
||||
# access control around that bind address, this file carries a WireGuard
|
||||
|
|
@ -74,8 +72,8 @@ GATEWAY_CONTROL_PLANE_TARGET=furtka-gateway-control-plane:8090
|
|||
# Same-docker-network setup only (the recommended path — see the README's
|
||||
# Prerequisites checklist): your Traefik's docker network name
|
||||
# (`docker network ls` / `docker inspect <your-traefik-container>`).
|
||||
# ops/deploy.sh runs `docker network connect` with this after bringing the
|
||||
# stack up. Leave blank for a separate-host setup.
|
||||
# ops/deploy.sh writes a docker-compose.override.yaml that joins this
|
||||
# network. Leave blank for a separate-host setup.
|
||||
GATEWAY_TRAEFIK_NETWORK=
|
||||
|
||||
# The one box token a single-tenant deployment accepts at
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -10,3 +10,6 @@ traefik/dynamic/route-*.yml
|
|||
traefik/dynamic/control-plane.yml
|
||||
traefik/dynamic/default-cert.yml
|
||||
traefik/static/traefik.active.yml
|
||||
examples/starter-stack/dynamic/
|
||||
rendered/
|
||||
docker-compose.override.yaml
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
services:
|
||||
control-plane:
|
||||
build: ./control_plane
|
||||
# CI (.forgejo/workflows/build-image.yml) publishes this same build to
|
||||
# the registry on every push to master, tagged `latest` plus the short
|
||||
# commit sha. `docker compose up` builds fresh from `build:` above and
|
||||
# tags the result as this name, ignoring this being a registry ref;
|
||||
# `docker stack deploy` (Swarm ignores `build:` entirely, see the
|
||||
# Swarm caveats in README.md) pulls it instead — no manual
|
||||
# `docker build -t ... ./control_plane` step needed there anymore.
|
||||
# Always build from this checkout — never pull a registry image that may
|
||||
# be older than the code you just pulled. The image name below only
|
||||
# matters for `docker stack deploy` (Swarm can't build; see the Swarm
|
||||
# caveats in README.md), which pulls what CI publishes on every push to
|
||||
# master (`latest` plus the short commit sha).
|
||||
pull_policy: build
|
||||
image: ${GATEWAY_CONTROL_PLANE_IMAGE:-forgejo.sourcegate.online/robert/furtka-gateway-control-plane:latest}
|
||||
container_name: furtka-gateway-control-plane
|
||||
environment:
|
||||
|
|
@ -58,14 +57,10 @@ services:
|
|||
# implication of doing that when you choose it.
|
||||
- "${GATEWAY_CONTROL_PLANE_BIND:-127.0.0.1:8090}:8090"
|
||||
restart: unless-stopped
|
||||
# No custom `networks:` here on purpose — this stays on the default
|
||||
# bridge network compose creates unless you also run YOUR Traefik in
|
||||
# this SAME compose project. To reach a Traefik running elsewhere (its
|
||||
# own compose project, or your own homelab stack), ops/deploy.sh joins
|
||||
# this container to your Traefik's existing docker network after
|
||||
# `up` via `docker network connect` — no external-network dependency
|
||||
# baked into this file, so it works whether or not that network
|
||||
# exists yet at compose-up time.
|
||||
# No `networks:` here on purpose — your Traefik's network name differs
|
||||
# per setup. ops/deploy.sh writes a gitignored
|
||||
# docker-compose.override.yaml (auto-loaded by `docker compose`) that
|
||||
# joins this container to it from the first second.
|
||||
|
||||
volumes:
|
||||
control_plane_data:
|
||||
|
|
|
|||
303
ops/deploy.sh
303
ops/deploy.sh
|
|
@ -1,24 +1,23 @@
|
|||
#!/usr/bin/env bash
|
||||
# Interactive setup for furtka-gateway's control-plane, plugged into a
|
||||
# Traefik and wg-easy YOU already run (as sibling containers, or otherwise
|
||||
# reachable) — see the root README's Prerequisites checklist before
|
||||
# running this. No zero-infra option here: for that, use
|
||||
# examples/starter-stack/deploy.sh instead, which bundles its own
|
||||
# Traefik+wg-easy.
|
||||
# Sets up furtka-gateway's control-plane, plugged into a Traefik and
|
||||
# wg-easy YOU already run. Read the README's "Existing Traefik + wg-easy"
|
||||
# section first — in particular the routing requirement, which this script
|
||||
# can't set up or verify for you. On an empty server, use
|
||||
# examples/starter-stack/ instead.
|
||||
#
|
||||
# Safe to re-run — existing .env values become the defaults for each
|
||||
# prompt (press Enter to keep them), and rendering is always fully
|
||||
# regenerated from the current .env, never patched in place.
|
||||
# prompt (press Enter to keep them).
|
||||
#
|
||||
# Usage:
|
||||
# ops/deploy.sh full setup: prompts, render, compose up,
|
||||
# network join, health check
|
||||
# ops/deploy.sh --render-only only (re)write .env and the rendered
|
||||
# traefik/dynamic/control-plane.yml —
|
||||
# skips compose up / network join /
|
||||
# health check. Useful after hand-editing
|
||||
# .env, or to inspect what would be
|
||||
# generated before touching anything live.
|
||||
# ops/deploy.sh prompts, writes .env, starts the
|
||||
# control-plane, installs its Traefik
|
||||
# route, health-checks it
|
||||
# ops/deploy.sh --non-interactive same, but no prompts: every value
|
||||
# comes from .env (fails if one is missing)
|
||||
# ops/deploy.sh --render-only only (re)write .env and
|
||||
# rendered/control-plane.yml — touches
|
||||
# nothing live (not even your Traefik's
|
||||
# dynamic directory)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
|
@ -26,9 +25,14 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|||
cd "$REPO_ROOT"
|
||||
|
||||
RENDER_ONLY=0
|
||||
if [[ "${1:-}" == "--render-only" ]]; then
|
||||
RENDER_ONLY=1
|
||||
fi
|
||||
INTERACTIVE=1
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--render-only) RENDER_ONLY=1 ;;
|
||||
--non-interactive) INTERACTIVE=0 ;;
|
||||
*) echo "unknown option: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ENV_FILE=.env
|
||||
|
||||
|
|
@ -53,11 +57,13 @@ ask() {
|
|||
local __var="$1" __prompt="$2" __fallback="${3:-}"
|
||||
local __current="${!__var:-}"
|
||||
local __default="${__current:-$__fallback}"
|
||||
local __ans
|
||||
if [[ -n "$__default" ]]; then
|
||||
read -r -p "$__prompt [$__default]: " __ans
|
||||
else
|
||||
read -r -p "$__prompt: " __ans
|
||||
local __ans=""
|
||||
if [[ "$INTERACTIVE" -eq 1 ]]; then
|
||||
if [[ -n "$__default" ]]; then
|
||||
read -r -p "$__prompt [$__default]: " __ans
|
||||
else
|
||||
read -r -p "$__prompt: " __ans
|
||||
fi
|
||||
fi
|
||||
printf -v "$__var" '%s' "${__ans:-$__default}"
|
||||
}
|
||||
|
|
@ -65,105 +71,95 @@ ask() {
|
|||
ask_secret() {
|
||||
local __var="$1" __prompt="$2"
|
||||
local __current="${!__var:-}"
|
||||
local __ans
|
||||
if [[ -n "$__current" ]]; then
|
||||
read -r -s -p "$__prompt [unchanged, Enter to keep]: " __ans
|
||||
else
|
||||
read -r -s -p "$__prompt: " __ans
|
||||
local __ans=""
|
||||
if [[ "$INTERACTIVE" -eq 1 ]]; then
|
||||
if [[ -n "$__current" ]]; then
|
||||
read -r -s -p "$__prompt [unchanged, Enter to keep]: " __ans
|
||||
else
|
||||
read -r -s -p "$__prompt: " __ans
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
echo
|
||||
printf -v "$__var" '%s' "${__ans:-$__current}"
|
||||
}
|
||||
|
||||
require() {
|
||||
# require VARNAME — fail if empty
|
||||
if [[ -z "${!1:-}" ]]; then
|
||||
echo "$1 is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || { echo "missing required command: $1" >&2; exit 1; }
|
||||
}
|
||||
|
||||
require_cmd openssl
|
||||
if [[ "$RENDER_ONLY" -eq 0 ]]; then
|
||||
require_cmd docker
|
||||
require_cmd openssl
|
||||
require_cmd curl
|
||||
fi
|
||||
|
||||
echo "== furtka-gateway deploy (bring your own Traefik/wg-easy) =="
|
||||
echo "Haven't read the README's Prerequisites checklist yet? Do that first —"
|
||||
echo "this script can't verify your Traefik has a real network path to"
|
||||
echo "wg-easy's WireGuard peer subnet, and a broken deploy from skipping"
|
||||
echo "that is hard to debug after the fact."
|
||||
echo "== furtka-gateway: connect to your existing Traefik + wg-easy =="
|
||||
echo
|
||||
|
||||
# ---- tenancy mode ----
|
||||
while true; do
|
||||
ask GATEWAY_MODE "Mode (single / shared)" "single"
|
||||
case "$GATEWAY_MODE" in
|
||||
single|shared) break ;;
|
||||
*) echo "must be one of: single, shared" ;;
|
||||
esac
|
||||
done
|
||||
ask GATEWAY_MODE "Mode (single / shared)" "single"
|
||||
case "$GATEWAY_MODE" in
|
||||
single|shared) ;;
|
||||
*) echo "GATEWAY_MODE must be single or shared" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
# ---- wg-easy ----
|
||||
echo
|
||||
ask WG_EASY_URL "Your wg-easy's admin API URL (e.g. http://wg-easy:51821)" ""
|
||||
if [[ -z "$WG_EASY_URL" ]]; then
|
||||
echo "WG_EASY_URL is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
while true; do
|
||||
ask WG_EASY_API_VERSION "Your wg-easy image's version (14 or 15 — check the tag)" "14"
|
||||
case "$WG_EASY_API_VERSION" in
|
||||
14|15) break ;;
|
||||
*) echo "must be 14 or 15 — these are different, incompatible APIs (see control_plane/wgeasy.py vs wgeasy_v15.py)" ;;
|
||||
esac
|
||||
done
|
||||
ask WG_EASY_URL "Your wg-easy's admin API URL, as the control-plane will reach it (e.g. http://wg-easy:51821)" ""
|
||||
require WG_EASY_URL
|
||||
ask WG_EASY_API_VERSION "Your wg-easy image's major version (14 or 15 — check the tag)" "15"
|
||||
case "$WG_EASY_API_VERSION" in
|
||||
14|15) ;;
|
||||
*) echo "WG_EASY_API_VERSION must be 14 or 15 (different, incompatible APIs)" >&2; exit 1 ;;
|
||||
esac
|
||||
if [[ "$WG_EASY_API_VERSION" == "15" ]]; then
|
||||
ask WG_EASY_ADMIN_USERNAME "wg-easy admin username" "admin"
|
||||
ask GATEWAY_WG_ALLOWED_IPS "wg-easy's WireGuard peer subnet (restricts new clients to split-tunnel)" "10.8.0.0/24"
|
||||
ask GATEWAY_WG_ALLOWED_IPS "wg-easy's WireGuard peer subnet (new clients get split-tunnel to this)" "10.8.0.0/24"
|
||||
fi
|
||||
ask_secret WG_EASY_ADMIN_PASSWORD "wg-easy admin password"
|
||||
if [[ -z "${WG_EASY_ADMIN_PASSWORD:-}" ]]; then
|
||||
echo "wg-easy admin password is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
require WG_EASY_ADMIN_PASSWORD
|
||||
|
||||
# ---- Traefik ----
|
||||
echo
|
||||
ask GATEWAY_DYNAMIC_DIR_HOST "Host directory YOUR Traefik's file provider watches (or a new one you'll add as an extra providers.file directory)" ""
|
||||
if [[ -z "$GATEWAY_DYNAMIC_DIR_HOST" ]]; then
|
||||
echo "GATEWAY_DYNAMIC_DIR_HOST is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
ask GATEWAY_CERT_RESOLVER "Your Traefik's certResolver name (blank if it applies a default cert automatically)" ""
|
||||
ask GATEWAY_CONTROL_PLANE_HOST "Control-plane API hostname (its own A/AAAA record)" "gw.example.com"
|
||||
ask GATEWAY_DYNAMIC_DIR_HOST "Host directory your Traefik's file provider watches" ""
|
||||
require GATEWAY_DYNAMIC_DIR_HOST
|
||||
ask GATEWAY_CERT_RESOLVER "Your Traefik's certResolver name (blank if a default cert applies automatically)" ""
|
||||
ask GATEWAY_CONTROL_PLANE_HOST "Gateway API hostname (what you enter on the box)" ""
|
||||
require GATEWAY_CONTROL_PLANE_HOST
|
||||
|
||||
echo
|
||||
echo "How does YOUR Traefik reach this control-plane container?"
|
||||
echo " 1) same docker network as my Traefik (recommended)"
|
||||
echo "How does your Traefik reach the control-plane container?"
|
||||
echo " 1) it's on the same docker network as my Traefik (recommended)"
|
||||
echo " 2) separate host — I'll expose it at a reachable host:port myself"
|
||||
while true; do
|
||||
ask GATEWAY_REACHABILITY_MODE "Choice (1 or 2)" "1"
|
||||
case "$GATEWAY_REACHABILITY_MODE" in
|
||||
1|2) break ;;
|
||||
*) echo "must be 1 or 2" ;;
|
||||
esac
|
||||
done
|
||||
if [[ "$GATEWAY_REACHABILITY_MODE" == "1" ]]; then
|
||||
ask GATEWAY_TRAEFIK_NETWORK "Your Traefik's docker network name (docker network ls / docker inspect <traefik-container>)" ""
|
||||
if [[ -z "$GATEWAY_TRAEFIK_NETWORK" ]]; then
|
||||
echo "a network name is required for this choice" >&2
|
||||
exit 1
|
||||
fi
|
||||
GATEWAY_CONTROL_PLANE_BIND="127.0.0.1:8090"
|
||||
GATEWAY_CONTROL_PLANE_TARGET="furtka-gateway-control-plane:8090"
|
||||
else
|
||||
echo
|
||||
echo "WARNING: this binds the control-plane API beyond loopback. Its"
|
||||
echo "registration response carries a WireGuard private key in plaintext"
|
||||
echo "JSON — put real access control (firewall/security group) around"
|
||||
echo "whatever bind address you give it, not just this compose file."
|
||||
ask GATEWAY_CONTROL_PLANE_BIND "Bind address:port to expose it on (e.g. 10.0.0.5:8090)" ""
|
||||
ask GATEWAY_CONTROL_PLANE_TARGET "The same host:port, as YOUR Traefik will reach it" "$GATEWAY_CONTROL_PLANE_BIND"
|
||||
GATEWAY_TRAEFIK_NETWORK=""
|
||||
fi
|
||||
ask GATEWAY_REACHABILITY_MODE "Choice (1 or 2)" "1"
|
||||
case "$GATEWAY_REACHABILITY_MODE" in
|
||||
1)
|
||||
ask GATEWAY_TRAEFIK_NETWORK "Your Traefik's docker network name (see: docker inspect <traefik> --format '{{json .NetworkSettings.Networks}}')" ""
|
||||
require GATEWAY_TRAEFIK_NETWORK
|
||||
GATEWAY_CONTROL_PLANE_BIND="127.0.0.1:8090"
|
||||
GATEWAY_CONTROL_PLANE_TARGET="furtka-gateway-control-plane:8090"
|
||||
;;
|
||||
2)
|
||||
echo
|
||||
echo "WARNING: this binds the control-plane API beyond loopback. Its"
|
||||
echo "registration response carries a WireGuard private key in plaintext"
|
||||
echo "JSON — put real access control (firewall/security group) around"
|
||||
echo "whatever bind address you give it."
|
||||
ask GATEWAY_CONTROL_PLANE_BIND "Bind address:port to expose it on (e.g. 10.0.0.5:8090)" ""
|
||||
require GATEWAY_CONTROL_PLANE_BIND
|
||||
ask GATEWAY_CONTROL_PLANE_TARGET "The same host:port, as your Traefik will reach it" "$GATEWAY_CONTROL_PLANE_BIND"
|
||||
GATEWAY_TRAEFIK_NETWORK=""
|
||||
;;
|
||||
*) echo "GATEWAY_REACHABILITY_MODE must be 1 or 2" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
# ---- tokens (generated once, kept on re-run) ----
|
||||
GATEWAY_BOX_TOKEN="${GATEWAY_BOX_TOKEN:-$(openssl rand -hex 32)}"
|
||||
|
|
@ -183,74 +179,111 @@ echo "Writing $ENV_FILE"
|
|||
echo "GATEWAY_DYNAMIC_DIR_HOST=$GATEWAY_DYNAMIC_DIR_HOST"
|
||||
echo "GATEWAY_CERT_RESOLVER=$GATEWAY_CERT_RESOLVER"
|
||||
echo "GATEWAY_CONTROL_PLANE_HOST=$GATEWAY_CONTROL_PLANE_HOST"
|
||||
echo "GATEWAY_REACHABILITY_MODE=$GATEWAY_REACHABILITY_MODE"
|
||||
echo "GATEWAY_CONTROL_PLANE_BIND=$GATEWAY_CONTROL_PLANE_BIND"
|
||||
echo "GATEWAY_CONTROL_PLANE_TARGET=$GATEWAY_CONTROL_PLANE_TARGET"
|
||||
[[ -n "$GATEWAY_TRAEFIK_NETWORK" ]] && echo "GATEWAY_TRAEFIK_NETWORK=$GATEWAY_TRAEFIK_NETWORK"
|
||||
[[ -n "${GATEWAY_CONTROL_PLANE_IMAGE:-}" ]] && echo "GATEWAY_CONTROL_PLANE_IMAGE=$GATEWAY_CONTROL_PLANE_IMAGE"
|
||||
echo "GATEWAY_BOX_TOKEN=$GATEWAY_BOX_TOKEN"
|
||||
echo "GATEWAY_ADMIN_TOKEN=$GATEWAY_ADMIN_TOKEN"
|
||||
} > "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
|
||||
# ---- render traefik/dynamic/control-plane.yml ----
|
||||
echo "Rendering traefik/dynamic/control-plane.yml"
|
||||
mkdir -p "$GATEWAY_DYNAMIC_DIR_HOST"
|
||||
# Only the http: block onward — the .example's header comment explains the
|
||||
# placeholders BY NAME (for a human reading that template), which would
|
||||
# otherwise also get corrupted by these same substitutions.
|
||||
sed -n '/^http:/,$p' traefik/dynamic/control-plane.yml.example > "$GATEWAY_DYNAMIC_DIR_HOST/control-plane.yml"
|
||||
sed -i "s|GATEWAY_CONTROL_PLANE_HOST|$GATEWAY_CONTROL_PLANE_HOST|" "$GATEWAY_DYNAMIC_DIR_HOST/control-plane.yml"
|
||||
sed -i "s|GATEWAY_CONTROL_PLANE_TARGET|$GATEWAY_CONTROL_PLANE_TARGET|" "$GATEWAY_DYNAMIC_DIR_HOST/control-plane.yml"
|
||||
if [[ -z "$GATEWAY_CERT_RESOLVER" ]]; then
|
||||
# No resolver configured on your Traefik — drop the tls: block entirely
|
||||
# so its own default cert applies, mirroring what GATEWAY_CERT_RESOLVER
|
||||
# blank means for every app route (see traefikconf.py).
|
||||
sed -i "/^ tls:$/,/^ certResolver:.*$/d" "$GATEWAY_DYNAMIC_DIR_HOST/control-plane.yml"
|
||||
# ---- join your Traefik's network from the compose file itself ----
|
||||
# An override (auto-loaded by `docker compose`, gitignored) rather than a
|
||||
# `docker network connect` after startup: the control-plane talks to
|
||||
# wg-easy as soon as it starts, so the network must be there from the
|
||||
# first second, and it must survive container recreation.
|
||||
if [[ -n "$GATEWAY_TRAEFIK_NETWORK" ]]; then
|
||||
echo "Writing docker-compose.override.yaml (joins network: $GATEWAY_TRAEFIK_NETWORK)"
|
||||
cat > docker-compose.override.yaml <<EOF
|
||||
# Generated by ops/deploy.sh — joins your Traefik's docker network.
|
||||
services:
|
||||
control-plane:
|
||||
networks:
|
||||
- default
|
||||
- traefik
|
||||
networks:
|
||||
traefik:
|
||||
name: $GATEWAY_TRAEFIK_NETWORK
|
||||
external: true
|
||||
EOF
|
||||
else
|
||||
sed -i "s|certResolver: le|certResolver: $GATEWAY_CERT_RESOLVER|" "$GATEWAY_DYNAMIC_DIR_HOST/control-plane.yml"
|
||||
rm -f docker-compose.override.yaml
|
||||
fi
|
||||
|
||||
# ---- render the control-plane's Traefik route (not yet installed) ----
|
||||
echo "Writing rendered/control-plane.yml"
|
||||
mkdir -p rendered
|
||||
# Only the http: block onward — the .example's header comment explains the
|
||||
# placeholders BY NAME, which these substitutions would otherwise corrupt.
|
||||
sed -n '/^http:/,$p' traefik/dynamic/control-plane.yml.example > rendered/control-plane.yml
|
||||
sed -i "s|GATEWAY_CONTROL_PLANE_HOST|$GATEWAY_CONTROL_PLANE_HOST|" rendered/control-plane.yml
|
||||
sed -i "s|GATEWAY_CONTROL_PLANE_TARGET|$GATEWAY_CONTROL_PLANE_TARGET|" rendered/control-plane.yml
|
||||
if [[ -z "$GATEWAY_CERT_RESOLVER" ]]; then
|
||||
# No resolver on your Traefik — drop the tls: block so its default cert
|
||||
# applies, mirroring what a blank GATEWAY_CERT_RESOLVER means for every
|
||||
# app route (see traefikconf.py).
|
||||
sed -i "/^ tls:$/,/^ certResolver:.*$/d" rendered/control-plane.yml
|
||||
else
|
||||
sed -i "s|certResolver: le|certResolver: $GATEWAY_CERT_RESOLVER|" rendered/control-plane.yml
|
||||
fi
|
||||
|
||||
if [[ "$RENDER_ONLY" -eq 1 ]]; then
|
||||
echo
|
||||
echo "--render-only: stopping before compose up / network join / health check."
|
||||
echo "--render-only: nothing live was touched. Inspect .env and rendered/control-plane.yml."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- bring the control-plane up ----
|
||||
# ---- bring the control-plane up (always built from this checkout) ----
|
||||
echo
|
||||
echo "== docker compose up -d =="
|
||||
docker compose up -d
|
||||
echo "== docker compose up -d --build =="
|
||||
docker compose up -d --build
|
||||
|
||||
# ---- join your Traefik's network, if that's the reachability choice ----
|
||||
if [[ -n "$GATEWAY_TRAEFIK_NETWORK" ]]; then
|
||||
echo
|
||||
echo "== joining docker network: $GATEWAY_TRAEFIK_NETWORK =="
|
||||
docker network connect "$GATEWAY_TRAEFIK_NETWORK" furtka-gateway-control-plane 2>/dev/null \
|
||||
|| echo " (already connected, or connect failed — check \`docker network ls\` and the name above)"
|
||||
fi
|
||||
|
||||
# ---- health check ----
|
||||
echo
|
||||
echo "== health check =="
|
||||
for _ in $(seq 1 10); do
|
||||
healthy=0
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -sf "http://${GATEWAY_CONTROL_PLANE_BIND}/healthz" >/dev/null 2>&1; then
|
||||
echo "control-plane is up."
|
||||
healthy=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
sleep 2
|
||||
done
|
||||
if ! curl -sf "http://${GATEWAY_CONTROL_PLANE_BIND}/healthz" >/dev/null 2>&1; then
|
||||
if [[ "$healthy" -ne 1 ]]; then
|
||||
echo "control-plane did not become healthy — check: docker compose logs control-plane" >&2
|
||||
echo "(a wrong WG_EASY_URL or wg-easy password shows up there)" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " ok: control-plane is up"
|
||||
|
||||
# ---- install the route into your Traefik's dynamic directory ----
|
||||
mkdir -p "$GATEWAY_DYNAMIC_DIR_HOST"
|
||||
cp rendered/control-plane.yml "$GATEWAY_DYNAMIC_DIR_HOST/control-plane.yml"
|
||||
echo " installed $GATEWAY_DYNAMIC_DIR_HOST/control-plane.yml"
|
||||
|
||||
public_ok=0
|
||||
for _ in $(seq 1 20); do
|
||||
if curl -sf -m 10 "https://$GATEWAY_CONTROL_PLANE_HOST/healthz" >/dev/null 2>&1; then
|
||||
public_ok=1
|
||||
break
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
if [[ "$public_ok" -eq 1 ]]; then
|
||||
echo " ok: https://$GATEWAY_CONTROL_PLANE_HOST/healthz"
|
||||
else
|
||||
echo " WARNING: https://$GATEWAY_CONTROL_PLANE_HOST/healthz not reachable."
|
||||
echo " Check that DNS points at your Traefik and that Traefik watches"
|
||||
echo " $GATEWAY_DYNAMIC_DIR_HOST (providers.file.directory)."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "== done =="
|
||||
echo "Still needed before a real box can connect:"
|
||||
echo " - Add $GATEWAY_DYNAMIC_DIR_HOST as a providers.file directory on"
|
||||
echo " your Traefik, if it isn't already watching it."
|
||||
echo " - Verify Traefik can actually reach wg-easy's WireGuard peer subnet"
|
||||
echo " (the README's Prerequisites checklist has a command for this) —"
|
||||
echo " this is the one thing that can't be checked from here."
|
||||
echo " - DNS: A/AAAA for $GATEWAY_CONTROL_PLANE_HOST -> wherever your"
|
||||
echo " Traefik is reachable."
|
||||
echo " - On the box: furtka gateway connect <this gateway's URL> <GATEWAY_BOX_TOKEN from .env> <box name>"
|
||||
echo "Before publishing apps, make sure your Traefik can reach wg-easy's peer"
|
||||
echo "subnet — see the README's routing section. Quick check after a box connects:"
|
||||
echo " docker exec <your-traefik> ping -c1 10.8.0.<box>"
|
||||
echo
|
||||
echo "Connect a Furtka box: open its web UI -> Settings -> Gateway, and enter"
|
||||
echo " Gateway URL: https://$GATEWAY_CONTROL_PLANE_HOST"
|
||||
echo " Registration token: the GATEWAY_BOX_TOKEN value in $REPO_ROOT/.env"
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
# Copy to traefik/dynamic/control-plane.yml (into whatever directory YOUR
|
||||
# Traefik's file provider watches — see the root README's Prerequisites
|
||||
# checklist) with two placeholders substituted:
|
||||
# Template for the control-plane's own Traefik route. ops/deploy.sh renders
|
||||
# it to rendered/control-plane.yml and installs it into the directory YOUR
|
||||
# Traefik's file provider watches, with two placeholders substituted:
|
||||
#
|
||||
# GATEWAY_CONTROL_PLANE_HOST a real hostname you control (e.g.
|
||||
# gw.example.com), with its own A/AAAA record
|
||||
# pointing at this host.
|
||||
# GATEWAY_CONTROL_PLANE_TARGET where YOUR Traefik actually reaches the
|
||||
# control-plane container. If it joined your
|
||||
# Traefik's docker network (ops/deploy.sh
|
||||
# does this via `docker network connect` —
|
||||
# the primary, recommended path), this is
|
||||
# `furtka-gateway-control-plane:8090` — the
|
||||
# container's fixed name, which Docker's
|
||||
# embedded DNS resolves on any network it's
|
||||
# joined to (unlike the compose *service*
|
||||
# name `control-plane`, which is only
|
||||
# guaranteed to resolve on networks compose
|
||||
# itself manages — not one joined after the
|
||||
# fact via a bare `docker network connect`).
|
||||
# Traefik's docker network (the recommended
|
||||
# path — ops/deploy.sh sets that up), this is
|
||||
# `furtka-gateway-control-plane:8090`, the
|
||||
# container's fixed name (unambiguous on a
|
||||
# shared network, unlike a generic service
|
||||
# name like `control-plane`).
|
||||
# If instead you exposed it on a separate
|
||||
# host (GATEWAY_CONTROL_PLANE_BIND widened
|
||||
# beyond 127.0.0.1 in docker-compose.yaml),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue