mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-28 21:52:26 +03:00
GET /install.sh serves a POSIX script, embedded in the API at build time so the script and the routes that redeem its token ship together. It checks the platform, asks where box images should live, downloads the host agent at a pinned version, verifies it against SHA256SUMS, installs it for the calling user and hands over with the token in the environment rather than argv. GET /install/:component/:version/:asset redirects to a one-minute signed URL on a private S3-compatible bucket, so every download passes through a route that can be logged or switched off. The SigV4 signer is written against Web Crypto and checked against AWS's published example.
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
import { Env } from '@nestri/core/env';
|
|
import { Hono } from 'hono';
|
|
import { describeRoute } from 'hono-openapi';
|
|
|
|
// The installer, embedded at build time so the script and the API that redeems
|
|
// its token always ship as one version.
|
|
import script from '../../install/install.sh' with { type: 'text' };
|
|
import { presignGet } from '../utils/presign';
|
|
|
|
/**
|
|
* The host installer and the binaries it downloads.
|
|
*
|
|
* The bucket behind these is never public. A download is answered with a
|
|
* one-minute signed URL for exactly the object asked for, so every download
|
|
* passes through here, where it can be logged, rate-limited or switched off.
|
|
*/
|
|
export namespace InstallApi {
|
|
/** What may be downloaded: one component, versions and asset names by shape. */
|
|
const COMPONENTS = new Set(['host']);
|
|
const VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z.]+)?$/;
|
|
const ASSET = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
|
|
const SIGNED_SECONDS = 60;
|
|
|
|
export const route = new Hono()
|
|
.get(
|
|
'/install.sh',
|
|
describeRoute({
|
|
tags: ['Install'],
|
|
summary: 'The host installer',
|
|
description:
|
|
'A POSIX shell script that installs the host agent for the calling user and registers the machine with a one-time install token. Pipe it to `sh -s -- <token>`.',
|
|
responses: { 200: { description: 'The script' } }
|
|
}),
|
|
(c) =>
|
|
c.body(script, 200, {
|
|
'content-type': 'text/x-shellscript; charset=utf-8',
|
|
'cache-control': 'no-cache'
|
|
})
|
|
)
|
|
.get(
|
|
'/install/:component/:version/:asset',
|
|
describeRoute({
|
|
tags: ['Install'],
|
|
summary: 'Download an installable binary',
|
|
description:
|
|
'Redirects to a short-lived signed URL for one release asset. Used by the installer.',
|
|
responses: {
|
|
302: { description: 'Where to download it' },
|
|
404: { description: 'No such asset' }
|
|
}
|
|
}),
|
|
async (c) => {
|
|
const { component, version, asset } = c.req.param();
|
|
if (!COMPONENTS.has(component) || !VERSION.test(version) || !ASSET.test(asset)) {
|
|
return c.notFound();
|
|
}
|
|
const env = Env.get();
|
|
if (
|
|
!env.RELEASES_BUCKET ||
|
|
!env.RELEASES_ENDPOINT ||
|
|
!env.RELEASES_ACCESS_KEY_ID ||
|
|
!env.RELEASES_SECRET_ACCESS_KEY
|
|
) {
|
|
return c.json({ message: 'Downloads are not configured on this deployment.' }, 503);
|
|
}
|
|
const url = await presignGet(
|
|
{
|
|
endpoint: env.RELEASES_ENDPOINT,
|
|
bucket: env.RELEASES_BUCKET,
|
|
region: env.RELEASES_REGION,
|
|
accessKeyId: env.RELEASES_ACCESS_KEY_ID,
|
|
secretAccessKey: env.RELEASES_SECRET_ACCESS_KEY
|
|
},
|
|
`${component}/${version}/${asset}`,
|
|
SIGNED_SECONDS
|
|
);
|
|
return c.redirect(url, 302);
|
|
}
|
|
);
|
|
}
|