feat(new-service): --public opt-in via Cloudflare Tunnel; delete-service removes the CNAME
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4b6f898089
commit
e094cdbdd9
4 changed files with 110 additions and 6 deletions
|
|
@ -80,6 +80,9 @@ load_config() {
|
|||
for v in curl jq git kubectl; do need_cmd "$v"; done
|
||||
|
||||
export KUBECONFIG
|
||||
# Public access is optional config; only --public / delete-service use it.
|
||||
PUBLIC_DOMAIN=${PUBLIC_DOMAIN:-} CF_ZONE_ID=${CF_ZONE_ID:-} CF_TUNNEL_ID=${CF_TUNNEL_ID:-}
|
||||
CLOUDFLARE_TOKEN_FILE=${CLOUDFLARE_TOKEN_FILE:-}
|
||||
FORGEJO_TOKEN=$(tr -d '\n' <"$FORGEJO_ADMIN_TOKEN_FILE")
|
||||
REGISTRY_TOKEN=$(tr -d '\n' <"$REGISTRY_PUSH_TOKEN_FILE")
|
||||
GITOPS_TOKEN=$(tr -d '\n' <"$GITOPS_TOKEN_FILE")
|
||||
|
|
@ -126,6 +129,73 @@ api_write() {
|
|||
api "$method" "$path" "$body" "$as"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------- Cloudflare (public DNS)
|
||||
# cf_require: the Plan 13 settings must be present before touching DNS.
|
||||
cf_require() {
|
||||
local v
|
||||
for v in PUBLIC_DOMAIN CF_ZONE_ID CF_TUNNEL_ID CLOUDFLARE_TOKEN_FILE; do
|
||||
[[ -n ${!v:-} ]] || die "platform.env: $v is not set (needed for public access, see platform.env.example)"
|
||||
done
|
||||
[[ -r $CLOUDFLARE_TOKEN_FILE ]] || die "platform.env: CLOUDFLARE_TOKEN_FILE=$CLOUDFLARE_TOKEN_FILE is not readable"
|
||||
}
|
||||
|
||||
# cf_api METHOD PATH [JSON]: sets CF_BODY; dies if Cloudflare reports failure.
|
||||
# The token goes through a header file so it never appears in argv.
|
||||
CF_BODY=''
|
||||
cf_api() {
|
||||
local method=$1 path=$2 body=${3:-} hdr args
|
||||
hdr=$(tmpfile)
|
||||
chmod 600 "$hdr"
|
||||
printf 'Authorization: Bearer %s\n' "$(tr -d '\n' <"$CLOUDFLARE_TOKEN_FILE")" >"$hdr"
|
||||
args=(-sS -X "$method" -H "@$hdr" -H 'Content-Type: application/json')
|
||||
[[ -n $body ]] && args+=(--data-binary "$body")
|
||||
CF_BODY=$(curl "${args[@]}" "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID$path") || die "curl failed: Cloudflare $method $path"
|
||||
[[ $(jq -r .success <<<"$CF_BODY") == true ]] || die "Cloudflare $method $path failed: $(jq -c .errors <<<"$CF_BODY")"
|
||||
}
|
||||
|
||||
# cf_record_id NAME: id of the DNS record for NAME.$PUBLIC_DOMAIN, or ''.
|
||||
cf_record_id() {
|
||||
cf_api GET "/dns_records?name=$1.$PUBLIC_DOMAIN&type=CNAME"
|
||||
jq -r '.result[0].id // empty' <<<"$CF_BODY"
|
||||
}
|
||||
|
||||
# cf_cname_ensure NAME: proxied CNAME NAME -> <tunnel>.cfargotunnel.com.
|
||||
cf_cname_ensure() {
|
||||
local name=$1 id body
|
||||
cf_require
|
||||
body=$(jq -nc --arg n "$name" --arg t "$CF_TUNNEL_ID.cfargotunnel.com" \
|
||||
'{type:"CNAME", name:$n, content:$t, proxied:true, comment:"platform new-service --public"}')
|
||||
if is_dry; then
|
||||
dry "Cloudflare: ensure proxied CNAME $name.$PUBLIC_DOMAIN -> $CF_TUNNEL_ID.cfargotunnel.com ($body)"
|
||||
return 0
|
||||
fi
|
||||
id=$(cf_record_id "$name")
|
||||
if [[ -n $id ]]; then
|
||||
ok "DNS $name.$PUBLIC_DOMAIN exists"
|
||||
else
|
||||
cf_api POST /dns_records "$body"
|
||||
ok "DNS $name.$PUBLIC_DOMAIN -> tunnel created"
|
||||
fi
|
||||
}
|
||||
|
||||
# cf_cname_delete NAME: remove NAME.$PUBLIC_DOMAIN if it is one of ours (points at the tunnel).
|
||||
cf_cname_delete() {
|
||||
local name=$1 id
|
||||
[[ -n $PUBLIC_DOMAIN && -n $CF_ZONE_ID && -n $CF_TUNNEL_ID && -r $CLOUDFLARE_TOKEN_FILE ]] || return 0
|
||||
if is_dry; then
|
||||
dry "Cloudflare: delete CNAME $name.$PUBLIC_DOMAIN if it points at $CF_TUNNEL_ID.cfargotunnel.com"
|
||||
return 0
|
||||
fi
|
||||
cf_api GET "/dns_records?name=$name.$PUBLIC_DOMAIN&type=CNAME"
|
||||
id=$(jq -r --arg t "$CF_TUNNEL_ID.cfargotunnel.com" '.result[] | select(.content == $t) | .id' <<<"$CF_BODY" | head -1)
|
||||
if [[ -n $id ]]; then
|
||||
cf_api DELETE "/dns_records/$id"
|
||||
ok "DNS $name.$PUBLIC_DOMAIN deleted"
|
||||
else
|
||||
ok "no tunnel DNS record for $name.$PUBLIC_DOMAIN"
|
||||
fi
|
||||
}
|
||||
|
||||
repo_exists() {
|
||||
api GET "/repos/$FORGEJO_OWNER/$1"
|
||||
[[ $API_CODE == 200 ]]
|
||||
|
|
@ -217,13 +287,14 @@ validate_name() {
|
|||
|
||||
# Print the URLs a human wants after a deploy.
|
||||
print_urls() {
|
||||
local name=$1 kind=$2
|
||||
local name=$1 kind=$2 public=${3:-0}
|
||||
local host="$name.$INGRESS_DOMAIN"
|
||||
local kql
|
||||
kql=$(jq -rn --arg s "$name" '"service:\"\($s)\"" | @uri')
|
||||
cat <<EOF
|
||||
|
||||
service : https://$host/
|
||||
$([[ $public == 1 ]] && echo " public : https://$name.$PUBLIC_DOMAIN/")
|
||||
health : https://$host/$([[ $kind == backend ]] && echo readyz || echo healthz)
|
||||
repo : $FORGEJO_URL/$FORGEJO_OWNER/$name
|
||||
argocd : https://argocd.$INGRESS_DOMAIN/applications/argocd/$name
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue