netbirdio/netbird · error

--expiration must be a positive duration (e.g., 720h, 365d,

Error message

--expiration must be a positive duration (e.g., 720h, 365d, 8760h)

What it means

Validation inside create-root-key's RunE (rootkey.go:26): the --expiration flag, parsed by cobra via time.ParseDuration, was <= 0. The flag defaults to 0 and is marked required, so simply omitting it trips cobra's required-flag error first; this specific message means an explicit non-positive value was passed. Note Go durations have no 'd' unit — the '365d' example in the message itself would fail earlier at flag parsing with 'time: unknown unit "d" in "365d"'.

Source

Thrown at client/cmd/signer/rootkey.go:27

	"github.com/netbirdio/netbird/client/internal/updater/reposign"
)

var (
	privKeyFile    string
	pubKeyFile     string
	rootExpiration time.Duration
)

var createRootKeyCmd = &cobra.Command{
	Use:          "create-root-key",
	Short:        "Create a new root key pair",
	Long:         `Create a new root key pair and specify an expiration time for it.`,
	SilenceUsage: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Validate expiration
		if rootExpiration <= 0 {
			return fmt.Errorf("--expiration must be a positive duration (e.g., 720h, 365d, 8760h)")
		}

		// Run main logic
		if err := handleGenerateRootKey(cmd, privKeyFile, pubKeyFile, rootExpiration); err != nil {
			return fmt.Errorf("failed to generate root key: %w", err)
		}
		return nil
	},
}

func init() {
	rootCmd.AddCommand(createRootKeyCmd)
	createRootKeyCmd.Flags().StringVar(&privKeyFile, "priv-key-file", "", "Path to output private key file")
	createRootKeyCmd.Flags().StringVar(&pubKeyFile, "pub-key-file", "", "Path to output public key file")
	createRootKeyCmd.Flags().DurationVar(&rootExpiration, "expiration", 0, "Expiration time for the root key (e.g., 720h,)")

	if err := createRootKeyCmd.MarkFlagRequired("priv-key-file"); err != nil {
		panic(err)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Pass a positive duration in Go units — hours is most practical: --expiration 8760h for 1 year, 720h for 30 days
  2. Do not use d suffixes; convert days to hours (days * 24) in scripts
  3. If scripting the value, guard it: [ "$DAYS" -gt 0 ] && FLAG="--expiration $((DAYS * 24))h"

Example fix

# before
signer create-root-key --priv-key-file root.pem --pub-key-file root-public.pem --expiration 365d
# (cobra: invalid argument "365d" for "--expiration" flag: time: unknown unit)

# after
signer create-root-key --priv-key-file root.pem --pub-key-file root-public.pem --expiration 8760h
Defensive patterns

Strategy: validation

Validate before calling

func parseExpirationFlag(raw string) (time.Duration, error) {
    d, err := time.ParseDuration(raw)
    if err != nil {
        return 0, fmt.Errorf("%q is not a Go duration (use hours, e.g. 8760h): %w", raw, err)
    }
    if d <= 0 {
        return 0, fmt.Errorf("expiration must be positive, got %s", d)
    }
    return d, nil
}

Type guard

func isValidExpiration(s string) bool {
    d, err := time.ParseDuration(s)
    return err == nil && d > 0
}

Prevention

When it happens

Trigger: Running create-root-key with --expiration 0, --expiration 0s, or a negative value such as --expiration -24h; values like 365d or 30d never reach this check because cobra rejects the unit during flag parsing.

Common situations: Copy/pasting the error message's own '365d' example; scripting that computes the flag from a variable that evaluated to zero; assuming day units like Kubernetes durations.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/22b91586a8687dab. Report an issue: GitHub.