@cardelli/ambit 0.1.4 → 0.2.0

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.
Files changed (141) hide show
  1. package/esm/cli/commands/create/index.d.ts +2 -0
  2. package/esm/cli/commands/create/index.d.ts.map +1 -0
  3. package/esm/cli/commands/create/index.js +292 -0
  4. package/esm/cli/commands/create/machine.d.ts +33 -0
  5. package/esm/cli/commands/create/machine.d.ts.map +1 -0
  6. package/esm/cli/commands/create/machine.js +162 -0
  7. package/esm/cli/commands/deploy/index.d.ts +2 -0
  8. package/esm/cli/commands/deploy/index.d.ts.map +1 -0
  9. package/esm/cli/commands/deploy/index.js +290 -0
  10. package/esm/cli/commands/deploy/machine.d.ts +52 -0
  11. package/esm/cli/commands/deploy/machine.d.ts.map +1 -0
  12. package/esm/cli/commands/deploy/machine.js +116 -0
  13. package/esm/cli/commands/deploy/modes.d.ts +18 -0
  14. package/esm/cli/commands/deploy/modes.d.ts.map +1 -0
  15. package/esm/cli/commands/deploy/modes.js +152 -0
  16. package/esm/cli/commands/destroy/app.d.ts +2 -0
  17. package/esm/cli/commands/destroy/app.d.ts.map +1 -0
  18. package/esm/cli/commands/destroy/app.js +173 -0
  19. package/esm/cli/commands/destroy/index.d.ts +2 -0
  20. package/esm/cli/commands/destroy/index.d.ts.map +1 -0
  21. package/esm/cli/commands/destroy/index.js +63 -0
  22. package/esm/cli/commands/destroy/network.d.ts +2 -0
  23. package/esm/cli/commands/destroy/network.d.ts.map +1 -0
  24. package/esm/cli/commands/destroy/network.js +210 -0
  25. package/esm/cli/commands/doctor.d.ts.map +1 -0
  26. package/esm/cli/commands/doctor.js +295 -0
  27. package/esm/{src/cli → cli}/commands/list.d.ts.map +1 -1
  28. package/esm/{src/cli → cli}/commands/list.js +39 -54
  29. package/esm/cli/commands/status.d.ts.map +1 -0
  30. package/esm/cli/commands/status.js +331 -0
  31. package/esm/cli/mod.d.ts.map +1 -0
  32. package/esm/{src/cli → cli}/mod.js +4 -4
  33. package/esm/deno.d.ts +4 -18
  34. package/esm/deno.js +5 -19
  35. package/esm/deps/jsr.io/@std/path/1.1.4/constants.d.ts +1 -1
  36. package/esm/deps/jsr.io/@zod/zod/4.3.6/src/v4/core/json-schema-generator.d.ts +1 -1
  37. package/esm/lib/args.d.ts +11 -0
  38. package/esm/lib/args.d.ts.map +1 -0
  39. package/esm/lib/args.js +28 -0
  40. package/esm/lib/cli.d.ts +0 -2
  41. package/esm/lib/cli.d.ts.map +1 -1
  42. package/esm/lib/cli.js +41 -27
  43. package/esm/lib/command.d.ts +21 -49
  44. package/esm/lib/command.d.ts.map +1 -1
  45. package/esm/lib/command.js +55 -95
  46. package/esm/lib/machine.d.ts +11 -0
  47. package/esm/lib/machine.d.ts.map +1 -0
  48. package/esm/lib/machine.js +15 -0
  49. package/esm/lib/output.d.ts +3 -2
  50. package/esm/lib/output.d.ts.map +1 -1
  51. package/esm/lib/output.js +25 -11
  52. package/esm/lib/result.d.ts +18 -7
  53. package/esm/lib/result.d.ts.map +1 -1
  54. package/esm/lib/result.js +46 -1
  55. package/esm/main.d.ts +6 -6
  56. package/esm/main.d.ts.map +1 -1
  57. package/esm/main.js +7 -9
  58. package/esm/providers/fly.d.ts +81 -0
  59. package/esm/providers/fly.d.ts.map +1 -0
  60. package/esm/providers/fly.js +372 -0
  61. package/esm/providers/tailscale.d.ts +31 -0
  62. package/esm/providers/tailscale.d.ts.map +1 -0
  63. package/esm/providers/tailscale.js +150 -0
  64. package/esm/{src/schemas → schemas}/fly.d.ts +1 -11
  65. package/esm/schemas/fly.d.ts.map +1 -0
  66. package/esm/{src/schemas → schemas}/fly.js +14 -56
  67. package/esm/{src/schemas → schemas}/tailscale.d.ts +1 -2
  68. package/esm/schemas/tailscale.d.ts.map +1 -0
  69. package/esm/{src/schemas → schemas}/tailscale.js +2 -3
  70. package/esm/src/{docker/router → router}/Dockerfile +0 -11
  71. package/esm/src/router/start.sh +101 -0
  72. package/esm/util/constants.d.ts +13 -0
  73. package/esm/util/constants.d.ts.map +1 -0
  74. package/esm/util/constants.js +34 -0
  75. package/esm/{src → util}/credentials.d.ts +0 -1
  76. package/esm/util/credentials.d.ts.map +1 -0
  77. package/esm/{src → util}/credentials.js +3 -5
  78. package/esm/{src → util}/discovery.d.ts +20 -4
  79. package/esm/util/discovery.d.ts.map +1 -0
  80. package/esm/{src → util}/discovery.js +38 -15
  81. package/esm/util/fly-transforms.d.ts +27 -0
  82. package/esm/util/fly-transforms.d.ts.map +1 -0
  83. package/esm/util/fly-transforms.js +87 -0
  84. package/esm/{src → util}/guard.d.ts +1 -2
  85. package/esm/util/guard.d.ts.map +1 -0
  86. package/esm/{src → util}/guard.js +27 -27
  87. package/esm/util/naming.d.ts +5 -0
  88. package/esm/util/naming.d.ts.map +1 -0
  89. package/esm/util/naming.js +12 -0
  90. package/esm/{src → util}/resolve.d.ts +2 -3
  91. package/esm/util/resolve.d.ts.map +1 -0
  92. package/esm/{src → util}/resolve.js +1 -2
  93. package/esm/util/session.d.ts +16 -0
  94. package/esm/util/session.d.ts.map +1 -0
  95. package/esm/util/session.js +19 -0
  96. package/esm/util/tailscale-local.d.ts +13 -0
  97. package/esm/util/tailscale-local.d.ts.map +1 -0
  98. package/esm/util/tailscale-local.js +63 -0
  99. package/esm/{src → util}/template.d.ts +2 -4
  100. package/esm/util/template.d.ts.map +1 -0
  101. package/esm/{src → util}/template.js +14 -17
  102. package/package.json +1 -43
  103. package/esm/lib/paths.d.ts +0 -3
  104. package/esm/lib/paths.d.ts.map +0 -1
  105. package/esm/lib/paths.js +0 -5
  106. package/esm/src/cli/commands/create.d.ts +0 -2
  107. package/esm/src/cli/commands/create.d.ts.map +0 -1
  108. package/esm/src/cli/commands/create.js +0 -294
  109. package/esm/src/cli/commands/deploy.d.ts +0 -2
  110. package/esm/src/cli/commands/deploy.d.ts.map +0 -1
  111. package/esm/src/cli/commands/deploy.js +0 -426
  112. package/esm/src/cli/commands/destroy.d.ts +0 -2
  113. package/esm/src/cli/commands/destroy.d.ts.map +0 -1
  114. package/esm/src/cli/commands/destroy.js +0 -340
  115. package/esm/src/cli/commands/doctor.d.ts.map +0 -1
  116. package/esm/src/cli/commands/doctor.js +0 -141
  117. package/esm/src/cli/commands/status.d.ts.map +0 -1
  118. package/esm/src/cli/commands/status.js +0 -152
  119. package/esm/src/cli/mod.d.ts.map +0 -1
  120. package/esm/src/credentials.d.ts.map +0 -1
  121. package/esm/src/discovery.d.ts.map +0 -1
  122. package/esm/src/docker/router/start.sh +0 -146
  123. package/esm/src/guard.d.ts.map +0 -1
  124. package/esm/src/providers/fly.d.ts +0 -70
  125. package/esm/src/providers/fly.d.ts.map +0 -1
  126. package/esm/src/providers/fly.js +0 -411
  127. package/esm/src/providers/tailscale.d.ts +0 -31
  128. package/esm/src/providers/tailscale.d.ts.map +0 -1
  129. package/esm/src/providers/tailscale.js +0 -195
  130. package/esm/src/resolve.d.ts.map +0 -1
  131. package/esm/src/schemas/config.d.ts +0 -5
  132. package/esm/src/schemas/config.d.ts.map +0 -1
  133. package/esm/src/schemas/config.js +0 -22
  134. package/esm/src/schemas/fly.d.ts.map +0 -1
  135. package/esm/src/schemas/tailscale.d.ts.map +0 -1
  136. package/esm/src/template.d.ts.map +0 -1
  137. /package/esm/{src/cli → cli}/commands/doctor.d.ts +0 -0
  138. /package/esm/{src/cli → cli}/commands/list.d.ts +0 -0
  139. /package/esm/{src/cli → cli}/commands/status.d.ts +0 -0
  140. /package/esm/{src/cli → cli}/mod.d.ts +0 -0
  141. /package/esm/src/{docker/router → router}/fly.toml +0 -0
@@ -1,294 +0,0 @@
1
- // =============================================================================
2
- // Create Command - Create Tailscale Subnet Router on Fly.io Custom Network
3
- // =============================================================================
4
- import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
5
- import { bold, randomId, readSecret } from "../../../lib/cli.js";
6
- import { createOutput } from "../../../lib/output.js";
7
- import { registerCommand } from "../mod.js";
8
- import { extractSubnet, getRouterTag } from "../../schemas/config.js";
9
- import { isPublicTld } from "../../guard.js";
10
- import { createFlyProvider, FlyDeployError, getRouterAppName, } from "../../providers/fly.js";
11
- import { createTailscaleProvider, enableAcceptRoutes, isAcceptRoutesEnabled, isTailscaleInstalled, waitForDevice, } from "../../providers/tailscale.js";
12
- import { getCredentialStore } from "../../credentials.js";
13
- import { resolveOrg } from "../../resolve.js";
14
- // =============================================================================
15
- // Create Command
16
- // =============================================================================
17
- const create = async (argv) => {
18
- const args = parseArgs(argv, {
19
- string: ["org", "region", "api-key", "tag"],
20
- boolean: ["help", "yes", "json", "self-approve"],
21
- alias: { y: "yes" },
22
- });
23
- if (args.help) {
24
- console.log(`
25
- ${bold("ambit create")} - Create Tailscale Subnet Router
26
-
27
- ${bold("USAGE")}
28
- ambit create <network> [options]
29
-
30
- ${bold("OPTIONS")}
31
- --org <org> Fly.io organization slug
32
- --region <region> Fly.io region (default: iad)
33
- --api-key <key> Tailscale API access token (tskey-api-...)
34
- --tag <tag> Tailscale ACL tag for the router (default: tag:ambit-<network>)
35
- --self-approve Approve subnet routes via API (when autoApprovers not configured)
36
- -y, --yes Skip confirmation prompts
37
- --json Output as JSON
38
-
39
- ${bold("DESCRIPTION")}
40
- Deploys a Tailscale subnet router onto a Fly.io custom private network.
41
- The network name becomes a TLD on your tailnet:
42
-
43
- my-app.${args._[0] || "<network>"} resolves to my-app.flycast
44
-
45
- ${bold("EXAMPLES")}
46
- ambit create browsers
47
- ambit create browsers --org my-org --region sea
48
- ambit create browsers --self-approve
49
- `);
50
- return;
51
- }
52
- const out = createOutput(args.json);
53
- const networkArg = args._[0];
54
- if (!networkArg || typeof networkArg !== "string") {
55
- return out.die("Network Name Required. Usage: ambit create <network>");
56
- }
57
- const network = networkArg;
58
- if (isPublicTld(network)) {
59
- return out.die(`"${network}" Is a Public TLD and Cannot Be Used as a Network Name`);
60
- }
61
- const tag = args.tag || getRouterTag(network);
62
- const selfApprove = args["self-approve"] ?? false;
63
- out.blank()
64
- .header("=".repeat(50))
65
- .header(` ambit Create: ${network}`)
66
- .header("=".repeat(50))
67
- .blank();
68
- // ==========================================================================
69
- // Step 1: Fly.io Authentication
70
- // ==========================================================================
71
- out.header("Step 1: Fly.io Configuration").blank();
72
- const fly = createFlyProvider();
73
- await fly.ensureInstalled();
74
- const email = await fly.ensureAuth({ interactive: !args.json });
75
- out.ok(`Authenticated as ${email}`);
76
- const org = await resolveOrg(fly, args, out);
77
- const region = args.region || "iad";
78
- out.ok(`Using Region: ${region}`).blank();
79
- // ==========================================================================
80
- // Step 2: Tailscale Configuration
81
- // ==========================================================================
82
- out.header("Step 2: Tailscale Configuration").blank();
83
- const credentials = getCredentialStore();
84
- let apiKey = args["api-key"] || (await credentials.getTailscaleApiKey());
85
- if (!apiKey) {
86
- if (args.json) {
87
- return out.die("--api-key Is Required in JSON Mode");
88
- }
89
- out.dim("ambit needs an API access token (not an auth key) to manage your tailnet.")
90
- .dim("Create one at: https://login.tailscale.com/admin/settings/keys")
91
- .blank();
92
- apiKey = await readSecret("API access token (tskey-api-...): ");
93
- if (!apiKey) {
94
- return out.die("Tailscale API Access Token Required");
95
- }
96
- }
97
- if (!apiKey.startsWith("tskey-api-")) {
98
- return out.die("Invalid Token Format. Expected 'tskey-api-...' (API access token, not auth key)");
99
- }
100
- const tailscale = createTailscaleProvider("-", apiKey);
101
- const validateSpinner = out.spinner("Validating API Access Token");
102
- const isValid = await tailscale.validateApiKey();
103
- if (!isValid) {
104
- validateSpinner.fail("Invalid API Access Token");
105
- return out.die("Failed to Validate Tailscale API Access Token");
106
- }
107
- validateSpinner.success("API Access Token Validated");
108
- await credentials.setTailscaleApiKey(apiKey);
109
- // ==========================================================================
110
- // Step 2.5: Check tagOwners
111
- // ==========================================================================
112
- const tagOwnerSpinner = out.spinner(`Checking tagOwners for ${tag}`);
113
- const hasTagOwner = await tailscale.isTagOwnerConfigured(tag);
114
- if (!hasTagOwner) {
115
- tagOwnerSpinner.fail(`Tag ${tag} Not Configured in tagOwners`);
116
- out.blank()
117
- .text(` The tag ${tag} does not exist in your Tailscale ACL tagOwners.`)
118
- .text(" Tailscale will reject auth keys for undefined tags.")
119
- .blank()
120
- .text(" Add this tag in your Tailscale ACL settings:")
121
- .dim(" https://login.tailscale.com/admin/acls/visual/tags")
122
- .blank()
123
- .dim(` "tagOwners": { "${tag}": ["autogroup:admin"] }`)
124
- .blank();
125
- return out.die(`Add ${tag} to tagOwners Before Creating Router`);
126
- }
127
- tagOwnerSpinner.success(`Tag ${tag} Configured in tagOwners`);
128
- // ==========================================================================
129
- // Step 2.6: Check autoApprovers
130
- // ==========================================================================
131
- if (!selfApprove) {
132
- const autoApproverSpinner = out.spinner("Checking autoApprovers Configuration");
133
- const hasAutoApprover = await tailscale.isAutoApproverConfigured(tag);
134
- if (!hasAutoApprover) {
135
- autoApproverSpinner.fail("autoApprovers Not Configured");
136
- out.blank()
137
- .text(` The tag ${tag} is not listed in your Tailscale ACL autoApprovers.`)
138
- .text(" Routes advertised by the router will not be auto-approved.")
139
- .blank()
140
- .text(" Either configure autoApprovers in your Tailscale ACL policy:")
141
- .dim(` "autoApprovers": { "routes": { "fdaa:X:XXXX::/48": ["${tag}"] } }`)
142
- .blank()
143
- .text(" Or re-run with --self-approve to approve routes via API:")
144
- .dim(` ambit create ${network} --self-approve`)
145
- .blank();
146
- return out.die("Configure autoApprovers or Use --self-approve");
147
- }
148
- autoApproverSpinner.success("autoApprovers Configured");
149
- }
150
- else {
151
- out.info("Self-Approve Mode: Routes Will Be Approved via API");
152
- }
153
- out.blank();
154
- // ==========================================================================
155
- // Step 3: Deploy Router on Custom 6PN
156
- // ==========================================================================
157
- out.header("Step 3: Deploy Subnet Router").blank();
158
- const routerAppName = getRouterAppName(network, randomId(6));
159
- out.info(`Creating Router App: ${routerAppName}`)
160
- .info(`Custom Network: ${network}`)
161
- .info(`Router Tag: ${tag}`);
162
- await fly.createApp(routerAppName, org, { network });
163
- out.ok(`Created App: ${routerAppName}`);
164
- await fly.setSecrets(routerAppName, {
165
- TAILSCALE_API_TOKEN: apiKey,
166
- NETWORK_NAME: network,
167
- TAILSCALE_TAGS: tag,
168
- }, { stage: true });
169
- out.ok("Set Router Secrets");
170
- const dockerDir = new URL("../../docker/router", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).pathname;
171
- out.blank().dim("Deploying Router...");
172
- try {
173
- await fly.routerDeploy(routerAppName, dockerDir, { region });
174
- }
175
- catch (e) {
176
- if (e instanceof FlyDeployError) {
177
- out.dim(` ${e.detail}`);
178
- return out.die(e.message);
179
- }
180
- throw e;
181
- }
182
- out.ok("Router Deployed");
183
- out.blank();
184
- const joinSpinner = out.spinner("Waiting for Router to Join Tailnet");
185
- const device = await waitForDevice(tailscale, routerAppName, 180000);
186
- joinSpinner.success(`Router Joined Tailnet: ${device.addresses[0]}`);
187
- const machines = await fly.listMachines(routerAppName);
188
- const routerMachine = machines.find((m) => m.private_ip);
189
- const subnet = routerMachine?.private_ip
190
- ? extractSubnet(routerMachine.private_ip)
191
- : null;
192
- if (subnet) {
193
- out.ok(`Subnet: ${subnet}`);
194
- }
195
- if (selfApprove && subnet) {
196
- const approveSpinner = out.spinner("Approving Subnet Routes via API");
197
- await tailscale.approveSubnetRoutes(device.id, [subnet]);
198
- approveSpinner.success("Subnet Routes Approved via API");
199
- }
200
- if (device.advertisedRoutes && device.advertisedRoutes.length > 0) {
201
- out.ok(`Routes: ${device.advertisedRoutes.join(", ")}`);
202
- }
203
- const dnsSpinner = out.spinner("Configuring Split DNS");
204
- await tailscale.setSplitDns(network, [device.addresses[0]]);
205
- dnsSpinner.success(`Split DNS Configured: *.${network} -> Router`);
206
- // ==========================================================================
207
- // Step 4: Local Client Configuration
208
- // ==========================================================================
209
- out.blank().header("Step 4: Local Client Configuration").blank();
210
- if (await isTailscaleInstalled()) {
211
- if (await isAcceptRoutesEnabled()) {
212
- out.ok("Accept Routes Already Enabled");
213
- }
214
- else {
215
- const routeSpinner = out.spinner("Enabling Accept Routes");
216
- if (await enableAcceptRoutes()) {
217
- routeSpinner.success("Accept Routes Enabled");
218
- }
219
- else {
220
- routeSpinner.fail("Could Not Enable Accept Routes");
221
- out.blank()
222
- .dim("Run Manually with Elevated Permissions:")
223
- .dim(" sudo tailscale set --accept-routes");
224
- }
225
- }
226
- }
227
- else {
228
- out.warn("Tailscale CLI Not Found")
229
- .dim(" Ensure Accept-Routes Is Enabled on This Device");
230
- }
231
- // ==========================================================================
232
- // Done
233
- // ==========================================================================
234
- out.done({
235
- network,
236
- router: { appName: routerAppName, tailscaleIp: device.addresses[0] },
237
- subnet: subnet || "unknown",
238
- tag,
239
- });
240
- out.blank()
241
- .header("=".repeat(50))
242
- .header(" Router Created!")
243
- .header("=".repeat(50))
244
- .blank()
245
- .text(`Any Flycast app on the "${network}" network is reachable as:`)
246
- .text(` <app-name>.${network}`)
247
- .blank()
248
- .text(`SOCKS5 proxy available at:`)
249
- .text(` socks5://[${routerMachine?.private_ip ?? "ROUTER_IP"}]:1080`)
250
- .dim("Containers on this network can use it to reach your tailnet.")
251
- .blank()
252
- .dim("Deploy an app to this network:")
253
- .dim(` ambit deploy my-app --network ${network}`)
254
- .blank()
255
- .dim("Invite people to your tailnet:")
256
- .dim(" https://login.tailscale.com/admin/users")
257
- .dim("Control their access:")
258
- .dim(" https://login.tailscale.com/admin/acls/visual/general-access-rules")
259
- .blank();
260
- if (subnet && selfApprove) {
261
- out.header("Recommended Tailscale ACL Policy:")
262
- .blank()
263
- .dim(" Add these to your tailnet policy file at:")
264
- .dim(" https://login.tailscale.com/admin/acls/file")
265
- .blank()
266
- .text(` "tagOwners": { "${tag}": ["autogroup:admin"] }`)
267
- .text(` "autoApprovers": { "routes": { "${subnet}": ["${tag}"] } }`)
268
- .blank()
269
- .dim(" To restrict access, add ACL rules:")
270
- .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${tag}:53"]}`)
271
- .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${subnet}:*"]}`)
272
- .blank();
273
- }
274
- else if (subnet) {
275
- out.header("Recommended ACL Rules:")
276
- .blank()
277
- .dim(" To restrict access, add ACL rules to your policy file:")
278
- .dim(" https://login.tailscale.com/admin/acls/file")
279
- .blank()
280
- .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${tag}:53"]}`)
281
- .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${subnet}:*"]}`)
282
- .blank();
283
- }
284
- out.print();
285
- };
286
- // =============================================================================
287
- // Register Command
288
- // =============================================================================
289
- registerCommand({
290
- name: "create",
291
- description: "Create a Tailscale subnet router on a Fly.io custom network",
292
- usage: "ambit create <network> [--org <org>] [--region <region>]",
293
- run: create,
294
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=deploy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/commands/deploy.ts"],"names":[],"mappings":""}