Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
d3cd87a
fix(validate): use from_str_raw to skip \ resolution on /validate
robotizeit Aug 14, 2026
ce860e5
chore: merge validate fix (from_str_raw) from feature/immutable-deploy
robotizeit Aug 14, 2026
01c32cf
server type/region validation hetzner
robotizeit Aug 14, 2026
f365b3a
assign ipv4 using hetzner api
robotizeit Aug 15, 2026
099a1f5
assign ipv4 using hetzner api
robotizeit Aug 15, 2026
050019e
feat: post-deploy Ansible setup for one-click clone deploys
robotizeit Aug 15, 2026
d89e121
fix: map publish result to () in post_deploy_clone
robotizeit Aug 15, 2026
9bfe504
fix: add add_ssh_key to MockOk impl of HetznerCloudConnector
robotizeit Aug 15, 2026
e2a7684
fix: unique server names for clone deploys (avoid 409 collision)
robotizeit Aug 15, 2026
554ca7f
feat: deployment_daily billing model - schema, clone authorize, serve…
robotizeit Aug 16, 2026
16d2a71
feat: sweeper daily billing sweep for deployment_daily
robotizeit Aug 16, 2026
1276eab
feat: server delete stops deployment_daily billing
robotizeit Aug 16, 2026
eac10c5
feat: daily_capture_install_charge for partial captures
robotizeit Aug 16, 2026
89a9f9e
fix: install user SSH public key on cloud VMs (#229)
robotizeit Aug 17, 2026
d5127d7
style: apply cargo fmt to billing and connector code
robotizeit Aug 17, 2026
ebad08a
Merge pull request #231 from trydirect/feature/deployment-daily-billing
vsilent Aug 17, 2026
ae3b123
fix: fail fast when Docker is missing on intranet server (#228)
robotizeit Aug 17, 2026
ad7f344
fix: persist proxy config to stacker.yml before agent call (#224)
robotizeit Aug 17, 2026
1d9e773
fix: prefer newest server on --force-new (#221) and skip IP retry on …
robotizeit Aug 17, 2026
f5aa3ea
fix: skip post_deploy and on_failure hooks for remote deploys (#227)
robotizeit Aug 17, 2026
26f38b9
fix: fall back to access_control.conf.dist in CI
robotizeit Aug 17, 2026
6cd079a
Revert "fix: fall back to access_control.conf.dist in CI"
robotizeit Aug 17, 2026
db8d9ed
fix: track access_control.conf in git
robotizeit Aug 17, 2026
d5337e6
fix: copy access_control.conf.dist before BDD tests in CI
robotizeit Aug 17, 2026
b3e1f06
fix: preserve stacker.yml structure when adding/removing services
robotizeit Aug 17, 2026
bdaa485
fix: add missing billing fields to test fixtures
robotizeit Aug 17, 2026
1a402bc
fix: add daily_rate/monthly_cap to test fixtures and webhook payload
robotizeit Aug 17, 2026
bdd4b4d
Merge branch 'fix/publish-confirm-type' into feature/deployment-daily…
robotizeit Aug 17, 2026
b7fd2eb
Merge branch 'dev' of github.com:trydirect/stacker into dev
robotizeit Aug 17, 2026
ae6992c
fix: read STACKER_PER_INSTALL_BILLING_ENABLED from env var
robotizeit Aug 17, 2026
40a64bb
fix: add debug logging and proper error handling for billing flow
robotizeit Aug 17, 2026
4d842dd
feat: add daily_rate/monthly_cap to admin pricing API + template crea…
robotizeit Aug 17, 2026
26407d7
fix: use surgical YAML updates for service import, proxy add, and con…
robotizeit Aug 17, 2026
fbffb9e
fix: prevent --force-new and --force-rebuild from being used together…
robotizeit Aug 17, 2026
6bd6d81
fix: cast daily_rate/monthly_cap to float8 in get_approved_by_slug query
robotizeit Aug 18, 2026
da63ac5
feat: show git short hash in startup banner for deployment verification
robotizeit Aug 18, 2026
816bb33
fix: write provider enum name (hetzner) to stacker.yml, not short cod…
robotizeit Aug 18, 2026
0622a76
fix: persist proxy type when proxy section already exists
robotizeit Aug 19, 2026
0543d24
fix: use option_env! for git hash so Docker builds work without .git
robotizeit Aug 19, 2026
08cc7a0
fix: migration to cast daily_rate/monthly_cap columns to float8
robotizeit Aug 19, 2026
718a4dd
fix: refuse one-click deploy of deployment_daily template when user h…
robotizeit Aug 19, 2026
adf5066
Revert "fix: refuse one-click deploy of deployment_daily template whe…
robotizeit Aug 19, 2026
1e5e561
fix: require user access token up-front in one-click clone (401 inste…
robotizeit Aug 19, 2026
1cdfa03
test: cover clone auth guard (missing/blank token -> 401, present tok…
robotizeit Aug 19, 2026
6c9e3e0
fix: return 404 for unregistered stacks in one-click clone instead of…
robotizeit Aug 19, 2026
7b3c96f
require_app_image_for_remote_deploy
robotizeit Aug 19, 2026
144d1a6
Fix: after merging, explicitly declare (external: true) every network…
robotizeit Aug 19, 2026
84e1d2c
config renderer: Fix: derive the top-level network name from the apps…
robotizeit Aug 20, 2026
7982db8
Fix (src/cli/install_runner.rs):
robotizeit Aug 20, 2026
af2f36e
Fix: added is_named_volume() to tell named volumes apart from bind mo…
robotizeit Aug 20, 2026
08fb683
default_network: external=true
robotizeit Aug 20, 2026
9cf1e44
support traefik and caddy as a proxy manager
robotizeit Aug 20, 2026
5f3df1d
fix: per-project directory namespacing for server deploys (#240)
robotizeit Aug 21, 2026
8a12712
fix: use sanitized project name in all remote path construction
robotizeit Aug 21, 2026
b980cda
fix: ensure stack_code uniqueness by appending project.id
robotizeit Aug 21, 2026
d43faaa
fix: propagate stack_code uniqueness suffix to Vault and MCP explain
robotizeit Aug 21, 2026
d3a2183
fix: update cli_explain test assertions for per-project paths
robotizeit Aug 21, 2026
9405202
fix: update test assertions for per-project paths
robotizeit Aug 21, 2026
5326203
the NPM double-deploy fix
robotizeit Aug 23, 2026
b1caa5a
#2 — proxy/service ingress port-conflict warning (W003) - done
robotizeit Aug 24, 2026
f314622
fixed. Port host binding. The extract_host_port bug
robotizeit Aug 24, 2026
3010ea5
#4 all proxies are now platform-managed (backend-role-owned)
robotizeit Aug 24, 2026
a5b6daa
save the ssh key early (while provisioning is in progress)
robotizeit Aug 24, 2026
1a4dfb3
caddy proxy_domains, SslMOdes, 3 hops
robotizeit Aug 24, 2026
4bf8671
proxy_domains to project install_data
robotizeit Aug 24, 2026
c331c17
proxy_domains now reaches the install service
robotizeit Aug 24, 2026
ab2270d
Caddy file generation for target=local
robotizeit Aug 25, 2026
f7070de
new host_is_unresolved_placeholder() helper in src/console/commands/…
robotizeit Aug 25, 2026
4ebd25f
Fixed:
robotizeit Aug 25, 2026
db8c769
active agent per project fix
robotizeit Aug 25, 2026
0c2bd13
#225 fixed
robotizeit Aug 25, 2026
0b2d6a6
#223 fix
robotizeit Aug 25, 2026
4787d47
PIPE manual, improvements
robotizeit Aug 25, 2026
ef3ee20
#214 is fully resolved and verified live on both dev and production: …
robotizeit Aug 25, 2026
c88b2a5
stacker.yml → pipe diff → pipe apply [--prune] → in sync + ide…
robotizeit Aug 26, 2026
3e9cfda
terminal-notify monitor on pipe-webhook-demo, verified live, Added a …
robotizeit Aug 26, 2026
140f1be
prepare for v0.3.2
robotizeit Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ jobs:
run: cargo nextest run --partition count:${{ matrix.shard }}/${{ matrix.shards }} -E 'not binary(bdd)'
# The `bdd` test uses a custom harness (harness = false), which nextest
# cannot run, so execute it once on shard 1 with the standard test runner.
- name: Prepare config files for BDD tests
if: ${{ matrix.shard == 1 }}
run: cp access_control.conf.dist access_control.conf
- name: Run BDD suite
if: ${{ matrix.shard == 1 }}
run: cargo test --test bdd
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
target
.idea/
files
access_control.conf
configuration.yaml
configuration.yaml.backup
configuration.yaml.orig
Expand All @@ -17,3 +16,5 @@ plan/
.stacker
done.txt
post-deploy-ran.txt

bake/
96 changes: 96 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,102 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.3.2] — 2026-08-26

### Added — Container-health monitoring & alerts (`stacker monitor`)

- New `stacker monitor` command: watches the deployment's live container health
(via the Status Panel agent) and raises an alert when a container is not
running, then again when everything recovers. Edge-triggered — it notifies
once per transition, not on every poll.
- `stacker monitor --once` runs a single check and exits (cron-friendly);
without it, the command loops on an interval. `--interval <seconds>` overrides
the configured poll interval.
- New `monitoring.alerts` block in `stacker.yml` configures the alarm:

```yaml
monitoring:
alerts:
interval: 60 # poll interval, seconds (default 60)
on_recovery: true # also notify when containers recover (default true)
target:
terminal: true # terminal + desktop notification
# url: "https://ntfy.example.com/alerts" # or: HTTP webhook
# method: POST
# pipe: oncall-notify # or: run a declared pipe (deferred)
```

- Alert state is persisted to `.stacker/monitor.state`, so `--once` invocations
stay edge-triggered across cron runs.
- The alarm logic lives in a new, dependency-free `health-monitor` crate (pure
health evaluation + transition detection), reusable by the CLI and, later, the
agent.

### Added — Declarative pipes (Infrastructure-as-Code)

- New `pipes:` block in `stacker.yml` declares pipes as committable config
(name, source/target, endpoints, fields, trigger, retry, handlers) — the
same surface as the `pipe create` flags, now version-controllable.
- `stacker pipe diff` compares the declared `pipes:` against what is deployed
and prints a plan: `create` (declared, not deployed), `update` (differs, with
the exact field changes), `unchanged`, and `orphan` (deployed, not declared).
`--json` for scripting; a clean tree reports "In sync".
- `stacker pipe apply` reconciles the declaration into the deployment: creates
declared-but-missing pipes (template + instance) and, with `--prune`, deletes
deployed pipes not in `stacker.yml`. `--dry-run` shows the plan without acting;
re-running is idempotent.

### Added — Manual pipe endpoints & non-interactive `pipe create`

- `stacker pipe create` now accepts explicit endpoints, bypassing endpoint
discovery entirely: `--source-endpoint "METHOD /path"`,
`--target-endpoint "METHOD /path"`, `--source-fields`, `--target-fields`, and
`--name`. This lets any app (or arbitrary HTTP endpoint) be piped — including
apps whose APIs aren't at auto-discoverable paths — and makes pipe creation
fully scriptable.

### Added — Pipe resilience: retry policy & lifecycle handlers

- `stacker pipe create` gained `--retry <n>`, `--retry-backoff-ms <base>`,
`--retry-backoff-max-ms <max>`, `--on-failure <pipe>`, and
`--on-success <pipe>`. These are persisted in the pipe's typed config
(retry policy + handler references) so the runtime can honor them.

### Added — Reverse-proxy routing (traefik, caddy, nginx-proxy-manager)

- `proxy.type` now produces working routing for all supported proxies:
Traefik via container labels, Caddy via a generated `Caddyfile`, and
Nginx Proxy Manager via auto-created proxy hosts — all driven by
`proxy.domains` (`{domain, upstream, ssl}`), which is now forwarded end-to-end
to the Install Service.
- Proxies are **platform-managed**: the synthesized proxy service is stripped
from the compose shipped to the server (deployed by its own role instead), so
a `proxy:` block no longer double-deploys the proxy or collides on ports
80/443/81.
- For `--target local`, the CLI now renders the proxy config file itself
(`.stacker/Caddyfile`) so the bind mount is a real file locally.

### Fixed — Proxy config validation & port-conflict detection

- New `W003` warning: a `proxy:` block plus a service publishing the same
ingress host port (80/443/81) is flagged as a likely conflict.
- Fixed `W001` host-port comparison to correctly handle the
`ip:host:container` binding form (previously produced false positives and
false negatives on loopback bindings).

### Fixed — SSH backup key saved during provisioning

- The local emergency SSH backup keypair is now saved the moment the server
first appears during a cloud deploy's watch loop, instead of only after the
watch completes. Interrupting the watch (timeout / Ctrl-C / network) no longer
loses SSH access to a successfully-deployed server.

### Fixed — Remote compose cleanup

- Stripping a platform-managed proxy from the remote compose now also prunes the
named volumes only that proxy used (e.g. `caddy_data`/`caddy_config`), instead
of leaving orphaned top-level volume declarations.

### Added — Deployment command aliases and `--pinned` flag

- Added `stacker deployment status` as a visible alias for `stacker deployment state`.
Expand Down
11 changes: 10 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[package]
name = "stacker"
version = "0.3.1"
version = "0.3.2"
edition = "2021"
default-run= "server"

[workspace]
members = ["crates/pipe-adapter-sdk", "crates/pipe-adapter-mail", "crates/td-audit"]
members = ["crates/pipe-adapter-sdk", "crates/pipe-adapter-mail", "crates/td-audit", "crates/health-monitor"]
resolver = "2"

[lib]
Expand Down Expand Up @@ -39,6 +39,7 @@ config = "0.13.4"
reqwest = { version = "0.11.23", features = ["json", "blocking", "stream", "native-tls"] }
serde = { version = "1.0.195", features = ["derive"] }
td-audit = { path = "crates/td-audit" }
health-monitor = { path = "crates/health-monitor" }
tokio = { version = "1.28.1", features = ["full"] }
tracing = { version = "0.1.40", features = ["log"] }
tracing-bunyan-formatter = "0.3.8"
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ monitoring:
healthcheck:
endpoint: /health
interval: 30s
alerts: # container-down alarm for `stacker monitor` (0.3.2)
interval: 30
target:
terminal: true # or: { url: "https://ntfy.example.com/alerts" }
```

Full schema reference: [docs/STACKER_YML_REFERENCE.md](docs/STACKER_YML_REFERENCE.md)
Expand Down Expand Up @@ -239,14 +243,17 @@ The end-user tool. No server required for local deploys.
| `stacker pipe scan` | Discover local endpoints/resources from running containers (when target is `local`) |
| `stacker pipe scan --containers [filter]` | Discover local endpoints/resources for matching containers |
| `stacker pipe scan --app <app>` | Probe a remote app for API endpoints |
| `stacker pipe create <src> <tgt>` | Create a data pipe between two containers (interactive) |
| `stacker pipe create <src> <tgt>` | Create a data pipe between two containers (interactive; or non-interactive with `--source-endpoint`/`--target-endpoint`/`--name` — added in 0.3.2). `--retry`/`--on-failure`/`--on-success` attach a retry policy + lifecycle handlers |
| `stacker pipe diff` | Compare the declared `pipes:` block against deployed pipes (added in 0.3.2) |
| `stacker pipe apply` | Reconcile declared pipes into the deployment; `--prune` removes orphans, `--dry-run` previews (added in 0.3.2) |
| `stacker pipe list` | List pipe instances for the current deployment |
| `stacker pipe activate <id>` | Activate a pipe (start listening for triggers) |
| `stacker pipe deactivate <id>` | Pause an active pipe |
| `stacker pipe trigger <id>` | One-shot pipe execution with optional input data |
| `stacker pipe deploy <id>` | Promote a local pipe to a remote deployment |
| `stacker pipe history <id>` | View execution history for a pipe |
| `stacker pipe replay <exec-id>` | Re-run a previous pipe execution |
| `stacker monitor` | Watch container health and alert on problems (added in 0.3.2); `--once` for a single check (cron-friendly). Configure via `monitoring.alerts` in `stacker.yml` |
| `stacker target [local\|cloud\|server]` | Switch deployment target mode |
| `stacker env [local\|dev\|prod]` | Show or persist the active deploy environment/profile used by app-only updates |
| `stacker whoami` | Show the active login, subscription plan, and current project deployment context |
Expand Down Expand Up @@ -376,7 +383,9 @@ curl -sL https://marketplace.try.direct/<purchase-token>/install.sh | sh
- **Agent control** — `stacker agent` subcommand to manage remote Status Panel agents (health, logs, restart, deploy, proxy) with `--json` output
- **SSH key management** — generate, view, upload, and repair server SSH keys
(Vault-backed), with automatic local backup SSH access after cloud deploy
- **Reverse proxy** — auto-detects Nginx / Nginx Proxy Manager, configures domains + SSL
- **Reverse proxy** — Traefik (labels), Caddy (Caddyfile), and Nginx Proxy Manager, platform-managed and driven by `proxy.domains` end-to-end (0.3.2)
- **Container-health alarm** — `stacker monitor` watches container health and alerts on problems (terminal, webhook, or pipe), configured via `monitoring.alerts` (0.3.2)
- **Declarative pipes (IaC)** — declare pipes in `stacker.yml` and reconcile with `stacker pipe diff` / `pipe apply [--prune]` (0.3.2)
- **Cloud deployment** — Hetzner, DigitalOcean, AWS, Linode, with provider firewall operations and paused/failed install IP retention
- **MCP Server** — 85+ tools, including deployment, agent control, config, proxy, firewall, and remote service secret management
- **Marketplace** — submit stacks for review, auto-publish on approval, check status from CLI
Expand Down Expand Up @@ -520,6 +529,7 @@ cargo test user_service_client # User Service connector
cargo test marketplace_webhook # Marketplace webhook flows
cargo test deployment_validator # Deployment validation
cargo test --test security_cli # CLI endpoint IDOR security tests
SQLX_OFFLINE=true cargo test --lib -- proxy_domains 2>&1 | tail -10
```

---
Expand Down
14 changes: 14 additions & 0 deletions access_control.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && r.act == p.act
12 changes: 12 additions & 0 deletions crates/health-monitor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "health-monitor"
version = "0.1.0"
edition = "2021"
description = "Pure container-health alarm engine for TryDirect Stacker: evaluate container health snapshots, edge-detect down/recovery transitions, and format alerts. No I/O, network, or scheduler — the CLI/agent drive it."

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[features]
default = []
Loading
Loading