Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Fixed

- Made coordinatorless generic provider live smokes skip coordinator-only history and always clean up acquired leases after later lifecycle failures.
- Replaced privileged managed Linux Code Server and Tailscale installer scripts with checksum-verified archives or Tailscale's signed package repository with a pinned keyring in both CLI and coordinator bootstrap paths. Thanks @TurboTheTurtle.

## 0.35.0 - 2026-07-04

Expand Down
10 changes: 6 additions & 4 deletions docs/features/runner-bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ and built artifacts out of the image. See

## Tailscale

`--tailscale` on a managed Linux lease is also optional. Bootstrap installs the
Tailscale package, brings the box up on the configured tailnet, writes non-secret
metadata under `/var/lib/crabbox` (such as `tailscale-ipv4`, `tailscale-hostname`,
and exit-node details), and extends `crabbox-ready` with a bounded check that a
`--tailscale` on a managed Linux lease is also optional. The default package
mode installs Tailscale from its signed APT repository with a pinned keyring;
the opt-in pinned mode verifies a versioned static archive by SHA-256. Bootstrap
then brings the box up on the configured tailnet, writes non-secret metadata
under `/var/lib/crabbox` (such as `tailscale-ipv4`, `tailscale-hostname`, and
exit-node details), and extends `crabbox-ready` with a bounded check that a
`100.x` address has appeared.

The auth key is piped to `tailscale up` through stdin and is not persisted or
Expand Down
12 changes: 7 additions & 5 deletions docs/features/tailscale.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,13 @@ The auth key is never stored in lease records, provider labels, run logs, or loc
config. The short-lived key can still appear in user-data at the provider, so the
Worker only mints one-off ephemeral keys — never long-lived reusable keys.

The default installer mode runs Tailscale's package install script. Set
`CRABBOX_TAILSCALE_INSTALL_MODE=pinned` to download a static Tailscale archive,
verify the configured SHA-256 checksum, install the `tailscale` and `tailscaled`
binaries, and record the client version. The built-in pinned defaults track the
Islo Tailscale build so both bootstrap paths use the same binary version.
The default `package` mode configures Tailscale's signed stable APT repository,
verifies and scopes its pinned keyring with `signed-by`, installs the current
package, and records the client version. Set
`CRABBOX_TAILSCALE_INSTALL_MODE=pinned` to download a static
archive, verify the configured SHA-256 checksum, and install the `tailscale` and
`tailscaled` binaries. Neither mode pipes a downloaded installer script into a
root shell.

On release, `crabbox stop` attempts a best-effort remote `tailscale logout` before
provider cleanup when SSH is still reachable. Coordinator cleanup does not delete a
Expand Down
8 changes: 4 additions & 4 deletions docs/infrastructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,10 @@ CRABBOX_TAILSCALE_CLIENT_ID
CRABBOX_TAILSCALE_CLIENT_SECRET
CRABBOX_TAILSCALE_TAILNET=- # or an explicit tailnet/org
CRABBOX_TAILSCALE_TAGS=tag:crabbox # requested-tag allowlist/default
CRABBOX_TAILSCALE_INSTALL_MODE=package # or pinned
CRABBOX_TAILSCALE_VERSION=1.98.4 # pinned mode
CRABBOX_TAILSCALE_SHA256_AMD64=... # pinned mode
CRABBOX_TAILSCALE_SHA256_ARM64=... # pinned mode
CRABBOX_TAILSCALE_INSTALL_MODE=package # package (default) or pinned static archive
CRABBOX_TAILSCALE_VERSION=1.98.4 # static archive version
CRABBOX_TAILSCALE_SHA256_AMD64=... # amd64 archive checksum
CRABBOX_TAILSCALE_SHA256_ARM64=... # arm64 archive checksum
```

For one tag, assign the same tag to the OAuth client. For multiple tags, either
Expand Down
2 changes: 1 addition & 1 deletion docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ CRABBOX_TAILSCALE_CLIENT_SECRET required for brokered --tailscale
CRABBOX_TAILSCALE_TAILNET optional
CRABBOX_TAILSCALE_TAGS optional
CRABBOX_TAILSCALE_ENABLED optional; set 0 to disable brokered Tailscale
CRABBOX_TAILSCALE_INSTALL_MODE optional; package or pinned
CRABBOX_TAILSCALE_INSTALL_MODE optional; package (default) or pinned static archive
CRABBOX_TAILSCALE_VERSION optional pinned static build version
CRABBOX_TAILSCALE_SHA256_AMD64 optional pinned amd64 archive checksum
CRABBOX_TAILSCALE_SHA256_ARM64 optional pinned arm64 archive checksum
Expand Down
66 changes: 63 additions & 3 deletions internal/cli/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const (
defaultTailscaleVersion = "1.98.4"
defaultTailscaleAMD64SHA256 = "e6c08a8ee7e63e69aaf1b62ecd12672b3883fbcd2a176bf6cfa42a15fdce0b6b"
defaultTailscaleARM64SHA256 = "3cb068eb1368b6bb218d0ef0aa0a7a679a7156b7c979e2279cc2c2321b5f05c7"
defaultTailscaleKeyringSHA256 = "3e03dacf222698c60b8e2f990b809ca1b3e104de127767864284e6c228f1fb39"
defaultCodeServerVersion = "4.126.0"
defaultCodeServerAMD64SHA256 = "54b648d010c02b6583aa06bd8d2aaf109fc624479b9bc2ff71cb94807ac39afa"
defaultCodeServerARM64SHA256 = "441614708ae81b13f14b26db41da8f46f88d7d092c08343a42a0c6c52c51a69d"
googleLinuxSigningKeyFingerprint = "EB4C1BFD4F042F6DDDCCEC917721F63BD38B4796"
)

Expand Down Expand Up @@ -1455,12 +1459,37 @@ chmod 0755 /usr/local/bin/crabbox-configure-desktop-theme
}
if cfg.Code {
parts = append(parts, ` retry apt-get install -y --no-install-recommends libatomic1
retry env HOME=/root sh -c 'curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/usr/local'
`+cloudInitCodeServerInstallBootstrap()+`
/usr/local/bin/code-server --version >/dev/null`)
}
return strings.Join(parts, "\n")
}

func cloudInitCodeServerInstallBootstrap() string {
return `CS_VERSION=` + shellQuote(defaultCodeServerVersion) + `
case "$(uname -m)" in
x86_64) CS_ARCH=amd64; CS_SHA256=` + shellQuote(defaultCodeServerAMD64SHA256) + ` ;;
aarch64|arm64) CS_ARCH=arm64; CS_SHA256=` + shellQuote(defaultCodeServerARM64SHA256) + ` ;;
*) echo "unsupported code-server architecture: $(uname -m)" >&2; exit 3 ;;
esac
if [ -z "$CS_SHA256" ]; then echo "missing code-server checksum for $CS_ARCH" >&2; exit 3; fi
CS_INSTALL_DIR="$(mktemp -d)"
trap 'rm -rf "$CS_INSTALL_DIR"' EXIT
CS_ARCHIVE="$CS_INSTALL_DIR/code-server.tgz"
retry sh -c "curl -fsSL -o \"$CS_ARCHIVE\" \"https://github.com/coder/code-server/releases/download/v${CS_VERSION}/code-server-${CS_VERSION}-linux-${CS_ARCH}.tar.gz\""
printf '%s %s\n' "$CS_SHA256" "$CS_ARCHIVE" | sha256sum -c -
tar -xzf "$CS_ARCHIVE" -C "$CS_INSTALL_DIR" --strip-components=1
rm -f "$CS_ARCHIVE"
rm -rf /usr/local/lib/code-server
install -d -m 0755 /usr/local/lib/code-server
cp -a "$CS_INSTALL_DIR/." /usr/local/lib/code-server/
# cp -a preserves mktemp's private root mode; restore traversal for lease users.
chmod 0755 /usr/local/lib/code-server
ln -sfn /usr/local/lib/code-server/bin/code-server /usr/local/bin/code-server
rm -rf "$CS_INSTALL_DIR"
trap - EXIT`
}

func indentCloudInitRuncmd(script string) string {
if script == "" {
return ""
Expand Down Expand Up @@ -1651,8 +1680,8 @@ func cloudInitTailscaleBootstrap(cfg Config) string {
}

func cloudInitTailscaleInstallBootstrap() string {
if strings.TrimSpace(os.Getenv("CRABBOX_TAILSCALE_INSTALL_MODE")) != "pinned" {
return "retry sh -c 'curl -fsSL https://tailscale.com/install.sh | sh'"
if !strings.EqualFold(strings.TrimSpace(os.Getenv("CRABBOX_TAILSCALE_INSTALL_MODE")), "pinned") {
return cloudInitTailscalePackageInstallBootstrap()
}
version := firstNonEmpty(strings.TrimSpace(os.Getenv("CRABBOX_TAILSCALE_VERSION")), defaultTailscaleVersion)
amd64SHA := firstNonEmpty(strings.TrimSpace(os.Getenv("CRABBOX_TAILSCALE_SHA256_AMD64")), defaultTailscaleAMD64SHA256)
Expand All @@ -1663,6 +1692,7 @@ func cloudInitTailscaleInstallBootstrap() string {
aarch64|arm64) TS_ARCH=arm64; TS_SHA256=` + shellQuote(arm64SHA) + ` ;;
*) echo "unsupported Tailscale architecture: $(uname -m)" >&2; exit 3 ;;
esac
if [ -z "$TS_SHA256" ]; then echo "missing Tailscale checksum for $TS_ARCH" >&2; exit 3; fi
TS_INSTALL_DIR="$(mktemp -d)"
trap 'rm -rf "$TS_INSTALL_DIR"' EXIT
TS_ARCHIVE="$TS_INSTALL_DIR/tailscale.tgz"
Expand All @@ -1687,9 +1717,39 @@ func cloudInitTailscaleInstallBootstrap() string {
printf '%s\n' '[Install]'
printf '%s\n' 'WantedBy=multi-user.target'
} >/etc/systemd/system/tailscaled.service
rm -rf "$TS_INSTALL_DIR"
trap - EXIT
systemctl daemon-reload || true`
}

func cloudInitTailscalePackageInstallBootstrap() string {
return `if [ ! -r /etc/os-release ]; then
echo "Tailscale package install requires /etc/os-release" >&2
exit 3
fi
. /etc/os-release
TS_DIST_ID="${ID:-}"
TS_CODENAME="${VERSION_CODENAME:-}"
case "$TS_DIST_ID" in
ubuntu|debian) ;;
*) echo "unsupported Tailscale package distribution: $TS_DIST_ID" >&2; exit 3 ;;
esac
case "$TS_CODENAME" in
''|*[!a-z0-9.-]*) echo "invalid Tailscale package codename: $TS_CODENAME" >&2; exit 3 ;;
esac
install -d -m 0755 /usr/share/keyrings
TS_KEYRING_TMP="$(mktemp)"
trap 'rm -f "$TS_KEYRING_TMP"' EXIT
retry curl -fsSL -o "$TS_KEYRING_TMP" "https://pkgs.tailscale.com/stable/${TS_DIST_ID}/${TS_CODENAME}.noarmor.gpg"
printf '%s %s\n' '` + defaultTailscaleKeyringSHA256 + `' "$TS_KEYRING_TMP" | sha256sum -c -
install -m 0644 "$TS_KEYRING_TMP" /usr/share/keyrings/tailscale-archive-keyring.gpg
printf 'deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/%s %s main\n' "$TS_DIST_ID" "$TS_CODENAME" >/etc/apt/sources.list.d/tailscale.list
rm -f "$TS_KEYRING_TMP"
trap - EXIT
retry apt-get update
retry apt-get install -y --no-install-recommends tailscale`
}

// cloudInitPondHostsBootstrap installs /usr/local/bin/crabbox-pond-hosts and a
// systemd timer that rewrites /etc/hosts.cbx plus a managed /etc/hosts block
// every 30s with one entry per pond peer reachable on the local tailnet. Peers
Expand Down
38 changes: 33 additions & 5 deletions internal/cli/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,16 +404,32 @@ func TestCloudInitCodeProfile(t *testing.T) {
cfg.Code = true
got := cloudInit(cfg, "ssh-ed25519 test")
for _, want := range []string{
"https://code-server.dev/install.sh",
"env HOME=/root",
"--method=standalone --prefix=/usr/local",
"CS_VERSION='4.126.0'",
"x86_64) CS_ARCH=amd64; CS_SHA256='54b648d010c02b6583aa06bd8d2aaf109fc624479b9bc2ff71cb94807ac39afa'",
"aarch64|arm64) CS_ARCH=arm64; CS_SHA256='441614708ae81b13f14b26db41da8f46f88d7d092c08343a42a0c6c52c51a69d'",
"https://github.com/coder/code-server/releases/download/v${CS_VERSION}/code-server-${CS_VERSION}-linux-${CS_ARCH}.tar.gz",
"sha256sum -c -",
"/usr/local/lib/code-server",
"chmod 0755 /usr/local/lib/code-server",
`rm -rf "$CS_INSTALL_DIR"`,
"trap - EXIT",
"/usr/local/bin/code-server --version >/dev/null",
"test -x /usr/local/bin/code-server",
} {
if !strings.Contains(got, want) {
t.Fatalf("cloudInit(code) missing %q", want)
}
}
copyIndex := strings.Index(got, `cp -a "$CS_INSTALL_DIR/." /usr/local/lib/code-server/`)
archiveCleanupIndex := strings.Index(got, `rm -f "$CS_ARCHIVE"`)
chmodIndex := strings.Index(got, "chmod 0755 /usr/local/lib/code-server")
linkIndex := strings.Index(got, "ln -sfn /usr/local/lib/code-server/bin/code-server /usr/local/bin/code-server")
if archiveCleanupIndex < 0 || copyIndex <= archiveCleanupIndex || chmodIndex <= copyIndex || linkIndex <= chmodIndex {
t.Fatal("cloudInit(code) must restore install-root traversal after copying and before exposing the binary")
}
if strings.Contains(got, "https://code-server.dev/install.sh") || strings.Contains(got, "curl -fsSL https://code-server.dev/install.sh | sh") {
t.Fatal("cloudInit(code) must not pipe the code-server installer script to root shell")
}
if strings.Contains(cloudInit(baseConfig(), "ssh-ed25519 test"), "code-server") {
t.Fatal("cloudInit should not install code-server by default")
}
Expand All @@ -430,7 +446,10 @@ func TestCloudInitTailscaleProfile(t *testing.T) {
cfg.Tailscale.ExitNodeAllowLANAccess = true
got := cloudInit(cfg, "ssh-ed25519 test")
for _, want := range []string{
"https://tailscale.com/install.sh",
"https://pkgs.tailscale.com/stable/${TS_DIST_ID}/${TS_CODENAME}.noarmor.gpg",
"3e03dacf222698c60b8e2f990b809ca1b3e104de127767864284e6c228f1fb39",
"deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/%s %s main",
"retry apt-get install -y --no-install-recommends tailscale",
"install -d -m 0750 -o 'runner' -g 'runner' /var/lib/crabbox",
"printf '%s' \"$TS_AUTHKEY\" | tailscale up --auth-key=file:/dev/stdin --hostname='crabbox-blue-lobster' --advertise-tags='tag:crabbox' --exit-node='mac-studio.tailnet.ts.net' --exit-node-allow-lan-access",
"printf '%s\\n' 'crabbox-blue-lobster' > /var/lib/crabbox/tailscale-hostname",
Expand All @@ -455,13 +474,19 @@ func TestCloudInitTailscaleProfile(t *testing.T) {
if strings.Contains(got, "tailscale logout") || strings.Contains(got, "WantedBy=halt.target reboot.target shutdown.target") {
t.Fatal("cloudInit(tailscale) must not install a normal-reboot logout hook")
}
if strings.Contains(got, "https://tailscale.com/install.sh") {
t.Fatal("cloudInit(tailscale) must not pipe the Tailscale installer script to root shell")
}
if strings.Contains(got, "tailscale_${TS_VERSION}_${TS_ARCH}.tgz") {
t.Fatal("default Tailscale package mode must not use the pinned archive path")
}
if strings.Contains(cloudInit(baseConfig(), "ssh-ed25519 test"), "tailscale up") {
t.Fatal("cloudInit should not install Tailscale by default")
}
}

func TestCloudInitTailscalePinnedStaticInstall(t *testing.T) {
t.Setenv("CRABBOX_TAILSCALE_INSTALL_MODE", "pinned")
t.Setenv("CRABBOX_TAILSCALE_INSTALL_MODE", " Pinned ")
t.Setenv("CRABBOX_TAILSCALE_VERSION", "1.98.4")
t.Setenv("CRABBOX_TAILSCALE_SHA256_AMD64", "amd64sum")
t.Setenv("CRABBOX_TAILSCALE_SHA256_ARM64", "arm64sum")
Expand All @@ -486,6 +511,9 @@ func TestCloudInitTailscalePinnedStaticInstall(t *testing.T) {
if strings.Contains(got, "https://tailscale.com/install.sh") {
t.Fatal("pinned Tailscale install should not use package install script")
}
if strings.Contains(got, "tailscale-archive-keyring") {
t.Fatal("case-insensitive pinned Tailscale mode should not use the package repository")
}
}

// TestCloudInitTailscaleHonorsControlURL exercises the self-hosted control
Expand Down
69 changes: 67 additions & 2 deletions worker/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ const ubuntuWSLRootFSURL =
"https://cloud-images.ubuntu.com/wsl/releases/24.04/20240423/ubuntu-noble-wsl-amd64-wsl.rootfs.tar.gz";
const ubuntuWSLRootFSSHA256 = "8251e27ffff381a4af5f41dcb94d867de3e0d9774a9241908ab34555d99315ea";
const googleLinuxSigningKeyFingerprint = "EB4C1BFD4F042F6DDDCCEC917721F63BD38B4796";
const defaultCodeServerVersion = "4.126.0";
const defaultCodeServerSHA256 = {
amd64: "54b648d010c02b6583aa06bd8d2aaf109fc624479b9bc2ff71cb94807ac39afa",
arm64: "441614708ae81b13f14b26db41da8f46f88d7d092c08343a42a0c6c52c51a69d",
} as const;
const defaultTailscaleKeyringSHA256 =
"3e03dacf222698c60b8e2f990b809ca1b3e104de127767864284e6c228f1fb39";

export function awsUserData(config: LeaseConfig): string {
if (config.target === "windows") {
Expand Down Expand Up @@ -1475,12 +1482,39 @@ ${themeConfigure} systemctl daemon-reload
}
if (config.code) {
parts.push(` retry apt-get install -y --no-install-recommends libatomic1
retry env HOME=/root sh -c 'curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/usr/local'
${codeServerInstallBootstrap()}
/usr/local/bin/code-server --version >/dev/null`);
}
return parts.join("\n");
}

function codeServerInstallBootstrap(): string {
const version = shellQuote(defaultCodeServerVersion);
const amd64SHA = shellQuote(defaultCodeServerSHA256.amd64);
const arm64SHA = shellQuote(defaultCodeServerSHA256.arm64);
return `CS_VERSION=${version}
case "$(uname -m)" in
x86_64) CS_ARCH=amd64; CS_SHA256=${amd64SHA} ;;
aarch64|arm64) CS_ARCH=arm64; CS_SHA256=${arm64SHA} ;;
*) echo "unsupported code-server architecture: $(uname -m)" >&2; exit 3 ;;
esac
if [ -z "$CS_SHA256" ]; then echo "missing code-server checksum for $CS_ARCH" >&2; exit 3; fi
CS_INSTALL_DIR="$(mktemp -d)"
trap 'rm -rf "$CS_INSTALL_DIR"' EXIT
CS_ARCHIVE="$CS_INSTALL_DIR/code-server.tgz"
retry sh -c "curl -fsSL -o \\"$CS_ARCHIVE\\" \\"https://github.com/coder/code-server/releases/download/v\${CS_VERSION}/code-server-\${CS_VERSION}-linux-\${CS_ARCH}.tar.gz\\""
printf '%s %s\\n' "$CS_SHA256" "$CS_ARCHIVE" | sha256sum -c -
tar -xzf "$CS_ARCHIVE" -C "$CS_INSTALL_DIR" --strip-components=1
rm -f "$CS_ARCHIVE"
rm -rf /usr/local/lib/code-server
install -d -m 0755 /usr/local/lib/code-server
cp -a "$CS_INSTALL_DIR/." /usr/local/lib/code-server/
chmod 0755 /usr/local/lib/code-server
ln -sfn /usr/local/lib/code-server/bin/code-server /usr/local/bin/code-server
rm -rf "$CS_INSTALL_DIR"
trap - EXIT`;
}

function indentRuncmdScript(script: string): string {
if (script.length === 0) {
return "";
Expand All @@ -1498,7 +1532,7 @@ function indentRuncmdScript(script: string): string {

function tailscaleInstallBootstrap(config: LeaseConfig): string {
if (config.tailscaleInstallMode !== "pinned") {
return "retry sh -c 'curl -fsSL https://tailscale.com/install.sh | sh'";
return tailscalePackageInstallBootstrap();
}
const version = shellQuote(config.tailscaleVersion || "1.98.4");
const amd64SHA = shellQuote(config.tailscaleSHA256?.amd64 || "");
Expand All @@ -1509,6 +1543,7 @@ function tailscaleInstallBootstrap(config: LeaseConfig): string {
aarch64|arm64) TS_ARCH=arm64; TS_SHA256=${arm64SHA} ;;
*) echo "unsupported Tailscale architecture: $(uname -m)" >&2; exit 3 ;;
esac
if [ -z "$TS_SHA256" ]; then echo "missing Tailscale checksum for $TS_ARCH" >&2; exit 3; fi
TS_INSTALL_DIR="$(mktemp -d)"
trap 'rm -rf "$TS_INSTALL_DIR"' EXIT
TS_ARCHIVE="$TS_INSTALL_DIR/tailscale.tgz"
Expand All @@ -1517,6 +1552,8 @@ function tailscaleInstallBootstrap(config: LeaseConfig): string {
tar -xzf "$TS_ARCHIVE" -C "$TS_INSTALL_DIR" --strip-components=1
install -m 0755 "$TS_INSTALL_DIR/tailscale" /usr/local/bin/tailscale
install -m 0755 "$TS_INSTALL_DIR/tailscaled" /usr/local/sbin/tailscaled
rm -rf "$TS_INSTALL_DIR"
trap - EXIT
install -d -m 0755 /var/lib/tailscale /run/tailscale
{
printf '%s\\n' '[Unit]'
Expand All @@ -1536,6 +1573,34 @@ function tailscaleInstallBootstrap(config: LeaseConfig): string {
systemctl daemon-reload || true`;
}

function tailscalePackageInstallBootstrap(): string {
return `if [ ! -r /etc/os-release ]; then
echo "Tailscale package install requires /etc/os-release" >&2
exit 3
fi
. /etc/os-release
TS_DIST_ID="\${ID:-}"
TS_CODENAME="\${VERSION_CODENAME:-}"
case "$TS_DIST_ID" in
ubuntu|debian) ;;
*) echo "unsupported Tailscale package distribution: $TS_DIST_ID" >&2; exit 3 ;;
esac
case "$TS_CODENAME" in
''|*[!a-z0-9.-]*) echo "invalid Tailscale package codename: $TS_CODENAME" >&2; exit 3 ;;
esac
install -d -m 0755 /usr/share/keyrings
TS_KEYRING_TMP="$(mktemp)"
trap 'rm -f "$TS_KEYRING_TMP"' EXIT
retry curl -fsSL -o "$TS_KEYRING_TMP" "https://pkgs.tailscale.com/stable/\${TS_DIST_ID}/\${TS_CODENAME}.noarmor.gpg"
printf '%s %s\\n' '${defaultTailscaleKeyringSHA256}' "$TS_KEYRING_TMP" | sha256sum -c -
install -m 0644 "$TS_KEYRING_TMP" /usr/share/keyrings/tailscale-archive-keyring.gpg
printf 'deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/%s %s main\\n' "$TS_DIST_ID" "$TS_CODENAME" >/etc/apt/sources.list.d/tailscale.list
rm -f "$TS_KEYRING_TMP"
trap - EXIT
retry apt-get update
retry apt-get install -y --no-install-recommends tailscale`;
}

function tailscaleBootstrap(config: LeaseConfig): string {
if (!config.tailscaleAuthKey) {
return ` echo "tailscale requested but no auth key was injected" >&2
Expand Down
Loading