Files
edubox/server/lib/headscale.ts
EduBox Dev a414f03a59 feat(agent): v0.3.5 Windows inbound forwarding, UI actions, lifecycle
- Configure tailscale serve automatically for each instance on Windows userspace networking.
- Add local UI buttons: start/stop/reset/delete instances (stop/start preserve volumes).
- Clean shutdown: stop tailscaled and instances, notify server with instance_stopped.
- Restart tailscaled on agent boot using persisted state when pre-auth key is absent.
- Sync instance stopped/deleted status to dashboard (server/lib/websocket.ts).
- Security: include prior authz/scoping changes across API routes, ephemeral pre-auth keys, ACL policy, internal API key.
- Update SUIVI_VPN_ONDEMAND.md and docs/ONBOARDING_CLIENT.md.
- Bump agent version to 0.3.5.
2026-06-25 22:59:09 +00:00

76 lines
1.7 KiB
TypeScript

interface HeadscaleUser {
id: string;
name: string;
}
interface HeadscalePreAuthKey {
key: string;
expiration: string;
aclTags: string[];
}
export async function getHeadscaleUserId(
baseUrl: string,
apiKey: string,
userName: string
): Promise<string> {
const res = await fetch(
`${baseUrl}/api/v1/user?name=${encodeURIComponent(userName)}`,
{
headers: { Authorization: `Bearer ${apiKey}` },
}
);
if (!res.ok) {
throw new Error(
`Headscale list users failed: ${res.status} ${await res.text()}`
);
}
const data = (await res.json()) as { users: HeadscaleUser[] };
const user = data.users.find((u) => u.name === userName);
if (!user) {
throw new Error(`Headscale user not found: ${userName}`);
}
return user.id;
}
export async function createEphemeralPreAuthKey(
baseUrl: string,
apiKey: string,
userId: string,
options: {
expirationMinutes?: number;
aclTags?: string[];
} = {}
): Promise<string> {
const expirationMinutes = options.expirationMinutes ?? 15;
const aclTags = options.aclTags ?? [];
const expiration = new Date(
Date.now() + expirationMinutes * 60 * 1000
).toISOString();
const res = await fetch(`${baseUrl}/api/v1/preauthkey`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
user: userId,
reusable: false,
ephemeral: false,
expiration,
aclTags,
}),
});
if (!res.ok) {
throw new Error(
`Headscale create preauthkey failed: ${res.status} ${await res.text()}`
);
}
const data = (await res.json()) as { preAuthKey: HeadscalePreAuthKey };
return data.preAuthKey.key;
}