diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..ef4f05b --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,7 @@ +--- +printWidth: 120 +proseWrap: "always" +overrides: + - files: "**/*.yaml" + options: + proseWrap: "never" diff --git a/AGENTS.md b/AGENTS.md index 623fbe7..ee77ab5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ ComposeFlux is a Go application that implements a GitOps reconciliation loop for Docker Compose stacks. It polls a Git repository, detects changes via SHA256 checksums stored as container labels, and deploys/prunes stacks using the native Docker Compose SDK. **Module**: `github.com/veerendra2/composeflux` -**Go version**: 1.26.1 (CGO enabled — Bitwarden SDK uses cgo FFI into Rust) +**Go version**: 1.26.2 (CGO enabled — Bitwarden SDK uses cgo FFI into Rust) --- @@ -16,7 +16,7 @@ cmd/composeflux/ # Main binary entry point (CLI setup via kong) cmd/prune-playground/ # Dev scratch binary internal/reconcile/ # Core reconciliation logic (private to module) pkg/dockercompose/ # Docker Compose SDK wrapper (exported) -pkg/secrets/ # Secrets manager integrations (Bitwarden, Infisical) +pkg/secrets/ # Secrets manager integrations (Bitwarden, Infisical) — optional pkg/source/ # Git client (go-git) docs/ # MkDocs documentation ``` @@ -184,6 +184,6 @@ ctx.FatalIfErrorf(ctx.Run()) ## Docker / Build Notes - CGO is enabled (`CGO_ENABLED=1`) for the Bitwarden SDK (Rust FFI). -- Multi-stage Dockerfile: `golang:1.26.1` builder → `gcr.io/distroless/static-debian13` final image. +- Multi-stage Dockerfile: `golang:1.26.2` builder → `gcr.io/distroless/static-debian13` final image. - Version info injected at link time via `-ldflags` (git tag, commit SHA, branch, build date). - Local dev: `task compose` runs the app via `compose-dev.yml`. diff --git a/Dockerfile b/Dockerfile index 7b45e72..2af3848 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.26.1 AS builder +FROM golang:1.26.2 AS builder WORKDIR /app RUN curl -sL https://taskfile.dev/install.sh | sh \ && apt update \ diff --git a/README.md b/README.md index 988dc42..8b22821 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -![GitHub Repo stars](https://img.shields.io/github/stars/veerendra2/composeflux) ![GitHub forks](https://img.shields.io/github/forks/veerendra2/composeflux) ![GitHub License](https://img.shields.io/github/license/veerendra2/composeflux) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/veerendra2/composeflux) +![GitHub Repo stars](https://img.shields.io/github/stars/veerendra2/composeflux) +![GitHub forks](https://img.shields.io/github/forks/veerendra2/composeflux) +![GitHub License](https://img.shields.io/github/license/veerendra2/composeflux) +![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/veerendra2/composeflux) # ComposeFlux @@ -8,7 +11,8 @@ _A GitOps continuous deployment tool for Docker Compose_ ![ComposeFlux](./docs/assets/composeflux-banner.png) -ComposeFlux automates Docker Compose deployments using GitOps principles. Monitor your Git repository, detect changes, and automatically deploy your Docker stacks—all without manual intervention. +ComposeFlux automates Docker Compose deployments using GitOps principles. Monitor your Git repository, detect changes, +and automatically deploy your Docker stacks—all without manual intervention. --- @@ -16,8 +20,9 @@ ComposeFlux automates Docker Compose deployments using GitOps principles. Monito - **GitOps Driven** - Automatic deployment from Git repository - **Smart Change Detection** - Hash-based detection deploys only changed stacks -- **Pure Go Implementation** - Native Docker [Compose SDK](https://docs.docker.com/compose/compose-sdk/) without shell execution -- **Secrets Management** - Integrated Bitwarden Secrets Manager and Infisical support +- **Pure Go Implementation** - Native Docker [Compose SDK](https://docs.docker.com/compose/compose-sdk/) without shell + execution +- **Secrets Management** - Optional Bitwarden Secrets Manager and Infisical support - **Automatic Image Updates** - Scheduled registry checks redeploy stacks when newer images are available - **Flexible Configuration** - Startup order and shared environment variables - **Simple & Headless** - No UI, no backend database @@ -29,7 +34,8 @@ ComposeFlux automates Docker Compose deployments using GitOps principles. Monito ## Contributing -Contributions are welcome! Please see our [Development Guide](https://veerendra2.github.io/composeflux/Development/) for details. +Contributions are welcome! Please see our [Development Guide](https://veerendra2.github.io/composeflux/Development/) for +details. ## License diff --git a/Taskfile.yml b/Taskfile.yml index eafa647..0a3033a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -119,9 +119,7 @@ tasks: cmds: - go install golang.org/x/vuln/cmd/govulncheck@latest - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - status: - - test -f {{.GOPATH_BIN}}/govulncheck - - test -f {{.GOPATH_BIN}}/golangci-lint + - pip install "mkdocs>=1.6,<2.0" mkdocs-material all: desc: Run comprehensive checks; format, lint, security and test diff --git a/cmd/composeflux/common.go b/cmd/composeflux/common.go index 09a9297..93d06fe 100644 --- a/cmd/composeflux/common.go +++ b/cmd/composeflux/common.go @@ -17,13 +17,8 @@ import ( "github.com/veerendra2/gopackages/version" ) -// CommonConfig holds configuration shared between all commands type CommonConfig struct { - SecretsProvider string `name:"secrets-provider" enum:"bitwarden,infisical" env:"SECRETS_PROVIDER" required:"" help:"Secrets manager provider to use (bitwarden or infisical)"` - - Bitwarden secrets.BitwardenConfig `embed:"" prefix:"bitwarden-" envprefix:"BITWARDEN_" group:"Bitwarden Options:"` - Infisical secrets.InfisicalConfig `embed:"" prefix:"infisical-" envprefix:"INFISICAL_" group:"Infisical Options:"` - + Secrets secrets.Config `embed:""` Reconciler reconcile.Config `embed:"" group:"Reconciler Options:"` Source source.Config `embed:"" group:"Git Source Options:"` DockerCompose dockercompose.Config `embed:"" group:"Docker Compose Options:"` @@ -31,15 +26,19 @@ type CommonConfig struct { // Validate checks provider-specific configuration func (c *CommonConfig) Validate() error { - switch c.SecretsProvider { + switch c.Secrets.Provider { + case "": + if c.Source.DeployKeySecretRef != "" { + return fmt.Errorf("--deploy-key-secret-ref requires a secrets provider (--secrets-provider)") + } case "bitwarden": - if c.Bitwarden.AccessToken == "" || c.Bitwarden.OrgID == "" || c.Bitwarden.ProjectID == "" { + if c.Secrets.Bitwarden.AccessToken == "" || c.Secrets.Bitwarden.OrgID == "" || c.Secrets.Bitwarden.ProjectID == "" { return fmt.Errorf("bitwarden provider requires: --bitwarden-access-token, " + "--bitwarden-organization-id, --bitwarden-project-id") } case "infisical": - if c.Infisical.ClientID == "" || c.Infisical.ClientSecret == "" || - c.Infisical.Environment == "" || c.Infisical.ProjectID == "" { + if c.Secrets.Infisical.ClientID == "" || c.Secrets.Infisical.ClientSecret == "" || + c.Secrets.Infisical.Environment == "" || c.Secrets.Infisical.ProjectID == "" { return fmt.Errorf("infisical provider requires: --infisical-client-id, " + "--infisical-client-secret, --infisical-environment, --infisical-project-id") } @@ -49,24 +48,23 @@ func (c *CommonConfig) Validate() error { // InitClients initializes all required clients (secrets, git, docker, reconciler) func (c *CommonConfig) InitClients(ctx context.Context) (*reconcile.Reconciler, func(), error) { - // Create secrets client - secretsCfg := secrets.Config{ - BitwardenConfig: c.Bitwarden, - InfisicalConfig: c.Infisical, - } - - sClient, err := secrets.New(ctx, c.SecretsProvider, secretsCfg) + sClient, err := secrets.New(ctx, c.Secrets) if err != nil { - slog.Error("Failed to create secrets manager client", "provider", c.SecretsProvider, "error", err) + slog.Error("Failed to create secrets manager client", "provider", c.Secrets.Provider, "error", err) return nil, nil, err } cleanup := func() { - sClient.Close() + if sClient != nil { + sClient.Close() + } } // Fetch SSH deploy key from secrets manager if specified if c.Source.DeployKeySecretRef != "" { + if sClient == nil { + return nil, cleanup, fmt.Errorf("--deploy-key-secret-ref requires a secrets provider (--secrets-provider)") + } slog.Debug("Fetching SSH deploy key from secrets manager", "deploy_key_ref", c.Source.DeployKeySecretRef) keyContent, err := sClient.Get(c.Source.DeployKeySecretRef) @@ -128,7 +126,9 @@ func (c *CommonConfig) InitClients(ctx context.Context) (*reconcile.Reconciler, return nil, cleanup, err } - slog.Info("Reconciler configured", "stack_path", c.Reconciler.StackPath, "config_file", c.Reconciler.ConfigFile) + slog.Info("Reconciler configured", "stack_path", c.Reconciler.StackPath, "config_file", c.Reconciler.ConfigFile, + "secrets_manager", c.Secrets.Provider, "git_poll_interval", c.Reconciler.GitInterval, + "image_update_cron", c.Reconciler.ImageUpdateSchedule) return rClient, cleanup, nil } diff --git a/docs/Development.md b/docs/Development.md index 1dfadff..f2fc761 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -11,7 +11,9 @@ - Linux: `musl-gcc` - macOS: Xcode Command Line Tools -> _CGO must be enabled (`CGO_ENABLED=1`, already set in `Taskfile.yml`) because ComposeFlux uses the Bitwarden Go SDK, which calls into the Bitwarden Rust SDK via FFI using cgo. See [Bitwarden SDK Go instructions](https://github.com/bitwarden/sdk-go/blob/main/INSTRUCTIONS.md)._ +> _CGO must be enabled (`CGO_ENABLED=1`, already set in `Taskfile.yml`) because ComposeFlux uses the Bitwarden Go SDK, +> which calls into the Bitwarden Rust SDK via FFI using cgo. See +> [Bitwarden SDK Go instructions](https://github.com/bitwarden/sdk-go/blob/main/INSTRUCTIONS.md)._ ## Quick Setup @@ -26,7 +28,8 @@ task build ### MkDocs Local Setup -If you use direnv and pyenv, an [.envrc](../.envrc) is already configured. Otherwise, set up a Python virtual environment manually: +If you use direnv and pyenv, an [.envrc](../.envrc) is already configured. Otherwise, set up a Python virtual +environment manually: ```bash python3 -m venv venv/ diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index f37e9b8..62b812e 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -6,18 +6,23 @@ Deploy ComposeFlux and manage Docker Compose stacks via GitOps. - Docker with Compose v2+ - Git repository with Compose stacks -- Secrets manager: **Bitwarden** or **Infisical** (required) +- Secrets manager: **Bitwarden** or **Infisical** (optional, for secrets injection and deploy key fetching) - SSH key for Git access (store in secrets manager or mount as volume) ## Environment Variables ### Required -| Variable | Description | -| ------------------ | ------------------------------------------------------------- | -| `SECRETS_PROVIDER` | Secrets manager: `bitwarden` or `infisical` (required) | -| `GIT_REPO_URL` | Git repository SSH URL (e.g., `git@github.com:user/repo.git`) | -| `STACK_PATH` | Path to stacks directory in repo (relative to repo root) | +| Variable | Description | +| -------------- | ------------------------------------------------------------- | +| `GIT_REPO_URL` | Git repository SSH URL (e.g., `git@github.com:user/repo.git`) | +| `STACK_PATH` | Path to stacks directory in repo (relative to repo root) | + +### Optional - Secrets Provider + +| Variable | Description | +| ------------------ | ------------------------------------------------------ | +| `SECRETS_PROVIDER` | Secrets manager: `bitwarden` or `infisical` (optional) | **Bitwarden (when `SECRETS_PROVIDER=bitwarden`):** @@ -44,11 +49,11 @@ Deploy ComposeFlux and manage Docker Compose stacks via GitOps. | Variable | Description | Default | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `GIT_DEPLOY_KEY_SECRET_REF` | Deploy key secret reference (name or ID) in secrets manager (See [Deploy Key Secret Reference](#deploy-key-secret-reference)) | `SSH_PRIVATE_KEY` | +| `GIT_DEPLOY_KEY_SECRET_REF` | Deploy key secret reference (name or ID) in secrets manager (See [Deploy Key Secret Reference](#deploy-key-secret-reference)) | | | `GIT_SSH_KEY_PATH` | SSH key path inside container | `/.ssh/composeflux_id_rsa` | | `GIT_CLONE_PATH` | Local clone directory | `/opt/compose-stack` | | `GIT_INTERVAL` | Git sync interval | `5m` | -| `IMAGE_UPDATE_SCHEDULE` | Cron expression for Docker image update checks, e.g. `0 3 * * *`. Empty = disabled. | `""` | +| `IMAGE_UPDATE_SCHEDULE` | Cron expression for Docker image update checks, e.g. `0 3 * * *`. Empty = disabled. | `""` | | `GIT_BRANCH` | Git branch to track | `main` | | `CONFIG_FILE` | Stack config file name (see [Stack Configuration](Introduction.md#stack-configuration)) | `stack.yml` | | `LOG_LEVEL` | Log level (`debug`/`info`/`warn`/`error`) | `info` | @@ -78,8 +83,11 @@ Commands: Run "composeflux --help" for more information on a command. ``` -- **`run`** - Daemon mode with continuous reconciliation (default). Performs an initial sync at startup, then checks the Git repository for changes at configured intervals (default: 5 minutes). -- **`sync`** - One-shot sync and deploy. Manually triggers immediate synchronization. Useful when you update secrets in your secrets manager but haven't made Git changes. See [Hash-Based Change Detection](Introduction.md#hash-based-change-detection). +- **`run`** - Daemon mode with continuous reconciliation (default). Performs an initial sync at startup, then checks the + Git repository for changes at configured intervals (default: 5 minutes). +- **`sync`** - One-shot sync and deploy. Manually triggers immediate synchronization. Useful when you update secrets in + your secrets manager but haven't made Git changes. See + [Hash-Based Change Detection](Introduction.md#hash-based-change-detection). ```bash # Daemon mode (initial sync at startup, then checks Git every 5 minutes) @@ -89,7 +97,10 @@ composeflux run composeflux sync ``` -**Important**: After the initial startup sync, the `run` command fetches secrets and deploys changes only when Git updates are detected. If you update secrets in your secrets manager without changing anything in Git, run `composeflux sync` manually to apply updated secrets. See [Hash-Based Change Detection](Introduction.md#hash-based-change-detection). +**Important**: After the initial startup sync, the `run` command fetches secrets and deploys changes only when Git +updates are detected. If you update secrets in your secrets manager without changing anything in Git, run +`composeflux sync` manually to apply updated secrets. See +[Hash-Based Change Detection](Introduction.md#hash-based-change-detection). ## Deploy ComposeFlux @@ -109,7 +120,7 @@ composeflux sync GIT_REPO_URL=git@github.com:user/stacks-repo.git STACK_PATH=stacks -# Required - Choose one secrets provider: +# Optional - Choose a secrets provider (omit to run without secrets): # Option A: Bitwarden SECRETS_PROVIDER=bitwarden @@ -120,7 +131,7 @@ BITWARDEN_PROJECT_ID=your-project-id # Option B: Infisical # SECRETS_PROVIDER=infisical -# GIT_DEPLOY_KEY_SECRET_REF=SSH_PRIVATE_KEY (Default) +# GIT_DEPLOY_KEY_SECRET_REF=SSH_PRIVATE_KEY # INFISICAL_CLIENT_ID=your-client-id # INFISICAL_CLIENT_SECRET=your-client-secret # INFISICAL_ENVIRONMENT=prod @@ -143,12 +154,12 @@ services: # GIT_INTERVAL: 5m # Sync interval # GIT_BRANCH: main - # Secrets Manager - Bitwarden - SECRETS_PROVIDER: ${SECRETS_PROVIDER} - GIT_DEPLOY_KEY_SECRET_REF: ${GIT_DEPLOY_KEY_SECRET_REF} - BITWARDEN_ACCESS_TOKEN: ${BITWARDEN_ACCESS_TOKEN} - BITWARDEN_ORGANIZATION_ID: ${BITWARDEN_ORGANIZATION_ID} - BITWARDEN_PROJECT_ID: ${BITWARDEN_PROJECT_ID} + # Secrets Manager - Bitwarden (optional) + # SECRETS_PROVIDER: ${SECRETS_PROVIDER} + # GIT_DEPLOY_KEY_SECRET_REF: ${GIT_DEPLOY_KEY_SECRET_REF} + # BITWARDEN_ACCESS_TOKEN: ${BITWARDEN_ACCESS_TOKEN} + # BITWARDEN_ORGANIZATION_ID: ${BITWARDEN_ORGANIZATION_ID} + # BITWARDEN_PROJECT_ID: ${BITWARDEN_PROJECT_ID} # Secrets Manager - Infisical (comment out Bitwarden above if using this) # SECRETS_PROVIDER: infisical @@ -170,7 +181,6 @@ services: # - ./ssh_known_hosts:/etc/ssh/ssh_known_hosts:ro # Optional: Mount local SSH key instead of fetching from secrets manager - # Uncomment below and set GIT_DEPLOY_KEY_SECRET_REF="" in environment above # - ~/.ssh/id_rsa:/.ssh/composeflux_id_rsa:ro ``` @@ -178,20 +188,24 @@ services: If you prefer to mount your SSH key directly instead of storing it in the secrets manager: -1. Set `GIT_DEPLOY_KEY_SECRET_REF=""` (empty string) in environment variables to disable fetch +1. Leave `SECRETS_PROVIDER` unset (or omit it entirely) 2. Mount your SSH key to the container at `GIT_SSH_KEY_PATH` location (default: `/.ssh/composeflux_id_rsa`) ### Deploy Key Secret Reference -ComposeFlux can fetch your SSH deploy key from the secrets manager during startup, so it can clone private repositories without mounting a local key. +ComposeFlux can fetch your SSH deploy key from the secrets manager during startup, so it can clone private repositories +without mounting a local key. How `GIT_DEPLOY_KEY_SECRET_REF` works: -- When set to a value (e.g., `SSH_PRIVATE_KEY` or a Bitwarden secret ID), ComposeFlux fetches that secret from your secrets manager -- **Bitwarden**: Uses it as the secret ID to fetch (see [Bitwarden Add Secrets](how-to-guides/Bitwarden.md#2-add-secrets)) +- Requires `SECRETS_PROVIDER` to be set +- When set to a value (e.g., `SSH_PRIVATE_KEY` or a Bitwarden secret ID), ComposeFlux fetches that secret from your + secrets manager +- **Bitwarden**: Uses it as the secret ID to fetch (see + [Bitwarden Add Secrets](how-to-guides/Bitwarden.md#2-add-secrets)) - **Infisical**: Uses it as the secret key name to fetch - The fetched content must be your SSH private key -- When set to `""` (empty string), skips fetch and uses mounted key at `GIT_SSH_KEY_PATH` +- When left empty (default), skips fetch and uses mounted key at `GIT_SSH_KEY_PATH` Example: diff --git a/docs/Introduction.md b/docs/Introduction.md index 660a6a5..4a6fe88 100644 --- a/docs/Introduction.md +++ b/docs/Introduction.md @@ -1,6 +1,7 @@ # Introduction -ComposeFlux is a GitOps tool for managing Docker Compose stacks on home servers. It watches a Git repository and automatically deploys stacks when changes are detected. +ComposeFlux is a GitOps tool for managing Docker Compose stacks on home servers. It watches a Git repository and +automatically deploys stacks when changes are detected. ## Goals @@ -13,9 +14,12 @@ ComposeFlux is a GitOps tool for managing Docker Compose stacks on home servers. ![Arch](./assets/arch.png) -ComposeFlux runs a Git sync loop in daemon mode (`run` command). It performs an initial sync at startup, then checks the remote Git repository for changes and syncs again when updates are detected. +ComposeFlux runs a Git sync loop in daemon mode (`run` command). It performs an initial sync at startup, then checks the +remote Git repository for changes and syncs again when updates are detected. + +Optionally, a separate cron-scheduled image update check (`IMAGE_UPDATE_SCHEDULE`) pulls new images and redeploys stacks +when a new image digest is detected. -Optionally, a separate cron-scheduled image update check (`IMAGE_UPDATE_SCHEDULE`) pulls new images and redeploys stacks when a new image digest is detected. 1. Pulls latest commits 2. Fetches secrets from secrets manager 3. Loads environment variables from [`stack.yml`](#stack-configuration) (if present) @@ -29,8 +33,11 @@ Optionally, a separate cron-scheduled image update check (`IMAGE_UPDATE_SCHEDULE ComposeFlux uses a hash-based approach to decide whether a stack needs redeploying: - SHA256 hash is calculated from the entire Compose project (after variable substitution with secrets) -- **Includes secrets**: The hash includes resolved secrets at sync time. If secrets change, you can run `composeflux sync` (or wait for the next Git change) to fetch them and update the hash. -- To take full advantage of hash-based detection for app config changes, prefer Docker Compose [`configs`](https://docs.docker.com/reference/compose-file/configs/) in your Compose files instead of mounting plain app config files directly into containers. +- **Includes secrets**: The hash includes resolved secrets at sync time. If secrets change, you can run + `composeflux sync` (or wait for the next Git change) to fetch them and update the hash. +- To take full advantage of hash-based detection for app config changes, prefer Docker Compose + [`configs`](https://docs.docker.com/reference/compose-file/configs/) in your Compose files instead of mounting plain + app config files directly into containers. - Stack is redeployed only when the hash changes; otherwise it is skipped (no unnecessary redeployment) - Hash is stored in the `composeflux.stack-hash` label on deployed containers @@ -116,9 +123,10 @@ Each ComposeFlux instance only manages stacks in its configured directory. - Nested stack discovery (only scans one level deep) - Multi-server orchestration (no central controller) - Rolling updates or zero-downtime deployments -- No active reconciliation of stack/container status (e.g., stopped/exited containers are not auto-redeployed). This used to be part of the implementation but was removed for simplicity. (💡 _Use Docker [restart policies](https://docs.docker.com/engine/containers/start-containers-automatically/) instead_) +- No active reconciliation of stack/container status (e.g., stopped/exited containers are not auto-redeployed). This + used to be part of the implementation but was removed for simplicity. (💡 _Use Docker + [restart policies](https://docs.docker.com/engine/containers/start-containers-automatically/) instead_) - Built-in monitoring or alerting -- Secrets manager is required at the moment. You must configure Bitwarden or Infisical even if you don’t plan to use secrets (this may change later). **Stack Discovery is One Level Deep:** diff --git a/docs/how-to-guides/Bitwarden.md b/docs/how-to-guides/Bitwarden.md index 85420f4..1a0d72b 100644 --- a/docs/how-to-guides/Bitwarden.md +++ b/docs/how-to-guides/Bitwarden.md @@ -19,7 +19,9 @@ Set up Bitwarden Secrets Manager as the secrets provider for ComposeFlux. Add secrets to the project. -- If you want ComposeFlux to fetch your Git SSH deploy key from Bitwarden at startup, create a secret that stores the private key, then copy that secret's ID for `GIT_DEPLOY_KEY_SECRET_REF`. See [Deploy Key Secret Reference](../GettingStarted.md#deploy-key-secret-reference). +- If you want ComposeFlux to fetch your Git SSH deploy key from Bitwarden at startup, create a secret that stores the + private key, then copy that secret's ID for `GIT_DEPLOY_KEY_SECRET_REF`. See + [Deploy Key Secret Reference](../GettingStarted.md#deploy-key-secret-reference). ![Bitwarden Secret Creation](../assets/bw-example-secrets-add.png) @@ -75,7 +77,8 @@ GIT_DEPLOY_KEY_SECRET_REF= ## Usage in Compose Stacks -ComposeFlux fetches secrets from the configured Bitwarden project and exposes them as environment variables using each secret key name. +ComposeFlux fetches secrets from the configured Bitwarden project and exposes them as environment variables using each +secret key name. ```yaml services: diff --git a/docs/how-to-guides/GithubDeployKeys.md b/docs/how-to-guides/GithubDeployKeys.md index 4a59bf1..04f0005 100644 --- a/docs/how-to-guides/GithubDeployKeys.md +++ b/docs/how-to-guides/GithubDeployKeys.md @@ -45,32 +45,31 @@ For more details, see: ## Configure ComposeFlux -ComposeFlux automatically fetches `SSH_PRIVATE_KEY` from your secrets manager (default behavior). - -**No additional configuration needed** - just ensure: +To have ComposeFlux fetch `SSH_PRIVATE_KEY` from your secrets manager, set `GIT_DEPLOY_KEY_SECRET_REF`: ```yaml # In compose.yml environment: GIT_REPO_URL: git@github.com:user/repo.git # SSH URL - # GIT_DEPLOY_KEY_SECRET_REF: SSH_PRIVATE_KEY # Default, change if using different name + SECRETS_PROVIDER: bitwarden # or infisical + GIT_DEPLOY_KEY_SECRET_REF: SSH_PRIVATE_KEY # Change if using a different name ``` ## Alternative: Mount Local Key -Skip secrets manager and mount key directly: +Skip secrets manager and mount key directly (no `SECRETS_PROVIDER` needed): ```yaml # In compose.yml environment: - GIT_DEPLOY_KEY_SECRET_REF: "" # Empty string disables fetch from secrets manager GIT_SSH_KEY_PATH: /.ssh/composeflux_id_rsa # Where the key will be mounted volumes: - ~/.ssh/composeflux_deploy:/.ssh/composeflux_id_rsa:ro ``` -This bypasses the secrets manager for SSH keys entirely. Useful if you manage SSH keys separately or don't want to store them in Bitwarden/Infisical. +This bypasses the secrets manager for SSH keys entirely. Useful if you manage SSH keys separately or don't want to store +them in Bitwarden/Infisical. ## Test SSH Access diff --git a/docs/how-to-guides/Infisical.md b/docs/how-to-guides/Infisical.md index 1e6e1c8..7237cd1 100644 --- a/docs/how-to-guides/Infisical.md +++ b/docs/how-to-guides/Infisical.md @@ -29,7 +29,9 @@ ComposeFlux uses `INFISICAL_ENVIRONMENT`, so you must provide an environment slu Add secrets in the project environment you selected: -- If you want ComposeFlux to fetch your Git SSH deploy key from Infisical at startup, create a secret with key `SSH_PRIVATE_KEY` (or another key name and set `GIT_DEPLOY_KEY_SECRET_REF` to match). See [Deploy Key Secret Reference](../GettingStarted.md#deploy-key-secret-reference). +- If you want ComposeFlux to fetch your Git SSH deploy key from Infisical at startup, create a secret with key + `SSH_PRIVATE_KEY` (or another key name and set `GIT_DEPLOY_KEY_SECRET_REF` to match). See + [Deploy Key Secret Reference](../GettingStarted.md#deploy-key-secret-reference). ![SSH Key](../assets/infisical-ssh-key.png) @@ -37,7 +39,8 @@ Add secrets in the project environment you selected: ### 4. Create Folders (Optional) -Infisical supports organizing secrets in folders. If you use folders, set `INFISICAL_SECRET_PATH` to the folder path used by ComposeFlux. +Infisical supports organizing secrets in folders. If you use folders, set `INFISICAL_SECRET_PATH` to the folder path +used by ComposeFlux. 1. In the **Overview** tab, click **Add Secrets** and select **Add Folder**. 2. Note the folder path (for example, `/`, `/apps/prod`). @@ -95,7 +98,8 @@ INFISICAL_SECRET_PATH=/ ## Usage in Compose Stacks -ComposeFlux fetches secrets from the configured Infisical project/environment/path and exposes them as environment variables using each secret key name. +ComposeFlux fetches secrets from the configured Infisical project/environment/path and exposes them as environment +variables using each secret key name. ```yaml services: diff --git a/docs/index.md b/docs/index.md index 2caf544..83421fb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,8 @@ _A GitOps continuous deployment tool for Docker Compose_ ![ComposeFlux](./assets/composeflux-banner.png) -ComposeFlux automates Docker Compose deployments using GitOps principles. Monitor your Git repository, detect changes, and automatically deploy your Docker stacks—all without manual intervention. +ComposeFlux automates Docker Compose deployments using GitOps principles. Monitor your Git repository, detect changes, +and automatically deploy your Docker stacks—all without manual intervention. --- @@ -13,7 +14,7 @@ ComposeFlux automates Docker Compose deployments using GitOps principles. Monito - **GitOps Driven** - Automatic deployment from Git repository - **Smart Change Detection** - Hash-based detection deploys only changed stacks - **Pure Go Implementation** - Native Docker Compose SDK without shell execution -- **Secrets Management** - Integrated Bitwarden Secrets Manager and Infisical support +- **Secrets Management** - Optional Bitwarden Secrets Manager and Infisical support - **Flexible Configuration** - Startup order and shared environment variables - **Automatic Image Updates** - Scheduled registry checks redeploy stacks when newer images are available - **Simple & Headless** - No UI, no backend database diff --git a/go.mod b/go.mod index 2345570..5301c1b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/veerendra2/composeflux -go 1.26.1 +go 1.26 require ( github.com/docker/cli v29.3.1+incompatible @@ -13,8 +13,8 @@ require ( github.com/compose-spec/compose-go/v2 v2.10.1 github.com/distribution/reference v0.6.0 github.com/docker/docker v28.5.2+incompatible - github.com/go-git/go-git/v5 v5.17.0 - github.com/infisical/go-sdk v0.6.9 + github.com/go-git/go-git/v5 v5.17.2 + github.com/infisical/go-sdk v0.7.0 github.com/moby/moby/client v0.3.0 github.com/robfig/cron/v3 v3.0.1 github.com/sirupsen/logrus v1.9.4 @@ -23,8 +23,8 @@ require ( ) require ( - cloud.google.com/go/auth v0.15.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/auth v0.18.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.1.11 // indirect dario.cat/mergo v1.0.2 // indirect @@ -50,7 +50,7 @@ require ( github.com/buger/goterm v1.0.4 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/console v1.0.5 // indirect github.com/containerd/containerd/api v1.10.0 // indirect github.com/containerd/containerd/v2 v2.2.2 // indirect @@ -90,8 +90,8 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -175,18 +175,18 @@ require ( go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/api v0.226.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/api v0.267.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect + google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 1b5d7c1..0ddbb44 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= -cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= -cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= +cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw= @@ -81,10 +81,10 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU= @@ -166,11 +166,11 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c= @@ -185,8 +185,8 @@ github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDz github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM= -github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI= +github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104= +github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -274,10 +274,10 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g= -github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= +github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= @@ -299,8 +299,8 @@ github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/infisical/go-sdk v0.6.9 h1:gTUDyHvg2az6jDCE3s6wT2fBjoGKdtDfNTMNgpIJMcQ= -github.com/infisical/go-sdk v0.6.9/go.mod h1:A6l7EhwCkPw8tmJjgA09KtueEHYko+VdGCEupK8hL08= +github.com/infisical/go-sdk v0.7.0 h1:x9/1PczL+ioVD1jCp4LHQzPpBawatbmkjAC0S1OAtUA= +github.com/infisical/go-sdk v0.7.0/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -566,8 +566,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -591,10 +591,10 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -638,8 +638,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -648,8 +648,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= @@ -666,14 +666,16 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ= -google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= -google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:vk5TfqZHNn0obhPIYeS+cxIFKFQgser/M2jnI+9c6MM= -google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= +google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/internal/reconcile/cache.go b/internal/reconcile/cache.go index 8be578a..61b8fe6 100644 --- a/internal/reconcile/cache.go +++ b/internal/reconcile/cache.go @@ -21,6 +21,10 @@ func (r *Reconciler) cacheGet() []string { // cacheLoadSecrets fetches all secrets from external source // and stores in cache as "key=value" items func (r *Reconciler) cacheLoadSecrets() error { + if r.sClient == nil { + return nil + } + secrets, err := r.sClient.FetchAll() if err != nil { return err diff --git a/internal/reconcile/reconcile.go b/internal/reconcile/reconcile.go index 93210d7..a17834e 100644 --- a/internal/reconcile/reconcile.go +++ b/internal/reconcile/reconcile.go @@ -9,18 +9,13 @@ import ( "github.com/veerendra2/composeflux/pkg/source" ) -type Timers struct { +type Config struct { + StackPath string `name:"stack-path" help:"Path to compose stack directory in git repository" env:"STACK_PATH" required:"" group:"Reconciler Options:"` + ConfigFile string `name:"config-file" help:"Stack configuration file name" env:"CONFIG_FILE" default:"stack.yml" group:"Reconciler Options:"` GitInterval time.Duration `name:"git-interval" help:"Git repository polling interval" env:"GIT_INTERVAL" default:"5m" group:"Reconciler Options:"` ImageUpdateSchedule string `name:"image-update-schedule" help:"Cron expression for Docker image update checks, e.g. '0 3 * * 1'. Empty = disabled." env:"IMAGE_UPDATE_SCHEDULE" default:"" group:"Reconciler Options:"` } -type Config struct { - StackPath string `name:"stack-path" help:"Path to compose stack directory in git repository" env:"STACK_PATH" required:"" group:"Reconciler Options:"` - ConfigFile string `name:"config-file" help:"Stack configuration file name" env:"CONFIG_FILE" default:"stack.yml" group:"Reconciler Options:"` - - Timers Timers `embed:"" group:"Reconciler Options:"` -} - type Reconciler struct { configFile string stackPath string @@ -43,8 +38,8 @@ func New(cfg Config, sClient secrets.Client, gClient source.Client, dClient dock configFile: cfg.ConfigFile, stackPath: cfg.StackPath, - gitInterval: cfg.Timers.GitInterval, - imageUpdateSchedule: cfg.Timers.ImageUpdateSchedule, + gitInterval: cfg.GitInterval, + imageUpdateSchedule: cfg.ImageUpdateSchedule, dClient: dClient, gClient: gClient, diff --git a/internal/reconcile/run.go b/internal/reconcile/run.go index 4ba4d88..203f0e6 100644 --- a/internal/reconcile/run.go +++ b/internal/reconcile/run.go @@ -38,7 +38,7 @@ func (r *Reconciler) Run(ctx context.Context) { } } - slog.Info("Starting reconciliation", "git_poll_interval", r.gitInterval) + slog.Info("Starting reconciliation") for { select { diff --git a/pkg/secrets/bitwarden.go b/pkg/secrets/bitwarden.go index cf91a29..08b7a99 100644 --- a/pkg/secrets/bitwarden.go +++ b/pkg/secrets/bitwarden.go @@ -8,8 +8,8 @@ type BitwardenConfig struct { ApiURL string `name:"api-url" help:"API URL" env:"API_URL" default:"https://vault.bitwarden.com/api"` IdentityURL string `name:"identity-url" help:"Identity URL" env:"IDENTITY_URL" default:"https://vault.bitwarden.com/identity"` AccessToken string `name:"access-token" help:"Access token" env:"ACCESS_TOKEN"` - OrgID string `name:"organization-id" help:"Organization ID" env:"ORGANIZATION_ID"` - ProjectID string `name:"project-id" help:"Project ID" env:"PROJECT_ID"` + OrgID string `name:"organization-id" help:"Organization ID" env:"ORGANIZATION_ID"` + ProjectID string `name:"project-id" help:"Project ID" env:"PROJECT_ID"` } type bitwardenClient struct { diff --git a/pkg/secrets/client.go b/pkg/secrets/client.go index c7ea0c1..d0d9cbb 100644 --- a/pkg/secrets/client.go +++ b/pkg/secrets/client.go @@ -6,8 +6,10 @@ import ( ) type Config struct { - BitwardenConfig `prefix:"bitwarden-" envprefix:"BITWARDEN_" embed:""` - InfisicalConfig `prefix:"infisical-" envprefix:"INFISICAL_" embed:""` + Provider string `name:"secrets-provider" enum:",bitwarden,infisical" env:"SECRETS_PROVIDER" default:"" help:"Secrets manager provider to use (bitwarden or infisical)"` + + Bitwarden BitwardenConfig `embed:"" prefix:"bitwarden-" envprefix:"BITWARDEN_" group:"Bitwarden Options:"` + Infisical InfisicalConfig `embed:"" prefix:"infisical-" envprefix:"INFISICAL_" group:"Infisical Options:"` } type Secret struct { @@ -21,14 +23,17 @@ type Client interface { Close() } -// New creates a secrets client based on the provider type -func New(ctx context.Context, provider string, cfg Config) (Client, error) { - switch provider { +// New creates a secrets client based on the provider type. +// Returns nil, nil when no provider is configured. +func New(ctx context.Context, cfg Config) (Client, error) { + switch cfg.Provider { + case "": + return nil, nil case "bitwarden": - return NewBitwardenClient(cfg.BitwardenConfig) + return NewBitwardenClient(cfg.Bitwarden) case "infisical": - return NewInfisicalClient(ctx, cfg.InfisicalConfig) + return NewInfisicalClient(ctx, cfg.Infisical) default: - return nil, fmt.Errorf("unsupported secrets provider: %s", provider) + return nil, fmt.Errorf("unsupported secrets provider: %s", cfg.Provider) } } diff --git a/pkg/source/git.go b/pkg/source/git.go index 3497ebe..71e7904 100644 --- a/pkg/source/git.go +++ b/pkg/source/git.go @@ -20,7 +20,7 @@ const ( type Config struct { RepoURL string `name:"repo-url" help:"Git repository URL (SSH)" env:"GIT_REPO_URL" required:""` SSHKeyPath string `name:"ssh-key-path" help:"Path to SSH private key" env:"GIT_SSH_KEY_PATH" default:"/.ssh/composeflux_id_rsa"` - DeployKeySecretRef string `name:"deploy-key-secret-ref" help:"Deploy key secret reference (name or ID) to fetch from secrets manager (leave empty to use existing key at ssh-key-path)" env:"GIT_DEPLOY_KEY_SECRET_REF" default:"SSH_PRIVATE_KEY" group:"Git Source Options:"` + DeployKeySecretRef string `name:"deploy-key-secret-ref" help:"Deploy key secret reference (name or ID) to fetch from secrets manager (leave empty to use existing key at ssh-key-path)" env:"GIT_DEPLOY_KEY_SECRET_REF" default:"" group:"Git Source Options:"` ClonePath string `name:"clone-path" help:"Local directory for git clone" env:"GIT_CLONE_PATH" default:"/opt/compose-stack"` Branch string `name:"branch" help:"Git branch to track" env:"GIT_BRANCH" default:"main" group:"Git Source Options:"` } @@ -130,7 +130,7 @@ func New(cfg Config) (Client, error) { repo, err := git.PlainOpen(cfg.ClonePath) if err != nil { if errors.Is(err, git.ErrRepositoryNotExists) { - slog.Info("Cloning repository", "url", cfg.RepoURL, "branch", cfg.Branch) + slog.Info("Cloning repository", "url", cfg.RepoURL, "branch", cfg.Branch, "path", cfg.ClonePath) repo, err = git.PlainClone(cfg.ClonePath, false, &git.CloneOptions{ URL: cfg.RepoURL, Auth: sshAuth, @@ -143,7 +143,7 @@ func New(cfg Config) (Client, error) { return nil, fmt.Errorf("failed to open repository: %w", err) } } else { - slog.Info("Opened existing repository", "url", cfg.RepoURL, "branch", cfg.Branch) + slog.Info("Opened existing repository", "url", cfg.RepoURL, "branch", cfg.Branch, "path", cfg.ClonePath) branchRef := plumbing.NewBranchReferenceName(cfg.Branch) w, err := repo.Worktree() if err != nil {