package installer import ( "strings" "testing" "time" "rocketgit.ru/rsmon/worker/internal/sshinstall" ) // cannedSHA is a fixed 64-hex value used to exercise script rendering. const cannedSHA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" func TestPackageScript(t *testing.T) { pkgs := []string{"git", "ca-certificates", "curl", "tar", "gzip"} apk := packageScript(sshinstall.PkgApk, pkgs) if !strings.HasPrefix(apk, "apk add --no-cache ") { t.Fatalf("apk script = %q", apk) } for _, p := range pkgs { if !strings.Contains(apk, shellQuote(p)) { t.Fatalf("apk script missing quoted package %q: %q", p, apk) } } apt := packageScript(sshinstall.PkgApt, pkgs) for _, want := range []string{"export DEBIAN_FRONTEND=noninteractive", "apt-get update", "apt-get install -y --no-install-recommends"} { if !strings.Contains(apt, want) { t.Fatalf("apt script missing %q: %q", want, apt) } } pacman := packageScript(sshinstall.PkgPacman, pkgs) if !strings.Contains(pacman, "pacman -Sy --noconfirm --needed") { t.Fatalf("pacman script = %q", pacman) } dnf := packageScript(sshinstall.PkgDnf, pkgs) if !strings.Contains(dnf, "dnf install -y") { t.Fatalf("dnf script = %q", dnf) } if got := packageScript(sshinstall.PkgUnknown, pkgs); got != "" { t.Fatalf("unknown pkg script = %q, want empty", got) } } func TestPackageScriptNeverIncludesCompiler(t *testing.T) { pkgs := []string{"git", "ca-certificates", "curl", "tar", "gzip"} for _, pkg := range []sshinstall.PackageManager{sshinstall.PkgApk, sshinstall.PkgApt, sshinstall.PkgPacman, sshinstall.PkgDnf} { script := packageScript(pkg, pkgs) for _, bad := range []string{"build-essential", "gcc", "g++", "base-devel", "make", "gcc-c++"} { if strings.Contains(script, bad) { t.Fatalf("script for %s includes compiler hint %q: %q", pkg, bad, script) } } } } func TestToolchainScript(t *testing.T) { tc := sshinstall.Toolchain{ Version: "1.26.0", Arch: "linux-amd64", URL: "https://go.dev/dl/go1.26.0.linux-amd64.tar.gz", SHA256: cannedSHA, } script := toolchainScript(tc, "/usr/local/go") for _, want := range []string{ "set -eu", "mktemp -d /tmp/rsmon-toolchain-XXXXXX", "mktemp -d \"$parent/.go-staging-XXXXXX\"", "trap 'rm -rf \"$work\" \"$staging\"' EXIT HUP INT TERM", "curl -fsSL --retry 3 --retry-delay 2 -o \"$archive\" 'https://go.dev/dl/go1.26.0.linux-amd64.tar.gz'", "sha256sum -c -", cannedSHA, "tar -C \"$staging\" -xzf \"$archive\"", "staged=\"$($staging/go/bin/go version | awk '{print $3}')\"", "backup=\"$parent/.go-backup\"", "mv \"$toolchain\" \"$backup\"", "mv \"$staging/go\" \"$toolchain\"", "\"$toolchain/bin/go\" version", "go toolchain already present", } { if !strings.Contains(script, want) { t.Fatalf("toolchain script missing %q:\n%s", want, script) } } if !strings.Contains(script, shellQuote("go1.26.0")) { t.Fatalf("toolchain script missing version guard:\n%s", script) } } func TestToolchainScriptIdempotentSkipOnlyForMatchingVersion(t *testing.T) { script := toolchainScript(sshinstall.Toolchain{Version: "1.26.0", Arch: "linux-amd64", URL: "u", SHA256: cannedSHA}, "/usr/local/go") if strings.Count(script, "exit 0") != 1 { t.Fatalf("toolchain script should skip only once:\n%s", script) } // Replacement must be atomic: the prior toolchain is moved aside and // restored when the swap fails, never removed before staging is ready. for _, want := range []string{ "if [ -e \"$toolchain\" ]; then", "mv \"$toolchain\" \"$backup\"", "if ! mv \"$staging/go\" \"$toolchain\"; then", "mv \"$backup\" \"$toolchain\"", } { if !strings.Contains(script, want) { t.Fatalf("toolchain script missing %q:\n%s", want, script) } } if strings.Contains(script, "rm -rf \"$toolchain\"") { t.Fatalf("toolchain script must not delete the live toolchain directly:\n%s", script) } } func TestCloneUpdateScript(t *testing.T) { script := cloneUpdateScript("https://example.test/worker.git", "/opt/rsmon-worker-src") for _, want := range []string{ "set -u", "[ ! -d \"$dir/.git\" ]", "git clone \"$repo\" \"$dir\"", "while [ \"$attempt\" -lt 3 ]", "sleep 2", "git -C \"$dir\" config --get remote.origin.url", "existing checkout origin does not match configured repository", "git -C \"$dir\" fetch --prune origin", "exit 1", } { if !strings.Contains(script, want) { t.Fatalf("clone/update script missing %q:\n%s", want, script) } } } func TestCheckoutScriptFailsClosed(t *testing.T) { script := checkoutScript("/opt/rsmon-worker-src", "master") if !strings.HasPrefix(script, "set -eu\n") { t.Fatalf("checkout script must fail closed with set -eu:\n%s", script) } for _, want := range []string{ "git -C '/opt/rsmon-worker-src' diff --quiet ||", "git -C '/opt/rsmon-worker-src' diff --cached --quiet ||", "refusing to overwrite", "checkout -q -B 'master' 'origin/master'", "rev-parse HEAD", } { if !strings.Contains(script, want) { t.Fatalf("checkout script missing %q:\n%s", want, script) } } } func TestResolveBranchScriptFailsClosed(t *testing.T) { if !strings.HasPrefix(resolveBranchScript("/opt/rsmon-worker-src"), "set -eu\n") { t.Fatalf("resolve-branch script must fail closed:\n%s", resolveBranchScript("/opt/rsmon-worker-src")) } } func TestResolveBranchScript(t *testing.T) { script := resolveBranchScript("/opt/rsmon-worker-src") for _, want := range []string{ "remote set-head origin --auto", "symbolic-ref --short refs/remotes/origin/HEAD", } { if !strings.Contains(script, want) { t.Fatalf("resolve-branch script missing %q:\n%s", want, script) } } } func TestCheckoutScript(t *testing.T) { script := checkoutScript("/opt/rsmon-worker-src", "master") for _, want := range []string{ "checkout -q -B 'master' 'origin/master'", "rev-parse HEAD", } { if !strings.Contains(script, want) { t.Fatalf("checkout script missing %q:\n%s", want, script) } } } func TestRefExistsScript(t *testing.T) { script := refExistsScript("/opt/rsmon-worker-src", "refs/remotes/origin/main") if !strings.Contains(script, "show-ref --verify --quiet 'refs/remotes/origin/main'") { t.Fatalf("ref-exists script = %q", script) } } func TestCommitRecordScript(t *testing.T) { script := commitRecordScript("/opt/rsmon-worker-src", "master", strings.Repeat("a", 40)) for _, want := range []string{ "branch=%s\\ncommit=%s\\n", "'master'", strings.Repeat("a", 40), "'/opt/rsmon-worker-src/rsmon-worker.commit'", "chmod 0644", } { if !strings.Contains(script, want) { t.Fatalf("record script missing %q:\n%s", want, script) } } } func TestBuildScript(t *testing.T) { script := buildScript("/usr/local/go", "/opt/rsmon-worker-src", "", "/opt/rsmon-worker-src/rsmon-worker", `-s -w -X main.version=dev -X main.commit=abcdef012345 -X main.buildDate=2026-08-12T00:00:00Z`) for _, want := range []string{ "set -eu", "cd '/opt/rsmon-worker-src'", "export PATH='/usr/local/go/bin':$PATH", "export GOCACHE='/opt/rsmon-worker-src/.gocache'", "export GOMODCACHE='/opt/rsmon-worker-src/.gomodcache'", "CGO_ENABLED=0 '/usr/local/go/bin/go' build -trimpath", "-X main.commit=abcdef012345", "-o \"$tmp\" ./cmd/rsmon-worker", "tmp=\"$stage.new\"", "\"$tmp\" --version", "mv -f \"$tmp\" \"$stage\"", "trap 'rm -f \"$tmp\"' EXIT HUP INT TERM", } { if !strings.Contains(script, want) { t.Fatalf("build script missing %q:\n%s", want, script) } } if strings.Contains(script, "GOPROXY") { t.Fatalf("empty GOPROXY must not be exported:\n%s", script) } withProxy := buildScript("/usr/local/go", "/opt/rsmon-worker-src", "https://proxy.golang.org,direct", "/opt/rsmon-worker-src/rsmon-worker", "-s -w") if !strings.Contains(withProxy, "export GOPROXY='https://proxy.golang.org,direct'") { t.Fatalf("GOPROXY override not rendered:\n%s", withProxy) } } func TestBuildScriptVerifiesBeforeSwap(t *testing.T) { script := buildScript("/usr/local/go", "/opt/rsmon-worker-src", "", "/opt/rsmon-worker-src/rsmon-worker", "-s -w") verify := strings.Index(script, "\"$tmp\" --version") swap := strings.Index(script, "mv -f \"$tmp\" \"$stage\"") if verify < 0 || swap < 0 || verify > swap { t.Fatalf("build script must verify the temp binary before swapping it in:\n%s", script) } } func TestParseResolvedCommit(t *testing.T) { commit := strings.Repeat("abcdef", 6) + "abcd" // 40 hex if got, err := parseResolvedCommit(commit + "\n"); err != nil || got != commit { t.Fatalf("parseResolvedCommit() = %q, %v", got, err) } if got, err := parseResolvedCommit("ignored\n" + commit + "\n"); err != nil || got != commit { t.Fatalf("parseResolvedCommit() multi-line = %q, %v", got, err) } for _, bad := range []string{"", "abc", strings.Repeat("A", 40), strings.Repeat("a", 39), "x" + strings.Repeat("a", 39)} { if _, err := parseResolvedCommit(bad); err == nil { t.Fatalf("parseResolvedCommit(%q) succeeded", bad) } } } func TestParseResolvedBranch(t *testing.T) { if got, err := parseResolvedBranch("origin/master\n"); err != nil || got != "master" { t.Fatalf("parseResolvedBranch(origin/master) = %q, %v", got, err) } if got, err := parseResolvedBranch("master\n"); err != nil || got != "master" { t.Fatalf("parseResolvedBranch(master) = %q, %v", got, err) } // A multi-component short name is legal git and stays safe because // the value is validated and single-quoted everywhere it is used. if got, err := parseResolvedBranch("origin/release/1.0\n"); err != nil || got != "release/1.0" { t.Fatalf("parseResolvedBranch(nested) = %q, %v", got, err) } for _, bad := range []string{"origin/../evil\n", "origin/x y\n", "origin/x..y\n", "origin/x@{y\n", "origin/-x\n"} { if _, err := parseResolvedBranch(bad); err == nil { t.Fatalf("parseResolvedBranch(%q) succeeded", bad) } } } func TestValidBranchName(t *testing.T) { for _, ok := range []string{"main", "master", "release-1.0", "feature/x", "a", "v1.2.3", "a_b", "release/1.0"} { if !validBranchName(ok) { t.Fatalf("validBranchName(%q) rejected", ok) } } for _, bad := range []string{"", "-bad", "x..y", "x@{y", "x y", "/x", "x.", "x/", "x//y", "x\\y", "x;y", "$x", "`x`"} { if validBranchName(bad) { t.Fatalf("validBranchName(%q) accepted", bad) } } } func TestQuoteList(t *testing.T) { if got, want := quoteList([]string{"git", "ca-certificates"}), "'git' 'ca-certificates'"; got != want { t.Fatalf("quoteList() = %q, want %q", got, want) } if got := quoteList(nil); got != "" { t.Fatalf("quoteList(nil) = %q, want empty", got) } } func TestNormalizeSourceOptions(t *testing.T) { o, err := normalizeSourceOptions(SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}}) if err != nil { t.Fatal(err) } if o.Port != 22 || o.ToolchainDir != "/usr/local/go" || o.SessionTimeout != defaultSessionTimeout { t.Fatalf("defaults not applied: %+v", o) } if o, err := normalizeSourceOptions(SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, SessionTimeout: 7 * time.Minute}); err != nil || o.SessionTimeout != 7*time.Minute { t.Fatalf("explicit session timeout not honored: %+v, %v", o, err) } for _, tc := range []struct { name string opts SourceInstallOptions }{ {name: "missing host", opts: SourceInstallOptions{SSHOptions: SSHOptions{User: "u"}}}, {name: "missing user", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h"}}}, {name: "bad port", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u", Port: 70000}}}, {name: "bad branch", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, Branch: "x y"}}, {name: "bad go version", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, GoVersion: "1.26;rm"}}, {name: "bad go arch", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, GoArch: "amd64;rm"}}, {name: "bad repo scheme", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, Repo: "http://x/y"}}, {name: "repo userinfo", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, Repo: "https://user:pass@x/y"}}, {name: "relative toolchain", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, ToolchainDir: "usr/local/go"}}, {name: "non-go toolchain basename", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, ToolchainDir: "/opt/golang"}}, {name: "relative stage", opts: SourceInstallOptions{SSHOptions: SSHOptions{Host: "h", User: "u"}, StageBinary: "bin/worker"}}, } { t.Run(tc.name, func(t *testing.T) { if _, err := normalizeSourceOptions(tc.opts); err == nil { t.Fatalf("normalizeSourceOptions(%s) succeeded", tc.name) } }) } } func TestSudoWrap(t *testing.T) { cmd, stdin := sudoWrap("root", "", "echo hi") if cmd != "echo hi" || stdin != nil { t.Fatalf("root wrap = %q, %q", cmd, stdin) } cmd, stdin = sudoWrap("deploy", "", "echo hi") if cmd != "sudo -n -- echo hi" || stdin != nil { t.Fatalf("passwordless sudo wrap = %q, %q", cmd, stdin) } cmd, stdin = sudoWrap("deploy", "supersecret", "echo hi") if cmd != "sudo -S -p '' -- echo hi" || string(stdin) != "supersecret\n" { t.Fatalf("sudo -S wrap = %q, %q", cmd, stdin) } if strings.Contains(cmd, "supersecret") { t.Fatal("sudo password leaked into the command string") } } // TestSourceScriptsNeverContainSecrets documents the "secrets absent" // contract: none of the rendered remote scripts carry a credential. func TestBoundedBuffer(t *testing.T) { var b boundedBuffer b.max = 8 if _, err := b.Write([]byte("12345")); err != nil { t.Fatal(err) } if b.truncated { t.Fatal("truncated before exceeding max") } if _, err := b.Write([]byte("6789abcdef")); err != nil { t.Fatal(err) } if !b.truncated { t.Fatal("overflow not flagged") } if got, want := b.String(), "12345678"; got != want { t.Fatalf("boundedBuffer = %q, want %q", got, want) } } func TestSourceScriptsNeverContainSecrets(t *testing.T) { const secret = "super-secret-token-value" scripts := []string{ packageScript(sshinstall.PkgApk, []string{"git", "ca-certificates", "curl", "tar", "gzip"}), toolchainScript(sshinstall.Toolchain{Version: "1.26.0", Arch: "linux-amd64", URL: "https://go.dev/dl/go1.26.0.linux-amd64.tar.gz", SHA256: cannedSHA}, "/usr/local/go"), cloneUpdateScript("https://example.test/worker.git", "/opt/rsmon-worker-src"), resolveBranchScript("/opt/rsmon-worker-src"), checkoutScript("/opt/rsmon-worker-src", "master"), commitRecordScript("/opt/rsmon-worker-src", "master", strings.Repeat("a", 40)), buildScript("/usr/local/go", "/opt/rsmon-worker-src", "", "/opt/rsmon-worker-src/rsmon-worker", "-s -w"), } for i, script := range scripts { if strings.Contains(script, secret) { t.Fatalf("script %d contains a secret", i) } } }