The hostnames are already declared, once, in the fabio route tags. A domains.txt would be a second source of truth free to disagree with what is actually served, so myos cert reads the tags instead and decides on its own which name needs a wildcard: one is asked for where a tag uses one, and it absorbs the concrete names it covers. A wildcard covers a single label, so a.b.example.org keeps its own certificate. dehydrated issues them: a shell script, no python, which fits a tool that has to install on any server. It answers http-01 itself on a port bound to the loopback and routed by fabio, and delegates dns-01 to a provider hook. The deploy hook writes the two file names fabio watches for, through a temporary name so fabio never reads half a certificate. Fixed on the way: the port parser wanted six spaces of indentation and the catalogue writes four, so every stack that binds its ports was reported unbound.
120 lines
5.0 KiB
Bash
120 lines
5.0 KiB
Bash
#shellcheck shell=sh
|
|
# shellcheck disable=SC3028 # HOSTNAME is a myos variable, set by bin/myos
|
|
# expose: which addresses a published port binds to.
|
|
#
|
|
# Docker writes its own firewall rules, so on linux a port published with
|
|
# `-p 8080:80` answers the internet whatever the host firewall says. ufw-docker
|
|
# patches that afterwards, on linux only, as root.
|
|
#
|
|
# The portable answer is to publish where you mean to in the first place:
|
|
# `-p 127.0.0.1:8080:80` only ever listens on the loopback, identically on
|
|
# linux and on macOS, with no firewall and no privilege. A stack says which
|
|
# scope a port belongs to, and myos resolves the address.
|
|
#
|
|
# public the internet: a load balancer, a public DNS or mail service
|
|
# mesh the private network between the hosts of the fleet
|
|
# private this host only: everything the load balancer reaches for you
|
|
#
|
|
# A stack does not declare its scope on the side: it is which of these it binds
|
|
# to, read from the compose file. One source of truth, which cannot drift from
|
|
# what is actually published. MYOS_BIND_<SCOPE> sets the address of a scope on
|
|
# a given host, which is the part that belongs to the host rather than to the
|
|
# stack.
|
|
|
|
# myos_bind SCOPE the address a port of that scope binds to
|
|
myos_bind() {
|
|
case $1 in
|
|
public) printf '%s' "${MYOS_BIND_PUBLIC:-0.0.0.0}" ;;
|
|
mesh) printf '%s' "${MYOS_BIND_MESH:-$(myos_bind_mesh)}" ;;
|
|
private|*) printf '%s' "${MYOS_BIND_PRIVATE:-127.0.0.1}" ;;
|
|
esac
|
|
}
|
|
|
|
# myos_bind_mesh the address of the mesh interface, empty when there is none.
|
|
# Falls back to the private address so that a stack scoped to the mesh on a
|
|
# host that has none stays local rather than becoming public.
|
|
myos_bind_mesh() {
|
|
_if=${MYOS_MESH_IFACE:-}
|
|
if [ -z "$_if" ]; then
|
|
for _c in easytier tun0 tailscale0 mycelium wg0; do
|
|
if myos_iface_addr "$_c" >/dev/null 2>&1 && [ -n "$(myos_iface_addr "$_c")" ]; then
|
|
_if=$_c; break
|
|
fi
|
|
done
|
|
fi
|
|
[ -n "$_if" ] || { printf '%s' "${MYOS_BIND_PRIVATE:-127.0.0.1}"; return 0; }
|
|
_a=$(myos_iface_addr "$_if")
|
|
[ -n "$_a" ] || _a=${MYOS_BIND_PRIVATE:-127.0.0.1}
|
|
printf '%s' "$_a"
|
|
}
|
|
|
|
# myos_iface_addr NAME the first address of an interface, on linux or macOS
|
|
myos_iface_addr() {
|
|
if myos_have ip; then
|
|
ip -o addr show "$1" 2>/dev/null | awk '$3 ~ /^inet6?$/ {sub(/\/.*/,"",$4); print $4; exit}'
|
|
elif myos_have ifconfig; then
|
|
ifconfig "$1" 2>/dev/null | awk '$1 == "inet" || $1 == "inet6" {print $2; exit}'
|
|
fi
|
|
}
|
|
|
|
# myos_stack_prefix REF the prefix the settings of a stack use.
|
|
# A host stack is prefixed by HOST_, which is how the catalogue names them:
|
|
# HOST_FABIO_SERVICE_9998_TAGS, HOST_FTPS_UFW_DOCKER. Everything else uses the
|
|
# stack name alone: SUPABASE_KONG_SERVICE_8000_TAGS.
|
|
myos_stack_prefix() {
|
|
_n=$(myos_upper "$(myos_stack_name "$1")")
|
|
case $(myos_scope "$1") in
|
|
host) printf 'HOST_%s' "$_n" ;;
|
|
user) printf 'USER_%s' "$_n" ;;
|
|
*) printf '%s' "$_n" ;;
|
|
esac
|
|
}
|
|
|
|
# myos_expose_declared FILE... SERVICE|CONTAINER_PORT|SCOPE for every port a
|
|
# compose file publishes, read from the file as written rather than from the
|
|
# resolved configuration.
|
|
#
|
|
# The scope is not declared twice: it is which binding the file asks for.
|
|
# ${MYOS_BIND_PUBLIC}:443:443 public
|
|
# ${MYOS_BIND_PRIVATE}::8080 private
|
|
# ${MYOS_BIND_MESH}::7946 mesh
|
|
# 127.0.0.1:5432:5432 pinned to an address, deliberate but fixed
|
|
# 80 or 8080:80 unbound: docker binds every address, and
|
|
# nobody chose that
|
|
#
|
|
# Resolving first would lose the difference: ${MYOS_BIND_PRIVATE} and a
|
|
# hand-written 127.0.0.1 both become 127.0.0.1, and an unbound port becomes
|
|
# 0.0.0.0 exactly like a deliberate public one.
|
|
myos_expose_declared() {
|
|
awk '
|
|
function indent(line, n) { match(line, /^ */); return RLENGTH }
|
|
function emit(entry, e, scope, target, n, parts) {
|
|
e = entry
|
|
sub(/^ *- */, "", e)
|
|
gsub(/^["'"'"']|["'"'"']$/, "", e)
|
|
if (e ~ /\$\{MYOS_BIND_PUBLIC[^}]*\}/) scope = "public"
|
|
else if (e ~ /\$\{MYOS_BIND_MESH[^}]*\}/) scope = "mesh"
|
|
else if (e ~ /\$\{MYOS_BIND_PRIVATE[^}]*\}/) scope = "private"
|
|
else if (e ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:/) scope = "pinned"
|
|
else if (e ~ /^\[/) scope = "pinned"
|
|
else scope = "unbound"
|
|
target = e
|
|
sub(/\/[a-z]+$/, "", target)
|
|
n = split(target, parts, ":")
|
|
target = parts[n]
|
|
if (target ~ /^[0-9]+(-[0-9]+)?$/) printf "%s|%s|%s\n", svc, target, scope
|
|
}
|
|
/^services:[ \t]*$/ { insvc = 1; svcind = -1; next }
|
|
!insvc { next }
|
|
# a service is the first level of keys under services:
|
|
/^ *[a-zA-Z0-9_.-]+:[ \t]*$/ && (svcind == -1 || indent($0) == svcind) {
|
|
if (svcind == -1) svcind = indent($0)
|
|
svc = $1; sub(/:$/, "", svc); inports = 0; next
|
|
}
|
|
/^ *ports:/ { inports = 1; portind = indent($0); next }
|
|
# the list items of a ports: block, whatever indent they use
|
|
inports && /^ *- / && indent($0) >= portind { emit($0); next }
|
|
inports && /^ *[a-zA-Z0-9_.-]+:/ { inports = 0 }
|
|
' "$@"
|
|
}
|