feat(dev): support direct environment overrides
This commit is contained in:
5 files changed
+68
-9
No files matched your search
@@ -34,8 +34,10 @@ Images built by this CLI record their source checkout, revision, and optional pu
|
||||
## Run images
|
||||
|
||||
```bash
|
||||
homarr --pr <number> --env HOMARR_WEBSITE_URL=https://app-v2.preview.homarr.dev/
|
||||
homarr run <name>
|
||||
homarr run --pr <number>
|
||||
```
|
||||
|
||||
Add `--detach` to run in the background, `--demo` to enable demo data, or repeat `--env KEY=VALUE` to provide additional environment variables.
|
||||
The direct `homarr --pr <number>` form is shorthand for `homarr run --pr <number>`. Add `--detach` to run in the
|
||||
background, `--demo` to enable demo data, or repeat `--env KEY=VALUE` to provide additional environment variables.
|
||||
@@ -31,6 +31,7 @@ Checkout builds are tagged as `homarr:<name>`. The source checkout and revision
|
||||
## Launch
|
||||
|
||||
```sh
|
||||
homarr --pr 6441 --env WORKSHOP_WEB_URL=https://app-v2.preview.homarr.dev/
|
||||
pnpm dev:cli -- run dev
|
||||
pnpm dev:cli -- run --detach dev
|
||||
pnpm dev:cli -- run --pr 6441
|
||||
@@ -39,6 +40,8 @@ pnpm dev:cli -- run --env FOO=bar --env FEATURE=true dev
|
||||
```
|
||||
|
||||
PR launches always pull the latest GHCR tag before starting. Local tags remain local.
|
||||
The installed binary accepts `--pr`, `--demo`, `--detach`, and repeatable `--env KEY=VALUE` flags directly, so
|
||||
`homarr --pr <number> --env KEY=VALUE` is shorthand for `homarr run --pr <number> --env KEY=VALUE`.
|
||||
The first launch generates a per-user encryption key in the OS config directory and reuses it for later launches. Set `HOMARR_DEV_SECRET_ENCRYPTION_KEY` to a 64-character hexadecimal key to override it.
|
||||
|
||||
The command reference below assumes the optional `homarr` installation. Without it, prefix each command with `pnpm dev:cli --` (for example, `pnpm dev:cli -- dev`).
|
||||
@@ -47,6 +50,7 @@ The command reference below assumes the optional `homarr` installation. Without
|
||||
|
||||
```text
|
||||
homarr Interactive local and remote image browser
|
||||
homarr --pr <number> Pull and launch a PR image, with optional --env overrides
|
||||
homarr dev Explicit alias for the main browser
|
||||
homarr dash Start on the instance dashboard
|
||||
homarr build <name> Build this checkout as homarr:<name>
|
||||
|
||||
@@ -33,6 +33,24 @@ func TestBuildPlanFailsWhenNoPortIsAvailable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPlanPreservesEnvironmentOverrides(t *testing.T) {
|
||||
want := []string{
|
||||
"WORKSHOP_WEB_URL=https://app-v2.preview.homarr.dev/",
|
||||
"FEATURE_OPTIONS=one=two",
|
||||
}
|
||||
plan, err := BuildPlan(Options{
|
||||
PR: 1,
|
||||
Env: want,
|
||||
FindPort: func(int) int { return 7575 },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !slices.Equal(plan.Env, want) {
|
||||
t.Fatalf("environment = %v, want %v", plan.Env, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartPRIntegration(t *testing.T) {
|
||||
if os.Getenv("HOMARR_DOCKER_INTEGRATION_TEST") != "1" {
|
||||
t.Skip("set HOMARR_DOCKER_INTEGRATION_TEST=1 and HOMARR_TEST_PR=<number>")
|
||||
|
||||
@@ -21,6 +21,7 @@ var (
|
||||
flagDetach bool
|
||||
flagBuildPR int
|
||||
runMain = tui.RunDev
|
||||
runRootMain = runLaunch
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -39,6 +40,8 @@ Start a local image, a PR test build from ghcr.io, browse open PRs,
|
||||
or manage running instances from an interactive dashboard.
|
||||
|
||||
Examples:
|
||||
homarr --pr 6390 # launch remote PR test image directly
|
||||
homarr --pr 6390 -e FOO=bar # PR image with environment overrides
|
||||
homarr run dev # launch local homarr:dev image
|
||||
homarr run --pr 6390 # launch remote PR test image
|
||||
homarr run --pr 6390 --demo # PR + demo mode
|
||||
@@ -52,9 +55,7 @@ Examples:
|
||||
homarr logs homarr_pr_6390 # follow logs
|
||||
homarr doctor # check Docker and optional integrations`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runMain()
|
||||
},
|
||||
RunE: runRoot,
|
||||
}
|
||||
|
||||
var dashCmd = &cobra.Command{
|
||||
@@ -240,15 +241,29 @@ var doctorCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
runCmd.Flags().BoolVarP(&flagDemo, "demo", "m", false, "Demo mode (mock integrations)")
|
||||
runCmd.Flags().IntVarP(&flagPR, "pr", "p", 0, "Launch PR test image from ghcr.io")
|
||||
runCmd.Flags().StringArrayVarP(&flagEnv, "env", "e", nil, "Extra env vars (repeatable, KEY=VALUE)")
|
||||
runCmd.Flags().BoolVar(&flagDetach, "detach", false, "Start in the background")
|
||||
addRunFlags(rootCmd)
|
||||
addRunFlags(runCmd)
|
||||
buildCmd.Flags().IntVarP(&flagBuildPR, "pr", "p", 0, "Build a pull request in a temporary checkout")
|
||||
|
||||
rootCmd.AddCommand(runCmd, dashCmd, devCmd, buildCmd, rebuildCmd, listCmd, logsCmd, stopCmd, restartCmd, removeCmd, openCmd, doctorCmd)
|
||||
}
|
||||
|
||||
func addRunFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVarP(&flagDemo, "demo", "m", false, "Demo mode (mock integrations)")
|
||||
cmd.Flags().IntVarP(&flagPR, "pr", "p", 0, "Launch PR test image from ghcr.io")
|
||||
cmd.Flags().StringArrayVarP(&flagEnv, "env", "e", nil, "Extra env vars (repeatable, KEY=VALUE)")
|
||||
cmd.Flags().BoolVar(&flagDetach, "detach", false, "Start in the background")
|
||||
}
|
||||
|
||||
func runRoot(cmd *cobra.Command, args []string) error {
|
||||
for _, name := range []string{"pr", "env", "demo", "detach"} {
|
||||
if cmd.Flags().Changed(name) {
|
||||
return runRootMain(cmd, args)
|
||||
}
|
||||
}
|
||||
return runMain()
|
||||
}
|
||||
|
||||
func runLaunch(cmd *cobra.Command, args []string) error {
|
||||
tag := ""
|
||||
if len(args) > 0 {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestRootCommandLaunchesDevelopmentDashboard(t *testing.T) {
|
||||
@@ -12,7 +14,25 @@ func TestRootCommandLaunchesDevelopmentDashboard(t *testing.T) {
|
||||
t.Cleanup(func() { runMain = previous })
|
||||
runMain = func() error { return want }
|
||||
|
||||
if err := rootCmd.RunE(rootCmd, nil); !errors.Is(err, want) {
|
||||
cmd := &cobra.Command{}
|
||||
if err := runRoot(cmd, nil); !errors.Is(err, want) {
|
||||
t.Fatalf("root command error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootCommandLaunchesPRWhenRunFlagIsProvided(t *testing.T) {
|
||||
want := errors.New("PR launched")
|
||||
previous := runRootMain
|
||||
t.Cleanup(func() { runRootMain = previous })
|
||||
runRootMain = func(cmd *cobra.Command, args []string) error { return want }
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("pr", false, "")
|
||||
if err := cmd.Flags().Set("pr", "true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := runRoot(cmd, nil); !errors.Is(err, want) {
|
||||
t.Fatalf("root command error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user