* Add pluggable secret provider foundation
Introduces a provider-agnostic secret backend abstraction so stacks can
pull deploy-time secrets from 1Password, Infisical or HashiCorp Vault.
Providers implement the SecretProvider contract in secretproviders/shared.ts
and advertise which of the two resolution modes they support:
- reference interpolation: a variable's value is an inline reference in
the backend's own syntax (e.g. 1Password's op://vault/item/field)
- bulk pull: every secret under a provider-defined selector, fetched as
a flat key/value map
Adding a backend means dropping a file in secretproviders/ and registering
it in the index; no caller changes.
Four providers ship here: 1Password service account (references + bulk),
1Password Connect (references), Infisical (bulk) and HashiCorp Vault KV v2
(bulk). Provider config is persisted as an encrypted JSON blob in the new
secret_providers table, mirroring the existing notification_settings
type+config pattern, and stack_sources gains a nullable secret_provider_id.
Decrypted tokens stay in the provider modules for the duration of a call
and are never written to disk or returned to the client.
Co-authored-by: GooRoo <sergey.olendarenko@gmail.com>
* Add secret provider API routes and permissions
CRUD plus two test-connection endpoints under /api/secret-providers. The
unsaved variant validates a config posted from the provider form; the
stored variant tests a persisted provider by id.
Responses only ever carry provider summaries (id, type, name, timestamps).
The decrypted config never leaves the server.
Adds a "secrets" permission resource with view/create/edit/delete actions,
wired through the permission type, the role editor and the seeded roles,
and a secret_provider audit entity type so provider changes are logged.
Config rotations are audited as a boolean rather than a value.
* Resolve provider secrets at stack deploy time
Stacks can bind a secret provider; when one is bound, its secrets are
resolved and merged into the vars handed to docker compose.
Bulk pull is triggered by a selector variable: OP_ENVIRONMENT_ID, kept for
compatibility with the original 1Password integration, or the generic
DOCKHAND_SECRET_SELECTOR for any bulk-capable provider. The selector is
consumed by the provider and stripped before the vars reach the stack.
Values the bound provider recognises as inline references are resolved in
place, and references found only in the stack's .env file are promoted into
the secret set so they are never written back as literals.
Resolution is applied on deploy, on up, on recreate and on service update.
Explicitly configured secrets always win over bulk-pulled ones. A missing
or unsupported provider warns and leaves values untouched rather than
failing the deploy; a provider that errors mid-resolution fails it.
* Add secret provider settings UI and stack binding
A Secrets tab under settings lists configured providers and drives the
create/edit modal, which switches its fields on the selected provider type
and can test a connection before saving.
Both stack modals gain a provider selector so a stack can be bound to a
provider or left unbound. When a provider is bound, the bulk selector
variables are no longer reported as unused compose variables, since they
are consumed by the provider rather than the compose file.
---------
Co-authored-by: GooRoo <sergey.olendarenko@gmail.com>
Co-authored-by: Jarek Krochmalski <jotka@users.noreply.github.com>
startStack, restartStack, stopStack, and downStack each ran a separate
getStackSource query just to determine isGitStack for useOverrideFile,
even though requireComposeFile -> getStackComposeFile already looks up
the same stack_sources record.
Return sourceType from getStackComposeFile (GetComposeFileResult) and
requireComposeFile (RequireComposeResult), and have all four callers
read result.sourceType instead of querying the database again. No
behavior change: useOverrideFile is still set to true exactly when the
stack's source type is 'git'.
startStack and restartStack compute isGitStack and pass useOverrideFile
accordingly, so executeLocalCompose writes .env.dockhand with the DB
non-secret overrides before invoking docker compose. stopStack and
downStack never set this option, so whenever a .env file exists in the
stack's working directory (e.g. committed alongside compose.yaml in a
git stack), the legacy shell-env fallback in executeLocalCompose is
suppressed and .env.dockhand is never written or attached via
--env-file. Any required variable that only exists as a Dockhand panel
override then fails interpolation on stop/down, even though the same
stack deploys and starts successfully.
Mirror startStack/restartStack: compute isGitStack from the stack
source and pass useOverrideFile through in both operations.
- isHarborRegistry: derive scheme and port from the registry URL via
parseRegistryUrl instead of hardcoding https:// so HTTP-only mirrors and
non-443 ports are detected (same scheme handling as getRegistryAuthHeader).
- isHarborRegistry: only cache the detection result when a definitive answer
was obtained; a transient network error no longer pins "not Harbor" for the
whole TTL and keeps returning _catalog 403s after Harbor recovers.
- getHarborBasicAuth: trim username/password (pasted credentials with trailing
whitespace silently broke Basic auth).
- harborListRepositories: implement real cross-project pagination for the
no-orgPath case (enumerate every project and all repos, paginate the
flattened list) so >100 projects / >100 repos no longer truncate silently
with a hardcoded hasMore=false.
- harborSearchRepositories: sanitize the search term against Harbor's query
grammar (, = ~ ( )) so special characters can't break or alter the q filter;
paginate the project list as well.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Harbor denies access to the V2 _catalog endpoint for robot accounts
(scope registry:catalog:* returns an empty JWT). This adds automatic
Harbor detection and falls back to the native Harbor project API
(/api/v2.0/projects/{name}/repositories) for both catalog listing
and image search.
- Detect Harbor via WWW-Authenticate header + /api/v2.0/ping (cached 5min)
- List repositories through Harbor project API with pagination
- Search repositories using Harbor's q=name=~ filter
- Transparent fallback: no configuration change required
Fixes#360
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Bark as a supported Apprise notification protocol.
Supported URL formats:
- bark://bark_key uses the official Bark server at https://api.day.app/
- bark://host/bark_key uses a custom Bark server over HTTP
- barks://host/bark_key uses a custom Bark server over HTTPS
Bark notifications are sent with POST JSON payloads containing the device key, title, and body. The notification settings modal now
lists Bark examples in the Apprise URL placeholder and support text.