From d9b2e979b55479a46b286a197bd840e89f756d15 Mon Sep 17 00:00:00 2001 From: Cezar Craciunoiu Date: Wed, 2 Sep 2026 14:42:53 +0300 Subject: [PATCH 1/5] feat(config): Store user and org UUIDs on profile Profiles only store the organization name, which is mutable and is not the identity the console uses when it reports PostHog events. Add user_uuid and organization_uuid to the profile so a login can record the same identifiers the console reports and telemetry can read them back later. Signed-off-by: Cezar Craciunoiu --- cmd/unikraft/testdata/TestHelp/config | 18 ++++++++++-------- internal/config/profile.go | 4 ++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/cmd/unikraft/testdata/TestHelp/config b/cmd/unikraft/testdata/TestHelp/config index 63926df0..a7725789 100644 --- a/cmd/unikraft/testdata/TestHelp/config +++ b/cmd/unikraft/testdata/TestHelp/config @@ -13,10 +13,11 @@ Fields: path profile profiles, profiles.*, profiles.*.name, profiles.*.type, profiles.*.token, - profiles.*.organization, profiles.*.control-plane, profiles.*.insecure, - profiles.*.metros, profiles.*.metros.*, profiles.*.metros.*.name, - profiles.*.metros.*.endpoint, profiles.*.metros.*.location, - profiles.*.metros.*.insecure, profiles.*.metro-default + profiles.*.organization, profiles.*.user-uuid, profiles.*.organization-uuid, + profiles.*.control-plane, profiles.*.insecure, profiles.*.metros, + profiles.*.metros.*, profiles.*.metros.*.name, profiles.*.metros.*.endpoint, + profiles.*.metros.*.location, profiles.*.metros.*.insecure, profiles.*.metro- + default Global flags: -h, --help @@ -56,10 +57,11 @@ Fields: path profile profiles, profiles.*, profiles.*.name, profiles.*.type, profiles.*.token, - profiles.*.organization, profiles.*.control-plane, profiles.*.insecure, - profiles.*.metros, profiles.*.metros.*, profiles.*.metros.*.name, - profiles.*.metros.*.endpoint, profiles.*.metros.*.location, - profiles.*.metros.*.insecure, profiles.*.metro-default + profiles.*.organization, profiles.*.user-uuid, profiles.*.organization-uuid, + profiles.*.control-plane, profiles.*.insecure, profiles.*.metros, + profiles.*.metros.*, profiles.*.metros.*.name, profiles.*.metros.*.endpoint, + profiles.*.metros.*.location, profiles.*.metros.*.insecure, profiles.*.metro- + default Flags: -w, --watch= diff --git a/internal/config/profile.go b/internal/config/profile.go index 8b5f7a46..df8555a2 100644 --- a/internal/config/profile.go +++ b/internal/config/profile.go @@ -73,6 +73,10 @@ type Profile struct { Token string `json:"token,omitempty" field:",long"` // Organization is the organization associated with the profile. Organization string `json:"organization,omitempty" field:",short"` + // UserUUID is the UUID of the user who approved the login, if known. + UserUUID string `json:"user_uuid,omitempty" field:",long"` + // OrganizationUUID is the UUID of the organization of the token. + OrganizationUUID string `json:"organization_uuid,omitempty" field:",long"` // ControlPlane is the endpoint for the control plane associated with the profile. ControlPlane string `json:"controlplane,omitempty" field:",long"` // Insecure indicates whether to allow insecure connections to the control plane, skipping TLS verification. From 42eabe9441e6b31e9640f214fc47ae9de6c319ff Mon Sep 17 00:00:00 2001 From: Cezar Craciunoiu Date: Wed, 2 Sep 2026 14:43:53 +0300 Subject: [PATCH 2/5] feat(login): Store user and org UUIDs at login The login flow only kept the organization name, and the token path never asked the control plane for organization details when the name was given on the command line. Record the user and organization UUIDs from the login check, and query the control plane for the organization UUID whenever it is still missing. A failed query is only fatal when the name is also unknown, which keeps the previous behaviour. Signed-off-by: Cezar Craciunoiu --- internal/cmd/login/login.go | 44 +++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/internal/cmd/login/login.go b/internal/cmd/login/login.go index 23b9535c..360d1d47 100644 --- a/internal/cmd/login/login.go +++ b/internal/cmd/login/login.go @@ -61,7 +61,7 @@ func (cmd *LoginCmd) Run(ctx context.Context, cfg *config.Config) error { } // Get the token either from file or via browser authentication - var token, organization string + var token, organization, userUUID, orgUUID string if cmd.Token != nil { log.G(ctx).Info(). Msg("reading authentication token from file") @@ -88,20 +88,30 @@ func (cmd *LoginCmd) Run(ctx context.Context, cfg *config.Config) error { } token = resp.Data.Token organization = cmp.Or(resp.Data.OrganizationName, cmd.Organization) + userUUID = resp.Data.UserUuid + orgUUID = resp.Data.OrganizationUuid } tempProfile.Token = token - if organization == "" { - var err error - organization, err = cmd.getOrg(ctx, tempProfile) - if err != nil { + if orgUUID == "" || organization == "" { + auth, err := cmd.getAuthorization(ctx, tempProfile) + switch { + case err != nil && organization == "": log.G(ctx).Error(). Msg("could not determine organization from control plane, specify organization with --organization flag") return err - } else { - log.G(ctx).Info(). - Str("organization", organization). - Msg("found organization from control plane") + case err != nil: + log.G(ctx).Warn(). + Err(err). + Msg("could not fetch organization details from control plane") + default: + if organization == "" { + log.G(ctx).Info(). + Str("organization", auth.OrganizationName). + Msg("found organization from control plane") + } + organization = cmp.Or(organization, auth.OrganizationName) + orgUUID = auth.OrganizationUuid } } @@ -120,6 +130,12 @@ func (cmd *LoginCmd) Run(ctx context.Context, cfg *config.Config) error { } profile.Token = token profile.Organization = organization + if userUUID != "" { + profile.UserUUID = userUUID + } + if orgUUID != "" { + profile.OrganizationUUID = orgUUID + } profile.ControlPlane = loginControlPlane profile.Insecure = cmd.AllowInsecure @@ -293,22 +309,22 @@ func (cmd *LoginCmd) getAuth(ctx context.Context, profile *config.Profile) (*con } } -func (cmd *LoginCmd) getOrg(ctx context.Context, profile *config.Profile) (string, error) { +func (cmd *LoginCmd) getAuthorization(ctx context.Context, profile *config.Profile) (*controlplane.GetAuthorizationResponseData, error) { log.G(ctx).Trace(). Str("controlplane", profile.ControlPlane). Msg("fetching organization") client, err := multimetro.NewControlClientFromProfile(profile) if err != nil { - return "", err + return nil, err } resp, err := client.GetAuthorization(ctx) if err != nil { - return "", jujuerrors.Annotate(err, "getting authorization") + return nil, jujuerrors.Annotate(err, "getting authorization") } if resp.Data == nil || resp.Data.OrganizationName == "" { - return "", jujuerrors.New("no organization name received from control plane") + return nil, jujuerrors.New("no organization name received from control plane") } - return resp.Data.OrganizationName, nil + return resp.Data, nil } From de0bbd8d4009c6c79a68914cfd6f48d70a522a84 Mon Sep 17 00:00:00 2001 From: Cezar Craciunoiu Date: Wed, 2 Sep 2026 14:45:35 +0300 Subject: [PATCH 3/5] feat(telemetry): Key events on user and org UUIDs Every CLI event used a machine fingerprint hash as its distinct ID, so PostHog could not connect CLI usage to the person and company the console already reports. Use the profile user UUID as the distinct ID when it is known and add an organization group from the profile org UUID. Events without a user are marked anonymous, so PostHog keeps no Person for a machine that several people can share. Signed-off-by: Cezar Craciunoiu --- CONTRIBUTING.md | 2 +- README.md | 7 ++- cmd/unikraft/main.go | 17 +++--- cmd/unikraft/testdata/TestHelp/api | 2 +- cmd/unikraft/testdata/TestHelp/auth | 20 +++--- cmd/unikraft/testdata/TestHelp/build | 2 +- cmd/unikraft/testdata/TestHelp/certificates | 12 ++-- cmd/unikraft/testdata/TestHelp/config | 4 +- cmd/unikraft/testdata/TestHelp/general | 8 +-- cmd/unikraft/testdata/TestHelp/images | 10 +-- cmd/unikraft/testdata/TestHelp/instances | 68 ++++++++++----------- cmd/unikraft/testdata/TestHelp/resources | 4 +- cmd/unikraft/testdata/TestHelp/run | 2 +- cmd/unikraft/testdata/TestHelp/services | 14 ++--- cmd/unikraft/testdata/TestHelp/volumes | 34 +++++------ internal/cmd/root.go | 2 +- internal/telemetry/detach.go | 11 +++- internal/telemetry/telemetry.go | 27 ++++++-- internal/telemetry/track.go | 4 ++ 19 files changed, 140 insertions(+), 110 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4fc436f6..3914a447 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,7 +87,7 @@ The CLI follows a resource-oriented architecture: - **Resources** (`internal/resource/`) — Unified interface for API objects with field introspection - **Multi-Metro** (`internal/multimetro/`) — Client abstraction for global infrastructure operations - **Configuration** (`internal/config/`) — Profile and credential management -- **Telemetry** (`internal/telemetry/`) — Anonymous usage analytics (opt-out via `--no-telemetry`) +- **Telemetry** (`internal/telemetry/`) — Usage analytics (opt-out via `--no-telemetry`) ### Key Dependencies diff --git a/README.md b/README.md index 9e608e35..7ba5a58e 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,7 @@ The CLI stores configuration in `~/.config/unikraft/config.yaml` (or the path sp | `UNIKRAFT_PROFILE_ENV` | Use a virtual profile from `UKC_*` env vars instead of config (`true`, `false`) | | `UNIKRAFT_LOG_LEVEL` | Set log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | | `UNIKRAFT_LOG_TYPE` | Set output format (`text`, `json`) | -| `UNIKRAFT_TELEMETRY` | Enable/disable anonymous telemetry (`true`, `false`) | +| `UNIKRAFT_TELEMETRY` | Enable/disable usage analytics (`true`, `false`) | | `UKC_TOKEN` | API token for legacy profiles / `UNIKRAFT_PROFILE_ENV` | | `UKC_METRO` | Metro name (e.g. `fra`) or full endpoint URL for legacy profiles / `UNIKRAFT_PROFILE_ENV` | | `UKC_ALLOW_INSECURE` | Skip TLS verification for the metro from `UKC_METRO` (`true`, `false`) | @@ -417,8 +417,9 @@ unikraft completion -c powershell > unikraft.ps1 ## Telemetry -The CLI collects anonymous usage analytics to improve the product. -No personally identifiable information is collected. +The CLI collects usage analytics to improve the product. +When you are logged in, events are keyed to the user and organization UUIDs of the active profile. +Otherwise, events use an anonymous machine hash. To opt out: ```sh diff --git a/cmd/unikraft/main.go b/cmd/unikraft/main.go index 378ec884..df5f5dbe 100644 --- a/cmd/unikraft/main.go +++ b/cmd/unikraft/main.go @@ -154,12 +154,12 @@ func run(ctx context.Context, args []string, stdio config.Stdio) (context.Contex // Initialize analytics if telemetry is enabled. // - // These are anonymous usage analytics, and no personally identifiable - // information is collected. This information is used to help us understand - // how the CLI is being used and to improve it over time. We may collect - // information such as which commands are used, how often they are used, and - // any errors that occur. This data is aggregated and analyzed to identify - // trends and areas for improvement. + // These usage analytics are keyed to the logged-in account when there is + // one, and anonymous otherwise. This information is used to help us + // understand how the CLI is being used and to improve it over time. We may + // collect information such as which commands are used, how often they are + // used, and any errors that occur. This data is aggregated and analyzed to + // identify trends and areas for improvement. // // Unikraft is committed to user privacy and data protection; visit[0] for // more information. @@ -173,7 +173,8 @@ func run(ctx context.Context, args []string, stdio config.Stdio) (context.Contex // for our CLI. _, doNotTrack := os.LookupEnv("DO_NOT_TRACK") if !doNotTrack && opts.Telemetry && !isSendAnalytics { - if err := telemetry.Init(); err != nil { + profile, _ := config.G(ctx).CurrentProfile() + if err := telemetry.Init(profile); err != nil { log.G(ctx). Debug(). Err(err). @@ -181,7 +182,7 @@ func run(ctx context.Context, args []string, stdio config.Stdio) (context.Contex } else { log.G(ctx). Debug(). - Msg("collecting anonymous usage analytics, set `UNIKRAFT_TELEMETRY=false` to disable") + Msg("collecting usage analytics, set `UNIKRAFT_TELEMETRY=false` to disable") } } diff --git a/cmd/unikraft/testdata/TestHelp/api b/cmd/unikraft/testdata/TestHelp/api index 6f90cb5a..b6f6988d 100644 --- a/cmd/unikraft/testdata/TestHelp/api +++ b/cmd/unikraft/testdata/TestHelp/api @@ -199,7 +199,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/auth b/cmd/unikraft/testdata/TestHelp/auth index d4554e1c..d0becdeb 100644 --- a/cmd/unikraft/testdata/TestHelp/auth +++ b/cmd/unikraft/testdata/TestHelp/auth @@ -34,7 +34,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -60,7 +60,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -99,7 +99,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -146,7 +146,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -199,7 +199,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -232,7 +232,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -287,7 +287,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -352,7 +352,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -429,7 +429,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -461,7 +461,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/build b/cmd/unikraft/testdata/TestHelp/build index 43b94e84..d91e06cc 100644 --- a/cmd/unikraft/testdata/TestHelp/build +++ b/cmd/unikraft/testdata/TestHelp/build @@ -60,7 +60,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/certificates b/cmd/unikraft/testdata/TestHelp/certificates index 4bcb6af3..2a5a19ef 100644 --- a/cmd/unikraft/testdata/TestHelp/certificates +++ b/cmd/unikraft/testdata/TestHelp/certificates @@ -45,7 +45,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -101,7 +101,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -163,7 +163,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -219,7 +219,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -288,7 +288,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -363,7 +363,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/config b/cmd/unikraft/testdata/TestHelp/config index a7725789..091d5b46 100644 --- a/cmd/unikraft/testdata/TestHelp/config +++ b/cmd/unikraft/testdata/TestHelp/config @@ -33,7 +33,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -85,7 +85,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/general b/cmd/unikraft/testdata/TestHelp/general index 8686698c..8ee66168 100644 --- a/cmd/unikraft/testdata/TestHelp/general +++ b/cmd/unikraft/testdata/TestHelp/general @@ -86,7 +86,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -188,7 +188,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -291,7 +291,7 @@ Examples: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -392,7 +392,7 @@ Examples: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/images b/cmd/unikraft/testdata/TestHelp/images index 0dca8ca7..bb6cc8a2 100644 --- a/cmd/unikraft/testdata/TestHelp/images +++ b/cmd/unikraft/testdata/TestHelp/images @@ -36,7 +36,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -104,7 +104,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -160,7 +160,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -218,7 +218,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -265,7 +265,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/instances b/cmd/unikraft/testdata/TestHelp/instances index 41aead5a..ef718009 100644 --- a/cmd/unikraft/testdata/TestHelp/instances +++ b/cmd/unikraft/testdata/TestHelp/instances @@ -104,7 +104,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -185,7 +185,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -272,7 +272,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -353,7 +353,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -464,7 +464,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -661,7 +661,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -869,7 +869,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1065,7 +1065,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1196,7 +1196,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1257,7 +1257,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1322,7 +1322,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1393,7 +1393,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1466,7 +1466,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1549,7 +1549,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1630,7 +1630,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1694,7 +1694,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1758,7 +1758,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1828,7 +1828,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1892,7 +1892,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1964,7 +1964,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2046,7 +2046,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2126,7 +2126,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2166,7 +2166,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2215,7 +2215,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2260,7 +2260,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2311,7 +2311,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2357,7 +2357,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2410,7 +2410,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2463,7 +2463,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2503,7 +2503,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2563,7 +2563,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2626,7 +2626,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2680,7 +2680,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -2737,7 +2737,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/resources b/cmd/unikraft/testdata/TestHelp/resources index a5ea6f56..3c9a1539 100644 --- a/cmd/unikraft/testdata/TestHelp/resources +++ b/cmd/unikraft/testdata/TestHelp/resources @@ -35,7 +35,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -82,7 +82,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/run b/cmd/unikraft/testdata/TestHelp/run index 44a662f8..86fce8aa 100644 --- a/cmd/unikraft/testdata/TestHelp/run +++ b/cmd/unikraft/testdata/TestHelp/run @@ -114,7 +114,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/services b/cmd/unikraft/testdata/TestHelp/services index 39bb2047..efd8aae3 100644 --- a/cmd/unikraft/testdata/TestHelp/services +++ b/cmd/unikraft/testdata/TestHelp/services @@ -48,7 +48,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -105,7 +105,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -168,7 +168,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -225,7 +225,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -290,7 +290,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -382,7 +382,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -462,7 +462,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/cmd/unikraft/testdata/TestHelp/volumes b/cmd/unikraft/testdata/TestHelp/volumes index ad856368..d0cee107 100644 --- a/cmd/unikraft/testdata/TestHelp/volumes +++ b/cmd/unikraft/testdata/TestHelp/volumes @@ -66,7 +66,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -129,7 +129,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -198,7 +198,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -261,7 +261,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -331,7 +331,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -397,7 +397,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -454,7 +454,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -496,7 +496,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -545,7 +545,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -620,7 +620,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -701,7 +701,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -752,7 +752,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -807,7 +807,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -868,7 +868,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -931,7 +931,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -998,7 +998,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). @@ -1065,7 +1065,7 @@ Global flags: --profile= ($UNIKRAFT_PROFILE) Set the current profile. --[no-]telemetry ($UNIKRAFT_TELEMETRY) - Toggle anonymous usage analytics. + Toggle usage analytics. [default: true] --timeout= ($UNIKRAFT_TIMEOUT) Set a deadline for the command (e.g. 30s, 5m, 1h). diff --git a/internal/cmd/root.go b/internal/cmd/root.go index ddb58f97..2030b153 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -110,7 +110,7 @@ type globalFlags struct { Profile string `group:"flag-global" name:"profile" env:"UNIKRAFT_PROFILE" help:"Set the current profile." placeholder:"name"` - Telemetry bool `group:"flag-global" name:"telemetry" env:"UNIKRAFT_TELEMETRY" help:"Toggle anonymous usage analytics." default:"true" negatable:""` + Telemetry bool `group:"flag-global" name:"telemetry" env:"UNIKRAFT_TELEMETRY" help:"Toggle usage analytics." default:"true" negatable:""` Timeout time.Duration `group:"flag-global" name:"timeout" env:"UNIKRAFT_TIMEOUT" help:"Set a deadline for the command (e.g. 30s, 5m, 1h)." placeholder:"duration" optional:""` } diff --git a/internal/telemetry/detach.go b/internal/telemetry/detach.go index b91ea9f3..6c059813 100644 --- a/internal/telemetry/detach.go +++ b/internal/telemetry/detach.go @@ -26,7 +26,16 @@ func spawnDetachedAnalytics(event posthog.Capture) { return } - payload, err := json.Marshal(event.APIfy()) + if anonymous { + event.Properties.Set("$process_person_profile", false) + } + + payload, err := json.Marshal(EventPayload{ + Event: event.Event, + DistinctID: event.DistinctId, + Properties: event.Properties, + Groups: event.Groups, + }) if err != nil { return } diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index 53d6b145..9184e57c 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -22,6 +22,8 @@ import ( "unikraft.com/x/fingerprint" "unikraft.com/x/version" + + "unikraft.com/cli/internal/config" ) var ( @@ -37,8 +39,10 @@ var ( var ( distinctID string sessionID string + groups posthog.Groups enabled bool mu sync.Mutex + anonymous bool // commandStart tracks when the current command started for duration calculation. commandStart time.Time @@ -53,13 +57,13 @@ var ( type EventPayload struct { Event string `json:"event"` DistinctID string `json:"distinct_id"` - SessionID string `json:"session_id"` Properties map[string]any `json:"properties"` - Timestamp time.Time `json:"timestamp"` + Groups posthog.Groups `json:"groups,omitempty"` } -// Init initializes the PostHog analytics client. -func Init() error { +// Init initializes the PostHog analytics client for the given profile. +// If profile is nil, Init uses the anonymous machine fingerprint. +func Init(profile *config.Profile) error { mu.Lock() defer mu.Unlock() @@ -74,8 +78,19 @@ func Init() error { return fmt.Errorf("no API key set for PostHog; use UNIKRAFT_POSTHOG_API_KEY environment variable") } - // Generate anonymous distinct ID from machine fingerprint + // Use the user UUID when known, otherwise the machine fingerprint. distinctID = generateDistinctID() + groups = nil + anonymous = true + if profile != nil { + if profile.UserUUID != "" { + distinctID = profile.UserUUID + anonymous = false + } + if profile.OrganizationUUID != "" { + groups = posthog.Groups{"organization": profile.OrganizationUUID} + } + } // Generate unique session ID for this CLI invocation. sessionID = generateSessionID() @@ -175,6 +190,6 @@ func SendEvent(payloadJSON string) error { DistinctId: payload.DistinctID, Event: payload.Event, Properties: props, - Timestamp: payload.Timestamp, + Groups: payload.Groups, }) } diff --git a/internal/telemetry/track.go b/internal/telemetry/track.go index db387748..bf16a120 100644 --- a/internal/telemetry/track.go +++ b/internal/telemetry/track.go @@ -35,6 +35,7 @@ func TrackCrash(panicValue any, stack []byte) { DistinctId: distinctID, Event: "cli_crash", Properties: props, + Groups: groups, }) } @@ -75,6 +76,7 @@ func TrackCommandStart(cmdPath string) { Properties: posthog.NewProperties(). Set("command", cmdPath). Set("session_id", sessionID), + Groups: groups, }) } @@ -96,6 +98,7 @@ func TrackCommandSuccess(cmdPath string) { Set("command", cmdPath). Set("duration_ms", duration.Milliseconds()). Set("session_id", sessionID), + Groups: groups, }) } @@ -127,5 +130,6 @@ func TrackCommandError(cmdPath string, err error) { DistinctId: distinctID, Event: "command_failed", Properties: props, + Groups: groups, }) } From 3241fd027fe4d8d612529f6e42f1d564cd00bbd3 Mon Sep 17 00:00:00 2001 From: Cezar Craciunoiu Date: Tue, 8 Sep 2026 16:01:28 +0300 Subject: [PATCH 4/5] feat(telemetry): Add userless persons for tokens A token knows the organization but not the user, so those events had no person. PostHog drops the group link when an event has no person profile, which left CI usage without a company. Give a token-only run a person keyed on the organization UUID and mark it as userless, so that the organization group holds. Every event carries the machine id, because one person now covers all the machines of an organization. Signed-off-by: Cezar Craciunoiu --- internal/telemetry/detach.go | 3 +++ internal/telemetry/telemetry.go | 25 +++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/internal/telemetry/detach.go b/internal/telemetry/detach.go index 6c059813..078f1088 100644 --- a/internal/telemetry/detach.go +++ b/internal/telemetry/detach.go @@ -26,8 +26,11 @@ func spawnDetachedAnalytics(event posthog.Capture) { return } + event.Properties.Set("machine_id", machineID) if anonymous { event.Properties.Set("$process_person_profile", false) + } else if userless { + event.Properties.Set("$set", posthog.Properties{"userless": true}) } payload, err := json.Marshal(EventPayload{ diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index 9184e57c..3b896af6 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -38,11 +38,13 @@ var ( var ( distinctID string + machineID string sessionID string groups posthog.Groups enabled bool mu sync.Mutex anonymous bool + userless bool // commandStart tracks when the current command started for duration calculation. commandStart time.Time @@ -79,17 +81,24 @@ func Init(profile *config.Profile) error { } // Use the user UUID when known, otherwise the machine fingerprint. - distinctID = generateDistinctID() + machineID = generateMachineID() + distinctID = machineID groups = nil anonymous = true + userless = true if profile != nil { - if profile.UserUUID != "" { - distinctID = profile.UserUUID - anonymous = false - } if profile.OrganizationUUID != "" { groups = posthog.Groups{"organization": profile.OrganizationUUID} } + switch { + case profile.UserUUID != "": + distinctID = profile.UserUUID + anonymous = false + userless = false + case profile.OrganizationUUID != "": + distinctID = profile.OrganizationUUID + anonymous = false + } } // Generate unique session ID for this CLI invocation. @@ -98,9 +107,9 @@ func Init(profile *config.Profile) error { return nil } -// generateDistinctID creates an anonymous distinct ID based on machine fingerprint. +// generateMachineID creates an anonymous ID from the machine fingerprint. // The ID is a SHA-256 hash to ensure privacy while maintaining consistency. -func generateDistinctID() string { +func generateMachineID() string { fp, err := fingerprint.New() if err != nil { // Fallback to hostname-based ID @@ -121,7 +130,7 @@ func generateDistinctID() string { return hex.EncodeToString(hash[:16]) } -// generateDistinctID creates a unique session ID for this CLI invocation, which +// generateSessionID creates a unique session ID for this CLI invocation, which // can be used to group events together. func generateSessionID() string { buf := make([]byte, 8) From 8dfbbaba18cf31becc59f9ec73b58050867f386e Mon Sep 17 00:00:00 2001 From: Cezar Craciunoiu Date: Tue, 8 Sep 2026 16:01:29 +0300 Subject: [PATCH 5/5] fix(config): Save the config file atomically A save truncated the config file and then wrote it again, so a failure or a crash during the write left a damaged or empty file. Write the new content to a temporary file in the same directory and rename it over the config file. The file keeps its permissions, and a file that does not exist yet becomes private, because it holds a token. Signed-off-by: Cezar Craciunoiu --- internal/config/config.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index f338c8a1..54738eb8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -68,18 +68,37 @@ func (c *Config) Save() error { if err := os.MkdirAll(filepath.Dir(c.Path), 0o755); err != nil { return jujuerrors.Annotate(err, "creating config directory") } - f, err := os.Create(c.Path) + + mode := os.FileMode(0o600) + if info, err := os.Stat(c.Path); err == nil { + mode = info.Mode().Perm() + } + + f, err := os.CreateTemp(filepath.Dir(c.Path), "."+filepath.Base(c.Path)+".*") if err != nil { - return jujuerrors.Annotate(err, "opening config file") + return jujuerrors.Annotate(err, "creating temporary config file") } + tmp := f.Name() + defer os.Remove(tmp) if _, err := f.Write(updated); err != nil { f.Close() return jujuerrors.Annotate(err, "writing config file") } + if err := f.Sync(); err != nil { + f.Close() + return jujuerrors.Annotate(err, "syncing config file") + } if err := f.Close(); err != nil { return jujuerrors.Annotate(err, "closing config file") } + + if err := os.Chmod(tmp, mode); err != nil { + return jujuerrors.Annotate(err, "setting config file permissions") + } + if err := os.Rename(tmp, c.Path); err != nil { + return jujuerrors.Annotate(err, "replacing config file") + } return nil }