@mesh-tech/mesh-cli 0.20.2 → 0.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mesh.d.ts +0 -12
- package/dist/bin/mesh.js +15 -13
- package/dist/bin/mesh.js.map +1 -2
- package/dist/build-info.json +2 -2
- package/dist/src/commands/agent-api-client.d.ts +0 -70
- package/dist/src/commands/agent-api-client.js +0 -80
- package/dist/src/commands/app-check.d.ts +0 -56
- package/dist/src/commands/app-check.js +0 -72
- package/dist/src/commands/artifacts.d.ts +0 -11
- package/dist/src/commands/artifacts.js +0 -33
- package/dist/src/commands/cluster.d.ts +0 -39
- package/dist/src/commands/cluster.js +0 -73
- package/dist/src/commands/conversations.d.ts +0 -36
- package/dist/src/commands/conversations.js +0 -59
- package/dist/src/commands/create-app.d.ts +0 -98
- package/dist/src/commands/create-app.js +1 -163
- package/dist/src/commands/db/connect.d.ts +0 -7
- package/dist/src/commands/db/connect.js +0 -9
- package/dist/src/commands/db/credentials.d.ts +0 -7
- package/dist/src/commands/db/credentials.js +0 -10
- package/dist/src/commands/db/env.d.ts +0 -6
- package/dist/src/commands/db/env.js +0 -14
- package/dist/src/commands/db/exec.d.ts +0 -43
- package/dist/src/commands/db/exec.js +0 -69
- package/dist/src/commands/db/index.d.ts +0 -10
- package/dist/src/commands/db/index.js +0 -10
- package/dist/src/commands/db/psql.d.ts +0 -7
- package/dist/src/commands/db/psql.js +0 -18
- package/dist/src/commands/deploy.d.ts +0 -38
- package/dist/src/commands/deploy.js +1 -76
- package/dist/src/commands/dev-doctor.d.ts +0 -82
- package/dist/src/commands/dev-doctor.js +3 -132
- package/dist/src/commands/dev-launch.d.ts +0 -52
- package/dist/src/commands/dev-launch.js +0 -56
- package/dist/src/commands/dev-token-server.d.ts +0 -18
- package/dist/src/commands/dev-token-server.js +1 -24
- package/dist/src/commands/dev.d.ts +0 -246
- package/dist/src/commands/dev.js +5 -963
- package/dist/src/commands/docs.d.ts +0 -1
- package/dist/src/commands/docs.js +0 -12
- package/dist/src/commands/hub/index.d.ts +0 -60
- package/dist/src/commands/hub/index.js +2 -139
- package/dist/src/commands/init/wizard.d.ts +0 -128
- package/dist/src/commands/init/wizard.js +0 -109
- package/dist/src/commands/init.d.ts +0 -55
- package/dist/src/commands/init.js +0 -70
- package/dist/src/commands/install-shim.d.ts +0 -23
- package/dist/src/commands/install-shim.js +2 -29
- package/dist/src/commands/local/auth-provision.d.ts +0 -80
- package/dist/src/commands/local/auth-provision.js +0 -109
- package/dist/src/commands/local/dev-local.d.ts +0 -91
- package/dist/src/commands/local/dev-local.js +0 -147
- package/dist/src/commands/local/docker-runner.d.ts +0 -22
- package/dist/src/commands/local/docker-runner.js +2 -35
- package/dist/src/commands/local/helpers.d.ts +0 -14
- package/dist/src/commands/local/helpers.js +0 -13
- package/dist/src/commands/local/hub-local.d.ts +0 -96
- package/dist/src/commands/local/hub-local.js +0 -111
- package/dist/src/commands/local/index.d.ts +0 -14
- package/dist/src/commands/local/index.js +0 -86
- package/dist/src/commands/local/mocks.d.ts +0 -269
- package/dist/src/commands/local/mocks.js +1 -274
- package/dist/src/commands/local/seed-hub-catalog.d.ts +0 -25
- package/dist/src/commands/local/seed-hub-catalog.js +0 -41
- package/dist/src/commands/local/seed-zitadel.d.ts +0 -82
- package/dist/src/commands/local/seed-zitadel.js +0 -158
- package/dist/src/commands/local/seed.d.ts +0 -64
- package/dist/src/commands/local/seed.js +0 -78
- package/dist/src/commands/local/stack.d.ts +0 -141
- package/dist/src/commands/local/stack.js +0 -153
- package/dist/src/commands/login.d.ts +0 -265
- package/dist/src/commands/login.js +4 -418
- package/dist/src/commands/peer-addressing.d.ts +0 -39
- package/dist/src/commands/peer-addressing.js +0 -37
- package/dist/src/commands/port-ownership.d.ts +0 -44
- package/dist/src/commands/port-ownership.js +0 -47
- package/dist/src/commands/registry-publish.d.ts +0 -1
- package/dist/src/commands/registry-publish.js +0 -46
- package/dist/src/commands/registry.d.ts +4 -237
- package/dist/src/commands/registry.js +2 -316
- package/dist/src/commands/secrets/exec.d.ts +0 -16
- package/dist/src/commands/secrets/exec.js +0 -24
- package/dist/src/commands/secrets/index.d.ts +0 -9
- package/dist/src/commands/secrets/index.js +0 -10
- package/dist/src/commands/secrets/migrate-config.d.ts +0 -17
- package/dist/src/commands/secrets/migrate-config.js +0 -40
- package/dist/src/commands/secrets/reindex.d.ts +0 -38
- package/dist/src/commands/secrets/reindex.js +0 -39
- package/dist/src/commands/secrets/set.d.ts +0 -47
- package/dist/src/commands/secrets/set.js +0 -107
- package/dist/src/commands/site.d.ts +0 -1
- package/dist/src/commands/site.js +0 -61
- package/dist/src/commands/skills.d.ts +0 -61
- package/dist/src/commands/skills.js +0 -83
- package/dist/src/commands/stack.d.ts +0 -15
- package/dist/src/commands/stack.js +1 -128
- package/dist/src/commands/temporal.d.ts +0 -30
- package/dist/src/commands/temporal.js +3 -1
- package/dist/src/commands/temporal.js.map +1 -2
- package/dist/src/commands/tenant.d.ts +0 -31
- package/dist/src/commands/tenant.js +0 -55
- package/dist/src/commands/tunnel/index.d.ts +0 -40
- package/dist/src/commands/tunnel/index.js +0 -141
- package/dist/src/commands/vcs/clone.d.ts +0 -1
- package/dist/src/commands/vcs/clone.js +0 -4
- package/dist/src/commands/vcs/common.d.ts +0 -32
- package/dist/src/commands/vcs/common.js +0 -41
- package/dist/src/commands/vcs/drafts.d.ts +0 -1
- package/dist/src/commands/vcs/drafts.js +0 -1
- package/dist/src/commands/vcs/get.d.ts +0 -1
- package/dist/src/commands/vcs/get.js +0 -11
- package/dist/src/commands/vcs/index.d.ts +0 -1
- package/dist/src/commands/vcs/index.js +0 -1
- package/dist/src/commands/vcs/propose.d.ts +0 -26
- package/dist/src/commands/vcs/propose.js +3 -34
- package/dist/src/commands/vcs/review.d.ts +0 -1
- package/dist/src/commands/vcs/review.js +0 -1
- package/dist/src/commands/vcs/rm.d.ts +0 -11
- package/dist/src/commands/vcs/rm.js +0 -14
- package/dist/src/commands/vcs-enrich.d.ts +0 -12
- package/dist/src/commands/vcs-enrich.js +0 -12
- package/dist/src/commands/vpn/index.d.ts +0 -17
- package/dist/src/commands/vpn/index.js +0 -102
- package/dist/src/commands/vpn/tunnel.d.ts +0 -9
- package/dist/src/commands/vpn/tunnel.js +0 -37
- package/dist/src/commands/workflow.d.ts +0 -22
- package/dist/src/commands/workflow.js +0 -80
- package/dist/src/docs/assemble.d.ts +0 -117
- package/dist/src/docs/assemble.js +0 -142
- package/dist/src/docs/cli-reference.d.ts +0 -58
- package/dist/src/docs/cli-reference.js +0 -77
- package/dist/src/docs/discover.d.ts +0 -40
- package/dist/src/docs/discover.js +2 -60
- package/dist/src/docs/portal.d.ts +0 -44
- package/dist/src/docs/portal.js +0 -49
- package/dist/src/docs/registry-docs.d.ts +0 -29
- package/dist/src/docs/registry-docs.js +0 -40
- package/dist/src/docs/schema.d.ts +0 -36
- package/dist/src/docs/schema.js +0 -52
- package/dist/src/docs/serve.d.ts +0 -18
- package/dist/src/docs/serve.js +2 -30
- package/dist/src/docs/start.d.ts +0 -42
- package/dist/src/docs/start.js +0 -70
- package/dist/src/eval/agent-eval.d.mts +0 -12
- package/dist/src/eval/agent-eval.mjs +0 -22
- package/dist/src/index.d.ts +0 -6
- package/dist/src/index.js.map +0 -1
- package/dist/src/program.d.ts +0 -20
- package/dist/src/program.js +0 -24
- package/dist/src/utils/auth-preflight.d.ts +0 -137
- package/dist/src/utils/auth-preflight.js +0 -149
- package/dist/src/utils/aws-auth.d.ts +0 -163
- package/dist/src/utils/aws-auth.js +3 -232
- package/dist/src/utils/bastion.d.ts +0 -27
- package/dist/src/utils/bastion.js +0 -28
- package/dist/src/utils/build-info.d.ts +0 -22
- package/dist/src/utils/build-info.js +0 -27
- package/dist/src/utils/cache-home.d.ts +0 -23
- package/dist/src/utils/cache-home.js +0 -25
- package/dist/src/utils/capture-history.d.ts +0 -54
- package/dist/src/utils/capture-history.js +1 -55
- package/dist/src/utils/context.d.ts +0 -23
- package/dist/src/utils/context.js +0 -43
- package/dist/src/utils/credentials.d.ts +0 -38
- package/dist/src/utils/credentials.js +0 -39
- package/dist/src/utils/deploy-preflight.d.ts +0 -38
- package/dist/src/utils/deploy-preflight.js +1 -71
- package/dist/src/utils/errors.d.ts +0 -53
- package/dist/src/utils/errors.js +0 -49
- package/dist/src/utils/first-party-contexts.d.ts +0 -38
- package/dist/src/utils/first-party-contexts.js +0 -44
- package/dist/src/utils/index.d.ts +0 -4
- package/dist/src/utils/index.js +0 -4
- package/dist/src/utils/kubeconfig.d.ts +0 -40
- package/dist/src/utils/kubeconfig.js +0 -44
- package/dist/src/utils/log.d.ts +0 -20
- package/dist/src/utils/log.js +0 -20
- package/dist/src/utils/mesh-json.d.ts +0 -46
- package/dist/src/utils/mesh-json.js +0 -39
- package/dist/src/utils/pid.d.ts +0 -42
- package/dist/src/utils/pid.js +0 -48
- package/dist/src/utils/pulumi-run.d.ts +0 -14
- package/dist/src/utils/pulumi-run.js +0 -42
- package/dist/src/utils/pulumi.d.ts +0 -34
- package/dist/src/utils/pulumi.js +0 -38
- package/dist/src/utils/reachability.d.ts +0 -27
- package/dist/src/utils/reachability.js +0 -40
- package/dist/src/utils/recover-conversation.d.ts +0 -110
- package/dist/src/utils/recover-conversation.js +0 -108
- package/dist/src/utils/registry-broker.d.ts +5 -84
- package/dist/src/utils/registry-broker.js +0 -62
- package/dist/src/utils/registry-identity.d.ts +0 -149
- package/dist/src/utils/registry-identity.js +0 -143
- package/dist/src/utils/scaffold-versions.d.ts +0 -41
- package/dist/src/utils/scaffold-versions.js +0 -42
- package/dist/src/utils/socks-forward.d.ts +0 -24
- package/dist/src/utils/socks-forward.js +2 -25
- package/dist/src/utils/stack-flag.d.ts +0 -13
- package/dist/src/utils/stack-flag.js +0 -21
- package/dist/src/utils/tailscale-targets.d.ts +0 -37
- package/dist/src/utils/tailscale-targets.js +0 -33
- package/dist/src/utils/tailscale.d.ts +0 -177
- package/dist/src/utils/tailscale.js +6 -337
- package/dist/src/utils/temporal-auth.d.ts +0 -41
- package/dist/src/utils/temporal-auth.js +0 -52
- package/dist/src/utils/temporal-codec.d.ts +0 -89
- package/dist/src/utils/temporal-codec.js.map +0 -1
- package/dist/src/utils/tunnel-ownership.d.ts +0 -79
- package/dist/src/utils/tunnel-ownership.js +0 -74
- package/dist/src/utils/vpn-join.d.ts +0 -16
- package/dist/src/utils/vpn-join.js +0 -17
- package/dist/src/utils/vpn.d.ts +0 -42
- package/dist/src/utils/vpn.js +0 -52
- package/dist/src/utils/workflow-fingerprint.d.ts +0 -39
- package/dist/src/utils/workflow-fingerprint.js +1 -63
- package/dist/src/utils/worktree-identity.d.ts +0 -62
- package/dist/src/utils/worktree-identity.js +0 -88
- package/package.json +2 -2
- package/skills/core/SKILL.md +9 -10
- package/templates/api-auth/api/src/index.ts.hbs +1 -1
- package/templates/api-auth/api/src/spicedb-http-provider.ts.hbs +2 -2
- package/templates/api-role-gating/api/src/authn.ts.hbs +2 -2
- package/templates/api-role-gating/index.ts.hbs +1 -1
- package/templates/api-role-gating/skills/develop/SKILL.md.hbs +1 -1
- package/templates/platform-repo/core/Pulumi.{{tenant}}-{{env}}.yaml.hbs +2 -2
- package/templates/platform-repo/platform/index.ts +1 -1
- package/dist/bin/mesh.d.ts.map +0 -1
- package/dist/src/commands/agent-api-client.d.ts.map +0 -1
- package/dist/src/commands/agent-api-client.js.map +0 -1
- package/dist/src/commands/app-check.d.ts.map +0 -1
- package/dist/src/commands/app-check.js.map +0 -1
- package/dist/src/commands/artifacts.d.ts.map +0 -1
- package/dist/src/commands/artifacts.js.map +0 -1
- package/dist/src/commands/cluster.d.ts.map +0 -1
- package/dist/src/commands/cluster.js.map +0 -1
- package/dist/src/commands/conversations.d.ts.map +0 -1
- package/dist/src/commands/conversations.js.map +0 -1
- package/dist/src/commands/create-app.d.ts.map +0 -1
- package/dist/src/commands/create-app.js.map +0 -1
- package/dist/src/commands/db/connect.d.ts.map +0 -1
- package/dist/src/commands/db/connect.js.map +0 -1
- package/dist/src/commands/db/credentials.d.ts.map +0 -1
- package/dist/src/commands/db/credentials.js.map +0 -1
- package/dist/src/commands/db/env.d.ts.map +0 -1
- package/dist/src/commands/db/env.js.map +0 -1
- package/dist/src/commands/db/exec.d.ts.map +0 -1
- package/dist/src/commands/db/exec.js.map +0 -1
- package/dist/src/commands/db/index.d.ts.map +0 -1
- package/dist/src/commands/db/index.js.map +0 -1
- package/dist/src/commands/db/psql.d.ts.map +0 -1
- package/dist/src/commands/db/psql.js.map +0 -1
- package/dist/src/commands/deploy.d.ts.map +0 -1
- package/dist/src/commands/deploy.js.map +0 -1
- package/dist/src/commands/dev-doctor.d.ts.map +0 -1
- package/dist/src/commands/dev-doctor.js.map +0 -1
- package/dist/src/commands/dev-launch.d.ts.map +0 -1
- package/dist/src/commands/dev-launch.js.map +0 -1
- package/dist/src/commands/dev-token-server.d.ts.map +0 -1
- package/dist/src/commands/dev-token-server.js.map +0 -1
- package/dist/src/commands/dev.d.ts.map +0 -1
- package/dist/src/commands/dev.js.map +0 -1
- package/dist/src/commands/docs.d.ts.map +0 -1
- package/dist/src/commands/docs.js.map +0 -1
- package/dist/src/commands/hub/index.d.ts.map +0 -1
- package/dist/src/commands/hub/index.js.map +0 -1
- package/dist/src/commands/init/wizard.d.ts.map +0 -1
- package/dist/src/commands/init/wizard.js.map +0 -1
- package/dist/src/commands/init.d.ts.map +0 -1
- package/dist/src/commands/init.js.map +0 -1
- package/dist/src/commands/install-shim.d.ts.map +0 -1
- package/dist/src/commands/install-shim.js.map +0 -1
- package/dist/src/commands/local/auth-provision.d.ts.map +0 -1
- package/dist/src/commands/local/auth-provision.js.map +0 -1
- package/dist/src/commands/local/dev-local.d.ts.map +0 -1
- package/dist/src/commands/local/dev-local.js.map +0 -1
- package/dist/src/commands/local/docker-runner.d.ts.map +0 -1
- package/dist/src/commands/local/docker-runner.js.map +0 -1
- package/dist/src/commands/local/helpers.d.ts.map +0 -1
- package/dist/src/commands/local/helpers.js.map +0 -1
- package/dist/src/commands/local/hub-local.d.ts.map +0 -1
- package/dist/src/commands/local/hub-local.js.map +0 -1
- package/dist/src/commands/local/index.d.ts.map +0 -1
- package/dist/src/commands/local/index.js.map +0 -1
- package/dist/src/commands/local/mocks.d.ts.map +0 -1
- package/dist/src/commands/local/mocks.js.map +0 -1
- package/dist/src/commands/local/seed-hub-catalog.d.ts.map +0 -1
- package/dist/src/commands/local/seed-hub-catalog.js.map +0 -1
- package/dist/src/commands/local/seed-zitadel.d.ts.map +0 -1
- package/dist/src/commands/local/seed-zitadel.js.map +0 -1
- package/dist/src/commands/local/seed.d.ts.map +0 -1
- package/dist/src/commands/local/seed.js.map +0 -1
- package/dist/src/commands/local/stack.d.ts.map +0 -1
- package/dist/src/commands/local/stack.js.map +0 -1
- package/dist/src/commands/login.d.ts.map +0 -1
- package/dist/src/commands/login.js.map +0 -1
- package/dist/src/commands/peer-addressing.d.ts.map +0 -1
- package/dist/src/commands/peer-addressing.js.map +0 -1
- package/dist/src/commands/port-ownership.d.ts.map +0 -1
- package/dist/src/commands/port-ownership.js.map +0 -1
- package/dist/src/commands/registry-publish.d.ts.map +0 -1
- package/dist/src/commands/registry-publish.js.map +0 -1
- package/dist/src/commands/registry.d.ts.map +0 -1
- package/dist/src/commands/registry.js.map +0 -1
- package/dist/src/commands/secrets/exec.d.ts.map +0 -1
- package/dist/src/commands/secrets/exec.js.map +0 -1
- package/dist/src/commands/secrets/index.d.ts.map +0 -1
- package/dist/src/commands/secrets/index.js.map +0 -1
- package/dist/src/commands/secrets/migrate-config.d.ts.map +0 -1
- package/dist/src/commands/secrets/migrate-config.js.map +0 -1
- package/dist/src/commands/secrets/reindex.d.ts.map +0 -1
- package/dist/src/commands/secrets/reindex.js.map +0 -1
- package/dist/src/commands/secrets/set.d.ts.map +0 -1
- package/dist/src/commands/secrets/set.js.map +0 -1
- package/dist/src/commands/site.d.ts.map +0 -1
- package/dist/src/commands/site.js.map +0 -1
- package/dist/src/commands/skills.d.ts.map +0 -1
- package/dist/src/commands/skills.js.map +0 -1
- package/dist/src/commands/stack.d.ts.map +0 -1
- package/dist/src/commands/stack.js.map +0 -1
- package/dist/src/commands/temporal.d.ts.map +0 -1
- package/dist/src/commands/tenant.d.ts.map +0 -1
- package/dist/src/commands/tenant.js.map +0 -1
- package/dist/src/commands/tunnel/index.d.ts.map +0 -1
- package/dist/src/commands/tunnel/index.js.map +0 -1
- package/dist/src/commands/vcs/clone.d.ts.map +0 -1
- package/dist/src/commands/vcs/clone.js.map +0 -1
- package/dist/src/commands/vcs/common.d.ts.map +0 -1
- package/dist/src/commands/vcs/common.js.map +0 -1
- package/dist/src/commands/vcs/drafts.d.ts.map +0 -1
- package/dist/src/commands/vcs/drafts.js.map +0 -1
- package/dist/src/commands/vcs/get.d.ts.map +0 -1
- package/dist/src/commands/vcs/get.js.map +0 -1
- package/dist/src/commands/vcs/index.d.ts.map +0 -1
- package/dist/src/commands/vcs/index.js.map +0 -1
- package/dist/src/commands/vcs/propose.d.ts.map +0 -1
- package/dist/src/commands/vcs/propose.js.map +0 -1
- package/dist/src/commands/vcs/review.d.ts.map +0 -1
- package/dist/src/commands/vcs/review.js.map +0 -1
- package/dist/src/commands/vcs/rm.d.ts.map +0 -1
- package/dist/src/commands/vcs/rm.js.map +0 -1
- package/dist/src/commands/vcs-enrich.d.ts.map +0 -1
- package/dist/src/commands/vcs-enrich.js.map +0 -1
- package/dist/src/commands/vpn/index.d.ts.map +0 -1
- package/dist/src/commands/vpn/index.js.map +0 -1
- package/dist/src/commands/vpn/tunnel.d.ts.map +0 -1
- package/dist/src/commands/vpn/tunnel.js.map +0 -1
- package/dist/src/commands/workflow.d.ts.map +0 -1
- package/dist/src/commands/workflow.js.map +0 -1
- package/dist/src/docs/assemble.d.ts.map +0 -1
- package/dist/src/docs/assemble.js.map +0 -1
- package/dist/src/docs/cli-reference.d.ts.map +0 -1
- package/dist/src/docs/cli-reference.js.map +0 -1
- package/dist/src/docs/discover.d.ts.map +0 -1
- package/dist/src/docs/discover.js.map +0 -1
- package/dist/src/docs/portal.d.ts.map +0 -1
- package/dist/src/docs/portal.js.map +0 -1
- package/dist/src/docs/registry-docs.d.ts.map +0 -1
- package/dist/src/docs/registry-docs.js.map +0 -1
- package/dist/src/docs/schema.d.ts.map +0 -1
- package/dist/src/docs/schema.js.map +0 -1
- package/dist/src/docs/serve.d.ts.map +0 -1
- package/dist/src/docs/serve.js.map +0 -1
- package/dist/src/docs/start.d.ts.map +0 -1
- package/dist/src/docs/start.js.map +0 -1
- package/dist/src/eval/agent-eval.d.mts.map +0 -1
- package/dist/src/eval/agent-eval.mjs.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/program.d.ts.map +0 -1
- package/dist/src/program.js.map +0 -1
- package/dist/src/utils/auth-preflight.d.ts.map +0 -1
- package/dist/src/utils/auth-preflight.js.map +0 -1
- package/dist/src/utils/aws-auth.d.ts.map +0 -1
- package/dist/src/utils/aws-auth.js.map +0 -1
- package/dist/src/utils/bastion.d.ts.map +0 -1
- package/dist/src/utils/bastion.js.map +0 -1
- package/dist/src/utils/build-info.d.ts.map +0 -1
- package/dist/src/utils/build-info.js.map +0 -1
- package/dist/src/utils/cache-home.d.ts.map +0 -1
- package/dist/src/utils/cache-home.js.map +0 -1
- package/dist/src/utils/capture-history.d.ts.map +0 -1
- package/dist/src/utils/capture-history.js.map +0 -1
- package/dist/src/utils/context.d.ts.map +0 -1
- package/dist/src/utils/context.js.map +0 -1
- package/dist/src/utils/credentials.d.ts.map +0 -1
- package/dist/src/utils/credentials.js.map +0 -1
- package/dist/src/utils/deploy-preflight.d.ts.map +0 -1
- package/dist/src/utils/deploy-preflight.js.map +0 -1
- package/dist/src/utils/errors.d.ts.map +0 -1
- package/dist/src/utils/errors.js.map +0 -1
- package/dist/src/utils/first-party-contexts.d.ts.map +0 -1
- package/dist/src/utils/first-party-contexts.js.map +0 -1
- package/dist/src/utils/index.d.ts.map +0 -1
- package/dist/src/utils/index.js.map +0 -1
- package/dist/src/utils/kubeconfig.d.ts.map +0 -1
- package/dist/src/utils/kubeconfig.js.map +0 -1
- package/dist/src/utils/log.d.ts.map +0 -1
- package/dist/src/utils/log.js.map +0 -1
- package/dist/src/utils/mesh-json.d.ts.map +0 -1
- package/dist/src/utils/mesh-json.js.map +0 -1
- package/dist/src/utils/pid.d.ts.map +0 -1
- package/dist/src/utils/pid.js.map +0 -1
- package/dist/src/utils/pulumi-run.d.ts.map +0 -1
- package/dist/src/utils/pulumi-run.js.map +0 -1
- package/dist/src/utils/pulumi.d.ts.map +0 -1
- package/dist/src/utils/pulumi.js.map +0 -1
- package/dist/src/utils/reachability.d.ts.map +0 -1
- package/dist/src/utils/reachability.js.map +0 -1
- package/dist/src/utils/recover-conversation.d.ts.map +0 -1
- package/dist/src/utils/recover-conversation.js.map +0 -1
- package/dist/src/utils/registry-broker.d.ts.map +0 -1
- package/dist/src/utils/registry-broker.js.map +0 -1
- package/dist/src/utils/registry-identity.d.ts.map +0 -1
- package/dist/src/utils/registry-identity.js.map +0 -1
- package/dist/src/utils/scaffold-versions.d.ts.map +0 -1
- package/dist/src/utils/scaffold-versions.js.map +0 -1
- package/dist/src/utils/socks-forward.d.ts.map +0 -1
- package/dist/src/utils/socks-forward.js.map +0 -1
- package/dist/src/utils/stack-flag.d.ts.map +0 -1
- package/dist/src/utils/stack-flag.js.map +0 -1
- package/dist/src/utils/tailscale-targets.d.ts.map +0 -1
- package/dist/src/utils/tailscale-targets.js.map +0 -1
- package/dist/src/utils/tailscale.d.ts.map +0 -1
- package/dist/src/utils/tailscale.js.map +0 -1
- package/dist/src/utils/temporal-auth.d.ts.map +0 -1
- package/dist/src/utils/temporal-auth.js.map +0 -1
- package/dist/src/utils/temporal-codec.d.ts.map +0 -1
- package/dist/src/utils/tunnel-ownership.d.ts.map +0 -1
- package/dist/src/utils/tunnel-ownership.js.map +0 -1
- package/dist/src/utils/vpn-join.d.ts.map +0 -1
- package/dist/src/utils/vpn-join.js.map +0 -1
- package/dist/src/utils/vpn.d.ts.map +0 -1
- package/dist/src/utils/vpn.js.map +0 -1
- package/dist/src/utils/workflow-fingerprint.d.ts.map +0 -1
- package/dist/src/utils/workflow-fingerprint.js.map +0 -1
- package/dist/src/utils/worktree-identity.d.ts.map +0 -1
- package/dist/src/utils/worktree-identity.js.map +0 -1
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Userspace-Tailscale daemon management + tunnel-session state for
|
|
3
|
-
* `mesh vpn tunnel` (MESH-2040 / Slice 1 of MESH-2014).
|
|
4
|
-
*
|
|
5
|
-
* A per-tenant `tailscaled` runs in userspace-networking mode (brew formula,
|
|
6
|
-
* NOT the GUI app) exposing a SOCKS5 proxy; forwarders bridge `localhost:PORT`
|
|
7
|
-
* to VPC services through it. State lives under `~/.config/mesh/tailscale/<tenant>/`
|
|
8
|
-
* (r+w-granted under safehouse — see the MESH-2036 spike).
|
|
9
|
-
*/
|
|
10
1
|
import * as fs from "node:fs";
|
|
11
2
|
import * as net from "node:net";
|
|
12
3
|
import * as os from "node:os";
|
|
@@ -22,9 +13,6 @@ import { resolveTunnelTargets, resolveElbIp, tenantPortIndex, PORT_BLOCK } from
|
|
|
22
13
|
import { createForwarder } from "./socks-forward.js";
|
|
23
14
|
import { mintRunnerToken, readRunnerManifest, writeRunnerManifest, clearRunnerManifest, getProcessGroupId, discoverRunnerPids, startControlServer, verifyRunnerOwnership, pollOwnership, findSquattedPorts, buildSquatterError, } from "./tunnel-ownership.js";
|
|
24
15
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
25
|
-
// ============================================================================
|
|
26
|
-
// Paths
|
|
27
|
-
// ============================================================================
|
|
28
16
|
export function tenantStateDir(tenant) {
|
|
29
17
|
return path.join(CONFIG_DIR, "tailscale", tenant);
|
|
30
18
|
}
|
|
@@ -37,22 +25,9 @@ export function logPath(tenant) {
|
|
|
37
25
|
export function statePath(tenant) {
|
|
38
26
|
return path.join(tenantStateDir(tenant), "tunnel-state.json");
|
|
39
27
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Daemon-scoped metadata, written by `ensureDaemon` when it actually spawns the
|
|
42
|
-
* daemon. Unlike `tunnel-state.json` (which `down` clears), this survives `down`
|
|
43
|
-
* so a later `up` reuses the RUNNING daemon's SOCKS port instead of minting a
|
|
44
|
-
* fresh one the daemon isn't listening on (which would leave every forwarder
|
|
45
|
-
* dialing a dead port). Also records the daemon pid so `stopDaemon` can kill it.
|
|
46
|
-
*/
|
|
47
28
|
export function daemonMetaPath(tenant) {
|
|
48
29
|
return path.join(tenantStateDir(tenant), "daemon.json");
|
|
49
30
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Ownership manifest written by the RUNNER (the process that binds the
|
|
52
|
-
* forwarder ports) — pid, process-group id, per-run token, control port. The
|
|
53
|
-
* ownership handshake (MESH-2407) verifies against it; teardown signals the
|
|
54
|
-
* recorded process group.
|
|
55
|
-
*/
|
|
56
31
|
export function runnerManifestPath(tenant) {
|
|
57
32
|
return path.join(tenantStateDir(tenant), "runner.json");
|
|
58
33
|
}
|
|
@@ -76,7 +51,6 @@ export function clearTunnelState(tenant) {
|
|
|
76
51
|
fs.unlinkSync(statePath(tenant));
|
|
77
52
|
}
|
|
78
53
|
catch {
|
|
79
|
-
/* already gone */
|
|
80
54
|
}
|
|
81
55
|
}
|
|
82
56
|
export function readDaemonMeta(tenant) {
|
|
@@ -99,13 +73,8 @@ export function clearDaemonMeta(tenant) {
|
|
|
99
73
|
fs.unlinkSync(daemonMetaPath(tenant));
|
|
100
74
|
}
|
|
101
75
|
catch {
|
|
102
|
-
/* already gone */
|
|
103
76
|
}
|
|
104
77
|
}
|
|
105
|
-
// ============================================================================
|
|
106
|
-
// Daemon status parsing
|
|
107
|
-
// ============================================================================
|
|
108
|
-
/** Parse `tailscale status --json` → the fields we care about. */
|
|
109
78
|
export function parseDaemonState(statusJson) {
|
|
110
79
|
try {
|
|
111
80
|
const j = JSON.parse(statusJson);
|
|
@@ -115,14 +84,9 @@ export function parseDaemonState(statusJson) {
|
|
|
115
84
|
return { backendState: "Unknown" };
|
|
116
85
|
}
|
|
117
86
|
}
|
|
118
|
-
// ============================================================================
|
|
119
|
-
// Binary resolution (macOS / brew — Slice 1)
|
|
120
|
-
// ============================================================================
|
|
121
|
-
/** The brew `tailscaled` (NOT the GUI app, which ships no standalone daemon). */
|
|
122
87
|
export function tailscaledBinPath() {
|
|
123
88
|
return resolveBrewBin("tailscaled");
|
|
124
89
|
}
|
|
125
|
-
/** The brew `tailscale` CLI (prefer it over the GUI shim, which aborts under sandbox). */
|
|
126
90
|
export function tailscaleBinPath() {
|
|
127
91
|
return resolveBrewBin("tailscale");
|
|
128
92
|
}
|
|
@@ -136,14 +100,9 @@ function resolveBrewBin(name) {
|
|
|
136
100
|
return p;
|
|
137
101
|
}
|
|
138
102
|
catch {
|
|
139
|
-
/* fall through */
|
|
140
103
|
}
|
|
141
104
|
throw new Error(`${name} not found — install the standalone binary: brew install tailscale`);
|
|
142
105
|
}
|
|
143
|
-
// ============================================================================
|
|
144
|
-
// Daemon lifecycle
|
|
145
|
-
// ============================================================================
|
|
146
|
-
/** Backend state via the tenant's control socket; "Down" when no daemon answers. */
|
|
147
106
|
export function daemonState(tenant) {
|
|
148
107
|
if (!fs.existsSync(socketPath(tenant)))
|
|
149
108
|
return { backendState: "Down" };
|
|
@@ -155,12 +114,6 @@ export function daemonState(tenant) {
|
|
|
155
114
|
return { backendState: "Down" };
|
|
156
115
|
}
|
|
157
116
|
}
|
|
158
|
-
/**
|
|
159
|
-
* Extract the `--socks5-server` port from a `tailscaled` command line. Pure so
|
|
160
|
-
* it's unit-testable. Accepts both `--socks5-server=127.0.0.1:PORT` and the
|
|
161
|
-
* space-separated form, and a `localhost` host literal. Returns null if absent
|
|
162
|
-
* or malformed.
|
|
163
|
-
*/
|
|
164
117
|
export function parseSocksPortFromArgv(argv) {
|
|
165
118
|
const m = argv.match(/--socks5-server[=\s]+(?:127\.0\.0\.1|localhost):(\d+)/);
|
|
166
119
|
if (!m)
|
|
@@ -168,13 +121,6 @@ export function parseSocksPortFromArgv(argv) {
|
|
|
168
121
|
const port = Number(m[1]);
|
|
169
122
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null;
|
|
170
123
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Find the running userspace `tailscaled` for a tenant by its unique
|
|
173
|
-
* `--socket=<path>` argument, returning its pid and the SOCKS port it ACTUALLY
|
|
174
|
-
* bound (parsed from argv). Authoritative when `daemon.json` is missing or stale
|
|
175
|
-
* (e.g. a daemon left by an older build that predates the meta file). Returns
|
|
176
|
-
* null when no matching process is found or `ps` is unavailable.
|
|
177
|
-
*/
|
|
178
124
|
export function findRunningDaemon(tenant) {
|
|
179
125
|
const sock = socketPath(tenant);
|
|
180
126
|
let out;
|
|
@@ -188,8 +134,6 @@ export function findRunningDaemon(tenant) {
|
|
|
188
134
|
return null;
|
|
189
135
|
}
|
|
190
136
|
for (const line of out.split("\n")) {
|
|
191
|
-
// Match this tenant's daemon by its unique --socket path (skips the ps line,
|
|
192
|
-
// other tenants' daemons, and the GUI app).
|
|
193
137
|
if (!line.includes("tailscaled") || !line.includes(`--socket=${sock}`))
|
|
194
138
|
continue;
|
|
195
139
|
const m = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
@@ -199,7 +143,6 @@ export function findRunningDaemon(tenant) {
|
|
|
199
143
|
}
|
|
200
144
|
return null;
|
|
201
145
|
}
|
|
202
|
-
/** True if something accepts TCP on 127.0.0.1:port within `timeoutMs`. */
|
|
203
146
|
export function portAccepts(port, timeoutMs = 400) {
|
|
204
147
|
return new Promise((resolve) => {
|
|
205
148
|
const s = new net.Socket();
|
|
@@ -218,18 +161,8 @@ export function portAccepts(port, timeoutMs = 400) {
|
|
|
218
161
|
s.connect(port, "127.0.0.1");
|
|
219
162
|
});
|
|
220
163
|
}
|
|
221
|
-
/**
|
|
222
|
-
* Ensure a userspace `tailscaled` is running for the tenant and return the SOCKS
|
|
223
|
-
* port the forwarders should dial. Idempotent — reuses a live daemon, but
|
|
224
|
-
* RECONCILES the port: a daemon that's up on a different port than requested
|
|
225
|
-
* (older build with no `daemon.json`, or stale meta) would otherwise leave every
|
|
226
|
-
* forwarder dialing a dead port (MESH-2169). Trust `opts.socksPort` only if it
|
|
227
|
-
* truly accepts; else adopt the running daemon's real port; else restart fresh.
|
|
228
|
-
*/
|
|
229
164
|
export async function ensureDaemon(tenant, opts) {
|
|
230
165
|
if (daemonState(tenant).backendState !== "Down") {
|
|
231
|
-
// The daemon's SOCKS proxy is only useful on the exact port the forwarders
|
|
232
|
-
// will dial. If the requested port already accepts, record it and use it.
|
|
233
166
|
if (await portAccepts(opts.socksPort)) {
|
|
234
167
|
writeDaemonMeta(tenant, {
|
|
235
168
|
socksPort: opts.socksPort,
|
|
@@ -237,17 +170,11 @@ export async function ensureDaemon(tenant, opts) {
|
|
|
237
170
|
});
|
|
238
171
|
return opts.socksPort;
|
|
239
172
|
}
|
|
240
|
-
// Requested port is dead — the daemon is on a different one. Discover its
|
|
241
|
-
// real SOCKS port from argv and adopt it (no restart → no severing other
|
|
242
|
-
// sessions), rewriting daemon.json so future runs read the right port.
|
|
243
173
|
const running = findRunningDaemon(tenant);
|
|
244
174
|
if (running?.socksPort && (await portAccepts(running.socksPort))) {
|
|
245
175
|
writeDaemonMeta(tenant, { socksPort: running.socksPort, pid: running.pid });
|
|
246
176
|
return running.socksPort;
|
|
247
177
|
}
|
|
248
|
-
// Can't discover/verify a working SOCKS port — the daemon is unusable to us.
|
|
249
|
-
// Tear it down (state dir keeps the node identity → no re-login) and fall
|
|
250
|
-
// through to a fresh start on opts.socksPort.
|
|
251
178
|
stopDaemon(tenant);
|
|
252
179
|
for (let i = 0; i < 12 && daemonState(tenant).backendState !== "Down"; i++) {
|
|
253
180
|
await sleep(250);
|
|
@@ -269,8 +196,6 @@ export async function ensureDaemon(tenant, opts) {
|
|
|
269
196
|
env: { ...process.env, AWS_REGION: opts.region },
|
|
270
197
|
});
|
|
271
198
|
child.unref();
|
|
272
|
-
// Persist the socks port + pid so `down` → `up` reuses this daemon's real
|
|
273
|
-
// port (not a fresh one it isn't listening on) and `stopDaemon` can kill it.
|
|
274
199
|
if (child.pid)
|
|
275
200
|
writeDaemonMeta(tenant, { socksPort: opts.socksPort, pid: child.pid });
|
|
276
201
|
}
|
|
@@ -284,31 +209,11 @@ export async function ensureDaemon(tenant, opts) {
|
|
|
284
209
|
}
|
|
285
210
|
throw new Error(`tailscaled did not start for tenant '${tenant}' — see ${logPath(tenant)}`);
|
|
286
211
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Stop the detached `tailscaled` for a tenant (SIGTERM its pid) and remove the
|
|
289
|
-
* socket + daemon metadata. Leaves the Headscale node identity in `tailscaled.state`
|
|
290
|
-
* so a later `up` re-spawns and rejoins without an interactive re-login — unless
|
|
291
|
-
* the caller logged out first.
|
|
292
|
-
*
|
|
293
|
-
* Best-effort, and that includes a corrupt `daemon.json`: an unsignalable
|
|
294
|
-
* recorded pid is REFUSED (never handed to `kill`) but only warns — the socket
|
|
295
|
-
* and meta are still cleared. `ensureDaemon` calls this on the UP path to
|
|
296
|
-
* recycle a daemon it can't verify, so throwing here would dead-end
|
|
297
|
-
* `mesh vpn tunnel up` / `mesh dev` on the very file this function deletes.
|
|
298
|
-
* Safe to call when nothing is running.
|
|
299
|
-
*/
|
|
300
212
|
export function stopDaemon(tenant) {
|
|
301
|
-
// Prefer the recorded pid; fall back to discovering it from argv so a daemon
|
|
302
|
-
// left without a daemon.json (older build / cleared meta) can still be killed.
|
|
303
213
|
const meta = readDaemonMeta(tenant) ?? (() => {
|
|
304
214
|
const running = findRunningDaemon(tenant);
|
|
305
215
|
return running ? { socksPort: running.socksPort ?? 0, pid: running.pid } : null;
|
|
306
216
|
})();
|
|
307
|
-
// `ensureDaemon` records `pid: 0` when it adopts a running daemon whose pid it
|
|
308
|
-
// could not attribute, so absent-or-0 is this file's "no pid known" sentinel —
|
|
309
|
-
// skip it, don't signal and don't fail. EVERY other unsignalable value (1, a
|
|
310
|
-
// negative, a fraction, a string from a truncated/hand-edited daemon.json) is
|
|
311
|
-
// corruption and must be refused rather than handed to kill. See utils/pid.ts.
|
|
312
217
|
if (meta?.pid) {
|
|
313
218
|
try {
|
|
314
219
|
assertSignalablePid(meta.pid, {
|
|
@@ -318,13 +223,8 @@ export function stopDaemon(tenant) {
|
|
|
318
223
|
process.kill(meta.pid, "SIGTERM");
|
|
319
224
|
}
|
|
320
225
|
catch (err) {
|
|
321
|
-
// Refusing to SIGNAL is the whole point; refusing to CLEAN UP is not.
|
|
322
|
-
// The unlink + clearDaemonMeta below are what make the corrupt file
|
|
323
|
-
// self-healing, and `ensureDaemon` (the UP path) depends on them. Warn
|
|
324
|
-
// with the `rm` remediation intact and fall through.
|
|
325
226
|
if (err instanceof MeshCliError)
|
|
326
227
|
logWarn(renderErrorBody(err));
|
|
327
|
-
/* else: ESRCH/EPERM from process.kill — already gone, as before */
|
|
328
228
|
}
|
|
329
229
|
}
|
|
330
230
|
for (const p of [socketPath(tenant)]) {
|
|
@@ -332,16 +232,10 @@ export function stopDaemon(tenant) {
|
|
|
332
232
|
fs.unlinkSync(p);
|
|
333
233
|
}
|
|
334
234
|
catch {
|
|
335
|
-
/* already gone */
|
|
336
235
|
}
|
|
337
236
|
}
|
|
338
237
|
clearDaemonMeta(tenant);
|
|
339
238
|
}
|
|
340
|
-
/**
|
|
341
|
-
* Build the `tailscale up` argument vector. Pure so it can be unit-tested and so
|
|
342
|
-
* the interactive (browser-register) and zero-touch (pre-auth key) forms differ
|
|
343
|
-
* only by the trailing `--authkey`.
|
|
344
|
-
*/
|
|
345
239
|
export function buildTailscaleUpArgs(opts) {
|
|
346
240
|
const args = [
|
|
347
241
|
"--socket", opts.socket, "up",
|
|
@@ -353,12 +247,6 @@ export function buildTailscaleUpArgs(opts) {
|
|
|
353
247
|
args.push("--authkey", opts.preAuthKey);
|
|
354
248
|
return args;
|
|
355
249
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Join the tenant's Headscale tailnet via Zitadel OIDC if not already Running.
|
|
358
|
-
* Prints the device-flow registration URL and polls until registered. When a
|
|
359
|
-
* pre-auth key is supplied the join is non-interactive: no `/register/` URL is
|
|
360
|
-
* emitted, so the scrape/relay is skipped and we just wait for Running.
|
|
361
|
-
*/
|
|
362
250
|
export async function joinHeadscale(tenant, loginServer, opts = {}) {
|
|
363
251
|
if (daemonState(tenant).backendState === "Running")
|
|
364
252
|
return;
|
|
@@ -374,8 +262,6 @@ export async function joinHeadscale(tenant, loginServer, opts = {}) {
|
|
|
374
262
|
const m = buf.match(/https?:\/\/\S+\/register\/\S+/);
|
|
375
263
|
if (m) {
|
|
376
264
|
urlShown = true;
|
|
377
|
-
// Relay the registration URL for headless/SSH machines (there's no local
|
|
378
|
-
// browser to pop). One-time per machine — the node registration persists.
|
|
379
265
|
logInfo(`\n This machine isn't registered with the platform VPN yet.\n` +
|
|
380
266
|
` Open this URL in a browser to authorize it (one-time, sign in with Zitadel):\n\n` +
|
|
381
267
|
` ${m[0]}\n\n` +
|
|
@@ -385,7 +271,6 @@ export async function joinHeadscale(tenant, loginServer, opts = {}) {
|
|
|
385
271
|
child.stdout.on("data", capture);
|
|
386
272
|
child.stderr.on("data", capture);
|
|
387
273
|
}
|
|
388
|
-
// Poll up to ~5 min — the join is an interactive browser approval.
|
|
389
274
|
for (let i = 0; i < 600; i++) {
|
|
390
275
|
await sleep(500);
|
|
391
276
|
if (daemonState(tenant).backendState === "Running") {
|
|
@@ -396,29 +281,17 @@ export async function joinHeadscale(tenant, loginServer, opts = {}) {
|
|
|
396
281
|
child.kill();
|
|
397
282
|
throw new Error(`VPN join not completed for tenant '${tenant}' within 5 min. Re-run \`mesh vpn tunnel up\` to resume — the daemon is still up.`);
|
|
398
283
|
}
|
|
399
|
-
/** Log the tenant daemon out of Headscale (disconnect). Daemon process persists. */
|
|
400
284
|
export function logout(tenant) {
|
|
401
285
|
try {
|
|
402
286
|
execFileSync(tailscaleBinPath(), ["--socket", socketPath(tenant), "logout"], { stdio: "ignore" });
|
|
403
287
|
}
|
|
404
288
|
catch {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
// ============================================================================
|
|
409
|
-
// Forwarder supervisor (shared per tenant) + devOutput rewrite
|
|
410
|
-
// ============================================================================
|
|
411
|
-
/**
|
|
412
|
-
* True iff two target sets are identical down to targetIp + remotePort. A live
|
|
413
|
-
* runner pins each forwarder's target at startup, so if the ELB IP rotated (or a
|
|
414
|
-
* port changed) the running forwarders point at a dead address and must be
|
|
415
|
-
* respawned, not reused. Pid-agnostic (a launchd runner records no supervisor pid).
|
|
416
|
-
*/
|
|
289
|
+
}
|
|
290
|
+
}
|
|
417
291
|
export function tunnelTargetsEqual(a, b) {
|
|
418
292
|
const key = (ts) => ts.map((t) => `${t.name}:${t.localPort}:${t.targetIp}:${t.remotePort}`).sort().join(",");
|
|
419
293
|
return key(a) === key(b);
|
|
420
294
|
}
|
|
421
|
-
/** True iff `state` has a supervisor pid and its target set equals `targets`. */
|
|
422
295
|
export function supervisorTargetsMatch(state, targets) {
|
|
423
296
|
if (!state || !state.supervisorPid)
|
|
424
297
|
return false;
|
|
@@ -435,60 +308,19 @@ function pidAlive(pid) {
|
|
|
435
308
|
return false;
|
|
436
309
|
}
|
|
437
310
|
}
|
|
438
|
-
/**
|
|
439
|
-
* SIGTERM the supervisor's whole process GROUP, not the bare pid.
|
|
440
|
-
*
|
|
441
|
-
* `state.supervisorPid` is the pid of the detached spawn — which, under the
|
|
442
|
-
* canonical source launcher, is `bin/mesh.mjs`. That launcher blocks in
|
|
443
|
-
* `execFileSync` around the *real* runner (a `node --import tsx … mesh.ts`
|
|
444
|
-
* grandchild that actually binds the forwarder ports), and `execFileSync` does
|
|
445
|
-
* not forward signals. So signalling the bare pid kills only the launcher and
|
|
446
|
-
* ORPHANS the runner, which keeps its ports bound — the orphan then answers
|
|
447
|
-
* `allForwardersListening`'s ownership-less liveness dial and silently pins
|
|
448
|
-
* every tunnel to a stale target (the MESH-2169 / MESH-2014 failure class).
|
|
449
|
-
*
|
|
450
|
-
* The launcher is spawned `detached: true`, so it is a process-group leader
|
|
451
|
-
* (`pgid === pid`); `process.kill(-pid, …)` reaps the launcher and the runner
|
|
452
|
-
* together. Falls back to the bare pid for a legacy state file whose pid was
|
|
453
|
-
* not a group leader, and tolerates ESRCH (already gone).
|
|
454
|
-
*
|
|
455
|
-
* `pid` is always recovered — from `tunnel-state.json`, `runner.json`, or a
|
|
456
|
-
* `ps` scan — never constructed here, so it is guarded first. The guard rejects
|
|
457
|
-
* anything below 2, which is what makes the NEGATED form safe: `pid >= 2` ⇒
|
|
458
|
-
* `-pid <= -2`, a real process group, never `kill(0)` (this shell's own group)
|
|
459
|
-
* or `kill(-1)` (broadcast to everything the user can signal). See utils/pid.ts.
|
|
460
|
-
*
|
|
461
|
-
* @throws {MeshCliError} when `pid` is unsignalable. Callers reaping SEVERAL
|
|
462
|
-
* independently-recovered pids must catch per-pid (see `stopTunnelRunners`) —
|
|
463
|
-
* letting one refusal propagate would skip the other pids and leave a live
|
|
464
|
-
* runner holding ports.
|
|
465
|
-
*/
|
|
466
311
|
export function killSupervisor(pid, source) {
|
|
467
|
-
// MUST be outside the try — both signals below are wrapped in catch-alls, so a
|
|
468
|
-
// guard placed inside would have its refusal swallowed and fall through to the
|
|
469
|
-
// bare-pid kill it was meant to prevent.
|
|
470
312
|
assertSignalablePid(pid, source ?? { what: "tunnel supervisor pid" });
|
|
471
313
|
try {
|
|
472
|
-
process.kill(-pid, "SIGTERM");
|
|
314
|
+
process.kill(-pid, "SIGTERM");
|
|
473
315
|
}
|
|
474
316
|
catch {
|
|
475
317
|
try {
|
|
476
|
-
process.kill(pid, "SIGTERM");
|
|
318
|
+
process.kill(pid, "SIGTERM");
|
|
477
319
|
}
|
|
478
320
|
catch {
|
|
479
|
-
/* already gone (ESRCH) */
|
|
480
321
|
}
|
|
481
322
|
}
|
|
482
323
|
}
|
|
483
|
-
/**
|
|
484
|
-
* The ownership handshake for a tenant's tunnel runner (MESH-2407): manifest →
|
|
485
|
-
* live pid → control endpoint → token + SOCKS port + port coverage. This is
|
|
486
|
-
* the ONLY readiness/reuse signal — a bare TCP dial proves a listener exists,
|
|
487
|
-
* not that it is ours, and adopting a foreign listener is exactly the
|
|
488
|
-
* silent-dead-tunnel class (MESH-2169 / MESH-2014 / #2991). As a bonus the
|
|
489
|
-
* handshake reads listening state from the runner's in-process servers, so it
|
|
490
|
-
* opens no SOCKS→VPC data connection the way port dials do.
|
|
491
|
-
*/
|
|
492
324
|
export async function verifyTunnelOwnership(tenant, expected) {
|
|
493
325
|
return verifyRunnerOwnership({
|
|
494
326
|
manifest: readRunnerManifest(runnerManifestPath(tenant)),
|
|
@@ -496,37 +328,12 @@ export async function verifyTunnelOwnership(tenant, expected) {
|
|
|
496
328
|
expectedPorts: expected.ports,
|
|
497
329
|
});
|
|
498
330
|
}
|
|
499
|
-
/**
|
|
500
|
-
* Poll the ownership handshake until it passes or `deadlineMs` of WALL CLOCK
|
|
501
|
-
* elapses (each check can cost up to the control-query timeout, so the bound
|
|
502
|
-
* must be time, not rounds); returns the last check.
|
|
503
|
-
*/
|
|
504
331
|
function awaitOwnership(tenant, expected, deadlineMs) {
|
|
505
332
|
return pollOwnership(() => verifyTunnelOwnership(tenant, expected), deadlineMs);
|
|
506
333
|
}
|
|
507
|
-
/**
|
|
508
|
-
* Tear down EVERY runner we can attribute to the tenant: the recorded
|
|
509
|
-
* supervisor pid (as a process group — see `killSupervisor`), the launchd
|
|
510
|
-
* agent, the manifest's process group, and any `__run`/`__supervise` process
|
|
511
|
-
* discovered from argv (a runner from an older build, or one whose state file
|
|
512
|
-
* was lost). This is the single teardown path — the #2991 lesson generalized:
|
|
513
|
-
* no kill path may leave a grandchild with ports still bound.
|
|
514
|
-
*/
|
|
515
334
|
export function stopTunnelRunners(tenant, prior) {
|
|
516
335
|
if (prior?.managed === "launchd")
|
|
517
336
|
uninstallLaunchAgent(tenant);
|
|
518
|
-
// Each pid below is RECOVERED (two state files and a ps scan), so each names
|
|
519
|
-
// its own provenance — the refusal has to point at the exact file to delete,
|
|
520
|
-
// or a corrupt state dir bricks the very command that would have cleared it.
|
|
521
|
-
//
|
|
522
|
-
// The three sources are INDEPENDENT, so a refusal on one must not skip the
|
|
523
|
-
// others or `clearRunnerManifest`. Aborting would let one corrupt integer in
|
|
524
|
-
// `tunnel-state.json` strand a live, uncorrupted runner from `runner.json`
|
|
525
|
-
// with its forwarder ports still bound — the silent-squatter class this
|
|
526
|
-
// teardown exists to prevent ("no kill path may leave a grandchild with ports
|
|
527
|
-
// still bound", MESH-2407). Refuse the bad pid, reap the rest;
|
|
528
|
-
// `assertTunnelPortsFree` is the backstop that still fails loudly if an
|
|
529
|
-
// un-reaped runner really is holding ports.
|
|
530
337
|
const killOrWarn = (pid, source) => {
|
|
531
338
|
try {
|
|
532
339
|
killSupervisor(pid, source);
|
|
@@ -552,12 +359,6 @@ export function stopTunnelRunners(tenant, prior) {
|
|
|
552
359
|
killOrWarn(pid, { what: `a tunnel runner pid discovered from 'ps' for tenant '${tenant}'` });
|
|
553
360
|
clearRunnerManifest(runnerManifestPath(tenant));
|
|
554
361
|
}
|
|
555
|
-
/**
|
|
556
|
-
* After teardown, wait for the tenant's forwarder ports to actually free. Any
|
|
557
|
-
* port still accepting is held by a process we could not kill — a foreign
|
|
558
|
-
* squatter — and spawning a runner over it would produce forwarders that
|
|
559
|
-
* silently fail to bind (the false-green readiness gate). Fail LOUDLY instead.
|
|
560
|
-
*/
|
|
561
362
|
async function assertTunnelPortsFree(tenant, ports) {
|
|
562
363
|
let held = ports;
|
|
563
364
|
for (let i = 0; i < 8; i++) {
|
|
@@ -574,40 +375,15 @@ async function assertTunnelPortsFree(tenant, ports) {
|
|
|
574
375
|
const squatted = await findSquattedPorts(held, (p) => portAccepts(p, 250));
|
|
575
376
|
throw new Error(buildSquatterError(tenant, squatted, "still bound after teardown"));
|
|
576
377
|
}
|
|
577
|
-
/**
|
|
578
|
-
* Ensure the per-tenant forwarder supervisor is running for `state.tunnels`.
|
|
579
|
-
* Idempotent: reuses a live runner ONLY when it passes the ownership handshake
|
|
580
|
-
* (our manifest token, our SOCKS port, every target port listening in-process);
|
|
581
|
-
* otherwise tears down every attributable runner, verifies the ports actually
|
|
582
|
-
* freed (a still-held port is a foreign squatter → loud failure, never spawn
|
|
583
|
-
* over it), and (re)spawns a fresh runner — whose readiness is again the
|
|
584
|
-
* handshake, not a bare port dial. Persists the state and returns the pid.
|
|
585
|
-
*/
|
|
586
378
|
export async function ensureForwarders(state) {
|
|
587
379
|
const prior = readTunnelState(state.tenant);
|
|
588
380
|
const expected = { socksPort: state.socksPort, ports: state.tunnels.map((t) => t.localPort) };
|
|
589
|
-
// Reuse-if-healthy must be mode-aware. A launchd runner records no supervisorPid
|
|
590
|
-
// (KeepAlive owns liveness), so gating reuse on pidAlive would tear down + rebind
|
|
591
|
-
// the shared tunnels on EVERY `up`/`mesh dev` — severing other concurrent sessions,
|
|
592
|
-
// the exact failure MESH-2014 exists to kill. Treat the launchd agent as "alive"
|
|
593
|
-
// when its plist is installed; keep the pid check for the detached path.
|
|
594
381
|
const runnerAlive = prior?.managed === "launchd"
|
|
595
382
|
? fs.existsSync(launchAgentPath(state.tenant))
|
|
596
383
|
: pidAlive(prior?.supervisorPid);
|
|
597
384
|
if (prior &&
|
|
598
385
|
runnerAlive &&
|
|
599
386
|
tunnelTargetsEqual(prior.tunnels, state.tunnels) &&
|
|
600
|
-
// A prior supervisor spawned for a DIFFERENT SOCKS port still binds the same
|
|
601
|
-
// local ports but dials a dead upstream — reusing it silently strands every
|
|
602
|
-
// tunnel (MESH-2169). The port must match, and the runner must PROVE it is
|
|
603
|
-
// ours via the ownership handshake (liveness ≠ ownership — a port answering
|
|
604
|
-
// a dial can be another session's SSM tunnel or an orphaned runner, #2991).
|
|
605
|
-
// Poll briefly rather than sample once: `ports-not-listening` during the
|
|
606
|
-
// runner's own rebind window and `no-control-answer` under load are
|
|
607
|
-
// transient, and a single miss here would tear down a healthy runner SHARED
|
|
608
|
-
// by every concurrent session on the tenant (MESH-2014). This branch is
|
|
609
|
-
// only reached when the runner is alive with matching targets, so the
|
|
610
|
-
// retry costs nothing on cold-start or genuinely-dead paths.
|
|
611
387
|
prior.socksPort === state.socksPort &&
|
|
612
388
|
(await awaitOwnership(state.tenant, expected, 1000)).ok) {
|
|
613
389
|
state.supervisorPid = prior.supervisorPid;
|
|
@@ -615,38 +391,17 @@ export async function ensureForwarders(state) {
|
|
|
615
391
|
writeTunnelState(state.tenant, state);
|
|
616
392
|
return prior.supervisorPid ?? 0;
|
|
617
393
|
}
|
|
618
|
-
// Tear down every attributable runner (state pid group, launchd agent,
|
|
619
|
-
// manifest process group, argv-discovered strays), then require the ports to
|
|
620
|
-
// actually free — a still-held port is foreign and must fail loudly here,
|
|
621
|
-
// not surface later as a forwarder that silently failed to bind.
|
|
622
394
|
stopTunnelRunners(state.tenant, prior);
|
|
623
395
|
await assertTunnelPortsFree(state.tenant, expected.ports);
|
|
624
|
-
// Write state first so the runner reads the correct targets on start.
|
|
625
396
|
state.supervisorPid = null;
|
|
626
|
-
// Prefer a launchd-supervised runner (survives reboot/crash); fall back to a
|
|
627
|
-
// detached runner where launchd isn't usable (e.g. sandboxed shells). Both run
|
|
628
|
-
// the same self-healing `__run`.
|
|
629
|
-
// argv[1] under the canonical source launcher is bin/mesh.ts, which plain
|
|
630
|
-
// node cannot execute (same failure class as the credential_process fix in
|
|
631
|
-
// aws-auth.ts) — resolve to the stable launcher, or the bare `mesh` fallback.
|
|
632
397
|
const meshBin = resolveStableMeshBin(process.argv[1]);
|
|
633
398
|
if (launchdAvailable()) {
|
|
634
399
|
state.managed = "launchd";
|
|
635
400
|
writeTunnelState(state.tenant, state);
|
|
636
|
-
// launchd does no PATH lookup, so the bare `mesh` fallback can't back a
|
|
637
|
-
// LaunchAgent (it would become `node mesh …`, crash-looping under KeepAlive).
|
|
638
|
-
// Require an absolute path; otherwise fall through to the detached runner,
|
|
639
|
-
// where spawn *does* resolve `mesh` on PATH.
|
|
640
401
|
if (path.isAbsolute(meshBin) && installLaunchAgent(state.tenant, meshBin)) {
|
|
641
|
-
// Installing the agent proves nothing about the forwarders — wait for the
|
|
642
|
-
// runner's ownership handshake (this path used to return success blind).
|
|
643
402
|
if ((await awaitOwnership(state.tenant, expected, 8000)).ok)
|
|
644
403
|
return 0;
|
|
645
404
|
uninstallLaunchAgent(state.tenant);
|
|
646
|
-
// `launchctl bootout` signals the job but doesn't wait for it — spawning
|
|
647
|
-
// the detached runner immediately would race the dying launchd runner for
|
|
648
|
-
// the ports (EADDRINUSE → a transient turned into a hard failure). Every
|
|
649
|
-
// teardown path must be followed by the ports-actually-freed check.
|
|
650
405
|
await assertTunnelPortsFree(state.tenant, expected.ports);
|
|
651
406
|
logInfo("launchd runner did not take ownership of the tunnel ports — using a detached runner.");
|
|
652
407
|
}
|
|
@@ -656,29 +411,17 @@ export async function ensureForwarders(state) {
|
|
|
656
411
|
}
|
|
657
412
|
state.managed = "detached";
|
|
658
413
|
writeTunnelState(state.tenant, state);
|
|
659
|
-
// meshBin is the stable launcher, or the bare `mesh` fallback which spawn
|
|
660
|
-
// resolves on PATH here (no PATH-lookup ternary needed — that was a no-op).
|
|
661
414
|
const child = spawn(meshBin, ["vpn", "tunnel", "__run", state.tenant], {
|
|
662
415
|
detached: true,
|
|
663
416
|
stdio: "ignore",
|
|
664
417
|
});
|
|
665
|
-
// ENOENT from the bare `mesh` fallback (mesh-cli not installed globally)
|
|
666
|
-
// arrives asynchronously — without this handler an unhandled 'error' throws
|
|
667
|
-
// an uncaught exception and takes down `mesh dev` instead of degrading.
|
|
668
418
|
child.on("error", (err) => logWarn(`tunnel supervisor failed to start (${err.message}) — falling back to SSM.`));
|
|
669
419
|
child.unref();
|
|
670
420
|
state.supervisorPid = child.pid ?? null;
|
|
671
421
|
writeTunnelState(state.tenant, state);
|
|
672
|
-
// Readiness = the ownership handshake, not a port dial: the fresh runner must
|
|
673
|
-
// present its manifest token and report every forwarder listening from its
|
|
674
|
-
// own in-process servers. A dial would also pass for another session's SSM
|
|
675
|
-
// tunnel on the same 2XXXX port — the false-green this contract kills.
|
|
676
|
-
// Throwing here lands in startServices' catch → clean SSM fallback.
|
|
677
422
|
const last = await awaitOwnership(state.tenant, expected, 5000);
|
|
678
423
|
if (last.ok)
|
|
679
424
|
return child.pid ?? -1;
|
|
680
|
-
// Diagnose before failing: a port that accepts TCP while ownership failed is
|
|
681
|
-
// held by someone else — name it and say exactly what to do.
|
|
682
425
|
const squatted = await findSquattedPorts(expected.ports, (p) => portAccepts(p, 250));
|
|
683
426
|
if (squatted.length > 0) {
|
|
684
427
|
throw new Error(buildSquatterError(state.tenant, squatted, `ownership check: ${last.reason}`));
|
|
@@ -689,11 +432,6 @@ export async function ensureForwarders(state) {
|
|
|
689
432
|
throw new Error(`Tailscale forwarders did not take ownership for tenant '${state.tenant}' ` +
|
|
690
433
|
`(${last.reason}${bindErrors ? `; ${bindErrors}` : ""}) — see ${logPath(state.tenant)}.`);
|
|
691
434
|
}
|
|
692
|
-
/**
|
|
693
|
-
* Rewrite `devOutput.tunnels` so each resolved target points at its local
|
|
694
|
-
* forwarder (`localhost:localPort`) — the same shape `startSsmTunnels`
|
|
695
|
-
* produces. Unmatched tunnels are left untouched. Pure (no mutation of input).
|
|
696
|
-
*/
|
|
697
435
|
export function rewriteTunnelsForTargets(devOutput, targets) {
|
|
698
436
|
const tunnels = { ...devOutput.tunnels };
|
|
699
437
|
for (const t of targets) {
|
|
@@ -701,7 +439,6 @@ export function rewriteTunnelsForTargets(devOutput, targets) {
|
|
|
701
439
|
}
|
|
702
440
|
return { ...devOutput, tunnels };
|
|
703
441
|
}
|
|
704
|
-
/** True when the brew `tailscaled` binary is resolvable (Tailscale transport is usable). */
|
|
705
442
|
export function tailscaleAvailable() {
|
|
706
443
|
try {
|
|
707
444
|
tailscaledBinPath();
|
|
@@ -711,23 +448,9 @@ export function tailscaleAvailable() {
|
|
|
711
448
|
return false;
|
|
712
449
|
}
|
|
713
450
|
}
|
|
714
|
-
// ============================================================================
|
|
715
|
-
// Shared engine: ensure/start Tailscale tunnels (used by the command + mesh dev)
|
|
716
|
-
// ============================================================================
|
|
717
|
-
/**
|
|
718
|
-
* Ensure the tenant's Tailscale tunnels are fully up: the shared per-tenant
|
|
719
|
-
* daemon (started + OIDC-joined if needed), the resolved VPC targets, and the
|
|
720
|
-
* forwarder supervisor. Idempotent (shared across worktrees/invocations of the
|
|
721
|
-
* same tenant). Returns the resolved targets. Used by both `mesh vpn tunnel up`
|
|
722
|
-
* and `mesh dev --transport=tailscale`.
|
|
723
|
-
*/
|
|
724
451
|
export async function ensureTailscaleTunnels(tenant, env, opts) {
|
|
725
|
-
// Reconcile the daemon's SOCKS port — an already-running daemon may be on a
|
|
726
|
-
// different port than requested; the forwarders MUST dial the real one (MESH-2169).
|
|
727
452
|
const socksPort = await ensureDaemon(tenant, { region: opts.region, socksPort: opts.socksPort });
|
|
728
453
|
await joinHeadscale(tenant, opts.loginServer, { preAuthKey: opts.preAuthKey });
|
|
729
|
-
// Pass the region explicitly rather than clobbering process.env.AWS_REGION —
|
|
730
|
-
// this runs mid `mesh dev` launch and must not mutate the global env.
|
|
731
454
|
const bastion = await getPlatformBastionInfo(tenant, env, opts.region);
|
|
732
455
|
if (!bastion) {
|
|
733
456
|
throw new Error(`Could not read the platform bastion for ${tenant}/${env} (check AWS credentials).`);
|
|
@@ -749,10 +472,6 @@ export async function ensureTailscaleTunnels(tenant, env, opts) {
|
|
|
749
472
|
await ensureForwarders(state);
|
|
750
473
|
return targets;
|
|
751
474
|
}
|
|
752
|
-
/**
|
|
753
|
-
* `startSsmTunnels` drop-in for `mesh dev`: ensure the tenant's Tailscale
|
|
754
|
-
* tunnels, then rewrite `devOutput.tunnels` to the local forwarder ports.
|
|
755
|
-
*/
|
|
756
475
|
export async function startTailscaleTunnels(devOutput, ctx) {
|
|
757
476
|
const targets = await ensureTailscaleTunnels(ctx.tenant, ctx.env, {
|
|
758
477
|
region: ctx.region,
|
|
@@ -762,20 +481,15 @@ export async function startTailscaleTunnels(devOutput, ctx) {
|
|
|
762
481
|
});
|
|
763
482
|
return rewriteTunnelsForTargets(devOutput, targets);
|
|
764
483
|
}
|
|
765
|
-
// ============================================================================
|
|
766
|
-
// launchd supervision + self-heal (Slice 2a)
|
|
767
|
-
// ============================================================================
|
|
768
484
|
export function launchAgentLabel(tenant) {
|
|
769
485
|
return `tech.mesh.tunnel.${tenant}`;
|
|
770
486
|
}
|
|
771
|
-
/** Minimal XML-escape for plist string values (controlled inputs, but paths can vary). */
|
|
772
487
|
function xmlEscape(s) {
|
|
773
488
|
return s
|
|
774
489
|
.replace(/&/g, "&")
|
|
775
490
|
.replace(/</g, "<")
|
|
776
491
|
.replace(/>/g, ">");
|
|
777
492
|
}
|
|
778
|
-
/** Render the per-tenant LaunchAgent plist (pure). */
|
|
779
493
|
export function renderLaunchAgentPlist(args) {
|
|
780
494
|
const pathEnv = args.pathEnv ?? "/usr/bin:/bin:/usr/sbin:/sbin";
|
|
781
495
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -813,7 +527,6 @@ export function renderLaunchAgentPlist(args) {
|
|
|
813
527
|
</plist>
|
|
814
528
|
`;
|
|
815
529
|
}
|
|
816
|
-
/** Targets whose local forwarder port isn't currently accepting connections. */
|
|
817
530
|
export function forwardersNeedingRebind(targets, listening) {
|
|
818
531
|
return targets.filter((t) => !listening.has(t.localPort));
|
|
819
532
|
}
|
|
@@ -824,7 +537,6 @@ function guiDomain() {
|
|
|
824
537
|
return `gui/${process.getuid?.() ?? 0}`;
|
|
825
538
|
}
|
|
826
539
|
let _launchdAvailable;
|
|
827
|
-
/** True when launchctl works for this user (blocked under the seatbelt sandbox → false). */
|
|
828
540
|
export function launchdAvailable() {
|
|
829
541
|
if (_launchdAvailable !== undefined)
|
|
830
542
|
return _launchdAvailable;
|
|
@@ -838,32 +550,20 @@ export function launchdAvailable() {
|
|
|
838
550
|
}
|
|
839
551
|
return _launchdAvailable;
|
|
840
552
|
}
|
|
841
|
-
/**
|
|
842
|
-
* Write + (re)load the per-tenant LaunchAgent. Returns true iff it fully
|
|
843
|
-
* succeeded — any failure (plist write blocked by the sandbox, `launchctl`
|
|
844
|
-
* bootstrap denied, …) returns false so the caller falls back to a detached
|
|
845
|
-
* runner. Never throws.
|
|
846
|
-
*/
|
|
847
553
|
export function installLaunchAgent(tenant, meshBin) {
|
|
848
554
|
const plistPath = launchAgentPath(tenant);
|
|
849
555
|
try {
|
|
850
|
-
// Resolve symlinks so the plist embeds a stable path (a since-deleted
|
|
851
|
-
// worktree symlink would leave a KeepAlive agent respawning a dead binary).
|
|
852
556
|
let realMeshBin = meshBin;
|
|
853
557
|
try {
|
|
854
558
|
realMeshBin = fs.realpathSync(meshBin);
|
|
855
559
|
}
|
|
856
560
|
catch {
|
|
857
|
-
/* keep the raw path */
|
|
858
561
|
}
|
|
859
|
-
// Bake a PATH that includes the tailscaled dir so the runner's daemon reheal
|
|
860
|
-
// works under launchd's minimal PATH (Intel installs live in /usr/local/bin).
|
|
861
562
|
let pathEnv = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
862
563
|
try {
|
|
863
564
|
pathEnv = `${path.dirname(tailscaledBinPath())}:${pathEnv}`;
|
|
864
565
|
}
|
|
865
566
|
catch {
|
|
866
|
-
/* fall back to the default PATH */
|
|
867
567
|
}
|
|
868
568
|
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
|
869
569
|
fs.writeFileSync(plistPath, renderLaunchAgentPlist({
|
|
@@ -877,7 +577,6 @@ export function installLaunchAgent(tenant, meshBin) {
|
|
|
877
577
|
execFileSync("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
|
|
878
578
|
}
|
|
879
579
|
catch {
|
|
880
|
-
/* not loaded — fine */
|
|
881
580
|
}
|
|
882
581
|
execFileSync("launchctl", ["bootstrap", guiDomain(), plistPath], { stdio: "ignore" });
|
|
883
582
|
return true;
|
|
@@ -886,50 +585,28 @@ export function installLaunchAgent(tenant, meshBin) {
|
|
|
886
585
|
return false;
|
|
887
586
|
}
|
|
888
587
|
}
|
|
889
|
-
/** Unload + remove the per-tenant LaunchAgent (best-effort). */
|
|
890
588
|
export function uninstallLaunchAgent(tenant) {
|
|
891
589
|
const plistPath = launchAgentPath(tenant);
|
|
892
590
|
try {
|
|
893
591
|
execFileSync("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
|
|
894
592
|
}
|
|
895
593
|
catch {
|
|
896
|
-
/* not loaded */
|
|
897
594
|
}
|
|
898
595
|
try {
|
|
899
596
|
fs.unlinkSync(plistPath);
|
|
900
597
|
}
|
|
901
598
|
catch {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
905
|
-
// ============================================================================
|
|
906
|
-
// The runner (__run): forwarders + continuous self-heal
|
|
907
|
-
// ============================================================================
|
|
908
|
-
/**
|
|
909
|
-
* Long-lived runner for a tenant's tunnels — used both under launchd and as the
|
|
910
|
-
* detached fallback. Runs a forwarder per target and, every ~5s, self-heals:
|
|
911
|
-
* respawns the daemon if its process died, and rebinds any forwarder whose port
|
|
912
|
-
* stopped listening. As the process that actually binds the ports, it is the
|
|
913
|
-
* sole author of the ownership manifest + control endpoint (MESH-2407) that
|
|
914
|
-
* callers verify against. Needs no AWS (targets come from tunnel-state). Never
|
|
915
|
-
* returns.
|
|
916
|
-
*/
|
|
599
|
+
}
|
|
600
|
+
}
|
|
917
601
|
export async function runTunnels(tenant) {
|
|
918
602
|
const st = readTunnelState(tenant);
|
|
919
603
|
if (!st) {
|
|
920
|
-
// State is gone (torn down). Under launchd's KeepAlive a non-zero exit would
|
|
921
|
-
// respawn every ~10s forever (surviving reboots) — so self-uninstall the
|
|
922
|
-
// agent (no-op when detached), drop any manifest a previous incarnation
|
|
923
|
-
// left (it describes a runner that no longer exists), and exit cleanly.
|
|
924
604
|
uninstallLaunchAgent(tenant);
|
|
925
605
|
clearRunnerManifest(runnerManifestPath(tenant));
|
|
926
606
|
process.exit(0);
|
|
927
607
|
}
|
|
928
608
|
const token = mintRunnerToken();
|
|
929
609
|
const servers = new Map();
|
|
930
|
-
// port → last bind error code. Surfaced through the control endpoint so a
|
|
931
|
-
// failed bind (EADDRINUSE from a squatted port) is a visible, attributable
|
|
932
|
-
// failure at the caller — never a silently-dead forwarder.
|
|
933
610
|
const bindErrors = new Map();
|
|
934
611
|
const spawnFor = (t) => {
|
|
935
612
|
servers.get(t.localPort)?.close();
|
|
@@ -971,8 +648,6 @@ export async function runTunnels(tenant) {
|
|
|
971
648
|
control.server.close();
|
|
972
649
|
for (const s of servers.values())
|
|
973
650
|
s.close();
|
|
974
|
-
// Token-guarded: never clobber the manifest of a newer runner that already
|
|
975
|
-
// replaced us (teardown kills groups, and signal delivery order is loose).
|
|
976
651
|
clearRunnerManifest(runnerManifestPath(tenant), token);
|
|
977
652
|
process.exit(0);
|
|
978
653
|
};
|
|
@@ -987,10 +662,6 @@ export async function runTunnels(tenant) {
|
|
|
987
662
|
socksPort: st.socksPort,
|
|
988
663
|
});
|
|
989
664
|
}
|
|
990
|
-
// Check the in-process net.Server directly — do NOT dial the forwarder.
|
|
991
|
-
// A dial makes the forwarder open a real SOCKS→VPC-target connection, and
|
|
992
|
-
// the probe's clean FIN doesn't tear the upstream leg down, leaking a
|
|
993
|
-
// connection to Temporal/RDS/etc. every 5s per target.
|
|
994
665
|
const listening = new Set();
|
|
995
666
|
for (const t of st.tunnels) {
|
|
996
667
|
if (servers.get(t.localPort)?.listening)
|
|
@@ -1000,8 +671,6 @@ export async function runTunnels(tenant) {
|
|
|
1000
671
|
spawnFor(t);
|
|
1001
672
|
}
|
|
1002
673
|
catch {
|
|
1003
|
-
/* keep the loop alive across transient errors */
|
|
1004
674
|
}
|
|
1005
675
|
}
|
|
1006
676
|
}
|
|
1007
|
-
//# sourceMappingURL=tailscale.js.map
|