Skip to content

Commit 7833865

Browse files
committed
proxy config update
1 parent c1b6ff6 commit 7833865

11 files changed

Lines changed: 1082 additions & 58 deletions

File tree

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ minimal = []
1010

1111
[dependencies]
1212
anyhow = "1"
13+
async-trait = "0.1"
1314
thiserror = "2"
1415
serde = { version = "1", features = ["derive"] }
1516
serde_json = "1"
@@ -46,6 +47,7 @@ daemonize = "0.5"
4647
dotenvy = "0.15"
4748
# Runtime Rust version info
4849
rustc_version_runtime = "0.3"
50+
zeroize = "1"
4951

5052
[target.'cfg(unix)'.dependencies]
5153
nix = { version = "0.29", features = ["signal"] }

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ The agent accepts signed commands from the Stacker dashboard covering the full l
197197

198198
## Configuration
199199

200+
`STATUS_PANEL_USERNAME` / `STATUS_PANEL_PASSWORD` only control the Status Panel UI. `configure_proxy`
201+
uses a separate Nginx Proxy Manager credential resolved from Vault with `STACKER_SERVER_ID`.
202+
200203
| Environment Variable | Description |
201204
|---------------------|-------------|
202205
| `STATUS_PANEL_USERNAME` | **Required.** Login username |
@@ -205,6 +208,9 @@ The agent accepts signed commands from the Stacker dashboard covering the full l
205208
| `AGENT_TOKEN` | Authentication token for signed requests |
206209
| `DASHBOARD_URL` | Remote dashboard URL |
207210
| `VAULT_ADDRESS` | HashiCorp Vault server URL |
211+
| `STACKER_SERVER_ID` | Stable server UUID used to resolve host-scoped NPM credentials in Vault |
212+
| `STATUS_PANEL_PROXY_OWNER` | Set `true` on the single agent allowed to manage shared proxy state |
213+
| `NPM_ALLOW_ENV_FALLBACK` | Temporary migration switch for legacy `NPM_*` env credentials |
208214
| `UPDATE_SERVER_URL` | Remote update server for version checks |
209215
| `UPDATE_EXPECTED_SHA256` | Expected SHA256 hash for self-update binary |
210216
| `COMPOSE_AGENT_ENABLED` | Enable compose-agent mode |

TODO.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
- [x] Support unlinking from dashboard (agent continues to work standalone)
2020
- [ ] **Login-based linking flow (Entry Point C):**
2121
- User logs in with TryDirect email + password from Status Panel UI
22-
- Status Panel calls Stacker: `POST /api/v1/auth/login { email, password }` → returns `session_token` + user's deployments
23-
- User selects a deployment from the list → Status Panel calls Stacker: `POST /api/v1/agents/link { session_token, deployment_id, server_fingerprint }`
22+
- Status Panel calls Stacker: `POST /api/v1/agent/login { email, password }` → returns `session_token` + user's deployments
23+
- User selects a deployment from the list → Status Panel calls Stacker: `POST /api/v1/agent/link { session_token, deployment_id, server_fingerprint, capabilities }`
2424
- Stacker validates session, checks user owns the deployment, issues `agent_id` + `agent_token`
2525
- No purchase_token needed — user's identity is the trust anchor
2626
- `purchase_token` flow retained only for headless Entry Point B (curl one-liner)

src/agent/init.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ STATUS_PANEL_PASSWORD=
5656
# ── Vault (optional) ────────────────────────────────────────────────
5757
# VAULT_ADDRESS=http://127.0.0.1:8200
5858
# VAULT_TOKEN=
59+
# STACKER_SERVER_ID=
60+
# STATUS_PANEL_PROXY_OWNER=true
61+
# NPM_ALLOW_ENV_FALLBACK=false
5962
6063
# ── Self-update (optional) ──────────────────────────────────────────
6164
# UPDATE_SERVER_URL=

src/agent/registration.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ use serde::{Deserialize, Serialize};
22
use std::path::Path;
33
use tracing::{debug, info};
44

5+
const STATUS_PANEL_CAPABILITY: &str = "status_panel";
6+
const NPM_CREDENTIAL_SOURCE_VAULT: &str = "npm_credential_source=vault";
7+
58
#[derive(Debug, Serialize)]
69
pub struct RegistrationRequest {
710
pub purchase_token: String,
@@ -40,6 +43,7 @@ pub struct LinkAgentRequest {
4043
pub session_token: String,
4144
pub deployment_id: String,
4245
pub server_fingerprint: ServerFingerprint,
46+
pub capabilities: Vec<String>,
4347
}
4448

4549
#[derive(Debug, Serialize)]
@@ -120,6 +124,54 @@ pub fn collect_fingerprint() -> ServerFingerprint {
120124
}
121125
}
122126

127+
pub async fn collect_capabilities(default_compose_agent_enabled: bool) -> Vec<String> {
128+
let compose_agent = std::env::var("COMPOSE_AGENT_ENABLED")
129+
.ok()
130+
.and_then(|value| value.parse::<bool>().ok())
131+
.unwrap_or(default_compose_agent_enabled);
132+
133+
let mut features = vec![
134+
STATUS_PANEL_CAPABILITY.to_string(),
135+
"monitoring".to_string(),
136+
NPM_CREDENTIAL_SOURCE_VAULT.to_string(),
137+
];
138+
139+
if cfg!(feature = "docker") {
140+
features.push("docker".to_string());
141+
features.push("compose".to_string());
142+
features.push("logs".to_string());
143+
features.push("restart".to_string());
144+
}
145+
146+
if compose_agent {
147+
features.push("compose_agent".to_string());
148+
}
149+
150+
#[cfg(feature = "docker")]
151+
{
152+
if crate::commands::stacker::detect_kata_runtime().await {
153+
features.push("kata".to_string());
154+
}
155+
}
156+
157+
features
158+
}
159+
160+
fn marketplace_registration_url(dashboard_url: &str) -> String {
161+
format!(
162+
"{}/api/v1/marketplace/agents/register",
163+
dashboard_url.trim_end_matches('/')
164+
)
165+
}
166+
167+
fn agent_link_url(stacker_url: &str) -> String {
168+
format!("{}/api/v1/agent/link", stacker_url.trim_end_matches('/'))
169+
}
170+
171+
fn agent_login_url(stacker_url: &str) -> String {
172+
format!("{}/api/v1/agent/login", stacker_url.trim_end_matches('/'))
173+
}
174+
123175
/// Register this agent with the Stacker Server using a purchase token.
124176
pub async fn register_with_stacker(
125177
dashboard_url: &str,
@@ -142,7 +194,7 @@ pub async fn register_with_stacker(
142194
stack_id: stack_id.to_string(),
143195
};
144196

145-
let url = format!("{}/api/v1/agents/register", dashboard_url);
197+
let url = marketplace_registration_url(dashboard_url);
146198
info!(url = %url, stack_id = %stack_id, "sending registration request to Stacker");
147199

148200
let client = reqwest::Client::new();
@@ -193,7 +245,7 @@ pub async fn login_to_stacker(
193245
password: password.to_string(),
194246
};
195247

196-
let url = format!("{}/api/v1/auth/login", stacker_url);
248+
let url = agent_login_url(stacker_url);
197249
info!(url = %url, email = %email, "sending login request to Stacker");
198250

199251
let client = reqwest::Client::new();
@@ -219,8 +271,10 @@ pub async fn link_agent_to_deployment(
219271
stacker_url: &str,
220272
session_token: &str,
221273
deployment_id: &str,
274+
default_compose_agent_enabled: bool,
222275
) -> Result<RegistrationResponse, Box<dyn std::error::Error>> {
223276
let fingerprint = collect_fingerprint();
277+
let capabilities = collect_capabilities(default_compose_agent_enabled).await;
224278
debug!(
225279
hostname = %fingerprint.hostname,
226280
deployment_id = %deployment_id,
@@ -231,9 +285,10 @@ pub async fn link_agent_to_deployment(
231285
session_token: session_token.to_string(),
232286
deployment_id: deployment_id.to_string(),
233287
server_fingerprint: fingerprint,
288+
capabilities,
234289
};
235290

236-
let url = format!("{}/api/v1/agents/link", stacker_url);
291+
let url = agent_link_url(stacker_url);
237292
info!(url = %url, deployment_id = %deployment_id, "sending link request to Stacker");
238293

239294
let client = reqwest::Client::new();
@@ -286,4 +341,39 @@ mod tests {
286341
assert_eq!(parsed["deployment_hash"], "hash-abc");
287342
assert_eq!(parsed["dashboard_url"], "https://stacker.try.direct");
288343
}
344+
345+
#[test]
346+
fn marketplace_registration_url_uses_marketplace_scope() {
347+
assert_eq!(
348+
marketplace_registration_url("https://stacker.try.direct/"),
349+
"https://stacker.try.direct/api/v1/marketplace/agents/register"
350+
);
351+
}
352+
353+
#[test]
354+
fn agent_link_url_uses_singular_agent_scope() {
355+
assert_eq!(
356+
agent_link_url("https://stacker.try.direct/"),
357+
"https://stacker.try.direct/api/v1/agent/link"
358+
);
359+
}
360+
361+
#[test]
362+
fn agent_login_url_uses_agent_scope() {
363+
assert_eq!(
364+
agent_login_url("https://stacker.try.direct/"),
365+
"https://stacker.try.direct/api/v1/agent/login"
366+
);
367+
}
368+
369+
#[tokio::test]
370+
async fn collect_capabilities_includes_vault_proxy_marker() {
371+
let capabilities = collect_capabilities(false).await;
372+
assert!(capabilities
373+
.iter()
374+
.any(|cap| cap == STATUS_PANEL_CAPABILITY));
375+
assert!(capabilities
376+
.iter()
377+
.any(|cap| cap == NPM_CREDENTIAL_SOURCE_VAULT));
378+
}
289379
}

0 commit comments

Comments
 (0)