@cardelli/ambit 0.1.5 → 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 (139) 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/lib/args.d.ts +11 -0
  37. package/esm/lib/args.d.ts.map +1 -0
  38. package/esm/lib/args.js +28 -0
  39. package/esm/lib/cli.d.ts +0 -1
  40. package/esm/lib/cli.d.ts.map +1 -1
  41. package/esm/lib/cli.js +0 -1
  42. package/esm/lib/command.d.ts +0 -3
  43. package/esm/lib/command.d.ts.map +1 -1
  44. package/esm/lib/command.js +2 -13
  45. package/esm/lib/machine.d.ts +11 -0
  46. package/esm/lib/machine.d.ts.map +1 -0
  47. package/esm/lib/machine.js +15 -0
  48. package/esm/lib/output.d.ts +2 -1
  49. package/esm/lib/output.d.ts.map +1 -1
  50. package/esm/lib/output.js +21 -3
  51. package/esm/lib/result.d.ts +0 -1
  52. package/esm/lib/result.d.ts.map +1 -1
  53. package/esm/lib/result.js +0 -1
  54. package/esm/main.d.ts +6 -6
  55. package/esm/main.d.ts.map +1 -1
  56. package/esm/main.js +7 -9
  57. package/esm/providers/fly.d.ts +81 -0
  58. package/esm/providers/fly.d.ts.map +1 -0
  59. package/esm/providers/fly.js +372 -0
  60. package/esm/providers/tailscale.d.ts +31 -0
  61. package/esm/providers/tailscale.d.ts.map +1 -0
  62. package/esm/providers/tailscale.js +150 -0
  63. package/esm/{src/schemas → schemas}/fly.d.ts +1 -11
  64. package/esm/schemas/fly.d.ts.map +1 -0
  65. package/esm/{src/schemas → schemas}/fly.js +14 -56
  66. package/esm/{src/schemas → schemas}/tailscale.d.ts +1 -2
  67. package/esm/schemas/tailscale.d.ts.map +1 -0
  68. package/esm/{src/schemas → schemas}/tailscale.js +2 -3
  69. package/esm/src/{docker/router → router}/Dockerfile +0 -11
  70. package/esm/src/{docker/router → router}/start.sh +18 -9
  71. package/esm/util/constants.d.ts +13 -0
  72. package/esm/util/constants.d.ts.map +1 -0
  73. package/esm/util/constants.js +34 -0
  74. package/esm/{src → util}/credentials.d.ts +0 -1
  75. package/esm/util/credentials.d.ts.map +1 -0
  76. package/esm/{src → util}/credentials.js +3 -5
  77. package/esm/{src → util}/discovery.d.ts +16 -3
  78. package/esm/util/discovery.d.ts.map +1 -0
  79. package/esm/{src → util}/discovery.js +24 -15
  80. package/esm/util/fly-transforms.d.ts +27 -0
  81. package/esm/util/fly-transforms.d.ts.map +1 -0
  82. package/esm/util/fly-transforms.js +87 -0
  83. package/esm/{src → util}/guard.d.ts +1 -2
  84. package/esm/util/guard.d.ts.map +1 -0
  85. package/esm/{src → util}/guard.js +27 -27
  86. package/esm/util/naming.d.ts +5 -0
  87. package/esm/util/naming.d.ts.map +1 -0
  88. package/esm/util/naming.js +12 -0
  89. package/esm/{src → util}/resolve.d.ts +2 -3
  90. package/esm/util/resolve.d.ts.map +1 -0
  91. package/esm/{src → util}/resolve.js +1 -2
  92. package/esm/util/session.d.ts +16 -0
  93. package/esm/util/session.d.ts.map +1 -0
  94. package/esm/util/session.js +19 -0
  95. package/esm/util/tailscale-local.d.ts +13 -0
  96. package/esm/util/tailscale-local.d.ts.map +1 -0
  97. package/esm/util/tailscale-local.js +63 -0
  98. package/esm/{src → util}/template.d.ts +0 -1
  99. package/esm/util/template.d.ts.map +1 -0
  100. package/esm/{src → util}/template.js +0 -1
  101. package/package.json +1 -49
  102. package/esm/lib/paths.d.ts +0 -3
  103. package/esm/lib/paths.d.ts.map +0 -1
  104. package/esm/lib/paths.js +0 -5
  105. package/esm/src/cli/commands/create.d.ts +0 -2
  106. package/esm/src/cli/commands/create.d.ts.map +0 -1
  107. package/esm/src/cli/commands/create.js +0 -308
  108. package/esm/src/cli/commands/deploy.d.ts +0 -2
  109. package/esm/src/cli/commands/deploy.d.ts.map +0 -1
  110. package/esm/src/cli/commands/deploy.js +0 -430
  111. package/esm/src/cli/commands/destroy.d.ts +0 -2
  112. package/esm/src/cli/commands/destroy.d.ts.map +0 -1
  113. package/esm/src/cli/commands/destroy.js +0 -340
  114. package/esm/src/cli/commands/doctor.d.ts.map +0 -1
  115. package/esm/src/cli/commands/doctor.js +0 -141
  116. package/esm/src/cli/commands/status.d.ts.map +0 -1
  117. package/esm/src/cli/commands/status.js +0 -152
  118. package/esm/src/cli/mod.d.ts.map +0 -1
  119. package/esm/src/credentials.d.ts.map +0 -1
  120. package/esm/src/discovery.d.ts.map +0 -1
  121. package/esm/src/guard.d.ts.map +0 -1
  122. package/esm/src/providers/fly.d.ts +0 -76
  123. package/esm/src/providers/fly.d.ts.map +0 -1
  124. package/esm/src/providers/fly.js +0 -407
  125. package/esm/src/providers/tailscale.d.ts +0 -31
  126. package/esm/src/providers/tailscale.d.ts.map +0 -1
  127. package/esm/src/providers/tailscale.js +0 -189
  128. package/esm/src/resolve.d.ts.map +0 -1
  129. package/esm/src/schemas/config.d.ts +0 -5
  130. package/esm/src/schemas/config.d.ts.map +0 -1
  131. package/esm/src/schemas/config.js +0 -22
  132. package/esm/src/schemas/fly.d.ts.map +0 -1
  133. package/esm/src/schemas/tailscale.d.ts.map +0 -1
  134. package/esm/src/template.d.ts.map +0 -1
  135. /package/esm/{src/cli → cli}/commands/doctor.d.ts +0 -0
  136. /package/esm/{src/cli → cli}/commands/list.d.ts +0 -0
  137. /package/esm/{src/cli → cli}/commands/status.d.ts +0 -0
  138. /package/esm/{src/cli → cli}/mod.d.ts +0 -0
  139. /package/esm/src/{docker/router → router}/fly.toml +0 -0
@@ -0,0 +1,150 @@
1
+ // =============================================================================
2
+ // Tailscale API Client
3
+ // =============================================================================
4
+ import { die } from "../lib/cli.js";
5
+ import { createAuthKeyPayload, TailscaleDevicesListSchema, } from "../schemas/tailscale.js";
6
+ // =============================================================================
7
+ // Constants
8
+ // =============================================================================
9
+ const API_BASE = "https://api.tailscale.com/api/v2";
10
+ // =============================================================================
11
+ // Create Tailscale Provider
12
+ // =============================================================================
13
+ export const createTailscaleProvider = (apiKey, tailnet = "-") => {
14
+ const headers = () => ({
15
+ "Content-Type": "application/json",
16
+ Accept: "application/json",
17
+ Authorization: `Basic ${btoa(apiKey + ":")}`,
18
+ });
19
+ const request = async (method, path, body) => {
20
+ try {
21
+ const response = await fetch(`${API_BASE}${path}`, {
22
+ method,
23
+ headers: headers(),
24
+ body: body ? JSON.stringify(body) : undefined,
25
+ });
26
+ if (!response.ok) {
27
+ const text = await response.text();
28
+ return {
29
+ ok: false,
30
+ status: response.status,
31
+ error: text || `HTTP ${response.status}`,
32
+ };
33
+ }
34
+ const text = await response.text();
35
+ if (!text) {
36
+ return { ok: true, status: response.status };
37
+ }
38
+ return { ok: true, status: response.status, data: JSON.parse(text) };
39
+ }
40
+ catch (error) {
41
+ return {
42
+ ok: false,
43
+ status: 0,
44
+ error: error instanceof Error ? error.message : String(error),
45
+ };
46
+ }
47
+ };
48
+ const provider = {
49
+ auth: {
50
+ async validateKey() {
51
+ const result = await request("GET", `/tailnet/${tailnet}/devices`);
52
+ return result.ok;
53
+ },
54
+ async createKey(opts) {
55
+ const payload = createAuthKeyPayload(opts ?? {});
56
+ const result = await request("POST", `/tailnet/${tailnet}/keys`, payload);
57
+ if (!result.ok || !result.data?.key) {
58
+ return die(`Failed to Create Auth Key: ${result.error}`);
59
+ }
60
+ return result.data.key;
61
+ },
62
+ },
63
+ devices: {
64
+ async list() {
65
+ const result = await request("GET", `/tailnet/${tailnet}/devices`);
66
+ if (!result.ok) {
67
+ return die(`Failed to List Devices: ${result.error}`);
68
+ }
69
+ const parsed = TailscaleDevicesListSchema.safeParse(result.data);
70
+ return parsed.success ? parsed.data.devices : [];
71
+ },
72
+ async getByHostname(hostname) {
73
+ const devices = await provider.devices.list();
74
+ const exact = devices.find((d) => d.hostname === hostname);
75
+ if (exact)
76
+ return exact;
77
+ // Fallback: find by prefix if Tailscale added suffix (e.g., hostname-1)
78
+ // Prefer online devices, then most recently seen
79
+ const prefixMatches = devices
80
+ .filter((d) => d.hostname.startsWith(hostname + "-"))
81
+ .sort((a, b) => {
82
+ if (a.online && !b.online)
83
+ return -1;
84
+ if (!a.online && b.online)
85
+ return 1;
86
+ const aTime = a.lastSeen ? new Date(a.lastSeen).getTime() : 0;
87
+ const bTime = b.lastSeen ? new Date(b.lastSeen).getTime() : 0;
88
+ return bTime - aTime;
89
+ });
90
+ return prefixMatches[0] ?? null;
91
+ },
92
+ async delete(id) {
93
+ const result = await request("DELETE", `/device/${id}`);
94
+ if (!result.ok) {
95
+ return die(`Failed to Delete Device: ${result.error}`);
96
+ }
97
+ },
98
+ },
99
+ routes: {
100
+ async get(deviceId) {
101
+ const result = await request("GET", `/device/${deviceId}/routes`);
102
+ if (!result.ok || !result.data)
103
+ return null;
104
+ const advertised = result.data.advertisedRoutes ?? [];
105
+ const enabled = result.data.enabledRoutes ?? [];
106
+ const enabledSet = new Set(enabled);
107
+ return {
108
+ advertised,
109
+ enabled,
110
+ unapproved: advertised.filter((r) => !enabledSet.has(r)),
111
+ };
112
+ },
113
+ async approve(deviceId, routes) {
114
+ const result = await request("POST", `/device/${deviceId}/routes`, { routes });
115
+ if (!result.ok) {
116
+ return die(`Failed to Approve Routes: ${result.error}`);
117
+ }
118
+ },
119
+ },
120
+ dns: {
121
+ async getSplit() {
122
+ const result = await request("GET", `/tailnet/${tailnet}/dns/split-dns`);
123
+ return result.ok && result.data ? result.data : {};
124
+ },
125
+ async setSplit(domain, nameservers) {
126
+ // PATCH performs partial update - only specified domains are modified
127
+ const result = await request("PATCH", `/tailnet/${tailnet}/dns/split-dns`, { [domain]: nameservers });
128
+ if (!result.ok) {
129
+ return die(`Failed to Configure Split DNS: ${result.error}`);
130
+ }
131
+ },
132
+ async clearSplit(domain) {
133
+ const result = await request("PATCH", `/tailnet/${tailnet}/dns/split-dns`, { [domain]: null });
134
+ if (!result.ok) {
135
+ return die(`Failed to Clear Split DNS: ${result.error}`);
136
+ }
137
+ },
138
+ },
139
+ acl: {
140
+ async getPolicy() {
141
+ const result = await request("GET", `/tailnet/${tailnet}/acl`);
142
+ if (!result.ok || !result.data) {
143
+ return null;
144
+ }
145
+ return result.data;
146
+ },
147
+ },
148
+ };
149
+ return provider;
150
+ };
@@ -1,5 +1,4 @@
1
- import "../../_dnt.polyfills.js";
2
- import { z } from "../../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
1
+ import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
3
2
  export declare const FlyAuthSchema: z.ZodObject<{
4
3
  email: z.ZodString;
5
4
  }, z.core.$loose>;
@@ -106,15 +105,6 @@ export declare const FlyDeploySchema: z.ZodObject<{
106
105
  ID: z.ZodOptional<z.ZodString>;
107
106
  Status: z.ZodOptional<z.ZodString>;
108
107
  }, z.core.$loose>;
109
- /**
110
- * Map Fly machine state to internal state.
111
- * Fly states: created, starting, started, stopping, stopped, destroying, destroyed
112
- */
113
- export declare const mapFlyMachineState: (flyState: string) => "creating" | "running" | "frozen" | "failed";
114
- /**
115
- * Map Fly guest config to machine size enum.
116
- */
117
- export declare const mapFlyMachineSize: (guest?: z.infer<typeof FlyMachineGuestSchema>) => "shared-cpu-1x" | "shared-cpu-2x" | "shared-cpu-4x";
118
108
  export type FlyOrgs = z.infer<typeof FlyOrgsSchema>;
119
109
  export declare const FlyIpNetworkSchema: z.ZodObject<{
120
110
  Name: z.ZodString;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/schemas/fly.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,4CAA4C,CAAC;AAM/D,eAAO,MAAM,aAAa;;iBAEhB,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAMpD,eAAO,MAAM,YAAY;;;;;;iBAMf,CAAC;AAEX,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,eAAO,MAAM,iBAAiB;;;;;;kBAAwB,CAAC;AAMvD,eAAO,MAAM,eAAe;;;;;iBAKlB,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAMxD,eAAO,MAAM,qBAAqB;;;;iBAIxB,CAAC;AAEX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;iBAgBzB,CAAC;AAEX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;iBASnB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;kBAA4B,CAAC;AAM/D,eAAO,MAAM,aAAa,uCAAmC,CAAC;AAM9D,eAAO,MAAM,eAAe;;;iBAGlB,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAMpD,eAAO,MAAM,kBAAkB;;;;;iBAGrB,CAAC;AAEX,eAAO,MAAM,WAAW;;;;;;;;;;;;iBAOd,CAAC;AAEX,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,eAAO,MAAM,eAAe;;;;;;;;;;;;kBAAuB,CAAC;AAMpD,eAAO,MAAM,gBAAgB;;;;;;;iBAKnB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,oBAAoB;;;;;;;;;;iBAGvB,CAAC"}
@@ -1,14 +1,13 @@
1
1
  // =============================================================================
2
2
  // Fly.io CLI Response Schemas
3
3
  // =============================================================================
4
- import "../../_dnt.polyfills.js";
5
- import { z } from "../../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
4
+ import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
6
5
  // =============================================================================
7
6
  // Auth Response
8
7
  // =============================================================================
9
8
  export const FlyAuthSchema = z.object({
10
9
  email: z.string(),
11
- }).passthrough();
10
+ }).loose();
12
11
  // =============================================================================
13
12
  // App Schemas
14
13
  // =============================================================================
@@ -18,7 +17,7 @@ export const FlyAppSchema = z.object({
18
17
  Organization: z.object({
19
18
  Slug: z.string(),
20
19
  }).optional(),
21
- }).passthrough();
20
+ }).loose();
22
21
  export const FlyAppsListSchema = z.array(FlyAppSchema);
23
22
  // =============================================================================
24
23
  // App Status
@@ -28,7 +27,7 @@ export const FlyStatusSchema = z.object({
28
27
  Name: z.string().optional(),
29
28
  Hostname: z.string().optional(),
30
29
  Deployed: z.boolean().optional(),
31
- }).passthrough();
30
+ }).loose();
32
31
  // =============================================================================
33
32
  // Machine Schemas
34
33
  // =============================================================================
@@ -36,7 +35,7 @@ export const FlyMachineGuestSchema = z.object({
36
35
  cpu_kind: z.string(),
37
36
  cpus: z.number(),
38
37
  memory_mb: z.number(),
39
- }).passthrough();
38
+ }).loose();
40
39
  export const FlyMachineConfigSchema = z.object({
41
40
  guest: FlyMachineGuestSchema.optional(),
42
41
  metadata: z.record(z.string(), z.string()).optional(),
@@ -45,11 +44,11 @@ export const FlyMachineConfigSchema = z.object({
45
44
  ports: z.array(z.object({
46
45
  port: z.number(),
47
46
  handlers: z.array(z.string()).optional(),
48
- }).passthrough()).optional(),
47
+ }).loose()).optional(),
49
48
  protocol: z.string().optional(),
50
49
  internal_port: z.number().optional(),
51
- }).passthrough()).optional(),
52
- }).passthrough();
50
+ }).loose()).optional(),
51
+ }).loose();
53
52
  export const FlyMachineSchema = z.object({
54
53
  id: z.string(),
55
54
  name: z.string(),
@@ -59,7 +58,7 @@ export const FlyMachineSchema = z.object({
59
58
  config: FlyMachineConfigSchema.optional(),
60
59
  created_at: z.string().optional(),
61
60
  updated_at: z.string().optional(),
62
- }).passthrough();
61
+ }).loose();
63
62
  export const FlyMachinesListSchema = z.array(FlyMachineSchema);
64
63
  // =============================================================================
65
64
  // Organization Schemas
@@ -71,55 +70,14 @@ export const FlyOrgsSchema = z.record(z.string(), z.string());
71
70
  export const FlyDeploySchema = z.object({
72
71
  ID: z.string().optional(),
73
72
  Status: z.string().optional(),
74
- }).passthrough();
75
- // =============================================================================
76
- // Machine State Mapping
77
- // =============================================================================
78
- /**
79
- * Map Fly machine state to internal state.
80
- * Fly states: created, starting, started, stopping, stopped, destroying, destroyed
81
- */
82
- export const mapFlyMachineState = (flyState) => {
83
- switch (flyState.toLowerCase()) {
84
- case "started":
85
- return "running";
86
- case "stopped":
87
- case "suspended":
88
- return "frozen";
89
- case "created":
90
- case "starting":
91
- return "creating";
92
- case "destroying":
93
- case "destroyed":
94
- case "failed":
95
- return "failed";
96
- default:
97
- return "creating";
98
- }
99
- };
100
- // =============================================================================
101
- // Machine Size Mapping
102
- // =============================================================================
103
- /**
104
- * Map Fly guest config to machine size enum.
105
- */
106
- export const mapFlyMachineSize = (guest) => {
107
- if (!guest)
108
- return "shared-cpu-1x";
109
- const cpus = guest.cpus;
110
- if (cpus >= 4)
111
- return "shared-cpu-4x";
112
- if (cpus >= 2)
113
- return "shared-cpu-2x";
114
- return "shared-cpu-1x";
115
- };
73
+ }).loose();
116
74
  // =============================================================================
117
75
  // IP Schemas
118
76
  // =============================================================================
119
77
  export const FlyIpNetworkSchema = z.object({
120
78
  Name: z.string(),
121
79
  Organization: z.object({ Slug: z.string() }).optional(),
122
- }).passthrough();
80
+ }).loose();
123
81
  export const FlyIpSchema = z.object({
124
82
  ID: z.string().optional(),
125
83
  Address: z.string(),
@@ -127,7 +85,7 @@ export const FlyIpSchema = z.object({
127
85
  Region: z.string().optional(),
128
86
  CreatedAt: z.string().optional(),
129
87
  Network: FlyIpNetworkSchema.optional(),
130
- }).passthrough();
88
+ }).loose();
131
89
  export const FlyIpListSchema = z.array(FlyIpSchema);
132
90
  // =============================================================================
133
91
  // REST API Schemas (Machines API - api.machines.dev)
@@ -137,8 +95,8 @@ export const FlyAppInfoSchema = z.object({
137
95
  network: z.string(),
138
96
  status: z.string(),
139
97
  organization: z.object({ slug: z.string() }).optional(),
140
- }).passthrough();
98
+ }).loose();
141
99
  export const FlyAppInfoListSchema = z.object({
142
100
  total_apps: z.number(),
143
101
  apps: z.array(FlyAppInfoSchema),
144
- }).passthrough();
102
+ }).loose();
@@ -1,5 +1,4 @@
1
- import "../../_dnt.polyfills.js";
2
- import { z } from "../../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
1
+ import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
3
2
  export declare const TailscaleAuthKeySchema: z.ZodObject<{
4
3
  key: z.ZodString;
5
4
  id: z.ZodOptional<z.ZodString>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../src/schemas/tailscale.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,4CAA4C,CAAC;AAM/D,eAAO,MAAM,sBAAsB;;;;;iBAKjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAMtE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;iBAcxB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;iBAErC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAM9E,eAAO,MAAM,qBAAqB;;;iBAGhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE,eAAO,MAAM,6BAA6B;;iBAExC,CAAC;AAEH,eAAO,MAAM,uBAAuB,mDAGnC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAMxE,eAAO,MAAM,oBAAoB;;iBAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMlE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,mBAAmB,KAAG,MA+BhE,CAAC"}
@@ -1,8 +1,7 @@
1
1
  // =============================================================================
2
2
  // Tailscale API Response Schemas
3
3
  // =============================================================================
4
- import "../../_dnt.polyfills.js";
5
- import { z } from "../../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
4
+ import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
6
5
  // =============================================================================
7
6
  // Auth Key Schemas
8
7
  // =============================================================================
@@ -29,7 +28,7 @@ export const TailscaleDeviceSchema = z.object({
29
28
  tags: z.array(z.string()).optional(),
30
29
  advertisedRoutes: z.array(z.string()).optional(),
31
30
  enabledRoutes: z.array(z.string()).optional(),
32
- }).passthrough();
31
+ }).loose();
33
32
  export const TailscaleDevicesListSchema = z.object({
34
33
  devices: z.array(TailscaleDeviceSchema),
35
34
  });
@@ -6,7 +6,6 @@ FROM alpine:3.23.3
6
6
 
7
7
  ARG TAILSCALE_VERSION=1.94.1
8
8
  ARG COREDNS_VERSION=1.14.1
9
- ARG MICROSOCKS_VERSION=1.0.5
10
9
 
11
10
  RUN apk add --no-cache \
12
11
  bash \
@@ -25,16 +24,6 @@ RUN cd /tmp \
25
24
  && mv coredns /usr/local/bin/coredns \
26
25
  && chmod +x /usr/local/bin/coredns
27
26
 
28
- RUN apk add --no-cache gcc musl-dev make \
29
- && cd /tmp \
30
- && wget -q "https://github.com/rofl0r/microsocks/archive/refs/tags/v${MICROSOCKS_VERSION}.tar.gz" \
31
- && tar xzf "v${MICROSOCKS_VERSION}.tar.gz" \
32
- && cd "microsocks-${MICROSOCKS_VERSION}" \
33
- && make \
34
- && cp microsocks /usr/local/bin/ \
35
- && cd / && rm -rf /tmp/microsocks* /tmp/v${MICROSOCKS_VERSION}.tar.gz \
36
- && apk del gcc musl-dev make
37
-
38
27
  RUN mkdir -p /var/lib/tailscale /var/run/tailscale /etc/coredns
39
28
 
40
29
  COPY start.sh /start.sh
@@ -16,10 +16,16 @@ echo 'net.ipv4.ip_forward = 1' | tee -a /etc/sysctl.conf
16
16
  echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.conf
17
17
  sysctl -p /etc/sysctl.conf
18
18
 
19
- echo "Router: Starting Tailscaled"
19
+ PRIVATE_IP=$(grep fly-local-6pn /etc/hosts | awk '{print $1}')
20
+
21
+ # tailscaled's built-in SOCKS5 proxy routes through the WireGuard tunnel
22
+ # directly (no tun device needed). This handles both TCP routing and DNS
23
+ # resolution through the tailnet — MagicDNS, split DNS, everything.
24
+ echo "Router: Starting Tailscaled with SOCKS5 Proxy on [${PRIVATE_IP}]:1080"
20
25
  /usr/local/bin/tailscaled \
21
26
  --state=/var/lib/tailscale/tailscaled.state \
22
- --socket=/var/run/tailscale/tailscaled.sock &
27
+ --socket=/var/run/tailscale/tailscaled.sock \
28
+ --socks5-server=[${PRIVATE_IP}]:1080 &
23
29
 
24
30
  # Wait for tailscaled to be ready
25
31
  sleep 3
@@ -50,25 +56,26 @@ fi
50
56
 
51
57
  echo "Router: Fully Configured"
52
58
 
53
- # Start SOCKS5 proxy for bidirectional tailnet access
54
- PRIVATE_IP=$(grep fly-local-6pn /etc/hosts | awk '{print $1}')
55
- echo "Router: Starting SOCKS5 Proxy on [${PRIVATE_IP}]:1080"
56
- /usr/local/bin/microsocks -i "$PRIVATE_IP" -p 1080 &
59
+ # Get Tailscale IPv4 for CoreDNS bind — split DNS queries arrive here
60
+ TAILSCALE_IP=$(/usr/local/bin/tailscale ip -4)
61
+ echo "Router: Tailscale IP ${TAILSCALE_IP}"
57
62
 
58
63
  echo "Router: Starting DNS Proxy"
59
64
 
60
65
  # Generate Corefile for CoreDNS
66
+ # Binds to the Fly private IP and the Tailscale IP. Split DNS queries from
67
+ # tailnet clients arrive on the Tailscale IP. Does NOT bind to all interfaces,
68
+ # so tailscaled's MagicDNS resolver on 100.100.100.100:53 remains unblocked.
69
+ #
61
70
  # Rewrites NETWORK_NAME TLD to .flycast before forwarding to Fly DNS.
62
71
  # When ROUTER_ID is set, workload app names are suffixed: app.network ->
63
72
  # app-ROUTER_ID.flycast. This ties workloads to their router and avoids
64
73
  # name collisions across networks.
65
- # .flycast resolves to the Flycast address (private_v6) which routes through
66
- # Fly Proxy — enabling autostart/autostop and load balancing. The Flycast IP
67
- # is within the network's /48 subnet so it's routable through the tailnet.
68
74
  if [ -n "${NETWORK_NAME}" ] && [ -n "${ROUTER_ID}" ]; then
69
75
  echo "Router: DNS Rewrite *.${NETWORK_NAME} -> *-${ROUTER_ID}.flycast"
70
76
  cat > /etc/coredns/Corefile <<EOF
71
77
  .:53 {
78
+ bind ${PRIVATE_IP} ${TAILSCALE_IP}
72
79
  rewrite name regex (.+)\.${NETWORK_NAME}\. {1}-${ROUTER_ID}.flycast. answer auto
73
80
  forward . fdaa::3
74
81
  }
@@ -77,6 +84,7 @@ elif [ -n "${NETWORK_NAME}" ]; then
77
84
  echo "Router: DNS Rewrite ${NETWORK_NAME} -> flycast"
78
85
  cat > /etc/coredns/Corefile <<EOF
79
86
  .:53 {
87
+ bind ${PRIVATE_IP} ${TAILSCALE_IP}
80
88
  rewrite name suffix .${NETWORK_NAME}. .flycast. answer auto
81
89
  forward . fdaa::3
82
90
  }
@@ -84,6 +92,7 @@ EOF
84
92
  else
85
93
  cat > /etc/coredns/Corefile <<EOF
86
94
  .:53 {
95
+ bind ${PRIVATE_IP} ${TAILSCALE_IP}
87
96
  forward . fdaa::3
88
97
  }
89
98
  EOF
@@ -0,0 +1,13 @@
1
+ export declare const ROUTER_APP_PREFIX = "ambit-";
2
+ export declare const DEFAULT_FLY_NETWORK = "default";
3
+ export declare const ROUTER_DOCKER_DIR: string;
4
+ export declare const SOCKS_PROXY_PORT = 1080;
5
+ export declare const FLY_PRIVATE_SUBNET = "fdaa::/16";
6
+ export declare const TAILSCALE_API_KEY_PREFIX = "tskey-api-";
7
+ export declare const ENV_TAILSCALE_API_KEY = "TAILSCALE_API_KEY";
8
+ export declare const FLYCTL_INSTALL_URL = "https://fly.io/docs/flyctl/install/";
9
+ export declare const SECRET_TAILSCALE_AUTHKEY = "TAILSCALE_AUTHKEY";
10
+ export declare const SECRET_NETWORK_NAME = "NETWORK_NAME";
11
+ export declare const SECRET_ROUTER_ID = "ROUTER_ID";
12
+ export declare const SECRET_AMBIT_OUTBOUND_PROXY = "AMBIT_OUTBOUND_PROXY";
13
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,34 @@
1
+ // =============================================================================
2
+ // Constants - Shared Magic Values
3
+ // =============================================================================
4
+ // =============================================================================
5
+ // Router / App Identity
6
+ // =============================================================================
7
+ export const ROUTER_APP_PREFIX = "ambit-";
8
+ export const DEFAULT_FLY_NETWORK = "default";
9
+ export const ROUTER_DOCKER_DIR = new URL("../router", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url)
10
+ .pathname;
11
+ // =============================================================================
12
+ // Networking
13
+ // =============================================================================
14
+ export const SOCKS_PROXY_PORT = 1080;
15
+ export const FLY_PRIVATE_SUBNET = "fdaa::/16";
16
+ // =============================================================================
17
+ // Tailscale
18
+ // =============================================================================
19
+ export const TAILSCALE_API_KEY_PREFIX = "tskey-api-";
20
+ export const ENV_TAILSCALE_API_KEY = "TAILSCALE_API_KEY";
21
+ // =============================================================================
22
+ // External URLs
23
+ // =============================================================================
24
+ export const FLYCTL_INSTALL_URL = "https://fly.io/docs/flyctl/install/";
25
+ // =============================================================================
26
+ // Fly.io Secret Names (set on router machines)
27
+ // =============================================================================
28
+ export const SECRET_TAILSCALE_AUTHKEY = "TAILSCALE_AUTHKEY";
29
+ export const SECRET_NETWORK_NAME = "NETWORK_NAME";
30
+ export const SECRET_ROUTER_ID = "ROUTER_ID";
31
+ // =============================================================================
32
+ // Fly.io Secret Names (set on workload machines)
33
+ // =============================================================================
34
+ export const SECRET_AMBIT_OUTBOUND_PROXY = "AMBIT_OUTBOUND_PROXY";
@@ -1,4 +1,3 @@
1
- import "../_dnt.polyfills.js";
2
1
  export interface CredentialStore {
3
2
  getTailscaleApiKey(): Promise<string | null>;
4
3
  setTailscaleApiKey(key: string): Promise<void>;
@@ -0,0 +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,11 +1,10 @@
1
1
  // =============================================================================
2
2
  // Credential Store - Persistent Tailscale API Key Storage
3
3
  // =============================================================================
4
- import "../_dnt.polyfills.js";
5
4
  import * as dntShim from "../_dnt.shims.js";
6
5
  import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
7
- import { commandExists, ensureConfigDir, fileExists } from "../lib/cli.js";
8
- import { getConfigDir } from "./schemas/config.js";
6
+ import { commandExists, ensureConfigDir, fileExists, getConfigDir } from "../lib/cli.js";
7
+ import { ENV_TAILSCALE_API_KEY } from "./constants.js";
9
8
  // =============================================================================
10
9
  // Schema
11
10
  // =============================================================================
@@ -46,8 +45,7 @@ export const getCredentialStore = () => {
46
45
  const fileStore = createConfigCredentialStore();
47
46
  return {
48
47
  async getTailscaleApiKey() {
49
- // Environment variable takes priority
50
- const envKey = dntShim.Deno.env.get("TAILSCALE_API_KEY");
48
+ const envKey = dntShim.Deno.env.get(ENV_TAILSCALE_API_KEY);
51
49
  if (envKey)
52
50
  return envKey;
53
51
  return await fileStore.getTailscaleApiKey();
@@ -1,6 +1,5 @@
1
- import "../_dnt.polyfills.js";
2
- import type { FlyProvider } from "./providers/fly.js";
3
- import type { TailscaleProvider } from "./providers/tailscale.js";
1
+ import type { FlyProvider } from "../providers/fly.js";
2
+ import type { TailscaleProvider } from "../providers/tailscale.js";
4
3
  /** A router app discovered from the Fly REST API. */
5
4
  export interface RouterApp {
6
5
  appName: string;
@@ -42,4 +41,18 @@ export declare const findWorkloadApp: (fly: FlyProvider, org: string, appName: s
42
41
  export declare const getRouterMachineInfo: (fly: FlyProvider, appName: string) => Promise<RouterMachineInfo | null>;
43
42
  /** Get tailscale device info for a router. Returns null if not found. */
44
43
  export declare const getRouterTailscaleInfo: (tailscale: TailscaleProvider, appName: string) => Promise<RouterTailscaleInfo | null>;
44
+ /** A router app with its machine and Tailscale state hydrated. */
45
+ export type RouterWithInfo = RouterApp & {
46
+ machine: RouterMachineInfo | null;
47
+ tailscale: RouterTailscaleInfo | null;
48
+ };
49
+ /**
50
+ * Discover all routers in an org and hydrate each with machine + Tailscale state.
51
+ * Shows a spinner while fetching. Fetches all routers in parallel.
52
+ */
53
+ export declare const discoverRouters: (out: {
54
+ spinner(msg: string): {
55
+ success(msg: string): void;
56
+ };
57
+ }, fly: FlyProvider, tailscale: TailscaleProvider, org: string) => Promise<RouterWithInfo[]>;
45
58
  //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +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"}