From 8bfbd5d5f0c10202f74bbd848c1a7172008fb498 Mon Sep 17 00:00:00 2001 From: Joe Freeman Date: Thu, 2 Apr 2026 11:19:26 +0100 Subject: [PATCH] Post-release testing fixes/improvements (#145) * Add hidden options for configuring Studio URL and allowed origins * Don't attach stdin to server * Tidy server CLI command options * Disable Erlang break menu, and remove container * Fix Docker shutdown issue * Infer adapter if not configured * Update docs * Start server with default project * Create release branch * Automatically initialise changelogs for next release * Update changelogs * Tidy changelog --- .github/workflows/release.yml | 79 ++++++++++++++++++++++++-- README.md | 28 +++++---- cli/CHANGELOG.md | 5 +- cli/cmd/coflux/server.go | 65 ++++++++++++++++----- cli/cmd/coflux/setup.go | 25 ++++++++ cli/cmd/coflux/worker.go | 21 +++++-- cli/internal/adapter/adapter.go | 7 ++- cli/internal/config/config.go | 2 + docs/docs/authentication.md | 6 +- docs/docs/getting_started/server.md | 7 ++- docs/docs/getting_started/workers.md | 20 ++++--- docs/docs/getting_started/workflows.md | 6 +- docs/docs/server_config.md | 16 ++++-- 13 files changed, 226 insertions(+), 61 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c404704..ab284763 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -324,7 +324,7 @@ jobs: bump-dev: runs-on: ubuntu-latest - needs: [prepare, publish] + needs: [prepare, bump-version, publish] steps: - name: Generate app token id: app-token @@ -336,8 +336,14 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ needs.prepare.outputs.target_branch }} + fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Update to next dev version env: NEXT: ${{ needs.prepare.outputs.next_version }} @@ -350,6 +356,9 @@ jobs: next_version = os.environ["NEXT"] next_pep440 = os.environ["NEXT_PEP440"] + # Strip -dev suffix for changelog heading + release_version = next_version.removesuffix("-dev") + Path("VERSION").write_text(next_version + "\n") pyproject = Path("adapters/python/pyproject.toml") @@ -362,12 +371,74 @@ jobs: ) pyproject.write_text(content) + # Add empty changelog sections + changelogs = [ + "server/CHANGELOG.md", + "cli/CHANGELOG.md", + "adapters/python/CHANGELOG.md", + ] + for path in changelogs: + p = Path(path) + content = p.read_text() + p.write_text(f"## {release_version}\n\nNo changes.\n\n{content}") + - name: Commit and push env: NEXT: ${{ needs.prepare.outputs.next_version }} run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add VERSION adapters/python/pyproject.toml + git add VERSION adapters/python/pyproject.toml server/CHANGELOG.md cli/CHANGELOG.md adapters/python/CHANGELOG.md git commit -m "Begin $NEXT development" git push + + - name: Create release branch (minor releases only) + if: needs.prepare.outputs.target_branch == 'main' + env: + VERSION: ${{ needs.prepare.outputs.version }} + RELEASE_SHA: ${{ needs.bump-version.outputs.release_sha }} + shell: bash + run: | + # Derive release branch name (e.g., 0.9.0 -> release/0.9) + MINOR="${VERSION%.*}" + BRANCH="release/$MINOR" + + # Create release branch from the release commit + git checkout -b "$BRANCH" "$RELEASE_SHA" + + # Compute patch dev version (e.g., 0.9.0 -> 0.9.1-dev) + PATCH_DEV="${MINOR}.1-dev" + PATCH_PEP440="${MINOR}.1.dev0" + + python3 - "$PATCH_DEV" "$PATCH_PEP440" <<'PYEOF' + import re, sys + from pathlib import Path + + next_version, next_pep440 = sys.argv[1], sys.argv[2] + release_version = next_version.removesuffix("-dev") + + Path("VERSION").write_text(next_version + "\n") + + pyproject = Path("adapters/python/pyproject.toml") + content = pyproject.read_text() + content = re.sub( + r'^version\s*=\s*"[^"]*"', + f'version = "{next_pep440}"', + content, + flags=re.MULTILINE, + ) + pyproject.write_text(content) + + # Add empty changelog sections + changelogs = [ + "server/CHANGELOG.md", + "cli/CHANGELOG.md", + "adapters/python/CHANGELOG.md", + ] + for path in changelogs: + p = Path(path) + content = p.read_text() + p.write_text(f"## {release_version}\n\nNo changes.\n\n{content}") + PYEOF + + git add VERSION adapters/python/pyproject.toml server/CHANGELOG.md cli/CHANGELOG.md adapters/python/CHANGELOG.md + git commit -m "Begin $PATCH_DEV development" + git push -u origin "$BRANCH" diff --git a/README.md b/README.md index 461c0922..f37a29a2 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ ## Why Coflux? -- **Plain Python** — Workflows are regular Python functions with decorators. No DSLs, no YAML, no static DAGs. -- **Low latency** — Millisecond task startup using warm executor processes. -- **Real-time observability** — Watch workflows execute live in [Coflux Studio](https://studio.coflux.com), with graph visualisation, logs, and results. -- **Self-hosted** — You run the server; your data stays in your infrastructure. -- **Workspace inheritance** — Branch production into development workspaces and re-run individual steps with real data. +- **Plain Python**: workflows are regular Python functions with decorators - no DSLs, no YAML, no static DAGs. +- **Low latency**: millisecond task startup using warm executor processes. +- **Real-time observability**: watch workflows execute live using the CLI, or in [Coflux Studio](https://studio.coflux.com), with graph visualisation, logs, and results. +- **Self-hosted**: you run the server; your data stays in your infrastructure. +- **Workspace inheritance**: branch production into development workspaces and re-run individual steps with real data. ## Quick example @@ -136,7 +136,7 @@ def map_reduce(n: int): cf.log_info("Processing {count} items for {user}", count=42, user="alice") ``` -### And more +### More features - **Debouncing**: defer execution until a task stops being called (`defer=True`) - **Recurrence**: automatically re-execute workflows for polling (`recurrent=True`) @@ -158,19 +158,16 @@ Or download a binary from the [releases page](https://github.com/bitroot/coflux/ Use the CLI to start the server: ```bash -coflux server --no-auth --project myproject +coflux server --no-auth ``` -Or run it with Docker: - -```bash -docker run -p 7777:7777 ghcr.io/bitroot/coflux --no-auth --project myproject -``` +Or [run it with Docker](https://docs.coflux.com/getting_started/server): ### 3. Create a workflow ```python # myapp/workflows.py + import coflux as cf @cf.task() @@ -185,11 +182,12 @@ def hello(name: str): ### 4. Start a worker ```bash -pip install coflux coflux worker --dev myapp.workflows ``` -The `--dev` flag watches for code changes and automatically restarts the worker. +The worker attempts to automatically detect your Python environment. If you have [`uv`](https://docs.astral.sh/uv/) installed, the (dependency-less) `coflux` package is installed automatically. Otherwise, install it first with `pip install coflux`. + +The `--dev` flag (equivalent to `--watch --register`) watches for code changes and automatically restarts the worker. ### 5. Submit a run @@ -201,7 +199,7 @@ coflux submit myapp/hello '"world"' ### 6. Open Studio -Visit [studio.coflux.com](https://studio.coflux.com), enter your server address (`localhost:7777`), and watch your workflow execute in real time. +Visit [studio.coflux.com](https://studio.coflux.com) and create a project with your server address (`localhost:7777`) - a Studio account isn't required. Submit workflows runs and watch them execute in real time. ## License diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 617ae774..54d3c9c0 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,6 +1,9 @@ ## 0.9.1 -No changes. +Enhancements: + +- Updates `server` command to set default project ("default"), and improve Docker lifecycle handling. +- Updates `worker` command to infer adapter (to avoid running `setup`). ## 0.9.0 diff --git a/cli/cmd/coflux/server.go b/cli/cmd/coflux/server.go index 933a6a21..965e9c00 100644 --- a/cli/cmd/coflux/server.go +++ b/cli/cmd/coflux/server.go @@ -6,8 +6,10 @@ import ( "fmt" "os" "os/exec" + "os/signal" "path/filepath" "strings" + "syscall" "github.com/bitroot/coflux/cli/internal/version" "github.com/spf13/cobra" @@ -27,9 +29,8 @@ COFLUX_SERVER_* environment variables. Examples: coflux server - coflux server --port 8080 - coflux server --data-dir ./my-data - coflux server --super-token mytoken --no-auth`, + coflux server --no-auth --project myproject + coflux server --super-token mytoken --public-host %.localhost:7777`, RunE: runServer, } @@ -49,20 +50,30 @@ func init() { serverCmd.Flags().StringVar(&serverSuperToken, "super-token", "", "Super token (will be hashed)") serverCmd.Flags().StringVar(&serverSuperTokenHash, "super-token-hash", "", "Pre-hashed super token (SHA-256 hex)") serverCmd.Flags().String("secret", "", "Server secret for signing service tokens") - serverCmd.Flags().StringSlice("studio-teams", nil, "Team IDs allowed for Studio auth") - serverCmd.Flags().StringSlice("launcher-types", nil, "Allowed launcher types (docker, process)") + serverCmd.Flags().StringSlice("team", nil, "Team IDs allowed for Studio auth") + serverCmd.Flags().StringSlice("launcher", nil, "Allowed launcher types (docker, process)") + serverCmd.Flags().String("studio-url", "", "Studio URL") + serverCmd.Flags().StringSlice("allow-origin", nil, "Allowed CORS origins") + + serverCmd.Flags().MarkHidden("studio-url") + serverCmd.Flags().MarkHidden("allow-origin") serverCmd.MarkFlagsMutuallyExclusive("super-token", "super-token-hash") // Bind flags to viper under the server.* namespace + // Note: viper keys use plural forms (e.g., studio_teams) to match + // config file keys and COFLUX_SERVER_* environment variables, + // while CLI flags use singular forms (e.g., --team). viper.BindPFlag("server.port", serverCmd.Flags().Lookup("port")) viper.BindPFlag("server.data_dir", serverCmd.Flags().Lookup("data-dir")) viper.BindPFlag("server.image", serverCmd.Flags().Lookup("image")) viper.BindPFlag("server.project", serverCmd.Flags().Lookup("project")) viper.BindPFlag("server.public_host", serverCmd.Flags().Lookup("public-host")) viper.BindPFlag("server.secret", serverCmd.Flags().Lookup("secret")) - viper.BindPFlag("server.studio_teams", serverCmd.Flags().Lookup("studio-teams")) - viper.BindPFlag("server.launcher_types", serverCmd.Flags().Lookup("launcher-types")) + viper.BindPFlag("server.studio_teams", serverCmd.Flags().Lookup("team")) + viper.BindPFlag("server.launcher_types", serverCmd.Flags().Lookup("launcher")) + viper.BindPFlag("server.studio_url", serverCmd.Flags().Lookup("studio-url")) + viper.BindPFlag("server.allow_origins", serverCmd.Flags().Lookup("allow-origin")) } // getDefaultImage returns the default Docker image name. @@ -107,16 +118,25 @@ func runServer(cmd *cobra.Command, args []string) error { // Build docker command dockerArgs := []string{ "run", + "--rm", "--pull", pullPolicy, "--publish", fmt.Sprintf("%d:7777", port), "--volume", fmt.Sprintf("%s:/data", absDataDir), + // Disable Erlang's interactive break handler (Ctrl+C menu). + "--env", "ERL_FLAGS=+Bd", } // Add environment variables for server configuration - if project := viper.GetString("server.project"); project != "" { + project := viper.GetString("server.project") + publicHost := viper.GetString("server.public_host") + if project == "" && !strings.HasPrefix(publicHost, "%") { + project = "default" + fmt.Printf("No project specified, using %q\n", project) + } + if project != "" { dockerArgs = append(dockerArgs, "--env", "COFLUX_PROJECT="+project) } - if publicHost := viper.GetString("server.public_host"); publicHost != "" { + if publicHost != "" { dockerArgs = append(dockerArgs, "--env", "COFLUX_PUBLIC_HOST="+publicHost) } if serverNoAuth { @@ -148,6 +168,12 @@ func runServer(cmd *cobra.Command, args []string) error { if types := viper.GetStringSlice("server.launcher_types"); len(types) > 0 { dockerArgs = append(dockerArgs, "--env", "COFLUX_LAUNCHER_TYPES="+strings.Join(types, ",")) } + if studioURL := viper.GetString("server.studio_url"); studioURL != "" { + dockerArgs = append(dockerArgs, "--env", "COFLUX_STUDIO_URL="+studioURL) + } + if origins := viper.GetStringSlice("server.allow_origins"); len(origins) > 0 { + dockerArgs = append(dockerArgs, "--env", "COFLUX_ALLOW_ORIGINS="+strings.Join(origins, ",")) + } // Check config-level auth setting (--no-auth flag handled above) if !serverNoAuth { @@ -163,17 +189,30 @@ func runServer(cmd *cobra.Command, args []string) error { fmt.Printf("Starting Coflux server on port %d...\n", port) fmt.Printf("Data directory: %s\n", absDataDir) - // Run docker + // Run docker in its own process group so that Ctrl+C doesn't go + // directly to it. Instead, Go catches the signal and sends SIGTERM + // to docker, which proxies it to the container for a clean shutdown. dockerCmd := exec.Command("docker", dockerArgs...) dockerCmd.Stdout = os.Stdout dockerCmd.Stderr = os.Stderr - dockerCmd.Stdin = os.Stdin + dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + if err := dockerCmd.Start(); err != nil { + return fmt.Errorf("failed to start docker: %w", err) + } + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + go func() { + <-sigCh + dockerCmd.Process.Signal(syscall.SIGTERM) + }() - if err := dockerCmd.Run(); err != nil { + if err := dockerCmd.Wait(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { os.Exit(exitErr.ExitCode()) } - return fmt.Errorf("failed to run docker: %w", err) + return fmt.Errorf("docker exited with error: %w", err) } return nil diff --git a/cli/cmd/coflux/setup.go b/cli/cmd/coflux/setup.go index dd792e91..1f8ee940 100644 --- a/cli/cmd/coflux/setup.go +++ b/cli/cmd/coflux/setup.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "os/exec" "path/filepath" "strings" @@ -88,6 +89,30 @@ func detectAdapters() []AdapterDetection { }) } + // Fallback: uv available on PATH (auto-installs coflux via --with) + if _, err := exec.LookPath("uv"); err == nil { + detections = append(detections, AdapterDetection{ + Name: "Python (uv --with coflux)", + Command: []string{"uv", "run", "--with", "coflux", "python", "-m", "coflux"}, + Confidence: 30, + }) + } + + // Fallback: python available on PATH + if _, err := exec.LookPath("python3"); err == nil { + detections = append(detections, AdapterDetection{ + Name: "Python (system)", + Command: []string{"python3", "-m", "coflux"}, + Confidence: 20, + }) + } else if _, err := exec.LookPath("python"); err == nil { + detections = append(detections, AdapterDetection{ + Name: "Python (system)", + Command: []string{"python", "-m", "coflux"}, + Confidence: 20, + }) + } + return detections } diff --git a/cli/cmd/coflux/worker.go b/cli/cmd/coflux/worker.go index 4d25d42f..3a5a35b1 100644 --- a/cli/cmd/coflux/worker.go +++ b/cli/cmd/coflux/worker.go @@ -93,14 +93,25 @@ func runWorker(cmd *cobra.Command, args []string) error { cfg.Worker.Adapter = workerAdapter } - // Check adapter is configured - if len(cfg.Worker.Adapter) == 0 { - return fmt.Errorf("no adapter configured; run 'coflux setup' or add 'worker.adapter' to coflux.toml") - } - // Setup logging logger := getLogger() + // Auto-detect adapter if not configured + if len(cfg.Worker.Adapter) == 0 { + detections := detectAdapters() + if len(detections) == 0 { + return fmt.Errorf("no adapter configured; run 'coflux setup' or add 'worker.adapter' to coflux.toml") + } + best := detections[0] + for _, d := range detections[1:] { + if d.Confidence > best.Confidence { + best = d + } + } + cfg.Worker.Adapter = best.Command + logger.Info("auto-detected adapter", "name", best.Name, "command", strings.Join(best.Command, " ")) + } + // Create adapter from config cmdAdapter := adapter.NewCommandAdapter(cfg.Worker.Adapter) diff --git a/cli/internal/adapter/adapter.go b/cli/internal/adapter/adapter.go index 426a059f..2aa06e15 100644 --- a/cli/internal/adapter/adapter.go +++ b/cli/internal/adapter/adapter.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os/exec" + "strings" "sync" "time" @@ -72,7 +73,11 @@ func (a *CommandAdapter) Discover(ctx context.Context, modules []string) (*Disco output, err := cmd.Output() if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { - return nil, fmt.Errorf("discovery failed: %s", string(exitErr.Stderr)) + stderr := strings.TrimSpace(string(exitErr.Stderr)) + if strings.Contains(stderr, "No module named coflux") { + return nil, fmt.Errorf("discovery failed: the 'coflux' package does not appear to be installed in this environment") + } + return nil, fmt.Errorf("discovery failed: %s", stderr) } return nil, fmt.Errorf("failed to run discovery: %w", err) } diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go index 32e8ed6a..3adf7454 100644 --- a/cli/internal/config/config.go +++ b/cli/internal/config/config.go @@ -30,6 +30,8 @@ type ServerConfig struct { SuperTokenHash string `mapstructure:"super_token_hash"` Secret string `mapstructure:"secret"` StudioTeams []string `mapstructure:"studio_teams"` + StudioURL string `mapstructure:"studio_url"` + AllowOrigins []string `mapstructure:"allow_origins"` LauncherTypes []string `mapstructure:"launcher_types"` } diff --git a/docs/docs/authentication.md b/docs/docs/authentication.md index 6be49ba3..53453121 100644 --- a/docs/docs/authentication.md +++ b/docs/docs/authentication.md @@ -7,7 +7,7 @@ By default, the Coflux server requires authentication. There are several authent For local development, authentication can be disabled by starting the server with the `--no-auth` flag: ```bash -coflux server --no-auth --project myproject +coflux server --no-auth ``` This allows anonymous access to all endpoints. This is not recommended for production use. @@ -17,7 +17,7 @@ This allows anonymous access to all endpoints. This is not recommended for produ A super token provides full access to the server. It is configured when starting the server: ```bash -coflux server --super-token "my-secret-token" --project myproject +coflux server --super-token "my-secret-token" ``` The token can then be used with the CLI: @@ -62,7 +62,7 @@ coflux login This requires `COFLUX_STUDIO_TEAMS` to be configured on the server with the allowed team IDs: ```bash -coflux server --studio-teams "team-id-1,team-id-2" --project myproject +coflux server --team team-id-1 --team team-id-2 ``` To log out: diff --git a/docs/docs/getting_started/server.md b/docs/docs/getting_started/server.md index 6f691e7b..b92d6c00 100644 --- a/docs/docs/getting_started/server.md +++ b/docs/docs/getting_started/server.md @@ -3,10 +3,12 @@ Use the CLI to start the server locally: ```bash -coflux server --no-auth --project myproject +coflux server --no-auth ``` -The `--project` flag configures the server for single-project mode. A _project_ is a top-level unit of isolation — it has its own data, orchestration process, and set of workspaces. The `--no-auth` flag disables authentication, which simplifies getting started. See the [authentication documentation](/authentication) for options for setting up authentication for production use. +The `--no-auth` flag disables authentication, which simplifies getting started. See the [authentication documentation](/authentication) for options for setting up authentication for production use. + +By default, the server uses a project called "default". Use `--project` to choose a different name — a _project_ is a top-level unit of isolation with its own data, orchestration process, and set of workspaces. See [server configuration](/server_config) for more details. :::note The command is a wrapper around `docker run`, so you'll need to have Docker installed and running. @@ -18,7 +20,6 @@ docker run \ --pull always \ -p 7777:7777 \ -v $(pwd):/data \ - -e COFLUX_PROJECT=myproject \ -e COFLUX_REQUIRE_AUTH=false \ ghcr.io/bitroot/coflux ``` diff --git a/docs/docs/getting_started/workers.md b/docs/docs/getting_started/workers.md index 614e0382..086bf8cc 100644 --- a/docs/docs/getting_started/workers.md +++ b/docs/docs/getting_started/workers.md @@ -10,24 +10,28 @@ A worker will: Importantly, workers can be run locally, automatically watching for code changes, restarting, and registering workflows as needed. -## Set up +## Run -Use the `setup` command to populate a configuration file (`coflux.toml`). A configuration file isn't necessary, but avoids having to specify configuration manually in the following commands. Run the following command: +Start a worker with: ```bash -coflux setup +coflux worker --dev hello ``` -You will be prompted to enter the host (`localhost:7777`), the _workspace_ name, and the adapter command for your Python environment. A workspace is an environment within a project (e.g., `production`, `development/joe`) — see [Concepts](/concepts#workspaces) for more detail. Use `--detect` to auto-detect your Python environment. +The worker attempts to automatically detect your Python environment. It checks for virtual environments (`.venv/`, `venv/`), package managers (`poetry`, `uv`), and falls back to `uv run --with coflux` or system Python. You can also configure the adapter explicitly with `--adapter` or in `coflux.toml` (see below). -## Run +The `--dev` flag (equivalent to specifying `--watch` and `--register`) enables development mode, which watches for code changes, automatically restarts the worker, and registers workflows with the server. Without it, modules need to be registered separately (e.g., using `coflux manifests register`), and the worker would need to be restarted after making code changes. -Now the worker can be started. Run the following command: +## Configuration (optional) + +Use the `setup` command to populate a configuration file (`coflux.toml`): ```bash -coflux worker --dev hello +coflux setup ``` -The `--dev` flag (equivalent to specifying `--watch` and `--register`) enables development mode, which watches for code changes, automatically restarts the worker, and registers workflows with the server. Without it, modules need to be registered separately (e.g., using `coflux manifests register`), and the worker would need to be restarted after making code changes. +You will be prompted to enter the host (`localhost:7777`), the _workspace_ name, and the adapter command for your Python environment. A workspace is an environment within a project (e.g., `production`, `development/joe`) — see [Concepts](/concepts#workspaces) for more detail. Use `--detect` to auto-detect your Python environment. + +A configuration file isn't required for getting started, but is useful for customising the host, workspace, adapter, concurrency, and other settings. Next, let's submit a run... diff --git a/docs/docs/getting_started/workflows.md b/docs/docs/getting_started/workflows.md index 2a88d408..085f7123 100644 --- a/docs/docs/getting_started/workflows.md +++ b/docs/docs/getting_started/workflows.md @@ -4,13 +4,13 @@ Workflows are defined in code, using Python functions, which are decorated to in The decorators are intended to be unimposing so that functions can be executed outside of Coflux. -## Installing the Python package - -The `coflux` Python package provides the decorators and runtime for defining and executing workflows. Install it into the environment that your workflow code will run in: +:::note +The `coflux` Python package provides the decorators and runtime for defining and executing workflows. If you have [`uv`](https://docs.astral.sh/uv/) installed, the worker will automatically install it when it starts. Otherwise, install it manually: ```bash pip install coflux ``` +::: ## An example diff --git a/docs/docs/server_config.md b/docs/docs/server_config.md index 8c551c64..30250da8 100644 --- a/docs/docs/server_config.md +++ b/docs/docs/server_config.md @@ -14,11 +14,15 @@ This is a convenience wrapper around `docker run`. Docker must be installed and | Flag | Default | Description | |------|---------|-------------| -| `--project` | _(none)_ | Restrict the server to a single project | +| `--project` | `default` | Restrict the server to a single project | +| `--public-host` | _(none)_ | Public-facing host (use `%` prefix for subdomain routing) | | `--port` | `7777` | Port to run the server on | | `--data-dir` | `./data` | Directory for persistent data | | `--no-auth` | `false` | Disable authentication | -| `--super-token` | _(none)_ | Set a super token for authentication | +| `--super-token` or `--super-token-hash` | _(none)_ | Set a super token (plain text or pre-hashed SHA-256 hex) | +| `--secret` | _(none)_ | Server secret for signing service tokens | +| `--team` | _(none)_ | Team IDs allowed for Studio auth (repeatable) | +| `--launcher` | _(none)_ | Allowed launcher types (repeatable, e.g. `docker`, `process`) | | `--image` | _(auto)_ | Docker image to use | ## Projects @@ -27,12 +31,14 @@ The server can operate in two modes: ### Single-project mode -Use `--project` to restrict the server to a single project. All requests are routed to this project regardless of the hostname used to connect: +By default, the server runs in single-project mode with a project called "default". Use `--project` to choose a different name: ```bash coflux server --project myproject ``` +All requests are routed to this project regardless of the hostname used to connect. + ### Multi-project mode Configure `COFLUX_PUBLIC_HOST` with a `%` placeholder to enable subdomain-based routing, where the project is extracted from the subdomain: @@ -51,13 +57,13 @@ The server is configured via environment variables. When using `coflux server`, | Variable | Default | Description | |----------|---------|-------------| -| `COFLUX_PROJECT` | _(none)_ | Restrict to a single project | +| `COFLUX_PROJECT` | _(none)_ | Restrict to a single project (the CLI defaults to `default`) | | `COFLUX_PUBLIC_HOST` | `localhost:PORT` | Public host (use `%` prefix for subdomain routing) | | `COFLUX_REQUIRE_AUTH` | `true` | Whether authentication is required | | `COFLUX_SUPER_TOKEN_HASH` | _(none)_ | SHA-256 hex hash of the super token | | `COFLUX_SECRET` | _(none)_ | Server secret for signing service tokens | | `COFLUX_STUDIO_TEAMS` | _(none)_ | Comma-separated team IDs for Studio auth | -| `COFLUX_STUDIO_URL` | `https://studio.coflux.com` | Studio URL for JWKS | +| `COFLUX_STUDIO_URL` | `https://studio.coflux.com` | Studio URL | | `COFLUX_DATA_DIR` | `./data` | Data directory path | | `COFLUX_ALLOW_ORIGINS` | `https://studio.coflux.com` | Comma-separated CORS origins | | `COFLUX_LAUNCHER_TYPES` | _(none)_ | Allowed launcher types (e.g., `docker,process`) |