| index 1d0fd9a27..e8053f778 100644 |
| --- a/auth.go |
| +++ b/auth.go |
| @@ -48,20 +48,29 @@ import ( |
| "time" |
| ) |
| |
| -const authUsage = `usage: carlos auth <login|whoami|logout> |
| - |
| - login -console <url> log this terminal in to a deployment's console. |
| - Prints a code; you approve it in a browser that |
| - is already signed in to that console. No browser |
| - on this machine is needed — the code travels. |
| +const authUsage = `usage: carlos auth <login|whoami|logout|default> |
| + |
| + login [-console <url>] log this terminal in to a deployment's console |
| + (default: the configured default console, else |
| + https://platform.carlosframework.com). Prints a |
| + code; you approve it in a browser that is already |
| + signed in to that console. No browser on this |
| + machine is needed — the code travels. |
| [-label <name>] what to call this terminal on the console's |
| command-line access page (default: user@host) |
| whoami [-console <url>] ask the console who this terminal is |
| logout [-console <url>] revoke this terminal's token and forget it |
| + default [-project] [<url>] |
| + show the default console and where it is set, or |
| + set it: writes ~/.carlos/config — or, with |
| + -project, ./.carlos/config, which overrides the |
| + global and is meant to be committed so a checkout |
| + pins its own deployment |
| |
| The token lands in ~/.carlos/credentials (mode 0600), one entry per console |
| origin — a laptop can hold credentials for the flagship and a self-hosted |
| -deployment at once. -console may be omitted when there is exactly one. |
| +deployment at once. -console may be omitted when there is exactly one, or |
| +when a default is set. |
| |
| HEADLESS AND BOX-SIDE USE: do not log in. A box has no browser and no human, |
| and a long-lived token on it is a standing credential nobody is watching. |
| @@ -83,6 +92,8 @@ func RunAuth(args []string) { |
| err = authWhoami(os.Stdout, args[1:]) |
| case "logout": |
| err = authLogout(os.Stdout, args[1:]) |
| + case "default": |
| + err = authDefault(os.Stdout, args[1:]) |
| default: |
| fmt.Fprint(os.Stderr, authUsage) |
| os.Exit(1) |
| @@ -759,6 +770,63 @@ func authLogout(out io.Writer, args []string) error { |
| return nil |
| } |
| |
| +// authDefault shows or sets the default console. Bare: report the effective |
| +// configured default and which file said so (flag and env are not consulted |
| +// — this verb reports CONFIG, and someone with CARLOS_CONSOLE exported can |
| +// see their own shell). With a URL: write ~/.carlos/config, or |
| +// ./.carlos/config under -project — relative to the working directory, |
| +// deliberately not "the repo root": the CLI has no VCS awareness, and |
| +// "run it where you want the file" beats guessing. |
| +func authDefault(out io.Writer, args []string) error { |
| + fs := flag.NewFlagSet("auth default", flag.ContinueOnError) |
| + fs.SetOutput(out) |
| + project := fs.Bool("project", false, "write ./.carlos/config (this directory) instead of ~/.carlos/config") |
| + if err := fs.Parse(args); err != nil { |
| + return err |
| + } |
| + switch fs.NArg() { |
| + case 0: |
| + if *project { |
| + return errors.New("-project sets a default: carlos auth default -project <url>") |
| + } |
| + console, src, err := configDefault() |
| + if err != nil { |
| + return err |
| + } |
| + if console == "" { |
| + fmt.Fprintf(out, "no default console configured.\n") |
| + fmt.Fprintf(out, "`carlos auth login` defaults to %s; other commands use the\n", defaultConsole) |
| + fmt.Fprintf(out, "only stored credential, or ask when there are several.\n") |
| + fmt.Fprintf(out, "Set one with: carlos auth default <url> (-project pins this directory)\n") |
| + return nil |
| + } |
| + fmt.Fprintf(out, "%s (from %s)\n", console, src) |
| + return nil |
| + case 1: |
| + path, err := globalConfigPath() |
| + perm := os.FileMode(0o700) // ~/.carlos also holds credentials |
| + if *project { |
| + path, err = filepath.Join(".carlos", "config"), nil |
| + perm = 0o755 // meant to be committed; nothing secret inside |
| + } |
| + if err != nil { |
| + return err |
| + } |
| + if err := saveConfigFile(path, fs.Arg(0), perm); err != nil { |
| + return err |
| + } |
| + // Echo the NORMALIZED value: what was saved, not what was typed. |
| + console, err := normalizeConsole(fs.Arg(0)) |
| + if err != nil { |
| + return err |
| + } |
| + fmt.Fprintf(out, "default console: %s (written to %s)\n", console, path) |
| + return nil |
| + default: |
| + return errors.New("usage: carlos auth default [-project] [<url>]") |
| + } |
| +} |
| + |
| // ---- what other verbs use --------------------------------------------------- |
| |
| // loggedInCredential is the seam every other verb reaches through: the |