@cardelli/ambit 0.1.4 → 0.1.5

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.
@@ -5,8 +5,10 @@ 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: creates auth key (with tags), authenticates, approves routes.
8
+ # On first run: authenticates with a pre-minted auth key, advertises routes.
9
9
  # On restart: reuses existing state, no new device created.
10
+ # The router never receives the user's API token — only a single-use,
11
+ # tag-scoped auth key that expires after 5 minutes.
10
12
  # =============================================================================
11
13
 
12
14
  echo "Router: Enabling IP Forwarding"
@@ -33,42 +35,9 @@ if /usr/local/bin/tailscale status --json 2>/dev/null | jq -e '.BackendState ==
33
35
  --hostname="${FLY_APP_NAME:-ambit}" \
34
36
  --advertise-routes="${SUBNET}"
35
37
  else
36
- # First run - need to authenticate
37
- if [ -n "${TAILSCALE_API_TOKEN}" ]; then
38
- echo "Router: Creating Auth Key (First Run)"
39
-
40
- TAGS_JSON="[]"
41
- if [ -n "${TAILSCALE_TAGS}" ]; then
42
- TAGS_JSON=$(echo "${TAILSCALE_TAGS}" | jq -R 'split(",")')
43
- fi
44
-
45
- AUTH_KEY_RESPONSE=$(curl -s -X POST \
46
- -u "${TAILSCALE_API_TOKEN}:" \
47
- -H "Content-Type: application/json" \
48
- -d "$(jq -n \
49
- --argjson tags "${TAGS_JSON}" \
50
- '{
51
- capabilities: { devices: { create: {
52
- reusable: false,
53
- ephemeral: false,
54
- preauthorized: true,
55
- tags: $tags
56
- }}},
57
- expirySeconds: 300
58
- }' | jq 'if .capabilities.devices.create.tags == [] then .capabilities.devices.create |= del(.tags) else . end')" \
59
- "https://api.tailscale.com/api/v2/tailnet/-/keys")
60
-
61
- TAILSCALE_AUTHKEY=$(echo "${AUTH_KEY_RESPONSE}" | jq -r '.key')
62
-
63
- if [ -z "${TAILSCALE_AUTHKEY}" ] || [ "${TAILSCALE_AUTHKEY}" = "null" ]; then
64
- echo "Router: ERROR - Failed to Create Auth Key"
65
- echo "${AUTH_KEY_RESPONSE}"
66
- exit 1
67
- fi
68
-
69
- echo "Router: Auth Key Created"
70
- elif [ -z "${TAILSCALE_AUTHKEY}" ]; then
71
- echo "Router: ERROR - No TAILSCALE_API_TOKEN or TAILSCALE_AUTHKEY Provided"
38
+ # First run - authenticate with pre-minted auth key
39
+ if [ -z "${TAILSCALE_AUTHKEY}" ]; then
40
+ echo "Router: ERROR - No TAILSCALE_AUTHKEY Provided"
72
41
  exit 1
73
42
  fi
74
43
 
@@ -79,40 +48,6 @@ else
79
48
  --advertise-routes="${SUBNET}"
80
49
  fi
81
50
 
82
- echo "Router: Getting Node Key"
83
- NODE_KEY=$(/usr/local/bin/tailscale status --json | jq -r '.Self.PublicKey')
84
- echo "Router: Node Key ${NODE_KEY}"
85
-
86
- # Self-approve routes if we have API access
87
- # This is a fallback — if the user has autoApprovers configured in their
88
- # Tailscale policy file, routes are approved automatically and this block
89
- # is a no-op (routes are already enabled).
90
- if [ -n "${TAILSCALE_API_TOKEN}" ]; then
91
- echo "Router: Finding Device ID"
92
-
93
- DEVICES_RESPONSE=$(curl -s \
94
- -u "${TAILSCALE_API_TOKEN}:" \
95
- "https://api.tailscale.com/api/v2/tailnet/-/devices")
96
-
97
- DEVICE_ID=$(echo "${DEVICES_RESPONSE}" | jq -r ".devices[] | select(.nodeKey == \"${NODE_KEY}\") | .id")
98
-
99
- if [ -n "${DEVICE_ID}" ] && [ "${DEVICE_ID}" != "null" ]; then
100
- echo "Router: Device ID ${DEVICE_ID}"
101
- echo "Router: Approving Subnet Routes"
102
-
103
- curl -s -X POST \
104
- -u "${TAILSCALE_API_TOKEN}:" \
105
- -H "Content-Type: application/json" \
106
- -d "{\"routes\": [\"${SUBNET}\"]}" \
107
- "https://api.tailscale.com/api/v2/device/${DEVICE_ID}/routes" > /dev/null
108
-
109
- echo "Router: Routes Approved"
110
- else
111
- echo "Router: WARNING - Could Not Find Device ID"
112
- echo "Router: Routes May Need Manual Approval"
113
- fi
114
- fi
115
-
116
51
  echo "Router: Fully Configured"
117
52
 
118
53
  # Start SOCKS5 proxy for bidirectional tailnet access
@@ -124,10 +59,21 @@ echo "Router: Starting DNS Proxy"
124
59
 
125
60
  # Generate Corefile for CoreDNS
126
61
  # Rewrites NETWORK_NAME TLD to .flycast before forwarding to Fly DNS.
62
+ # When ROUTER_ID is set, workload app names are suffixed: app.network ->
63
+ # app-ROUTER_ID.flycast. This ties workloads to their router and avoids
64
+ # name collisions across networks.
127
65
  # .flycast resolves to the Flycast address (private_v6) which routes through
128
66
  # Fly Proxy — enabling autostart/autostop and load balancing. The Flycast IP
129
67
  # is within the network's /48 subnet so it's routable through the tailnet.
130
- if [ -n "${NETWORK_NAME}" ]; then
68
+ if [ -n "${NETWORK_NAME}" ] && [ -n "${ROUTER_ID}" ]; then
69
+ echo "Router: DNS Rewrite *.${NETWORK_NAME} -> *-${ROUTER_ID}.flycast"
70
+ cat > /etc/coredns/Corefile <<EOF
71
+ .:53 {
72
+ rewrite name regex (.+)\.${NETWORK_NAME}\. {1}-${ROUTER_ID}.flycast. answer auto
73
+ forward . fdaa::3
74
+ }
75
+ EOF
76
+ elif [ -n "${NETWORK_NAME}" ]; then
131
77
  echo "Router: DNS Rewrite ${NETWORK_NAME} -> flycast"
132
78
  cat > /etc/coredns/Corefile <<EOF
133
79
  .:53 {
@@ -32,6 +32,7 @@ export interface SafeDeployOptions {
32
32
  image?: string;
33
33
  config?: string;
34
34
  region?: string;
35
+ routerId?: string;
35
36
  }
36
37
  export interface FlyProvider {
37
38
  ensureInstalled(): Promise<void>;
@@ -41,6 +42,7 @@ export interface FlyProvider {
41
42
  listOrgs(): Promise<Record<string, string>>;
42
43
  createApp(name: string, org: string, options?: {
43
44
  network?: string;
45
+ routerId?: string;
44
46
  }): Promise<void>;
45
47
  deleteApp(name: string): Promise<void>;
46
48
  listApps(org?: string): Promise<FlyApp[]>;
@@ -67,4 +69,8 @@ export interface FlyProvider {
67
69
  }
68
70
  export declare const createFlyProvider: () => FlyProvider;
69
71
  export declare const getRouterAppName: (network: string, randomSuffix: string) => string;
72
+ /** Extract the routerId suffix from a router app name. */
73
+ export declare const getRouterSuffix: (routerAppName: string, network: string) => string;
74
+ /** Build the physical Fly app name for a workload. */
75
+ export declare const getWorkloadAppName: (name: string, routerId: string) => string;
70
76
  //# sourceMappingURL=fly.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../../src/src/providers/fly.ts"],"names":[],"mappings":"AAGA,OAAO,yBAAyB,CAAC;AAajC,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EAIf,KAAK,KAAK,EAEV,KAAK,UAAU,EAMhB,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;AAmBD,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;AAED,eAAO,MAAM,aAAa,GACxB,MAAM,WAAW,KAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CASlC,CAAC;AAMF,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;CACjB;AAMD,MAAM,WAAW,WAAW;IAC1B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjE,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACjD,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1D,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1E,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,UAAU,CACR,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,YAAY,CACV,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;CACzD;AAMD,eAAO,MAAM,iBAAiB,QAAO,WA6bpC,CAAC;AAMF,eAAO,MAAM,gBAAgB,GAC3B,SAAS,MAAM,EACf,cAAc,MAAM,KACnB,MAEF,CAAC"}
1
+ {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../../src/src/providers/fly.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,8 @@
3
3
  // =============================================================================
4
4
  import "../../_dnt.polyfills.js";
5
5
  import * as dntShim from "../../_dnt.shims.js";
6
- import { runCommand, runCommandJson, runInteractive, runQuiet, } from "../../lib/command.js";
6
+ import { runCommand, runJson, runQuiet } from "../../lib/command.js";
7
+ import { Result } from "../../lib/result.js";
7
8
  import { commandExists, die, Spinner } from "../../lib/cli.js";
8
9
  import { dirname, resolve } from "../../deps/jsr.io/@std/path/1.1.4/mod.js";
9
10
  import { FlyAppInfoListSchema, FlyAppsListSchema, FlyAuthSchema, FlyIpListSchema, FlyMachinesListSchema, FlyOrgsSchema, FlyStatusSchema, mapFlyMachineSize, mapFlyMachineState, } from "../schemas/fly.js";
@@ -64,40 +65,40 @@ export const createFlyProvider = () => {
64
65
  async ensureAuth(options) {
65
66
  const interactive = options?.interactive ?? true;
66
67
  const result = await runCommand(["fly", "auth", "whoami", "--json"]);
67
- if (result.success) {
68
- try {
69
- const parsed = FlyAuthSchema.safeParse(JSON.parse(result.stdout));
68
+ if (result.ok) {
69
+ const auth = result.json();
70
+ if (auth.ok) {
71
+ const parsed = FlyAuthSchema.safeParse(auth.value);
70
72
  if (parsed.success) {
71
73
  return parsed.data.email;
72
74
  }
73
75
  }
74
- catch {
75
- // Parse failed, need to authenticate
76
- }
77
76
  }
78
77
  if (!interactive) {
79
78
  return die("Not Authenticated with Fly.io. Run 'fly auth login' First");
80
79
  }
81
- const loginResult = await runInteractive(["fly", "auth", "login"]);
82
- if (!loginResult.success) {
80
+ const loginResult = await runCommand(["fly", "auth", "login"], {
81
+ interactive: true,
82
+ });
83
+ if (!loginResult.ok) {
83
84
  return die("Fly.io Authentication Failed");
84
85
  }
85
86
  const checkResult = await runCommand(["fly", "auth", "whoami", "--json"]);
86
- if (!checkResult.success) {
87
+ if (!checkResult.ok) {
87
88
  return die("Fly.io Authentication Verification Failed");
88
89
  }
89
- const parsed = FlyAuthSchema.safeParse(JSON.parse(checkResult.stdout));
90
+ const parsed = FlyAuthSchema.safeParse(checkResult.json().unwrap());
90
91
  if (!parsed.success || !parsed.data) {
91
92
  return die("Fly.io Authentication Response Invalid");
92
93
  }
93
94
  return parsed.data.email;
94
95
  },
95
96
  async listOrgs() {
96
- const result = await runCommandJson(["fly", "orgs", "list", "--json"]);
97
- if (!result.success || !result.data) {
97
+ const result = await runJson(["fly", "orgs", "list", "--json"]);
98
+ if (!result.ok) {
98
99
  return die("Failed to List Organizations");
99
100
  }
100
- const parsed = FlyOrgsSchema.safeParse(result.data);
101
+ const parsed = FlyOrgsSchema.safeParse(result.value);
101
102
  if (!parsed.success) {
102
103
  return die("Failed to Parse Organizations");
103
104
  }
@@ -109,25 +110,24 @@ export const createFlyProvider = () => {
109
110
  args.push("--org", org);
110
111
  }
111
112
  const result = await runCommand(args);
112
- if (!result.success) {
113
- return [];
114
- }
115
- try {
116
- const parsed = FlyAppsListSchema.safeParse(JSON.parse(result.stdout));
117
- return parsed.success ? parsed.data : [];
118
- }
119
- catch {
120
- return [];
121
- }
113
+ return result.json().flatMap((data) => {
114
+ const parsed = FlyAppsListSchema.safeParse(data);
115
+ return parsed.success
116
+ ? Result.ok(parsed.data)
117
+ : Result.err("Parse Failed");
118
+ }).unwrapOr([]);
122
119
  },
123
120
  async createApp(name, org, options) {
124
- const args = ["fly", "apps", "create", name, "--org", org, "--json"];
121
+ const appName = options?.routerId
122
+ ? getWorkloadAppName(name, options.routerId)
123
+ : name;
124
+ const args = ["fly", "apps", "create", appName, "--org", org, "--json"];
125
125
  if (options?.network) {
126
126
  args.push("--network", options.network);
127
127
  }
128
128
  const result = await runQuiet("Creating App", args);
129
- if (!result.success) {
130
- return die(`Failed to Create App '${name}'`);
129
+ if (!result.ok) {
130
+ return die(`Failed to Create App '${appName}'`);
131
131
  }
132
132
  },
133
133
  async deleteApp(name) {
@@ -138,29 +138,28 @@ export const createFlyProvider = () => {
138
138
  name,
139
139
  "--yes",
140
140
  ]);
141
- if (!result.success) {
141
+ if (!result.ok) {
142
142
  return die(`Failed to Delete App '${name}'`);
143
143
  }
144
144
  },
145
145
  async appExists(name) {
146
146
  const result = await runCommand(["fly", "status", "-a", name, "--json"]);
147
- if (!result.success)
148
- return false;
149
- try {
150
- const parsed = FlyStatusSchema.safeParse(JSON.parse(result.stdout));
151
- return parsed.success && !!parsed.data.ID;
152
- }
153
- catch {
154
- return false;
155
- }
147
+ return result.json().match({
148
+ ok: (data) => {
149
+ const parsed = FlyStatusSchema.safeParse(data);
150
+ return parsed.success && !!parsed.data.ID;
151
+ },
152
+ err: () => false,
153
+ });
156
154
  },
157
155
  async listMachines(app) {
158
- const result = await runCommandJson(["fly", "machines", "list", "-a", app, "--json"]);
159
- if (!result.success || !result.data) {
160
- return [];
161
- }
162
- const parsed = FlyMachinesListSchema.safeParse(result.data);
163
- return parsed.success ? parsed.data : [];
156
+ const result = await runJson(["fly", "machines", "list", "-a", app, "--json"]);
157
+ return result.flatMap((data) => {
158
+ const parsed = FlyMachinesListSchema.safeParse(data);
159
+ return parsed.success
160
+ ? Result.ok(parsed.data)
161
+ : Result.err("Parse Failed");
162
+ }).unwrapOr([]);
164
163
  },
165
164
  async listMachinesMapped(app) {
166
165
  const raw = await this.listMachines(app);
@@ -198,7 +197,7 @@ export const createFlyProvider = () => {
198
197
  const spinner = new Spinner();
199
198
  spinner.start(`Creating ${config.size} Machine`);
200
199
  const result = await runCommand(args);
201
- if (!result.success) {
200
+ if (!result.ok) {
202
201
  spinner.fail("Machine Creation Failed");
203
202
  return die(result.stderr || "Unknown Error");
204
203
  }
@@ -221,7 +220,7 @@ export const createFlyProvider = () => {
221
220
  app,
222
221
  "--force",
223
222
  ]);
224
- if (!result.success) {
223
+ if (!result.ok) {
225
224
  return die(`Failed to Destroy Machine '${shortId}'`);
226
225
  }
227
226
  },
@@ -236,7 +235,7 @@ export const createFlyProvider = () => {
236
235
  args.push("--stage");
237
236
  }
238
237
  const result = await runQuiet(`Setting ${pairs.length} Secret(s)`, args);
239
- if (!result.success) {
238
+ if (!result.ok) {
240
239
  return die("Failed to Set Secrets");
241
240
  }
242
241
  },
@@ -254,7 +253,7 @@ export const createFlyProvider = () => {
254
253
  args.push("--primary-region", config.region);
255
254
  }
256
255
  const result = await runCommand(args);
257
- if (!result.success) {
256
+ if (!result.ok) {
258
257
  throw new FlyDeployError(app, result.stderr);
259
258
  }
260
259
  },
@@ -267,15 +266,12 @@ export const createFlyProvider = () => {
267
266
  app,
268
267
  "--json",
269
268
  ]);
270
- if (!result.success)
271
- return [];
272
- try {
273
- const parsed = FlyIpListSchema.safeParse(JSON.parse(result.stdout));
274
- return parsed.success ? parsed.data : [];
275
- }
276
- catch {
277
- return [];
278
- }
269
+ return result.json().flatMap((data) => {
270
+ const parsed = FlyIpListSchema.safeParse(data);
271
+ return parsed.success
272
+ ? Result.ok(parsed.data)
273
+ : Result.err("Parse Failed");
274
+ }).unwrapOr([]);
279
275
  },
280
276
  async releaseIp(app, address) {
281
277
  const result = await runCommand([
@@ -286,7 +282,7 @@ export const createFlyProvider = () => {
286
282
  "-a",
287
283
  app,
288
284
  ]);
289
- if (!result.success) {
285
+ if (!result.ok) {
290
286
  return die(`Failed to Release IP ${address} from '${app}'`);
291
287
  }
292
288
  },
@@ -301,22 +297,21 @@ export const createFlyProvider = () => {
301
297
  "-a",
302
298
  app,
303
299
  ]);
304
- if (!result.success) {
300
+ if (!result.ok) {
305
301
  return die(`Failed to Allocate Flycast IP on Network '${network}'`);
306
302
  }
307
303
  },
308
304
  async getConfig(app) {
309
305
  const result = await runCommand(["fly", "config", "show", "-a", app]);
310
- if (!result.success)
311
- return null;
312
- try {
313
- return JSON.parse(result.stdout);
314
- }
315
- catch {
316
- return null;
317
- }
306
+ return result.json().match({
307
+ ok: (v) => v,
308
+ err: () => null,
309
+ });
318
310
  },
319
311
  async deploySafe(app, options) {
312
+ const appName = options.routerId
313
+ ? getWorkloadAppName(app, options.routerId)
314
+ : app;
320
315
  const args = ["fly", "deploy"];
321
316
  // When config is provided, use its parent directory as the build context
322
317
  // so fly deploy finds the Dockerfile and COPY picks up the correct files
@@ -325,7 +320,7 @@ export const createFlyProvider = () => {
325
320
  args.push(dirname(configAbs));
326
321
  args.push("--config", configAbs);
327
322
  }
328
- args.push("-a", app, "--yes", "--no-public-ips");
323
+ args.push("-a", appName, "--yes", "--no-public-ips");
329
324
  if (options.image) {
330
325
  args.push("--image", options.image);
331
326
  }
@@ -333,8 +328,8 @@ export const createFlyProvider = () => {
333
328
  args.push("--primary-region", options.region);
334
329
  }
335
330
  const result = await runCommand(args);
336
- if (!result.success) {
337
- throw new FlyDeployError(app, result.stderr);
331
+ if (!result.ok) {
332
+ throw new FlyDeployError(appName, result.stderr);
338
333
  }
339
334
  },
340
335
  async listCerts(app) {
@@ -346,17 +341,9 @@ export const createFlyProvider = () => {
346
341
  app,
347
342
  "--json",
348
343
  ]);
349
- if (!result.success)
350
- return [];
351
- try {
352
- const certs = JSON.parse(result.stdout);
353
- return certs
354
- .map((c) => c.Hostname)
355
- .filter((h) => typeof h === "string");
356
- }
357
- catch {
358
- return [];
359
- }
344
+ return result.json().map((certs) => certs
345
+ .map((c) => c.Hostname)
346
+ .filter((h) => typeof h === "string")).unwrapOr([]);
360
347
  },
361
348
  async removeCert(app, hostname) {
362
349
  await runCommand([
@@ -404,8 +391,17 @@ export const createFlyProvider = () => {
404
391
  };
405
392
  };
406
393
  // =============================================================================
407
- // Router App Naming
394
+ // App Naming
408
395
  // =============================================================================
409
396
  export const getRouterAppName = (network, randomSuffix) => {
410
397
  return `${ROUTER_APP_PREFIX}${network}-${randomSuffix}`;
411
398
  };
399
+ /** Extract the routerId suffix from a router app name. */
400
+ export const getRouterSuffix = (routerAppName, network) => {
401
+ const prefix = `${ROUTER_APP_PREFIX}${network}-`;
402
+ return routerAppName.slice(prefix.length);
403
+ };
404
+ /** Build the physical Fly app name for a workload. */
405
+ export const getWorkloadAppName = (name, routerId) => {
406
+ return `${name}-${routerId}`;
407
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../../src/src/providers/tailscale.ts"],"names":[],"mappings":"AAGA,OAAO,yBAAyB,CAAC;AAKjC,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAYjC,MAAM,WAAW,iBAAiB;IAChC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,aAAa,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAC1C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACvE,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACzD,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzD;AAiBD,eAAO,MAAM,uBAAuB,GAClC,SAAS,MAAM,EACf,QAAQ,MAAM,KACb,iBAoMF,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAY7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC"}
1
+ {"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../../src/src/providers/tailscale.ts"],"names":[],"mappings":"AAGA,OAAO,yBAAyB,CAAC;AAKjC,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAYjC,MAAM,WAAW,iBAAiB;IAChC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,aAAa,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,WAAW,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAC1C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACvE,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACzD,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzD;AAiBD,eAAO,MAAM,uBAAuB,GAClC,SAAS,MAAM,EACf,QAAQ,MAAM,KACb,iBAoMF,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAM7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC"}
@@ -174,16 +174,10 @@ export const isTailscaleInstalled = async () => {
174
174
  */
175
175
  export const isAcceptRoutesEnabled = async () => {
176
176
  const result = await runCommand(["tailscale", "debug", "prefs"]);
177
- if (!result.success) {
178
- return false;
179
- }
180
- try {
181
- const prefs = JSON.parse(result.stdout);
182
- return prefs.RouteAll === true;
183
- }
184
- catch {
185
- return false;
186
- }
177
+ return result.json().match({
178
+ ok: (prefs) => prefs.RouteAll === true,
179
+ err: () => false,
180
+ });
187
181
  };
188
182
  /**
189
183
  * Enable accept-routes on the local client.
@@ -191,5 +185,5 @@ export const isAcceptRoutesEnabled = async () => {
191
185
  */
192
186
  export const enableAcceptRoutes = async () => {
193
187
  const result = await runCommand(["tailscale", "set", "--accept-routes"]);
194
- return result.success;
188
+ return result.ok;
195
189
  };
@@ -1,6 +1,5 @@
1
- import type { Result } from "../lib/result.js";
2
- /** Kinds of errors that can occur when fetching a template from GitHub. */
3
- export type TemplateErrorKind = "NotFound" | "RateLimited" | "HttpError" | "ExtractionFailed" | "PathNotFound" | "PathNotDirectory" | "EmptyArchive" | "NetworkError";
1
+ import "../_dnt.polyfills.js";
2
+ import { Result } from "../lib/result.js";
4
3
  /** Parsed GitHub template reference. */
5
4
  export interface TemplateRef {
6
5
  owner: string;
@@ -12,7 +11,7 @@ export interface TemplateRef {
12
11
  export type TemplateFetchResult = Result<{
13
12
  tempDir: string;
14
13
  templateDir: string;
15
- }, TemplateErrorKind>;
14
+ }>;
16
15
  /**
17
16
  * Parse a template reference string into its components.
18
17
  * Returns null if the format is invalid.
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/src/template.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM/C,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,aAAa,GACb,WAAW,GACX,kBAAkB,GAClB,cAAc,GACd,kBAAkB,GAClB,cAAc,GACd,cAAc,CAAC;AAMnB,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CACtC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EACxC,iBAAiB,CAClB,CAAC;AAgCF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,WAAW,GAAG,IAyB5D,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,KACf,OAAO,CAAC,mBAAmB,CAsH7B,CAAC"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/src/template.ts"],"names":[],"mappings":"AAgBA,OAAO,sBAAsB,CAAC;AAO9B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CACtC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CACzC,CAAC;AAyBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,WAAW,GAAG,IAyB5D,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,KACf,OAAO,CAAC,mBAAmB,CAqG7B,CAAC"}
@@ -14,18 +14,16 @@
14
14
  // ToxicPine/ambit-templates/cdp@main → explicit branch
15
15
  //
16
16
  // =============================================================================
17
+ import "../_dnt.polyfills.js";
17
18
  import * as dntShim from "../_dnt.shims.js";
18
19
  import { join } from "../deps/jsr.io/@std/path/1.1.4/mod.js";
19
20
  import { runCommand } from "../lib/command.js";
21
+ import { Result } from "../lib/result.js";
20
22
  // =============================================================================
21
23
  // Internal Helpers
22
24
  // =============================================================================
23
25
  /** Shorthand for returning a typed fetch error. */
24
- const fail = (kind, message) => ({
25
- ok: false,
26
- kind,
27
- message,
28
- });
26
+ const fail = (message) => Result.err(message);
29
27
  /** Format a template reference for display. */
30
28
  const formatRef = (ref) => {
31
29
  const base = ref.path === "."
@@ -96,9 +94,9 @@ export const fetchTemplate = async (ref) => {
96
94
  }
97
95
  catch { /* ignore */ }
98
96
  };
99
- const cleanFail = (kind, message) => {
97
+ const cleanFail = (message) => {
100
98
  cleanup();
101
- return fail(kind, message);
99
+ return fail(message);
102
100
  };
103
101
  try {
104
102
  const url = ref.ref
@@ -113,13 +111,13 @@ export const fetchTemplate = async (ref) => {
113
111
  if (!response.ok) {
114
112
  const repo = formatRepo(ref);
115
113
  if (response.status === 404) {
116
- return cleanFail("NotFound", `Template Repository Not Found: ${repo}. ` +
114
+ return cleanFail(`Template Repository Not Found: ${repo}. ` +
117
115
  "Check that the repository exists and is public.");
118
116
  }
119
117
  if (response.status === 403) {
120
- return cleanFail("RateLimited", "GitHub API Rate Limit Exceeded. Try again later.");
118
+ return cleanFail("GitHub API Rate Limit Exceeded. Try again later.");
121
119
  }
122
- return cleanFail("HttpError", `GitHub API Returned HTTP ${response.status} for ${repo}`);
120
+ return cleanFail(`GitHub API Returned HTTP ${response.status} for ${repo}`);
123
121
  }
124
122
  // Write tarball to disk
125
123
  const tarballPath = join(tempDir, "template.tar.gz");
@@ -135,31 +133,31 @@ export const fetchTemplate = async (ref) => {
135
133
  "-C",
136
134
  extractDir,
137
135
  ]);
138
- if (!extractResult.success) {
139
- return cleanFail("ExtractionFailed", "Failed to Extract Template Archive");
136
+ if (!extractResult.ok) {
137
+ return cleanFail("Failed to Extract Template Archive");
140
138
  }
141
139
  // GitHub tarballs have a single top-level dir (owner-repo-commitish/)
142
140
  const entries = [...dntShim.Deno.readDirSync(extractDir)];
143
141
  const topLevel = entries.find((e) => e.isDirectory);
144
142
  if (!topLevel) {
145
- return cleanFail("EmptyArchive", "Template Archive Has No Top-Level Directory");
143
+ return cleanFail("Template Archive Has No Top-Level Directory");
146
144
  }
147
145
  // Locate the template subdirectory
148
146
  const templateDir = join(extractDir, topLevel.name, ref.path);
149
147
  try {
150
148
  const stat = dntShim.Deno.statSync(templateDir);
151
149
  if (!stat.isDirectory) {
152
- return cleanFail("PathNotDirectory", `Template Path '${ref.path}' Is Not a Directory in ${formatRepo(ref)}`);
150
+ return cleanFail(`Template Path '${ref.path}' Is Not a Directory in ${formatRepo(ref)}`);
153
151
  }
154
152
  }
155
153
  catch {
156
- return cleanFail("PathNotFound", `Template Path '${ref.path}' Not Found in ${formatRepo(ref)}`);
154
+ return cleanFail(`Template Path '${ref.path}' Not Found in ${formatRepo(ref)}`);
157
155
  }
158
- return { ok: true, tempDir, templateDir };
156
+ return Result.ok({ tempDir, templateDir });
159
157
  }
160
158
  catch (e) {
161
159
  if (e instanceof TypeError) {
162
- return cleanFail("NetworkError", "Network Error: Could Not Reach GitHub");
160
+ return cleanFail("Network Error: Could Not Reach GitHub");
163
161
  }
164
162
  cleanup();
165
163
  throw e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
6
  "module": "./esm/src/providers/fly.js",
@@ -29,6 +29,9 @@
29
29
  "./lib/output": {
30
30
  "import": "./esm/lib/output.js"
31
31
  },
32
+ "./lib/result": {
33
+ "import": "./esm/lib/result.js"
34
+ },
32
35
  "./lib/paths": {
33
36
  "import": "./esm/lib/paths.js"
34
37
  },
@@ -43,6 +46,9 @@
43
46
  },
44
47
  "./src/resolve": {
45
48
  "import": "./esm/src/resolve.js"
49
+ },
50
+ "./src/template": {
51
+ "import": "./esm/src/template.js"
46
52
  }
47
53
  },
48
54
  "scripts": {},