mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-12 17:59:06 +02:00
The debug-bundle paths that upload without a human picking a destination compiled the vendor endpoint in: the mobile clients and the desktop UI hold `https://upload.debug.netbird.io/upload-url` as a constant, the CLI defaults its flag to it, and the remote job falls back to it when nothing else is set. A self-hosted deployment therefore shipped peer logs, routes, DNS and firewall state to NetBird-run infrastructure without its operator ever configuring that, and had no way to point those paths anywhere else. #7147 and #7153 gave the remote job a per-job URL and an MDM override, but neither reaches the mobile, UI or CLI paths, and both fail open when unset. Publish the destination from the management server instead, on the channel that already carries stun/turn/signal/relay/flow/metrics: - `NetbirdConfig.debug.upload_url`, sourced from the new account setting `debug_bundle_upload_url` (REST + dashboard) and falling back to the new `DebugUpload.URL` in the management server config, which a self-hosted install can set once so a fresh account is not left on the vendor default. Both are validated as https-with-host where they are written; a change fans out to connected peers rather than waiting for the next login. - One resolver on the client, `debug.ResolveUploadURL`, used by every path: MDM override > explicitly named URL > destination published by management > the NetBird service, but only for a peer enrolled with NetBird's cloud. Anything else fails closed with ErrNoUploadDestination and the bundle stays local, which is the behaviour change: a self-hosted deployment that names no upload service no longer uploads at all. - The engine keeps the published value (`Engine.DebugUploadURL`) so the bundle paths, which run off the engine loop, do not have to read it back out of the opt-in sync-response store. - The daemon request grows `upload`, so "upload to wherever this deployment says" is expressible; an empty `uploadURL` no longer has to mean "no upload". The privilege gate is unchanged and still applies only to a URL the local caller named — a destination published by management is the operator naming their own service. - The desktop UI stops carrying a vendor URL of its own and sends the intent. Reported privately as GHSA-hf99-43rj-h577.
575 lines
18 KiB
Go
575 lines
18 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
|
|
"github.com/netbirdio/netbird/client/internal"
|
|
"github.com/netbirdio/netbird/client/internal/debug"
|
|
"github.com/netbirdio/netbird/client/internal/peer"
|
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
|
"github.com/netbirdio/netbird/client/proto"
|
|
"github.com/netbirdio/netbird/client/server"
|
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
|
"github.com/netbirdio/netbird/version"
|
|
)
|
|
|
|
const errCloseConnection = "Failed to close connection: %v"
|
|
|
|
// uploadBundleURLUsage documents that an empty flag is not "no upload" but
|
|
// "wherever this deployment says": the daemon takes the destination from the
|
|
// management server, and only falls back to the service NetBird runs for a peer
|
|
// enrolled with NetBird's cloud. Naming another one requires root, since the
|
|
// daemon fetches the URL and PUTs its own logs and state to whatever it returns.
|
|
const uploadBundleURLUsage = "Upload service URL to get an upload URL from. " +
|
|
"Defaults to the one the management server publishes; requires root when set explicitly"
|
|
|
|
var (
|
|
logFileCount uint32
|
|
systemInfoFlag bool
|
|
uploadBundleFlag bool
|
|
uploadBundleURLFlag string
|
|
uploadBundleInsecureFlag bool
|
|
)
|
|
|
|
var debugCmd = &cobra.Command{
|
|
Use: "debug",
|
|
Short: "Debugging commands",
|
|
Long: "Commands for debugging and logging within the NetBird daemon.",
|
|
}
|
|
|
|
var debugBundleCmd = &cobra.Command{
|
|
Use: "bundle",
|
|
Example: " netbird debug bundle",
|
|
Short: "Create a debug bundle",
|
|
Long: "Generates a compressed archive of the daemon's logs and status for debugging purposes.",
|
|
RunE: debugBundle,
|
|
}
|
|
|
|
var logCmd = &cobra.Command{
|
|
Use: "log",
|
|
Short: "Manage logging for the NetBird daemon",
|
|
Long: `Commands to manage logging settings for the NetBird daemon, including ICE, gRPC, and general log levels.`,
|
|
}
|
|
|
|
var logLevelCmd = &cobra.Command{
|
|
Use: "level <level>",
|
|
Short: "Set the logging level for this session",
|
|
Long: `Sets the logging level for the current session. This setting is temporary and will revert to the default on daemon restart.
|
|
Available log levels are:
|
|
panic: for panic level, highest level of severity
|
|
fatal: for fatal level errors that cause the program to exit
|
|
error: for error conditions
|
|
warn: for warning conditions
|
|
info: for informational messages
|
|
debug: for debug-level messages
|
|
trace: for trace-level messages, which include more fine-grained information than debug`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: setLogLevel,
|
|
}
|
|
|
|
var forCmd = &cobra.Command{
|
|
Use: "for <time>",
|
|
Short: "Run debug logs for a specified duration and create a debug bundle",
|
|
Long: `Sets the logging level to trace, runs for the specified duration, and then generates a debug bundle.`,
|
|
Example: " netbird debug for 5m",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runForDuration,
|
|
}
|
|
|
|
var persistenceCmd = &cobra.Command{
|
|
Use: "persistence [on|off]",
|
|
Short: "Set sync response memory persistence",
|
|
Long: `Configure whether the latest sync response should persist in memory. When enabled, the last known sync response will be kept in memory.`,
|
|
Example: " netbird debug persistence on",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: setSyncResponsePersistence,
|
|
}
|
|
|
|
var debugConfigCmd = &cobra.Command{
|
|
Use: "config",
|
|
Example: " netbird debug config",
|
|
Short: "Dump the effective configuration",
|
|
Long: "Prints the daemon's resolved configuration (after applying defaults, file, env, CLI input, and MDM policy overrides) as JSON. Includes the list of MDM-managed fields.",
|
|
RunE: debugConfigDump,
|
|
}
|
|
|
|
// debugConfigDump implements `netbird debug config`. It resolves the
|
|
// active profile, queries the daemon for the effective configuration
|
|
// via GetConfig, and prints the resulting GetConfigResponse as JSON
|
|
// (via protojson with EmitUnpopulated=true so the output is stable
|
|
// across runs and includes zero-valued fields).
|
|
//
|
|
// Useful for verifying MDM enforcement end-to-end: the response's
|
|
// mDMManagedFields array is the single source of truth for "which
|
|
// fields is the daemon currently enforcing from the MDM source", and
|
|
// every config field side-by-side with that list confirms the merge
|
|
// result. Secrets in the response (e.g. PreSharedKey) are already
|
|
// redacted by the daemon-side handler.
|
|
func debugConfigDump(cmd *cobra.Command, _ []string) error {
|
|
pm := profilemanager.NewProfileManager()
|
|
activeProf, err := pm.GetActiveProfile()
|
|
if err != nil {
|
|
return fmt.Errorf("get active profile: %v", err)
|
|
}
|
|
currUser, err := profilemanager.InvokingUser()
|
|
if err != nil {
|
|
return fmt.Errorf("get current user: %v", err)
|
|
}
|
|
|
|
conn, err := getClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
log.Errorf(errCloseConnection, err)
|
|
}
|
|
}()
|
|
|
|
client := proto.NewDaemonServiceClient(conn)
|
|
resp, err := client.GetConfig(cmd.Context(), &proto.GetConfigRequest{
|
|
ProfileName: string(activeProf.ID),
|
|
Username: currUser.Username,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get config: %v", status.Convert(err).Message())
|
|
}
|
|
|
|
// Use protojson so well-known fields render correctly; emit defaults so
|
|
// the operator sees every field even when zero/empty.
|
|
m := protojson.MarshalOptions{Multiline: true, Indent: " ", EmitUnpopulated: true}
|
|
out, err := m.Marshal(resp)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal config: %w", err)
|
|
}
|
|
cmd.Println(string(out))
|
|
return nil
|
|
}
|
|
|
|
// debugBundle requests the daemon to create a debug bundle and prints
|
|
// the resulting local file path and, if uploaded, the uploaded file
|
|
// key. It uses the package flags (anonymize, system info, log file
|
|
// count, CLI version, optional upload URL) to configure the bundle
|
|
// request. Returns an error if the RPC fails or if the daemon reports
|
|
// an upload failure reason.
|
|
func debugBundle(cmd *cobra.Command, _ []string) error {
|
|
anonymizeEnabled, anonymizeLevel, err := effectiveAnonymize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conn, err := getClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
log.Errorf(errCloseConnection, err)
|
|
}
|
|
}()
|
|
|
|
client := proto.NewDaemonServiceClient(conn)
|
|
request := &proto.DebugBundleRequest{
|
|
Anonymize: anonymizeEnabled,
|
|
AnonymizeLevel: anonymizeLevel.String(),
|
|
SystemInfo: systemInfoFlag,
|
|
LogFileCount: logFileCount,
|
|
CliVersion: version.NetbirdVersion(),
|
|
}
|
|
if uploadBundleFlag {
|
|
request.Upload = true
|
|
request.UploadURL = uploadBundleURLFlag
|
|
request.UploadInsecure = uploadBundleInsecureFlag
|
|
}
|
|
resp, err := client.DebugBundle(cmd.Context(), request)
|
|
if err != nil {
|
|
return daemonCallError("bundle debug", err)
|
|
}
|
|
cmd.Printf("Local file:\n%s\n", resp.GetPath())
|
|
|
|
if resp.GetUploadFailureReason() != "" {
|
|
return fmt.Errorf("upload failed: %s", resp.GetUploadFailureReason())
|
|
}
|
|
|
|
if err := printUploadKey(cmd, resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setLogLevel(cmd *cobra.Command, args []string) error {
|
|
conn, err := getClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
log.Errorf(errCloseConnection, err)
|
|
}
|
|
}()
|
|
|
|
client := proto.NewDaemonServiceClient(conn)
|
|
level := server.ParseLogLevel(args[0])
|
|
if level == proto.LogLevel_UNKNOWN {
|
|
//nolint
|
|
return fmt.Errorf("unknown log level: %s. Available levels are: panic, fatal, error, warn, info, debug, trace\n", args[0])
|
|
}
|
|
|
|
_, err = client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{
|
|
Level: level,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set log level: %v", status.Convert(err).Message())
|
|
}
|
|
|
|
cmd.Println("Log level set successfully to", args[0])
|
|
return nil
|
|
}
|
|
|
|
func runForDuration(cmd *cobra.Command, args []string) error {
|
|
duration, err := time.ParseDuration(args[0])
|
|
if err != nil {
|
|
return fmt.Errorf("invalid duration format: %v", err)
|
|
}
|
|
|
|
anonymizeEnabled, anonymizeLevel, err := effectiveAnonymize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conn, err := getClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
log.Errorf(errCloseConnection, err)
|
|
}
|
|
}()
|
|
|
|
client := proto.NewDaemonServiceClient(conn)
|
|
|
|
stat, err := client.Status(cmd.Context(), &proto.StatusRequest{ShouldRunProbes: true})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get status: %v", status.Convert(err).Message())
|
|
}
|
|
|
|
stateWasDown := stat.Status != string(internal.StatusConnected) && stat.Status != string(internal.StatusConnecting)
|
|
|
|
initialLogLevel, err := client.GetLogLevel(cmd.Context(), &proto.GetLogLevelRequest{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get log level: %v", status.Convert(err).Message())
|
|
}
|
|
|
|
if stateWasDown {
|
|
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to bring service up: %v\n", status.Convert(err).Message())
|
|
} else {
|
|
cmd.Println("netbird up")
|
|
time.Sleep(time.Second * 10)
|
|
}
|
|
}
|
|
|
|
initialLevelTrace := initialLogLevel.GetLevel() >= proto.LogLevel_TRACE
|
|
if !initialLevelTrace {
|
|
_, err = client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{
|
|
Level: proto.LogLevel_TRACE,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set log level to TRACE: %v", status.Convert(err).Message())
|
|
}
|
|
cmd.Println("Log level set to trace.")
|
|
}
|
|
|
|
needsRestoreUp := false
|
|
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to bring service down: %v\n", status.Convert(err).Message())
|
|
} else {
|
|
needsRestoreUp = !stateWasDown
|
|
cmd.Println("netbird down")
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// Enable sync response persistence before bringing the service up
|
|
if _, err := client.SetSyncResponsePersistence(cmd.Context(), &proto.SetSyncResponsePersistenceRequest{
|
|
Enabled: true,
|
|
}); err != nil {
|
|
cmd.PrintErrf("Failed to enable sync response persistence: %v\n", status.Convert(err).Message())
|
|
}
|
|
|
|
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to bring service up: %v\n", status.Convert(err).Message())
|
|
} else {
|
|
needsRestoreUp = false
|
|
cmd.Println("netbird up")
|
|
}
|
|
|
|
time.Sleep(3 * time.Second)
|
|
|
|
cpuProfilingStarted := false
|
|
if _, err := client.StartCPUProfile(cmd.Context(), &proto.StartCPUProfileRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to start CPU profiling: %v\n", err)
|
|
} else {
|
|
cpuProfilingStarted = true
|
|
defer func() {
|
|
if cpuProfilingStarted {
|
|
if _, err := client.StopCPUProfile(cmd.Context(), &proto.StopCPUProfileRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to stop CPU profiling: %v\n", err)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
captureStarted := false
|
|
if wantCapture, _ := cmd.Flags().GetBool("capture"); wantCapture {
|
|
captureTimeout := duration + 30*time.Second
|
|
const maxBundleCapture = 10 * time.Minute
|
|
if captureTimeout > maxBundleCapture {
|
|
captureTimeout = maxBundleCapture
|
|
}
|
|
_, err := client.StartBundleCapture(cmd.Context(), &proto.StartBundleCaptureRequest{
|
|
Timeout: durationpb.New(captureTimeout),
|
|
})
|
|
if err != nil {
|
|
cmd.PrintErrf("Failed to start packet capture: %v\n", status.Convert(err).Message())
|
|
} else {
|
|
captureStarted = true
|
|
cmd.Println("Packet capture started.")
|
|
// Safety: always stop on exit, even if the normal stop below runs too.
|
|
defer func() {
|
|
if captureStarted {
|
|
stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if _, err := client.StopBundleCapture(stopCtx, &proto.StopBundleCaptureRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to stop packet capture: %v\n", err)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
if waitErr := waitForDurationOrCancel(cmd.Context(), duration, cmd); waitErr != nil {
|
|
return waitErr
|
|
}
|
|
cmd.Println("\nDuration completed")
|
|
|
|
if captureStarted {
|
|
stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if _, err := client.StopBundleCapture(stopCtx, &proto.StopBundleCaptureRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to stop packet capture: %v\n", err)
|
|
} else {
|
|
captureStarted = false
|
|
cmd.Println("Packet capture stopped.")
|
|
}
|
|
}
|
|
|
|
if cpuProfilingStarted {
|
|
if _, err := client.StopCPUProfile(cmd.Context(), &proto.StopCPUProfileRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to stop CPU profiling: %v\n", err)
|
|
} else {
|
|
cpuProfilingStarted = false
|
|
}
|
|
}
|
|
|
|
cmd.Println("Creating debug bundle...")
|
|
|
|
request := &proto.DebugBundleRequest{
|
|
Anonymize: anonymizeEnabled,
|
|
AnonymizeLevel: anonymizeLevel.String(),
|
|
SystemInfo: systemInfoFlag,
|
|
LogFileCount: logFileCount,
|
|
CliVersion: version.NetbirdVersion(),
|
|
}
|
|
if uploadBundleFlag {
|
|
request.Upload = true
|
|
request.UploadURL = uploadBundleURLFlag
|
|
request.UploadInsecure = uploadBundleInsecureFlag
|
|
}
|
|
resp, err := client.DebugBundle(cmd.Context(), request)
|
|
if err != nil {
|
|
return daemonCallError("bundle debug", err)
|
|
}
|
|
|
|
if needsRestoreUp {
|
|
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to restore service up state: %v\n", status.Convert(err).Message())
|
|
} else {
|
|
cmd.Println("netbird up (restored)")
|
|
}
|
|
}
|
|
|
|
if stateWasDown {
|
|
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
|
cmd.PrintErrf("Failed to restore service down state: %v\n", status.Convert(err).Message())
|
|
} else {
|
|
cmd.Println("netbird down")
|
|
}
|
|
}
|
|
|
|
if !initialLevelTrace {
|
|
if _, err := client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{Level: initialLogLevel.GetLevel()}); err != nil {
|
|
cmd.PrintErrf("Failed to restore log level: %v\n", status.Convert(err).Message())
|
|
} else {
|
|
cmd.Println("Log level restored to", initialLogLevel.GetLevel())
|
|
}
|
|
}
|
|
|
|
cmd.Printf("Local file:\n%s\n", resp.GetPath())
|
|
|
|
if resp.GetUploadFailureReason() != "" {
|
|
return fmt.Errorf("upload failed: %s", resp.GetUploadFailureReason())
|
|
}
|
|
|
|
if err := printUploadKey(cmd, resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setSyncResponsePersistence(cmd *cobra.Command, args []string) error {
|
|
conn, err := getClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
log.Errorf(errCloseConnection, err)
|
|
}
|
|
}()
|
|
|
|
persistence := strings.ToLower(args[0])
|
|
if persistence != "on" && persistence != "off" {
|
|
return fmt.Errorf("invalid persistence value: %s. Use 'on' or 'off'", args[0])
|
|
}
|
|
|
|
client := proto.NewDaemonServiceClient(conn)
|
|
_, err = client.SetSyncResponsePersistence(cmd.Context(), &proto.SetSyncResponsePersistenceRequest{
|
|
Enabled: persistence == "on",
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set sync response persistence: %v", status.Convert(err).Message())
|
|
}
|
|
|
|
cmd.Printf("Sync response persistence set to: %s\n", persistence)
|
|
return nil
|
|
}
|
|
|
|
func waitForDurationOrCancel(ctx context.Context, duration time.Duration, cmd *cobra.Command) error {
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
startTime := time.Now()
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
elapsed := time.Since(startTime)
|
|
if elapsed >= duration {
|
|
return
|
|
}
|
|
remaining := duration - elapsed
|
|
cmd.Printf("\rRemaining time: %s", formatDuration(remaining))
|
|
}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-done:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func formatDuration(d time.Duration) string {
|
|
d = d.Round(time.Second)
|
|
h := d / time.Hour
|
|
d %= time.Hour
|
|
m := d / time.Minute
|
|
d %= time.Minute
|
|
s := d / time.Second
|
|
return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
|
|
}
|
|
|
|
func generateDebugBundle(config *profilemanager.Config, recorder *peer.Status, connectClient *internal.ConnectClient, logFilePath string) {
|
|
var syncResponse *mgmProto.SyncResponse
|
|
var err error
|
|
|
|
if connectClient != nil {
|
|
syncResponse, err = connectClient.GetLatestSyncResponse()
|
|
if err != nil {
|
|
log.Warnf("Failed to get latest sync response: %v", err)
|
|
}
|
|
}
|
|
|
|
bundleGenerator := debug.NewBundleGenerator(
|
|
debug.GeneratorDependencies{
|
|
InternalConfig: config,
|
|
StatusRecorder: recorder,
|
|
SyncResponse: syncResponse,
|
|
LogPath: logFilePath,
|
|
CPUProfile: nil,
|
|
DaemonVersion: version.NetbirdVersion(), // acting as daemon
|
|
},
|
|
debug.BundleConfig{
|
|
IncludeSystemInfo: true,
|
|
},
|
|
)
|
|
|
|
path, err := bundleGenerator.Generate()
|
|
if err != nil {
|
|
log.Errorf("Failed to generate debug bundle: %v", err)
|
|
return
|
|
}
|
|
log.Infof("Generated debug bundle from SIGUSR1 at: %s", path)
|
|
}
|
|
|
|
// printUploadKey reports the upload key, or why there is none. A daemon that
|
|
// predates the destination-from-management change ignores an empty upload URL
|
|
// and returns neither a key nor a failure reason, which would otherwise print
|
|
// as an empty key.
|
|
func printUploadKey(cmd *cobra.Command, resp *proto.DebugBundleResponse) error {
|
|
if !uploadBundleFlag {
|
|
return nil
|
|
}
|
|
if resp.GetUploadedKey() == "" {
|
|
return errors.New("the daemon did not upload the bundle; pass --upload-bundle-url explicitly or update the daemon")
|
|
}
|
|
cmd.Printf("Upload file key:\n%s\n", resp.GetUploadedKey())
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
debugBundleCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 1, "Number of rotated log files to include in debug bundle")
|
|
debugBundleCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle")
|
|
debugBundleCmd.Flags().BoolVarP(&uploadBundleFlag, "upload-bundle", "U", false, "Uploads the debug bundle to a server")
|
|
debugBundleCmd.Flags().StringVar(&uploadBundleURLFlag, "upload-bundle-url", "", uploadBundleURLUsage)
|
|
debugBundleCmd.Flags().BoolVar(&uploadBundleInsecureFlag, "upload-bundle-insecure", false, "Allow uploading to an http or untrusted-TLS upload server (self-hosted); requires root")
|
|
|
|
forCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 1, "Number of rotated log files to include in debug bundle")
|
|
forCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle")
|
|
forCmd.Flags().BoolVarP(&uploadBundleFlag, "upload-bundle", "U", false, "Uploads the debug bundle to a server")
|
|
forCmd.Flags().StringVar(&uploadBundleURLFlag, "upload-bundle-url", "", uploadBundleURLUsage)
|
|
forCmd.Flags().BoolVar(&uploadBundleInsecureFlag, "upload-bundle-insecure", false, "Allow uploading to an http or untrusted-TLS upload server (self-hosted); requires root")
|
|
forCmd.Flags().Bool("capture", false, "Capture packets during the debug duration and include in bundle")
|
|
}
|