feat(installer): build worker source over SSH
Все проверки выполнены успешно
CI / test (push) Successful in 3m13s
Docker / Build and publish worker image (push) Successful in 10m35s
Все проверки выполнены успешно
CI / test (push) Successful in 3m13s
Docker / Build and publish worker image (push) Successful in 10m35s
Этот коммит содержится в:
@@ -18,6 +18,15 @@ func TestDispatchManagementCommand(t *testing.T) {
|
||||
if handled, code := dispatchManagementCommand([]string{"install", "--help"}); !handled || code != 0 {
|
||||
t.Fatalf("install help = handled %t code %d", handled, code)
|
||||
}
|
||||
if handled, code := dispatchManagementCommand([]string{"deploy", "--help"}); !handled || code != 0 {
|
||||
t.Fatalf("deploy help = handled %t code %d", handled, code)
|
||||
}
|
||||
if handled, code := dispatchManagementCommand([]string{"source-install", "--help"}); !handled || code != 0 {
|
||||
t.Fatalf("source-install help = handled %t code %d", handled, code)
|
||||
}
|
||||
if handled, code := dispatchManagementCommand([]string{"source-install", "--identity-file", t.TempDir() + "/missing"}); !handled || code == 0 {
|
||||
t.Fatalf("source-install without host/user = handled %t code %d", handled, code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretValue(t *testing.T) {
|
||||
|
||||
@@ -19,6 +19,8 @@ func dispatchManagementCommand(args []string) (bool, int) {
|
||||
return true, installCommand(args[1:])
|
||||
case "deploy":
|
||||
return true, deployCommand(args[1:])
|
||||
case "source-install":
|
||||
return true, sourceInstallCommand(args[1:])
|
||||
default:
|
||||
return false, 0
|
||||
}
|
||||
@@ -153,3 +155,78 @@ func secretValue(direct, path string) (string, error) {
|
||||
}
|
||||
return strings.TrimRight(string(b), "\r\n"), nil
|
||||
}
|
||||
|
||||
// sourceInstallCommand drives the remote source installer (work package
|
||||
// 3 of docs/source-installation.md): prerequisites, verified Go
|
||||
// toolchain, clone/update, resolved branch/commit record, and a staging
|
||||
// build. It reuses the deploy SSH options; it never touches the running
|
||||
// service, configuration, or data directory on the remote host.
|
||||
func sourceInstallCommand(args []string) int {
|
||||
fs := flag.NewFlagSet("source-install", flag.ContinueOnError)
|
||||
fs.SetOutput(os.Stderr)
|
||||
var opts installer.SourceInstallOptions
|
||||
var passphraseFile, passwordFile, sudoPasswordFile string
|
||||
fs.StringVar(&opts.Host, "host", "", "SSH server hostname or address")
|
||||
fs.IntVar(&opts.Port, "port", 22, "SSH server port")
|
||||
fs.StringVar(&opts.User, "user", "", "SSH username")
|
||||
fs.StringVar(&opts.IdentityFile, "identity-file", "", "SSH private key path")
|
||||
fs.StringVar(&opts.KeyPassphrase, "key-passphrase", "", "SSH private key passphrase")
|
||||
fs.StringVar(&passphraseFile, "key-passphrase-file", "", "file containing the private key passphrase")
|
||||
fs.StringVar(&opts.Password, "password", "", "SSH login password")
|
||||
fs.StringVar(&passwordFile, "password-file", "", "file containing the SSH login password")
|
||||
fs.StringVar(&opts.SudoPassword, "sudo-password", "", "remote sudo password")
|
||||
fs.StringVar(&sudoPasswordFile, "sudo-password-file", "", "file containing the remote sudo password")
|
||||
fs.StringVar(&opts.KnownHostsFile, "known-hosts", "", "known_hosts path (default: ~/.ssh/known_hosts)")
|
||||
fs.StringVar(&opts.HostKeyFingerprint, "host-key-fingerprint", "", "expected SHA256 SSH host-key fingerprint")
|
||||
fs.BoolVar(&opts.InsecureHostKey, "insecure-host-key", false, "disable SSH host-key verification (unsafe)")
|
||||
fs.StringVar(&opts.Repo, "repo", "", "worker repository to clone/update (default: public rocketgit.ru repo)")
|
||||
fs.StringVar(&opts.Branch, "branch", "", "branch to build; empty resolves the remote default branch")
|
||||
fs.StringVar(&opts.GoVersion, "go-version", "", "Go toolchain version (default: pinned 1.26.0)")
|
||||
fs.StringVar(&opts.GoArch, "go-arch", "", "Go download archive suffix; empty derives it from the remote architecture")
|
||||
fs.StringVar(&opts.BuildDir, "build-dir", "", "remote clone/build directory (default /opt/rsmon-worker-src)")
|
||||
fs.StringVar(&opts.GoModuleProxy, "go-proxy", "", "GOPROXY for the remote build (default: Go default)")
|
||||
fs.StringVar(&opts.ToolchainDir, "toolchain-dir", "", "remote Go install path ending in /go (default /usr/local/go)")
|
||||
fs.StringVar(&opts.StageBinary, "stage-binary", "", "staging binary path (default <build-dir>/rsmon-worker)")
|
||||
fs.DurationVar(&opts.SessionTimeout, "session-timeout", 0, "per-remote-command timeout (default 30m; 0 uses the default)")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(fs.Output(), "Usage: rsmon-worker source-install --host HOST --user USER [SSH options] [source options]")
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0
|
||||
}
|
||||
return 2
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
fs.Usage()
|
||||
return 2
|
||||
}
|
||||
var err error
|
||||
if opts.KeyPassphrase, err = secretValue(opts.KeyPassphrase, passphraseFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "source-install failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if opts.Password, err = secretValue(opts.Password, passwordFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "source-install failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if opts.SudoPassword, err = secretValue(opts.SudoPassword, sudoPasswordFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "source-install failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
res, err := installer.SourceInstall(opts)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "source-install failed: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Printf("source install complete\n")
|
||||
fmt.Printf(" distro: %s\n", res.Detection.Summarize())
|
||||
fmt.Printf(" toolchain: %s (%s) at %s\n", res.Plan.Toolchain.Version, res.GoArch, res.ToolchainDir)
|
||||
fmt.Printf(" branch: %s\n", res.ResolvedBranch)
|
||||
fmt.Printf(" commit: %s\n", res.ResolvedCommit)
|
||||
fmt.Printf(" record file: %s\n", res.RecordFile)
|
||||
fmt.Printf(" staged build: %s\n", res.StageBinary)
|
||||
fmt.Println(" status: not installed as a service (activation is the next milestone)")
|
||||
return 0
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user