Shared GitHub Actions workflows for the intisy-compose stacks: reusable workflows that every stack repository calls instead of carrying its own logic.
The reusable workflows of the intisy-compose stacks. A stack repository carries only a thin caller
that uses: a workflow here; anything shared with other organisations lives in
intisy/workflows instead.
| workflow | what it checks or does |
|---|---|
check-access.yml |
every port the calling stack publishes or exposes on the tailnet is in access/PORTS.md and listens only where its kind (web, api, protocol, public) allows, the stack's gateway is the shared one in gateway/ and every service in its namespace declares where it listens, its project, container and volume names follow the org's rules, its docker-compose.sh has the commands of its docker-compose.ps1 less those it declares windows_only (cli/check-cli.sh) and is committed LF and executable, its docker-compose.sh, scripts/*.sh, tests/cli/*.sh and tests/cli/fakes/* are clean under shellcheck -x of the version pinned in cli/shellcheck.pin (cli/check-shell.sh; cli/install-shellcheck.sh installs it), and its tests/cli/run.sh passes when it has one |
self-test.yml |
this repository's own suites, called by test.yml on every push: each check's fixture test, and the data-on-disk helper's bash twin and Pester suite over the same cases |
data-on-disk/ holds the restore decision every stack with a dumped database uses,
twice: data-on-disk.ps1 for docker-compose.ps1 and data-on-disk.sh, a function-for-function
bash twin, for docker-compose.sh. A stack vendors them byte for byte as
scripts/data-on-disk.ps1 and scripts/data-on-disk.sh (the sh once it has a docker-compose.sh), and
check-volumes.sh fails on any copy that differs. The decision cases live once, as data, in
data-on-disk/tests/cases/; restore-decision.Tests.ps1 (Pester 3.4 syntax) and
restore-decision.test.sh both read them, so a case added for one runs against both.
gateway/ holds the gateway every stack with a web UI or a tailnet port uses: Caddy owns
the network namespace and serves each UI on the tailnet name, with certificates only from the
Tailscale socket, and on a http://:<port> site published on 127.0.0.1; the tailscale sidecar
joins Caddy's namespace. A stack copies Caddyfile, gateway.yml, gateway.sh,
namespace-address.sh, tailscale.sh, tailnet-node.sh and tailnet-node.ps1 into its own
gateway/ folder byte for byte, extends both
services from its compose file, and adds only its own values:
gateway/sites.caddy (its sites, each tailnet site with import tailnet), gateway/serve.json
(the tailnet ports, each a TCPForward to the same port on 127.0.0.1), container names,
TAILNET_HOST, TS_HOSTNAME and the gateway's loopback ports. A change to the template is copied
into every stack in the same step; the check fails on any copy that differs.
Nothing in the namespace listens on 127.0.0.1 or every address, except a tailnet port. The
tailscale sidecar runs in userspace mode and hands every tailnet connection, TCP or UDP, on a port
serve.json does not forward to 127.0.0.1:<port> of the namespace, so a listener there answers on
the tailnet. Caddy's local sites bind the namespace's Docker address (gateway.sh), and so does
every app that shares the namespace: it reads the address with namespace-address.sh --single
(mounted from gateway/), and the gateway proxies to it as gateway:<port>, which Docker resolves
to the same address. The stack lists each such service's ports under a top-level
x-namespace-listeners block, as gateway (bound to that address), tailnet (a tailnet port in
access/PORTS.md, forwarded in serve.json when TCP) or unbound (the app cannot choose its
address; the reason sits beside the entry, and the check prints a note), and the check fails on an
undeclared service, an address setting on loopback or every address, and a loopback upstream in
sites.caddy.
A deleted stack leaves the tailnet. tailnet-node.sh and tailnet-node.ps1, twins with the same
messages, are what a stack's delete command calls after its down: they read the stable node ID of
the current profile from the stack's tailscaled.state (never the name, which another device may
share), remove that device through the Tailscale API (DELETE /api/v2/device/<node ID>) with the
optional TS_API_TOKEN from the stack's config file, then remove the state folder, so a later up
joins fresh. The token travels only on curl's config on stdin or in a PowerShell header, never on a
command line. Without a token they print the node's name and ID and the admin console's Machines
page instead; on an API error, or a state that names no node, they keep the folder so delete can
run again. gateway/tests/tailnet-node.test.sh and gateway/tests/tailnet-node.Tests.ps1 run them
against a fake API.
Public access is one switch, the same in every stack: PUBLIC_ACCESS=true in the stack's
config.env (default false, and anything but true, 1, yes or on counts as off) makes
tailscale.sh add Tailscale Funnel to the serve config for every forwarded port Funnel carries
(443, 8443, 10000), so those sites answer from the internet, without a login of their own, until the
switch is off again and the stack restarted. It needs the funnel node attribute in the tailnet
policy. A stack never opens Funnel any other way, and the ports Funnel carries are only ever web
ports in access/PORTS.md.
name: Check dashboard access
on:
workflow_dispatch:
push:
pull_request:
permissions:
contents: read
jobs:
check:
uses: intisy-compose/workflows/.github/workflows/check-access.yml@mainWith the stack repositories cloned next to this one:
bash workflows/access/check-access.shIt checks every *-compose directory beside workflows and exits non-zero, naming each violation,
when one breaks the standard or when it finds no stack at all.
bash workflows/access/tests/check-access.test.sh plants each violation in a scratch stack and
asserts that the check names it; cli/tests/check-cli.test.sh, cli/tests/check-shell.test.sh
(it needs the pinned shellcheck, or SHELLCHECK naming a wrapper around it) and
data-on-disk/tests/check-volumes.test.sh do the same for their checks, and
data-on-disk/tests/restore-decision.test.sh and gateway/tests/tailnet-node.test.sh run the
bash twins of the two helpers, data-on-disk/tests/run-pester.ps1 (Windows PowerShell 5.1) their
PowerShell twins. CI runs all of them through self-test.yml.