#!/bin/sh # Joins the box it runs on to the fleet. One script, two callers: a person pastes the line # `./fleet invite ` prints, which fetches this file from join.databob-labs.com and runs it # with sudo, and a cloud box runs it from cloud-init at first boot with the key the tailscale root # minted. The arguments are the box's name, a machine in fleet-config.yaml, and its key, which works # once and dies within the hour: the one secret here, worth nothing once the join is done. Every box # joins the tailnet under its name with MagicDNS off and gets Docker from Docker's apt repository # with live-restore on. `--control` adds what the control plane alone needs: Tailscale SSH, so a # person's shell needs no key on the box, the fleet network on FLEET_SUBNET, the firewall rule # admitting only that network to the metadata address, and Coolify at COOLIFY_VERSION with # auto-update off. Every step is idempotent, so a second run changes nothing. POSIX sh, since the # pasted line runs it as `sh` on a box with nothing installed yet. set -eu COOLIFY_VERSION=4.3.23 FLEET_NETWORK=fleet FLEET_SUBNET=10.255.0.0/24 METADATA_ADDRESS=169.254.169.254 # Where Docker takes a new network's subnet from: outside the fleet VPC (10.20.0.0/16), FLEET_SUBNET # and the tailnet (100.64.0.0/10). Coolify's installer keeps a daemon.json that names a pool. DOCKER_ADDRESS_POOL=172.16.0.0/12 DOCKER_PACKAGES="docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin" fail() { echo "join.sh: $1" >&2 exit 1 } apt_install() { for package in "$@"; do if ! dpkg -s "$package" >/dev/null 2>&1; then apt-get update -q apt-get install -y -q "$@" return fi done } write_daemon_json() { mkdir -p /etc/docker [ -f /etc/docker/daemon.json ] || echo '{}' >/etc/docker/daemon.json jq --arg pool "$DOCKER_ADDRESS_POOL" '{ "log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}, "default-address-pools": [{"base": $pool, "size": 24}] } * . | .["live-restore"] = true' /etc/docker/daemon.json >/etc/docker/daemon.json.join mv /etc/docker/daemon.json.join /etc/docker/daemon.json } add_docker_repository() { os=$(sed -n 's/^ID=//p' /etc/os-release) codename=$(sed -n 's/^VERSION_CODENAME=//p' /etc/os-release) case $os in ubuntu | debian) ;; *) fail "Docker's apt repository covers Ubuntu and Debian; this box is $os" ;; esac install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/$os/gpg" -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc cat >/etc/apt/sources.list.d/docker.sources </dev/null 2>&1; then return fi docker network create "$FLEET_NETWORK" --subnet "$FLEET_SUBNET" } install_firewall() { apt_install nftables mkdir -p /etc/fleet cat >/etc/fleet/firewall.nft </etc/systemd/system/fleet-firewall.service </dev/null; then curl -fsSL https://tailscale.com/install.sh | sh fi tailscale up --auth-key="$key" --hostname="$box" --accept-dns=false --ssh="$control" apt_install ca-certificates curl jq write_daemon_json if command -v docker >/dev/null; then systemctl reload docker else add_docker_repository # shellcheck disable=SC2086 apt_install $DOCKER_PACKAGES fi if [ "$control" = true ]; then create_fleet_network install_firewall install_coolify fi tailscale status --self --peers=false