mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 03:49:31 +00:00
fix: add sudo to tarball mirror commands for non-root SSH users (#2922)
* fix: add sudo to tarball mirror commands for non-root SSH users The mirror step copies files from /root/ to $HOME/ for non-root users (e.g. ubuntu on AWS Lightsail), but cp and chown ran without sudo. A non-root user can't read /root/ or chown root-owned files, so the mirror silently failed (errors suppressed by 2>/dev/null || true). Adds sudo to cp/chown in both mirror blocks (tryTarballInstall and uploadAndExtractTarball) and removes error suppression so failures propagate to the caller. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: verify sudo in tarball mirror commands for both install paths Adds tests for tryTarballInstall and uploadAndExtractTarball that assert: - cp and chown use sudo (needed to read /root/ as non-root user) - error suppression (2>/dev/null || true) is not present Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Signed-off-by: Ahmed Abushagur <ahmed@abushagur.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
18b1a5f50f
commit
6a6ca87969
2 changed files with 62 additions and 9 deletions
|
|
@ -13,7 +13,7 @@ import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:te
|
|||
|
||||
// Suppress stderr (logStep/logWarn) with a spy in beforeEach.
|
||||
|
||||
const { tryTarballInstall } = await import("../shared/agent-tarball");
|
||||
const { tryTarballInstall, uploadAndExtractTarball } = await import("../shared/agent-tarball");
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -268,6 +268,23 @@ describe("tryTarballInstall", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("uses sudo for cp and chown in mirror commands", () => {
|
||||
// sudo is needed because non-root users can't read /root/ or chown root-owned files
|
||||
const sudo = '$([ "$(id -u)" != "0" ] && echo sudo || echo "")';
|
||||
// cp from /root/ needs sudo
|
||||
expect(mirrorCmd).toContain(`${sudo} cp -a "/root/$_d/." "$HOME/$_d/"`);
|
||||
// cp marker file needs sudo
|
||||
expect(mirrorCmd).toContain(`${sudo} cp /root/.spawn-tarball "$HOME/.spawn-tarball"`);
|
||||
// chown needs sudo
|
||||
expect(mirrorCmd).toContain(`${sudo} chown -R "$(id -u):$(id -g)" "$HOME/.spawn-tarball"`);
|
||||
expect(mirrorCmd).toContain(`${sudo} chown -R "$(id -u):$(id -g)" "$HOME/$_d"`);
|
||||
});
|
||||
|
||||
it("does not suppress errors with 2>/dev/null || true", () => {
|
||||
// Errors must propagate so failures are visible, not silently swallowed
|
||||
expect(mirrorCmd).not.toContain("2>/dev/null || true");
|
||||
});
|
||||
|
||||
it("returns true even when mirror step fails (non-fatal)", async () => {
|
||||
const fetchFn = mockFetch(new Response(JSON.stringify(RELEASE_PAYLOAD)));
|
||||
const runner = createMockRunner();
|
||||
|
|
@ -279,3 +296,39 @@ describe("tryTarballInstall", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("uploadAndExtractTarball", () => {
|
||||
let stderrSpy: ReturnType<typeof spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
stderrSpy = spyOn(process.stderr, "write").mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stderrSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("mirror step uses sudo for cp and chown", async () => {
|
||||
const runner = createMockRunner();
|
||||
|
||||
await uploadAndExtractTarball(runner, "/tmp/fake.tar.gz");
|
||||
|
||||
// 2 calls: extract, then mirror
|
||||
expect(runner.runServer).toHaveBeenCalledTimes(2);
|
||||
const mirrorCmd = String(runner.runServer.mock.calls[1][0]);
|
||||
const sudo = '$([ "$(id -u)" != "0" ] && echo sudo || echo "")';
|
||||
expect(mirrorCmd).toContain(`${sudo} cp -a "/root/$_d/." "$HOME/$_d/"`);
|
||||
expect(mirrorCmd).toContain(`${sudo} cp /root/.spawn-tarball "$HOME/.spawn-tarball"`);
|
||||
expect(mirrorCmd).toContain(`${sudo} chown -R "$(id -u):$(id -g)" "$HOME/.spawn-tarball"`);
|
||||
expect(mirrorCmd).toContain(`${sudo} chown -R "$(id -u):$(id -g)" "$HOME/$_d"`);
|
||||
});
|
||||
|
||||
it("mirror step does not suppress errors", async () => {
|
||||
const runner = createMockRunner();
|
||||
|
||||
await uploadAndExtractTarball(runner, "/tmp/fake.tar.gz");
|
||||
|
||||
const mirrorCmd = String(runner.runServer.mock.calls[1][0]);
|
||||
expect(mirrorCmd).not.toContain("2>/dev/null || true");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -134,16 +134,16 @@ export async function tryTarballInstall(
|
|||
" for _d in .claude .local .npm-global .cargo .opencode .hermes .bun; do",
|
||||
' if [ -d "/root/$_d" ]; then',
|
||||
' mkdir -p "$HOME/$_d"',
|
||||
' cp -a "/root/$_d/." "$HOME/$_d/" 2>/dev/null || true',
|
||||
` ${sudo} cp -a "/root/$_d/." "$HOME/$_d/"`,
|
||||
" fi",
|
||||
" done",
|
||||
" # Copy marker file",
|
||||
' cp /root/.spawn-tarball "$HOME/.spawn-tarball" 2>/dev/null || true',
|
||||
` ${sudo} cp /root/.spawn-tarball "$HOME/.spawn-tarball"`,
|
||||
" # Fix ownership — files were extracted as root",
|
||||
' chown -R "$(id -u):$(id -g)" "$HOME/.spawn-tarball" 2>/dev/null || true',
|
||||
` ${sudo} chown -R "$(id -u):$(id -g)" "$HOME/.spawn-tarball"`,
|
||||
" for _d in .claude .local .npm-global .cargo .opencode .hermes .bun; do",
|
||||
' if [ -d "$HOME/$_d" ]; then',
|
||||
' chown -R "$(id -u):$(id -g)" "$HOME/$_d" 2>/dev/null || true',
|
||||
` ${sudo} chown -R "$(id -u):$(id -g)" "$HOME/$_d"`,
|
||||
" fi",
|
||||
" done",
|
||||
"fi",
|
||||
|
|
@ -262,14 +262,14 @@ export async function uploadAndExtractTarball(runner: CloudRunner, localPath: st
|
|||
" for _d in .claude .local .npm-global .cargo .opencode .hermes .bun; do",
|
||||
' if [ -d "/root/$_d" ]; then',
|
||||
' mkdir -p "$HOME/$_d"',
|
||||
' cp -a "/root/$_d/." "$HOME/$_d/" 2>/dev/null || true',
|
||||
` ${sudo} cp -a "/root/$_d/." "$HOME/$_d/"`,
|
||||
" fi",
|
||||
" done",
|
||||
' cp /root/.spawn-tarball "$HOME/.spawn-tarball" 2>/dev/null || true',
|
||||
' chown -R "$(id -u):$(id -g)" "$HOME/.spawn-tarball" 2>/dev/null || true',
|
||||
` ${sudo} cp /root/.spawn-tarball "$HOME/.spawn-tarball"`,
|
||||
` ${sudo} chown -R "$(id -u):$(id -g)" "$HOME/.spawn-tarball"`,
|
||||
" for _d in .claude .local .npm-global .cargo .opencode .hermes .bun; do",
|
||||
' if [ -d "$HOME/$_d" ]; then',
|
||||
' chown -R "$(id -u):$(id -g)" "$HOME/$_d" 2>/dev/null || true',
|
||||
` ${sudo} chown -R "$(id -u):$(id -g)" "$HOME/$_d"`,
|
||||
" fi",
|
||||
" done",
|
||||
"fi",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue