~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
70 lines · 2.3 KB Raw History
#!/usr/bin/env node
// Runs a command as an image built by kaniko would run it, with no daemon.
//
//   node scripts/run-image.mjs <oci layout> [NAME=value]... [-- command...]
//
// kaniko leaves the last stage's filesystem at / once a build ends. This
// reads the image's config from the OCI layout the build wrote, and starts
// its entrypoint with the command given, or the image's own, in that
// filesystem: with the image's environment, working directory and user. It
// exits with the command's status.

import { spawn } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

const [layout, ...rest] = process.argv.slice(2);
if (!layout) {
  console.error(
    'usage: run-image.mjs <oci layout> [NAME=value]... [-- command...]',
  );
  process.exit(2);
}
const dash = rest.indexOf('--');
const vars = dash < 0 ? rest : rest.slice(0, dash);
const command = dash < 0 ? [] : rest.slice(dash + 1);

/** @param {string} digest */
const blob = (digest) =>
  JSON.parse(readFileSync(join(layout, 'blobs', ...digest.split(':')), 'utf8'));

const index = blob(
  JSON.parse(readFileSync(join(layout, 'index.json'), 'utf8')).manifests[0]
    .digest,
);
const manifest = index.manifests ? blob(index.manifests[0].digest) : index;
const { config } = blob(manifest.config.digest);

const env = Object.fromEntries(
  [...(config.Env ?? []), ...vars].map((pair) => {
    const at = pair.indexOf('=');
    return [pair.slice(0, at), pair.slice(at + 1)];
  }),
);

/** @param {string} user */
function ids(user) {
  if (!user) return {};
  const [name, group] = user.split(':');
  if (/^\d+$/.test(name)) {
    return { uid: Number(name), gid: Number(group ?? name) };
  }
  const entry = readFileSync('/etc/passwd', 'utf8')
    .split('\n')
    .map((line) => line.split(':'))
    .find((fields) => fields[0] === name);
  if (!entry) throw new Error(`the image's user ${name} is not in /etc/passwd`);
  return { uid: Number(entry[2]), gid: Number(entry[3]) };
}

const argv = [
  ...(config.Entrypoint ?? []),
  ...(command.length ? command : (config.Cmd ?? [])),
];
const child = spawn(argv[0], argv.slice(1), {
  cwd: config.WorkingDir || '/',
  env,
  stdio: 'inherit',
  ...ids(config.User ?? ''),
});
child.on('exit', (code, signal) => process.exit(code ?? (signal ? 128 : 1)));