fix(worker): keep-id compat, zombie tmux clients, nvm in box image

- Box init + chownInBox exec explicitly as root: under podman keep-id the
  container's default user is the mapped uid, which cannot useradd/chown.
- Init renames an existing uid-1000 passwd entry (podman keep-id injects one
  named after the HOST user) instead of adding a duplicate that would win the
  getpwuid lookup and make whoami report the wrong name.
- Terminal connections tag their shell with a per-connection marker and reap
  the process group on WebSocket close: closing the attach socket never
  killed the exec'd shell, so every disconnect leaked an attached tmux client
  (session stuck at the smallest zombie's size; enough dead clients starve
  tmux rendering for live ones).
- Job image: nvm 0.39.5 at /usr/local/nvm (user-writable) with
  /etc/profile.d/nvm.sh, matching what user dotfiles expect; docs for the box
  user + ownership model.
This commit is contained in:
Gabriel Brown
2026-07-13 10:36:44 -04:00
parent 028fbebcd6
commit 4505471149
8 changed files with 106 additions and 12 deletions
@@ -31,8 +31,14 @@ describe('buildBoxInitScript', () => {
username: 'gabriel',
home: '/home/Gabriel',
});
it('creates the user idempotently with uid 1000 at the given home', () => {
expect(script).toContain("id -u 'gabriel'");
it('renames an existing uid-1000 entry instead of duplicating it', () => {
// podman --userns=keep-id injects a passwd entry named after the HOST user
// at the mapped uid; a second uid-1000 entry would win the getpwuid lookup
// and make whoami report the host username.
expect(script).toContain('getent passwd 1000');
expect(script).toContain("usermod -l 'gabriel'");
});
it('creates the user with uid 1000 at the given home when none exists', () => {
expect(script).toContain('useradd');
expect(script).toContain('-u 1000');
expect(script).toContain("-d '/home/Gabriel'");
@@ -44,3 +44,18 @@ describe('parseResizeMessage', () => {
expect(parseResizeMessage(msg, false)).toEqual({ cols: 1000, rows: 1 });
});
});
describe('buildTerminalShellCommand', () => {
test('embeds the connection marker without exec-ing the shell away', async () => {
const { buildTerminalShellCommand } = await load();
const command = buildTerminalShellCommand('spoon-term-abc-123');
expect(command[0]).toBe('/bin/bash');
const script = command[2] ?? '';
// Marker must stay in the wrapper's argv for pgrep-based cleanup, so the
// inner shell/tmux must NOT be exec'd (exec would replace the argv).
expect(script).toContain('# spoon-term-abc-123');
expect(script).not.toContain('exec tmux');
expect(script).not.toContain('exec bash');
expect(script).toContain('tmux new-session -A -s spoon');
});
});