@cardelli/ambit 0.2.3 → 0.3.1

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 (43) hide show
  1. package/README.md +38 -22
  2. package/esm/cli/commands/create/index.js +53 -17
  3. package/esm/cli/commands/create/machine.d.ts +2 -1
  4. package/esm/cli/commands/create/machine.d.ts.map +1 -1
  5. package/esm/cli/commands/create/machine.js +70 -29
  6. package/esm/cli/commands/deploy/index.js +2 -4
  7. package/esm/cli/commands/deploy/machine.d.ts.map +1 -1
  8. package/esm/cli/commands/destroy/app.d.ts.map +1 -1
  9. package/esm/cli/commands/destroy/index.js +2 -0
  10. package/esm/cli/commands/destroy/network.d.ts.map +1 -1
  11. package/esm/cli/commands/destroy/network.js +66 -5
  12. package/esm/cli/commands/doctor.js +13 -4
  13. package/esm/cli/commands/list.js +1 -1
  14. package/esm/cli/commands/share.d.ts +2 -0
  15. package/esm/cli/commands/share.d.ts.map +1 -0
  16. package/esm/cli/commands/share.js +250 -0
  17. package/esm/cli/commands/status.js +4 -1
  18. package/esm/cli/mod.d.ts.map +1 -1
  19. package/esm/cli/mod.js +2 -0
  20. package/esm/deno.js +1 -1
  21. package/esm/lib/command.d.ts.map +1 -1
  22. package/esm/lib/command.js +5 -7
  23. package/esm/main.d.ts +1 -0
  24. package/esm/main.d.ts.map +1 -1
  25. package/esm/main.js +2 -0
  26. package/esm/providers/fly.d.ts.map +1 -1
  27. package/esm/providers/fly.js +14 -3
  28. package/esm/providers/tailscale.d.ts +7 -0
  29. package/esm/providers/tailscale.d.ts.map +1 -1
  30. package/esm/providers/tailscale.js +23 -1
  31. package/esm/router/start.sh +7 -4
  32. package/esm/util/constants.d.ts +0 -1
  33. package/esm/util/constants.d.ts.map +1 -1
  34. package/esm/util/constants.js +0 -1
  35. package/esm/util/credentials.d.ts.map +1 -1
  36. package/esm/util/credentials.js +1 -1
  37. package/esm/util/discovery.d.ts.map +1 -1
  38. package/esm/util/discovery.js +1 -1
  39. package/esm/util/tailscale-local.d.ts +41 -0
  40. package/esm/util/tailscale-local.d.ts.map +1 -1
  41. package/esm/util/tailscale-local.js +146 -0
  42. package/esm/util/template.d.ts.map +1 -1
  43. package/package.json +1 -1
@@ -130,7 +130,9 @@ const stageSummary = (out, results) => {
130
130
  });
131
131
  }
132
132
  out.blank();
133
- out.text(issues === 0 ? "All Checks Passed." : `${issues} Issue${issues > 1 ? "s" : ""} Found.`);
133
+ out.text(issues === 0
134
+ ? "All Checks Passed."
135
+ : `${issues} Issue${issues > 1 ? "s" : ""} Found.`);
134
136
  out.blank();
135
137
  out.print();
136
138
  };
@@ -138,7 +140,10 @@ const stageSummary = (out, results) => {
138
140
  // Doctor Network
139
141
  // =============================================================================
140
142
  const doctorNetwork = async (argv) => {
141
- const opts = { string: ["network", "org"], boolean: ["help", "json"] };
143
+ const opts = {
144
+ string: ["network", "org"],
145
+ boolean: ["help", "json"],
146
+ };
142
147
  const args = parseArgs(argv, opts);
143
148
  checkArgs(args, opts, "ambit doctor network");
144
149
  if (args.help) {
@@ -168,7 +173,8 @@ ${bold("CHECKS")}
168
173
  out.blank().header("ambit Doctor: Network").blank();
169
174
  const results = [];
170
175
  const report = makeReporter(results, out);
171
- const network = (typeof args._[0] === "string" ? args._[0] : undefined) || args.network;
176
+ const network = (typeof args._[0] === "string" ? args._[0] : undefined) ||
177
+ args.network;
172
178
  const { fly, tailscale, org } = await initSession(out, {
173
179
  json: args.json,
174
180
  org: args.org,
@@ -181,7 +187,10 @@ ${bold("CHECKS")}
181
187
  // Doctor App
182
188
  // =============================================================================
183
189
  const doctorApp = async (argv) => {
184
- const opts = { string: ["network", "org"], boolean: ["help", "json"] };
190
+ const opts = {
191
+ string: ["network", "org"],
192
+ boolean: ["help", "json"],
193
+ };
185
194
  const args = parseArgs(argv, opts);
186
195
  checkArgs(args, opts, "ambit doctor app");
187
196
  if (args.help) {
@@ -7,7 +7,7 @@ import { bold } from "../../lib/cli.js";
7
7
  import { checkArgs } from "../../lib/args.js";
8
8
  import { createOutput } from "../../lib/output.js";
9
9
  import { registerCommand } from "../mod.js";
10
- import { discoverRouters, } from "../../util/discovery.js";
10
+ import { discoverRouters } from "../../util/discovery.js";
11
11
  import { initSession } from "../../util/session.js";
12
12
  // =============================================================================
13
13
  // Stage: Render
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=share.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/share.ts"],"names":[],"mappings":""}
@@ -0,0 +1,250 @@
1
+ // =============================================================================
2
+ // Share Command - Grant Members Access to a Network via Tailscale ACL
3
+ // =============================================================================
4
+ import { parseArgs } from "../../deps/jsr.io/@std/cli/1.0.28/mod.js";
5
+ import { bold } from "../../lib/cli.js";
6
+ import { checkArgs } from "../../lib/args.js";
7
+ import { createOutput } from "../../lib/output.js";
8
+ import { registerCommand } from "../mod.js";
9
+ import { z } from "../../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
10
+ import { findRouterApp, getRouterMachineInfo } from "../../util/discovery.js";
11
+ import { getRouterTag } from "../../util/naming.js";
12
+ import { assertAdditivePatch, isAclRuleConfigured, patchAclRule, } from "../../util/tailscale-local.js";
13
+ import { initSession } from "../../util/session.js";
14
+ // =============================================================================
15
+ // Validation
16
+ // =============================================================================
17
+ /**
18
+ * Valid members: Tailscale group/tag/autogroup prefixes, or a plain email.
19
+ */
20
+ const MemberSchema = z.union([
21
+ z.string().startsWith("group:").min(7, 'Must be in the form "group:<name>"'),
22
+ z.string().startsWith("tag:").min(5, 'Must be in the form "tag:<name>"'),
23
+ z.string().startsWith("autogroup:").min(11, 'Must be in the form "autogroup:<name>"'),
24
+ z.email(),
25
+ ]);
26
+ const parseMembers = (raw) => {
27
+ const members = [];
28
+ const errors = [];
29
+ for (const entry of raw) {
30
+ const result = MemberSchema.safeParse(String(entry));
31
+ if (result.success) {
32
+ members.push(result.data);
33
+ }
34
+ else {
35
+ errors.push(` '${entry}' — Must Be a Group ("group:team"), Tag ("tag:router"), Autogroup ("autogroup:member"), or Email`);
36
+ }
37
+ }
38
+ return { members, errors };
39
+ };
40
+ // =============================================================================
41
+ // ACL Error Helper
42
+ // =============================================================================
43
+ const handleAclSetFailure = (out, result, action) => {
44
+ if (result.status === 403) {
45
+ out.err(`${action}: Permission Denied (HTTP 403)`);
46
+ return out.die("Your API Token Lacks ACL Write Permission (policy_file scope required)");
47
+ }
48
+ return out.die(`${action}: ${result.error ?? `HTTP ${result.status}`}`);
49
+ };
50
+ // =============================================================================
51
+ // Stage 1: Discover Router
52
+ // =============================================================================
53
+ const stageDiscover = async (ctx) => {
54
+ ctx.out.header("Step 1: Discover Router").blank();
55
+ const routerSpinner = ctx.out.spinner(`Looking Up Router for '${ctx.network}'`);
56
+ const router = await findRouterApp(ctx.fly, ctx.org, ctx.network);
57
+ if (!router) {
58
+ routerSpinner.fail(`No Router Found for '${ctx.network}'`);
59
+ return ctx.out.die(`No Router Found for Network '${ctx.network}'. Create It with: ambit create ${ctx.network}`);
60
+ }
61
+ ctx.appName = router.appName;
62
+ routerSpinner.success(`Found Router: ${router.appName}`);
63
+ const machineSpinner = ctx.out.spinner("Getting Subnet");
64
+ const machine = await getRouterMachineInfo(ctx.fly, router.appName);
65
+ if (!machine?.subnet) {
66
+ machineSpinner.fail("No Subnet Found");
67
+ return ctx.out.die(`Router Has No Subnet Yet. Ensure the Router Is Running: ambit status network ${ctx.network}`);
68
+ }
69
+ ctx.subnet = machine.subnet;
70
+ machineSpinner.success(`Subnet: ${machine.subnet}`);
71
+ const device = await ctx.tailscale.devices.getByHostname(router.appName);
72
+ ctx.tag = device?.tags?.[0] ?? getRouterTag(ctx.network);
73
+ ctx.out.ok(`Tag: ${ctx.tag}`);
74
+ ctx.out.blank();
75
+ };
76
+ // =============================================================================
77
+ // Stage 2: Update ACL Policy
78
+ // =============================================================================
79
+ const stageUpdateAcl = async (ctx) => {
80
+ ctx.out.header("Step 2: Update ACL Policy").blank();
81
+ const policy = await ctx.tailscale.acl.getPolicy();
82
+ if (!policy) {
83
+ return ctx.out.die("Could Not Read Tailscale ACL Policy");
84
+ }
85
+ const dnsDst = `${ctx.tag}:53`;
86
+ const subnetDst = `${ctx.subnet}:*`;
87
+ let updated = policy;
88
+ for (const member of ctx.members) {
89
+ const hasDns = isAclRuleConfigured(updated, member, dnsDst);
90
+ const hasSubnet = isAclRuleConfigured(updated, member, subnetDst);
91
+ if (hasDns && hasSubnet) {
92
+ ctx.out.skip(`${member} — Already Has Full Access`);
93
+ continue;
94
+ }
95
+ if (hasDns) {
96
+ ctx.out.skip(`${member} — DNS Rule Already Present`);
97
+ }
98
+ else {
99
+ updated = patchAclRule(updated, member, dnsDst);
100
+ ctx.rulesAdded++;
101
+ }
102
+ if (hasSubnet) {
103
+ ctx.out.skip(`${member} — Subnet Rule Already Present`);
104
+ }
105
+ else {
106
+ updated = patchAclRule(updated, member, subnetDst);
107
+ ctx.rulesAdded++;
108
+ }
109
+ }
110
+ if (ctx.rulesAdded === 0) {
111
+ ctx.out.blank().ok(`All Members Already Have Full Access to '${ctx.network}'`);
112
+ return;
113
+ }
114
+ assertAdditivePatch(policy, updated);
115
+ const validateSpinner = ctx.out.spinner("Validating Policy");
116
+ const validateResult = await ctx.tailscale.acl.validatePolicy(updated);
117
+ if (!validateResult.ok) {
118
+ validateSpinner.fail("Policy Validation Failed");
119
+ return handleAclSetFailure(ctx.out, validateResult, "Validating ACL Policy");
120
+ }
121
+ validateSpinner.success("Policy Valid");
122
+ const n = ctx.rulesAdded;
123
+ const patchSpinner = ctx.out.spinner(`Updating ACL Policy (${n} new rule${n !== 1 ? "s" : ""})`);
124
+ const result = await ctx.tailscale.acl.setPolicy(updated);
125
+ if (!result.ok) {
126
+ patchSpinner.fail("Failed to Update ACL Policy");
127
+ return handleAclSetFailure(ctx.out, result, "Updating ACL Policy");
128
+ }
129
+ patchSpinner.success(`ACL Policy Updated (${n} rule${n !== 1 ? "s" : ""} added)`);
130
+ };
131
+ // =============================================================================
132
+ // Stage 3: Summary
133
+ // =============================================================================
134
+ const stageSummary = (ctx) => {
135
+ const { out, network, members, tag, subnet, rulesAdded } = ctx;
136
+ out.done({ network, members, tag: tag, subnet: subnet, rulesAdded });
137
+ if (rulesAdded === 0) {
138
+ out.blank();
139
+ out.print();
140
+ return;
141
+ }
142
+ out.blank()
143
+ .header("=".repeat(50))
144
+ .header(" Access Granted!")
145
+ .header("=".repeat(50))
146
+ .blank();
147
+ for (const member of members) {
148
+ out.ok(`${member}`);
149
+ }
150
+ out.blank()
151
+ .dim(` DNS: → ${tag}:53`)
152
+ .dim(` Subnet: → ${subnet}:*`)
153
+ .blank()
154
+ .dim("Invite People to Your Tailnet:")
155
+ .link(" https://login.tailscale.com/admin/users")
156
+ .blank()
157
+ .dim("To Fine-Tune Which Apps They Can Reach:")
158
+ .link(" https://login.tailscale.com/admin/acls/visual/general-access-rules")
159
+ .blank();
160
+ out.print();
161
+ };
162
+ // =============================================================================
163
+ // Share Command
164
+ // =============================================================================
165
+ const share = async (argv) => {
166
+ const opts = {
167
+ string: ["org"],
168
+ boolean: ["help", "json"],
169
+ };
170
+ const args = parseArgs(argv, opts);
171
+ checkArgs(args, opts, "ambit share");
172
+ if (args.help) {
173
+ console.log(`
174
+ ${bold("ambit share")} - Grant Members Access to a Network
175
+
176
+ ${bold("USAGE")}
177
+ ambit share <network> <member> [<member>...] [options]
178
+
179
+ ${bold("MEMBER TYPES")}
180
+ group:<name> A Tailscale group (e.g. group:team)
181
+ tag:<name> A device tag (e.g. tag:router)
182
+ autogroup:<name> A built-in group (e.g. autogroup:member)
183
+ <email> A Tailscale user (e.g. alice@example.com)
184
+
185
+ ${bold("OPTIONS")}
186
+ --org <org> Fly.io organization slug
187
+ --json Output as JSON
188
+
189
+ ${bold("DESCRIPTION")}
190
+ Grants access to a network by updating your Tailscale ACL policy.
191
+ For each member, ambit adds two rules:
192
+ 1. DNS: <member> → <tag>:53 (resolve *.${args._[0] || "<network>"} names)
193
+ 2. Subnet: <member> → <subnet>:* (reach apps on the network)
194
+
195
+ The command is idempotent — re-running it is safe.
196
+
197
+ ${bold("EXAMPLES")}
198
+ ambit share browsers group:team
199
+ ambit share browsers group:team alice@example.com group:contractors
200
+ ambit share browsers group:team --org my-org
201
+ `);
202
+ return;
203
+ }
204
+ const out = createOutput(args.json);
205
+ const networkArg = args._[0];
206
+ if (!networkArg || typeof networkArg !== "string") {
207
+ return out.die("Network Name Required. Usage: ambit share <network> <member> [...]");
208
+ }
209
+ const rawMembers = args._.slice(1);
210
+ if (rawMembers.length === 0) {
211
+ return out.die("At Least One Member Required. Usage: ambit share <network> <member> [...]");
212
+ }
213
+ const { members, errors } = parseMembers(rawMembers);
214
+ if (errors.length > 0) {
215
+ for (const err of errors)
216
+ out.err(err);
217
+ return out.die(`Invalid Member${errors.length > 1 ? "s" : ""}: Must Be "group:<name>", "tag:<name>", "autogroup:<name>", or a Valid Email`);
218
+ }
219
+ out.blank()
220
+ .header("=".repeat(50))
221
+ .header(` ambit Share: ${networkArg}`)
222
+ .header("=".repeat(50))
223
+ .blank();
224
+ const { fly, tailscale, org } = await initSession(out, {
225
+ json: args.json,
226
+ org: args.org,
227
+ });
228
+ const ctx = {
229
+ fly,
230
+ tailscale,
231
+ out,
232
+ network: networkArg,
233
+ org,
234
+ members,
235
+ json: args.json,
236
+ rulesAdded: 0,
237
+ };
238
+ await stageDiscover(ctx);
239
+ await stageUpdateAcl(ctx);
240
+ stageSummary(ctx);
241
+ };
242
+ // =============================================================================
243
+ // Register Command
244
+ // =============================================================================
245
+ registerCommand({
246
+ name: "share",
247
+ description: "Grant members access to a network via Tailscale ACL rules",
248
+ usage: "ambit share <network> <member> [<member>...]",
249
+ run: share,
250
+ });
@@ -224,7 +224,10 @@ const stageAppStatus = async (fly, tailscale, app, network, org, json) => {
224
224
  // Status App Subcommand
225
225
  // =============================================================================
226
226
  const statusApp = async (argv) => {
227
- const opts = { string: ["network", "org"], boolean: ["help", "json"] };
227
+ const opts = {
228
+ string: ["network", "org"],
229
+ boolean: ["help", "json"],
230
+ };
228
231
  const args = parseArgs(argv, opts);
229
232
  checkArgs(args, opts, "ambit status app");
230
233
  if (args.help) {
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/cli/mod.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAQD,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,GAAG,SAEnD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,OAAO,EAExC,CAAC;AAQF,eAAO,MAAM,QAAQ,QAAO,IA6B3B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAMF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAuCzD,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/cli/mod.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAQD,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,GAAG,SAEnD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,OAAO,EAExC,CAAC;AAQF,eAAO,MAAM,QAAQ,QAAO,IA+B3B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAMF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAuCzD,CAAC"}
package/esm/cli/mod.js CHANGED
@@ -32,6 +32,7 @@ ${bold("USAGE")}
32
32
  ${bold("COMMANDS")}
33
33
  create Create a Tailscale subnet router on a Fly.io custom network
34
34
  deploy Deploy an app safely on a custom private network
35
+ share Grant a Tailscale group access to a network via ACL rules
35
36
  list List all discovered routers across networks
36
37
  status Show router status, network, and tailnet info
37
38
  destroy Destroy a network (router) or a workload app
@@ -43,6 +44,7 @@ ${bold("OPTIONS")}
43
44
 
44
45
  ${bold("EXAMPLES")}
45
46
  ambit create browsers
47
+ ambit share browsers group:team
46
48
  ambit list
47
49
  ambit status --network browsers
48
50
  ambit destroy network browsers
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
6
  "tasks": {
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/lib/command.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC5B;AAMD,qBAAa,SAAS;IAIlB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM;IALzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;gBAGV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM;IAKzB,iDAAiD;IACjD,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC;IAapB,8BAA8B;IAC9B,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF;AAMD;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EAAE,EACd,UAAU,UAAU,KACnB,OAAO,CAAC,SAAS,CA2CnB,CAAC;AAMF,oCAAoC;AACpC,eAAO,MAAM,OAAO,GAAI,CAAC,EACvB,MAAM,MAAM,EAAE,EACd,UAAU,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAuD,CAAC"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/lib/command.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC5B;AAMD,qBAAa,SAAS;IAIlB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM;IALzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;gBAGV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM;IAKzB,iDAAiD;IACjD,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC;IAapB,8BAA8B;IAC9B,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF;AAMD;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EAAE,EACd,UAAU,UAAU,KACnB,OAAO,CAAC,SAAS,CAyCnB,CAAC;AAMF,oCAAoC;AACpC,eAAO,MAAM,OAAO,GAAI,CAAC,EACvB,MAAM,MAAM,EAAE,EACd,UAAU,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAuD,CAAC"}
@@ -48,13 +48,11 @@ export const runCommand = (args, options) => {
48
48
  const child = spawn(cmd, cmdArgs, {
49
49
  cwd: options?.cwd,
50
50
  env: options?.env ? { ...process.env, ...options.env } : undefined,
51
- stdio: interactive
52
- ? "inherit"
53
- : [
54
- options?.stdin === "inherit" ? "inherit" : "ignore",
55
- "pipe",
56
- "pipe",
57
- ],
51
+ stdio: interactive ? "inherit" : [
52
+ options?.stdin === "inherit" ? "inherit" : "ignore",
53
+ "pipe",
54
+ "pipe",
55
+ ],
58
56
  });
59
57
  if (interactive) {
60
58
  child.on("error", () => {
package/esm/main.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import "./_dnt.polyfills.js";
3
3
  import "./cli/commands/create/index.js";
4
4
  import "./cli/commands/deploy/index.js";
5
+ import "./cli/commands/share.js";
5
6
  import "./cli/commands/list.js";
6
7
  import "./cli/commands/status.js";
7
8
  import "./cli/commands/destroy/index.js";
package/esm/main.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,qBAAqB,CAAC;AA8B7B,OAAO,gCAAgC,CAAC;AACxC,OAAO,gCAAgC,CAAC;AACxC,OAAO,wBAAwB,CAAC;AAChC,OAAO,0BAA0B,CAAC;AAClC,OAAO,iCAAiC,CAAC;AACzC,OAAO,0BAA0B,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,qBAAqB,CAAC;AA+B7B,OAAO,gCAAgC,CAAC;AACxC,OAAO,gCAAgC,CAAC;AACxC,OAAO,yBAAyB,CAAC;AACjC,OAAO,wBAAwB,CAAC;AAChC,OAAO,0BAA0B,CAAC;AAClC,OAAO,iCAAiC,CAAC;AACzC,OAAO,0BAA0B,CAAC"}
package/esm/main.js CHANGED
@@ -12,6 +12,7 @@ import * as dntShim from "./_dnt.shims.js";
12
12
  // Commands:
13
13
  // create Create a Tailscale subnet router on a Fly.io custom network
14
14
  // deploy Deploy an app safely on a custom private network
15
+ // share Grant a group access to a network via Tailscale ACL rules
15
16
  // status Show router status, network, and tailnet info
16
17
  // destroy Destroy a network (router) or a workload app
17
18
  // doctor Check that Tailscale and the router are working correctly
@@ -28,6 +29,7 @@ import { runCli } from "./cli/mod.js";
28
29
  import { Spinner, statusErr } from "./lib/cli.js";
29
30
  import "./cli/commands/create/index.js";
30
31
  import "./cli/commands/deploy/index.js";
32
+ import "./cli/commands/share.js";
31
33
  import "./cli/commands/list.js";
32
34
  import "./cli/commands/status.js";
33
35
  import "./cli/commands/destroy/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/providers/fly.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/providers/fly.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EAIf,KAAK,KAAK,EAEV,KAAK,UAAU,EAIhB,MAAM,mBAAmB,CAAC;AAa3B;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMxC;AAMD,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;AAE9E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE;QACJ,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACzD,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;IACF,IAAI,EAAE;QACJ,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KACzC,CAAC;IACF,IAAI,EAAE;QACJ,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACtC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QACpD,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;QACjB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACvC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,QAAQ,EAAE;QACR,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QACzC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACxD,CAAC;IACF,OAAO,EAAE;QACP,GAAG,CACD,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC;KAClB,CAAC;IACF,GAAG,EAAE;QACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACrD,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC9D,CAAC;IACF,KAAK,EAAE;QACL,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACtD,CAAC;IACF,MAAM,EAAE;QACN,MAAM,CACJ,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;QACjB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC7D,CAAC;CACH;AAMD,eAAO,MAAM,iBAAiB,QAAO,WAqcpC,CAAC"}
@@ -9,7 +9,7 @@ import { dirname, resolve } from "../deps/jsr.io/@std/path/1.1.4/mod.js";
9
9
  import { FlyAppInfoListSchema, FlyAppsListSchema, FlyAuthSchema, FlyIpListSchema, FlyMachinesListSchema, FlyOrgsSchema, FlyStatusSchema, } from "../schemas/fly.js";
10
10
  import { fileExists } from "../lib/cli.js";
11
11
  import { getWorkloadAppName } from "../util/naming.js";
12
- import { extractErrorDetail, getSizeConfig, mapMachines } from "../util/fly-transforms.js";
12
+ import { extractErrorDetail, getSizeConfig, mapMachines, } from "../util/fly-transforms.js";
13
13
  // =============================================================================
14
14
  // Deploy Error
15
15
  // =============================================================================
@@ -60,7 +60,12 @@ export const createFlyProvider = () => {
60
60
  if (!loginResult.ok) {
61
61
  return die("Fly.io Authentication Failed");
62
62
  }
63
- const checkResult = await runCommand(["fly", "auth", "whoami", "--json"]);
63
+ const checkResult = await runCommand([
64
+ "fly",
65
+ "auth",
66
+ "whoami",
67
+ "--json",
68
+ ]);
64
69
  if (!checkResult.ok) {
65
70
  return die("Fly.io Authentication Verification Failed");
66
71
  }
@@ -155,7 +160,13 @@ export const createFlyProvider = () => {
155
160
  }
156
161
  },
157
162
  async exists(name) {
158
- const result = await runCommand(["fly", "status", "-a", name, "--json"]);
163
+ const result = await runCommand([
164
+ "fly",
165
+ "status",
166
+ "-a",
167
+ name,
168
+ "--json",
169
+ ]);
159
170
  return result.json().match({
160
171
  ok: (data) => {
161
172
  const parsed = FlyStatusSchema.safeParse(data);
@@ -4,6 +4,11 @@ export interface DeviceRoutes {
4
4
  enabled: string[];
5
5
  unapproved: string[];
6
6
  }
7
+ export interface AclSetResult {
8
+ ok: boolean;
9
+ status: number;
10
+ error?: string;
11
+ }
7
12
  export interface TailscaleProvider {
8
13
  auth: {
9
14
  validateKey(): Promise<boolean>;
@@ -25,6 +30,8 @@ export interface TailscaleProvider {
25
30
  };
26
31
  acl: {
27
32
  getPolicy(): Promise<Record<string, unknown> | null>;
33
+ setPolicy(policy: Record<string, unknown>): Promise<AclSetResult>;
34
+ validatePolicy(policy: Record<string, unknown>): Promise<AclSetResult>;
28
35
  };
29
36
  }
30
37
  export declare const createTailscaleProvider: (apiKey: string, tailnet?: string) => TailscaleProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../src/providers/tailscale.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAYjC,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;KACxD,CAAC;IACF,OAAO,EAAE;QACP,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACnC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;QACjE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnC,CAAC;IACF,MAAM,EAAE;QACN,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QACpD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC5D,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC3C,CAAC;IACF,GAAG,EAAE;QACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;KACtD,CAAC;CACH;AAiBD,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,EACd,UAAS,MAAY,KACpB,iBAyMF,CAAC"}
1
+ {"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../src/providers/tailscale.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAYjC,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;KACxD,CAAC;IACF,OAAO,EAAE;QACP,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACnC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;QACjE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnC,CAAC;IACF,MAAM,EAAE;QACN,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QACpD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC5D,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC3C,CAAC;IACF,GAAG,EAAE;QACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACrD,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;QAClE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;KACxE,CAAC;CACH;AAiBD,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,EACd,UAAS,MAAY,KACpB,iBA4OF,CAAC"}
@@ -21,7 +21,7 @@ export const createTailscaleProvider = (apiKey, tailnet = "-") => {
21
21
  const response = await fetch(`${API_BASE}${path}`, {
22
22
  method,
23
23
  headers: headers(),
24
- body: body ? JSON.stringify(body) : undefined,
24
+ body: body ? JSON.stringify(body, null, 2) : undefined,
25
25
  });
26
26
  if (!response.ok) {
27
27
  const text = await response.text();
@@ -144,6 +144,28 @@ export const createTailscaleProvider = (apiKey, tailnet = "-") => {
144
144
  }
145
145
  return result.data;
146
146
  },
147
+ async setPolicy(policy) {
148
+ const result = await request("POST", `/tailnet/${tailnet}/acl`, policy);
149
+ if (!result.ok) {
150
+ return { ok: false, status: result.status, error: result.error };
151
+ }
152
+ return { ok: true, status: result.status };
153
+ },
154
+ async validatePolicy(policy) {
155
+ const result = await request("POST", `/tailnet/${tailnet}/acl/validate`, policy);
156
+ if (!result.ok) {
157
+ return { ok: false, status: result.status, error: result.error };
158
+ }
159
+ const body = result.data ?? {};
160
+ if (Object.keys(body).length > 0) {
161
+ return {
162
+ ok: false,
163
+ status: result.status,
164
+ error: JSON.stringify(body),
165
+ };
166
+ }
167
+ return { ok: true, status: result.status };
168
+ },
147
169
  },
148
170
  };
149
171
  return provider;
@@ -5,7 +5,8 @@ set -e
5
5
  # ambit - Self-Configuring Tailscale Subnet Router
6
6
  # =============================================================================
7
7
  # State is persisted to /var/lib/tailscale via Fly volume.
8
- # On first run: authenticates with a pre-minted auth key, advertises routes.
8
+ # On first run: waits for TAILSCALE_AUTHKEY (delivered by the CLI after
9
+ # autoApprovers are in place), then authenticates and advertises routes.
9
10
  # On restart: reuses existing state, no new device created.
10
11
  # The router never receives the user's API token — only a single-use,
11
12
  # tag-scoped auth key that expires after 5 minutes.
@@ -41,10 +42,12 @@ if /usr/local/bin/tailscale status --json 2>/dev/null | jq -e '.BackendState ==
41
42
  --hostname="${FLY_APP_NAME:-ambit}" \
42
43
  --advertise-routes="${SUBNET}"
43
44
  else
44
- # First run - authenticate with pre-minted auth key
45
+ # First run auth key is delivered by the CLI via `fly secrets set` after
46
+ # autoApprovers are configured. If it's not here yet, wait for the restart
47
+ # that the non-staged secrets set triggers.
45
48
  if [ -z "${TAILSCALE_AUTHKEY}" ]; then
46
- echo "Router: ERROR - No TAILSCALE_AUTHKEY Provided"
47
- exit 1
49
+ echo "Router: Waiting for Auth Key (CLI will deliver via secrets)"
50
+ while true; do sleep 5; done
48
51
  fi
49
52
 
50
53
  echo "Router: Authenticating to Tailscale"
@@ -2,7 +2,6 @@ export declare const ROUTER_APP_PREFIX = "ambit-";
2
2
  export declare const DEFAULT_FLY_NETWORK = "default";
3
3
  export declare const ROUTER_DOCKER_DIR: string;
4
4
  export declare const SOCKS_PROXY_PORT = 1080;
5
- export declare const FLY_PRIVATE_SUBNET = "fdaa::/16";
6
5
  export declare const TAILSCALE_API_KEY_PREFIX = "tskey-api-";
7
6
  export declare const ENV_TAILSCALE_API_KEY = "TAILSCALE_API_KEY";
8
7
  export declare const FLYCTL_INSTALL_URL = "https://fly.io/docs/flyctl/install/";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/util/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,eAAO,MAAM,iBAAiB,QACnB,CAAC;AAMZ,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAM9C,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAMzD,eAAO,MAAM,kBAAkB,wCAAwC,CAAC;AAMxE,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAC5D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAM5C,eAAO,MAAM,2BAA2B,yBAAyB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/util/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,eAAO,MAAM,iBAAiB,QACnB,CAAC;AAMZ,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAMrC,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAMzD,eAAO,MAAM,kBAAkB,wCAAwC,CAAC;AAMxE,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAC5D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAM5C,eAAO,MAAM,2BAA2B,yBAAyB,CAAC"}
@@ -12,7 +12,6 @@ export const ROUTER_DOCKER_DIR = new URL("../router", globalThis[Symbol.for("imp
12
12
  // Networking
13
13
  // =============================================================================
14
14
  export const SOCKS_PROXY_PORT = 1080;
15
- export const FLY_PRIVATE_SUBNET = "fdaa::/16";
16
15
  // =============================================================================
17
16
  // Tailscale
18
17
  // =============================================================================
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/util/credentials.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,eAAe;IAC9B,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,eAAO,MAAM,2BAA2B,QAAO,eA0B9C,CAAC;AAMF,eAAO,MAAM,kBAAkB,QAAO,eAerC,CAAC;AAMF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,KAC1D,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAyBlC,CAAC"}
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/util/credentials.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,eAAe;IAC9B,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,eAAO,MAAM,2BAA2B,QAAO,eA0B9C,CAAC;AAMF,eAAO,MAAM,kBAAkB,QAAO,eAerC,CAAC;AAMF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,KAC1D,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAyBlC,CAAC"}
@@ -3,7 +3,7 @@
3
3
  // =============================================================================
4
4
  import * as dntShim from "../_dnt.shims.js";
5
5
  import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
6
- import { commandExists, ensureConfigDir, fileExists, getConfigDir } from "../lib/cli.js";
6
+ import { commandExists, ensureConfigDir, fileExists, getConfigDir, } from "../lib/cli.js";
7
7
  import { ENV_TAILSCALE_API_KEY } from "./constants.js";
8
8
  // =============================================================================
9
9
  // Schema
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/util/discovery.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAWnE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAMD,wDAAwD;AACxD,eAAO,MAAM,cAAc,GACzB,KAAK,WAAW,EAChB,KAAK,MAAM,KACV,OAAO,CAAC,SAAS,EAAE,CAerB,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,SAAS,GAAG,IAAI,CAG1B,CAAC;AAMF,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,GACpC,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,WAAW,EAAE,CAcvB,CAAC;AAEF;;+EAE+E;AAC/E,eAAO,MAAM,eAAe,GAC1B,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,EACf,UAAU,MAAM,KACf,OAAO,CAAC,WAAW,GAAG,IAAI,CA4B5B,CAAC;AAMF,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,GAC/B,KAAK,WAAW,EAChB,SAAS,MAAM,KACd,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAclC,CAAC;AAMF,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,GACjC,WAAW,iBAAiB,EAC5B,SAAS,MAAM,KACd,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAcpC,CAAC;AAMF,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG;IACvC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,KAAK;IAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,EAC7D,KAAK,WAAW,EAChB,WAAW,iBAAiB,EAC5B,KAAK,MAAM,KACV,OAAO,CAAC,cAAc,EAAE,CAa1B,CAAC"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/util/discovery.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAQnE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAMD,wDAAwD;AACxD,eAAO,MAAM,cAAc,GACzB,KAAK,WAAW,EAChB,KAAK,MAAM,KACV,OAAO,CAAC,SAAS,EAAE,CAerB,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,SAAS,GAAG,IAAI,CAG1B,CAAC;AAMF,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,GACpC,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,WAAW,EAAE,CAcvB,CAAC;AAEF;;+EAE+E;AAC/E,eAAO,MAAM,eAAe,GAC1B,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,EACf,UAAU,MAAM,KACf,OAAO,CAAC,WAAW,GAAG,IAAI,CAgC5B,CAAC;AAMF,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,GAC/B,KAAK,WAAW,EAChB,SAAS,MAAM,KACd,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAclC,CAAC;AAMF,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,GACjC,WAAW,iBAAiB,EAC5B,SAAS,MAAM,KACd,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAcpC,CAAC;AAMF,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG;IACvC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,KAAK;IAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,EAC7D,KAAK,WAAW,EAChB,WAAW,iBAAiB,EAC5B,KAAK,MAAM,KACV,OAAO,CAAC,cAAc,EAAE,CAa1B,CAAC"}
@@ -14,7 +14,7 @@
14
14
  // =============================================================================
15
15
  import { getRouterSuffix, getWorkloadAppName } from "./naming.js";
16
16
  import { extractSubnet } from "./fly-transforms.js";
17
- import { DEFAULT_FLY_NETWORK, ROUTER_APP_PREFIX, } from "./constants.js";
17
+ import { DEFAULT_FLY_NETWORK, ROUTER_APP_PREFIX } from "./constants.js";
18
18
  // =============================================================================
19
19
  // 1. Which routers exist? (Fly REST API)
20
20
  // =============================================================================