mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 02:00:19 +08:00
Replace the embedded bootstrap manifest with a typed OAPI call to GET /open-apis/security_plugin/v1/sec_cli/manifest, resolving the download URL per-platform/per-arch against the live release set. TAT auth flows through the existing credential chain; an x-tt-env header is injected when LARKSUITE_CLI_X_TT_ENV is set, for BOE routing. Drop the standalone `sec install` verb — `sec run --auto-install` (default on) makes it redundant. Add a persistent --verbose / -v flag on the sec parent, inherited by every subcommand, that emits step-by-step trace output on stderr. bootstrap.json and bootstrap.go remain in-tree as dead code; they will be removed in a follow-up cleanup.
128 lines
4.9 KiB
Go
128 lines
4.9 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package sec
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os/exec"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/larksuite/cli/internal/cmdutil"
|
|
"github.com/larksuite/cli/internal/output"
|
|
intsec "github.com/larksuite/cli/internal/sec"
|
|
)
|
|
|
|
// RunOptions holds inputs for `lark-cli sec run`.
|
|
type RunOptions struct {
|
|
Factory *cmdutil.Factory
|
|
ProxyPort int
|
|
// AutoInstall runs `sec install` first when no binary is recorded.
|
|
AutoInstall bool
|
|
}
|
|
|
|
// NewCmdSecRun starts lark-sec-cli as a user-level system service so it
|
|
// persists across logins and gets restarted by the OS supervisor if it
|
|
// crashes. Under the hood it shells out to `lark-sec-cli service enable`,
|
|
// which is the recommended startup path per the lark-sec-cli manual:
|
|
//
|
|
// - macOS → user-level launchd plist with KeepAlive=true
|
|
// - Linux → user systemd unit with Restart=always
|
|
// - Windows → registry autostart + a VBS watchdog loop
|
|
//
|
|
// Switching to this from a detached `exec.Command(... Setsid:true)` spawn
|
|
// fixes two latent issues at once: (1) daemon logs survive past lark-cli
|
|
// exit because the service supervisor — not our terminated pipes — owns
|
|
// the daemon's stdout, and (2) the daemon's own self-upgrade module can
|
|
// now fire (it gates on running-under-supervisor).
|
|
func NewCmdSecRun(f *cmdutil.Factory, runF func(*RunOptions) error) *cobra.Command {
|
|
opts := &RunOptions{Factory: f, AutoInstall: true}
|
|
cmd := &cobra.Command{
|
|
Use: "run",
|
|
Short: "Enable lark-sec-cli as a user system service (the daemon runs in the background)",
|
|
Long: `Install lark-sec-cli as a user-level system service so the proxy
|
|
daemon runs automatically, persists across logins, and is restarted by the
|
|
OS if it exits. The daemon writes its own log file (default: under
|
|
~/.lark-sec-cli/logs/daemon.log) so logs persist independently of this
|
|
command.
|
|
|
|
After enabling, the daemon writes ~/.lark-cli/sec_config.json itself with
|
|
the proxy port and CA path, so subsequent lark-cli runs route through the
|
|
sidecar without any further action.
|
|
|
|
To stop and remove the service: lark-cli sec stop.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if runF != nil {
|
|
return runF(opts)
|
|
}
|
|
return runRun(cmd, opts)
|
|
},
|
|
}
|
|
cmd.Flags().IntVar(&opts.ProxyPort, "proxy-port", 0, "force lark-sec-cli to bind this port (default: dynamic)")
|
|
cmd.Flags().BoolVar(&opts.AutoInstall, "auto-install", true, "bootstrap-install lark-sec-cli first when no binary is recorded")
|
|
return cmd
|
|
}
|
|
|
|
func runRun(cmd *cobra.Command, opts *RunOptions) error {
|
|
ctx := cmd.Context()
|
|
errOut := opts.Factory.IOStreams.ErrOut
|
|
trace := verboseOut(cmd, errOut)
|
|
|
|
tracef(trace, "sec run", "constructing installer (lazy credentials)")
|
|
inst, paths, err := installer(opts.Factory)
|
|
if err != nil {
|
|
return output.Errorf(output.ExitInternal, "internal", "%v", err)
|
|
}
|
|
|
|
// Make sure we have a binary on disk before asking it to install itself
|
|
// as a service.
|
|
tracef(trace, "sec run", "loading state from %s", paths.StateFile())
|
|
state, err := intsec.LoadState(paths.StateFile())
|
|
if err != nil {
|
|
return output.Errorf(output.ExitInternal, "internal", "load sec state: %v", err)
|
|
}
|
|
if state == nil {
|
|
tracef(trace, "sec run", "no install on disk (auto-install=%t)", opts.AutoInstall)
|
|
if !opts.AutoInstall {
|
|
return output.ErrWithHint(output.ExitValidation, "sec_not_installed",
|
|
"lark-sec-cli is not installed",
|
|
"Re-run `lark-cli sec run` with --auto-install (default on), or remove --auto-install=false.")
|
|
}
|
|
state, err = inst.Install(ctx, intsec.InstallOptions{Verbose: trace})
|
|
if err != nil {
|
|
return output.Errorf(output.ExitNetwork, "sec_install", "auto-install lark-sec-cli: %v", err)
|
|
}
|
|
} else {
|
|
tracef(trace, "sec run", "existing install: version=%s binary=%s", state.Version, state.BinaryPath)
|
|
}
|
|
|
|
args := []string{"service", "enable"}
|
|
if opts.ProxyPort > 0 {
|
|
args = append(args, fmt.Sprintf("--proxy-port=%d", opts.ProxyPort))
|
|
}
|
|
|
|
fmt.Fprintf(errOut, "Running: %s %v\n", state.BinaryPath, args)
|
|
tracef(trace, "sec run", "shelling out to %s %v", state.BinaryPath, args)
|
|
|
|
c := exec.CommandContext(ctx, state.BinaryPath, args...)
|
|
var stdout, stderr bytes.Buffer
|
|
c.Stdout = &stdout
|
|
c.Stderr = &stderr
|
|
if err := c.Run(); err != nil {
|
|
return output.Errorf(output.ExitInternal, "sec_service_enable",
|
|
"`lark-sec-cli service enable` failed: %v\nstderr: %s", err, stderr.String())
|
|
}
|
|
tracef(trace, "sec run", "service enable returned ok (%d bytes stdout)", stdout.Len())
|
|
|
|
// Forward the installer's stdout to the user — it contains the launchd /
|
|
// systemd unit name, the registered executable path, and a confirmation
|
|
// that the supervisor will respawn the daemon on exit. Useful diagnostic
|
|
// output that's better seen than swallowed.
|
|
fmt.Fprint(errOut, stdout.String())
|
|
output.PrintSuccess(errOut,
|
|
"lark-sec-cli enabled as a user system service. Run `lark-cli sec status` to verify, `lark-cli sec stop` to disable.")
|
|
return nil
|
|
}
|