feat(#2415): hive-priv RegisterCiRunner primitive + c0re client
Part A of moving hive-ci runner registration off the boot-critical path. Adds a root-side hive-priv op that writes the runner registration token to the host env-file /run/hive-ci/runner-token (in-place, preserving the inode the container bind-mounts) and restarts the in-container gitea-runner-hive unit. The forge admin token stays in hive-c0re; only the registration token reaches the host env-file the container mounts read-only. The c0re-side caller (ensure_ci_runner_registered) + the nix boot-path change land next on this branch.
This commit is contained in:
parent
f902592e71
commit
858475549a
3 changed files with 68 additions and 0 deletions
|
|
@ -322,6 +322,18 @@ pub async fn restart_matrix_daemon(agent_name: &str) -> Result<()> {
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register the hive-ci Forgejo Actions runner: hand the freshly-minted
|
||||||
|
/// registration token to hive-priv, which writes it to the host-side
|
||||||
|
/// `/run/hive-ci/runner-token` env-file and restarts the in-container runner.
|
||||||
|
/// The forge admin token stays in hive-c0re; only the registration token
|
||||||
|
/// crosses to the (host-path) env-file the container bind-mounts read-only.
|
||||||
|
pub async fn register_ci_runner(token: &str) -> Result<()> {
|
||||||
|
ok(call(&PrivRequest::RegisterCiRunner {
|
||||||
|
token: token.to_owned(),
|
||||||
|
})
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Restart a hive infrastructure container on the host (thin wrapper over
|
/// Restart a hive infrastructure container on the host (thin wrapper over
|
||||||
/// [`control_infra_container`] with `action = Restart`). hive-priv
|
/// [`control_infra_container`] with `action = Restart`). hive-priv
|
||||||
/// re-validates `container` against its root-side allowlist; callers must
|
/// re-validates `container` against its root-side allowlist; callers must
|
||||||
|
|
|
||||||
|
|
@ -489,6 +489,19 @@ pub enum PrivRequest {
|
||||||
agent_name: String,
|
agent_name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Register the hive-ci Forgejo Actions runner: write the registration
|
||||||
|
/// token to the host-side `/run/hive-ci/runner-token` env-file (root-owned,
|
||||||
|
/// bind-mounted read-only into the container) as `TOKEN=<token>`, then
|
||||||
|
/// `systemctl --machine=hive-ci restart gitea-runner-hive.service` so the
|
||||||
|
/// runner picks up the credential. hive-c0re holds the forge admin token
|
||||||
|
/// and mints the registration token; only the registration token is written
|
||||||
|
/// here, and only to a host path — the admin token never enters the
|
||||||
|
/// container. The token is validated single-line + non-empty root-side.
|
||||||
|
RegisterCiRunner {
|
||||||
|
/// Forge runner registration token minted by hive-c0re.
|
||||||
|
token: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Start / stop / restart a hive infrastructure container on the host
|
/// Start / stop / restart a hive infrastructure container on the host
|
||||||
/// via `systemctl <action> container@<container>.service`. The
|
/// via `systemctl <action> container@<container>.service`. The
|
||||||
/// [`InfraContainer`] enum is the allowlist — serde rejects unknown /
|
/// [`InfraContainer`] enum is the allowlist — serde rejects unknown /
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,8 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
||||||
restart_matrix_daemon(agent_name).await
|
restart_matrix_daemon(agent_name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrivRequest::RegisterCiRunner { ref token } => register_ci_runner(token).await,
|
||||||
|
|
||||||
PrivRequest::ControlInfraContainer { container, action } => {
|
PrivRequest::ControlInfraContainer { container, action } => {
|
||||||
control_infra_container(container, action).await
|
control_infra_container(container, action).await
|
||||||
}
|
}
|
||||||
|
|
@ -596,6 +598,47 @@ async fn restart_matrix_daemon(agent_name: &str) -> Result<(String, String)> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `RegisterCiRunner` — write the runner registration token to the host-side
|
||||||
|
/// `/run/hive-ci/runner-token` env-file, then restart the in-container runner
|
||||||
|
/// so it re-registers. The forge admin token never enters the container; only
|
||||||
|
/// the registration token c0re passes here is written, and it lands on a host
|
||||||
|
/// path bind-mounted read-only into hive-ci.
|
||||||
|
async fn register_ci_runner(token: &str) -> Result<(String, String)> {
|
||||||
|
use std::os::unix::fs::PermissionsExt as _;
|
||||||
|
// Reject anything that could corrupt the `KEY=VALUE` env-file or smuggle a
|
||||||
|
// second line — a forge registration token is an opaque single-line string.
|
||||||
|
if token.is_empty() || token.contains(['\n', '\r', '\0']) {
|
||||||
|
bail!("ci runner registration token empty or contains control characters");
|
||||||
|
}
|
||||||
|
let token_path = "/run/hive-ci/runner-token";
|
||||||
|
// In-place truncate+write of the existing inode (mirrors the prefetch's
|
||||||
|
// `echo > $FILE`), NOT a temp+rename: nspawn pins this file's inode into
|
||||||
|
// hive-ci at container start, so a rename would leave the running runner
|
||||||
|
// reading the old content. Format + perms match the tmpfiles seed and the
|
||||||
|
// prefetch: `TOKEN=<tok>`, mode 0600, root-owned.
|
||||||
|
std::fs::write(token_path, format!("TOKEN={token}\n"))
|
||||||
|
.with_context(|| format!("write {token_path}"))?;
|
||||||
|
std::fs::set_permissions(token_path, std::fs::Permissions::from_mode(0o600))
|
||||||
|
.with_context(|| format!("chmod {token_path}"))?;
|
||||||
|
// Restart the in-container runner so it reads the new token and registers.
|
||||||
|
let out = Command::new("systemctl")
|
||||||
|
.args(["--machine=hive-ci", "restart", "gitea-runner-hive.service"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.context("systemctl restart gitea-runner-hive.service in hive-ci")?;
|
||||||
|
if !out.status.success() {
|
||||||
|
bail!(
|
||||||
|
"systemctl restart gitea-runner-hive.service in hive-ci exited {}: {}",
|
||||||
|
out.status,
|
||||||
|
String::from_utf8_lossy(&out.stderr).trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok((
|
||||||
|
String::from_utf8_lossy(&out.stdout).into_owned(),
|
||||||
|
String::from_utf8_lossy(&out.stderr).into_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// `ControlInfraContainer` — start/stop/restart a hive infrastructure
|
/// `ControlInfraContainer` — start/stop/restart a hive infrastructure
|
||||||
/// container via `systemctl <verb> container@<container>.service`. The
|
/// container via `systemctl <verb> container@<container>.service`. The
|
||||||
/// [`InfraContainer`] enum is the allowlist: serde already rejected any
|
/// [`InfraContainer`] enum is the allowlist: serde already rejected any
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue