Skip to content

Commit 992d0b4

Browse files
committed
feat(admin): auto-link API to node via Fly.io secrets after provisioning
1 parent abd7c4b commit 992d0b4

2 files changed

Lines changed: 78 additions & 1 deletion

File tree

apps/admin/src/app/api/provision/[serverId]/retry/route.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { decrypt } from '@/lib/encryption'
66
import { createSshConnection, execCommand } from '@/lib/ssh'
77
import { PROVISION_STEPS, resolveCommands, type StepResult, type ProvisionContext } from '@/lib/provisioner'
88
import { generateId, idToBytes, NODE_PREFIX } from '@sandchest/contract'
9+
import { setFlySecrets } from '@/lib/fly'
910

1011
export async function POST(
1112
_request: Request,
@@ -210,6 +211,44 @@ async function retryProvisioning(
210211
.where(eq(adminServers.id, serverIdBuf))
211212
}
212213

214+
// Link the API to the node via Fly.io secrets
215+
const flyToken = process.env.FLY_ACCESS_TOKEN
216+
const flyApp = process.env.FLY_APP_NAME || 'sandchest-api'
217+
218+
if (flyToken) {
219+
try {
220+
const [caResult, certResult, keyResult] = await Promise.all([
221+
execCommand(conn, 'cat /etc/sandchest/certs/ca.pem'),
222+
execCommand(conn, 'cat /etc/sandchest/certs/client.pem'),
223+
execCommand(conn, 'cat /etc/sandchest/certs/client.key'),
224+
])
225+
226+
if (caResult.code !== 0 || certResult.code !== 0 || keyResult.code !== 0) {
227+
throw new Error('Failed to read mTLS client certificates from server')
228+
}
229+
230+
await setFlySecrets(flyApp, {
231+
NODE_GRPC_ADDR: `${ip}:50051`,
232+
NODE_GRPC_NODE_ID: ctx.nodeId,
233+
MTLS_CA_PEM: caResult.stdout.trim(),
234+
MTLS_CLIENT_CERT_PEM: certResult.stdout.trim(),
235+
MTLS_CLIENT_KEY_PEM: keyResult.stdout.trim(),
236+
}, flyToken)
237+
} catch (err) {
238+
conn.end()
239+
await db
240+
.update(adminServers)
241+
.set({
242+
provisionStatus: 'failed',
243+
provisionError: `Provisioning succeeded but API linking failed: ${err instanceof Error ? err.message : String(err)}`,
244+
provisionSteps: [...stepResults],
245+
updatedAt: new Date(),
246+
})
247+
.where(eq(adminServers.id, serverIdBuf))
248+
return
249+
}
250+
}
251+
213252
conn.end()
214253
const nodeIdBytes = idToBytes(ctx.nodeId) as unknown as Uint8Array
215254
await db

apps/admin/src/app/api/provision/[serverId]/start/route.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { decrypt } from '@/lib/encryption'
66
import { createSshConnection, execCommand } from '@/lib/ssh'
77
import { PROVISION_STEPS, resolveCommands, type StepResult, type ProvisionContext } from '@/lib/provisioner'
88
import { generateId, idToBytes, NODE_PREFIX } from '@sandchest/contract'
9+
import { setFlySecrets } from '@/lib/fly'
910

1011
export async function POST(
1112
_request: Request,
@@ -209,7 +210,44 @@ async function runProvisioning(
209210
.where(eq(adminServers.id, serverIdBuf))
210211
}
211212

212-
// All steps completed — link the node ID to the server record
213+
// All steps completed — link the API to the node via Fly.io secrets
214+
const flyToken = process.env.FLY_ACCESS_TOKEN
215+
const flyApp = process.env.FLY_APP_NAME || 'sandchest-api'
216+
217+
if (flyToken) {
218+
try {
219+
const [caResult, certResult, keyResult] = await Promise.all([
220+
execCommand(conn, 'cat /etc/sandchest/certs/ca.pem'),
221+
execCommand(conn, 'cat /etc/sandchest/certs/client.pem'),
222+
execCommand(conn, 'cat /etc/sandchest/certs/client.key'),
223+
])
224+
225+
if (caResult.code !== 0 || certResult.code !== 0 || keyResult.code !== 0) {
226+
throw new Error('Failed to read mTLS client certificates from server')
227+
}
228+
229+
await setFlySecrets(flyApp, {
230+
NODE_GRPC_ADDR: `${ip}:50051`,
231+
NODE_GRPC_NODE_ID: ctx.nodeId,
232+
MTLS_CA_PEM: caResult.stdout.trim(),
233+
MTLS_CLIENT_CERT_PEM: certResult.stdout.trim(),
234+
MTLS_CLIENT_KEY_PEM: keyResult.stdout.trim(),
235+
}, flyToken)
236+
} catch (err) {
237+
conn.end()
238+
await db
239+
.update(adminServers)
240+
.set({
241+
provisionStatus: 'failed',
242+
provisionError: `Provisioning succeeded but API linking failed: ${err instanceof Error ? err.message : String(err)}`,
243+
provisionSteps: [...stepResults],
244+
updatedAt: new Date(),
245+
})
246+
.where(eq(adminServers.id, serverIdBuf))
247+
return
248+
}
249+
}
250+
213251
conn.end()
214252
const nodeIdBytes = idToBytes(ctx.nodeId) as unknown as Uint8Array
215253
await db

0 commit comments

Comments
 (0)