refactor(c0re): pass hive-c0re serve config via a --config file, shrink ExecStart
The systemd ExecStart carried every host-level setting as its own flag — nine of them, including two escaped JSON blobs (the context-window map and the model-price table). Collapse them into a single `--config <file>` JSON. - Reuse the existing HiveEnv as the container-injected config shape (add Deserialize + Default), and add a ServeConfig wrapper = flattened HiveEnv plus the hive-c0re-local model_prices table (kept out of HiveEnv since it is never injected into containers). serde(default) lets any field be omitted and fall back to its canonical default. - clap: add --config; the per-setting flags become optional overrides (config file is the base, explicit flags win — preserves hivectl/debug ergonomics and bare `hive-c0re serve`). - Coordinator::open and cmd_serve now take the bundled HiveEnv, which drops their too_many_arguments clippy allows. cmd_serve keeps a single too_many_lines allow (inherent daemon-boot orchestration, not arg-driven). - nix: write the config as JSON to the store + pass --config, so ExecStart is one short line. - Add a round-trip test proving the flatten + per-field defaults work. Closes the ExecStart-length issue.
This commit is contained in:
parent
09787dd1c3
commit
98660d134a
3 changed files with 223 additions and 120 deletions
|
|
@ -158,7 +158,14 @@ pub struct Coordinator {
|
|||
/// Cloned from `Coordinator` via [`Coordinator::hive_env`]. All fields
|
||||
/// are cheap to clone (small strings + small map); lifecycle ops are
|
||||
/// infrequent enough that the copy cost is irrelevant.
|
||||
#[derive(Clone, Debug)]
|
||||
///
|
||||
/// Also the container-injected subset of the `hive-c0re serve --config`
|
||||
/// JSON (see [`ServeConfig`]): `#[serde(default)]` lets a partial config
|
||||
/// fall back to [`Default`] (the canonical defaults that used to live in
|
||||
/// the clap `default_value`s), and bare `hive-c0re serve` with no
|
||||
/// `--config` boots from `Default`.
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct HiveEnv {
|
||||
pub hyperhive_flake: String,
|
||||
pub nixpkgs_flake: String,
|
||||
|
|
@ -172,6 +179,78 @@ pub struct HiveEnv {
|
|||
pub agent_memory_max: String,
|
||||
}
|
||||
|
||||
impl Default for HiveEnv {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hyperhive_flake: "/etc/hyperhive".to_string(),
|
||||
nixpkgs_flake: String::new(),
|
||||
nixpkgs_unstable_flake: String::new(),
|
||||
dashboard_port: 7000,
|
||||
operator_pronouns: "she/her".to_string(),
|
||||
context_window_tokens: std::collections::HashMap::from([
|
||||
("haiku".to_string(), 200_000),
|
||||
("sonnet".to_string(), 1_000_000),
|
||||
("opus".to_string(), 1_000_000),
|
||||
]),
|
||||
agent_cpu_quota: "200%".to_string(),
|
||||
agent_memory_max: "4G".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On-disk shape of the `hive-c0re serve --config <file>` JSON: the
|
||||
/// container-injected [`HiveEnv`] (flattened) plus the hive-c0re-local
|
||||
/// `model_prices` table (read only by the `/api/stats-hive` handler, never
|
||||
/// injected into containers — hence kept out of `HiveEnv`). The NixOS
|
||||
/// module writes this so the `ExecStart` carries one `--config` flag
|
||||
/// instead of every host-level setting as its own JSON-blob argument.
|
||||
/// `#[serde(default)]` lets any field be omitted and fall back to its
|
||||
/// canonical default.
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ServeConfig {
|
||||
#[serde(flatten)]
|
||||
pub env: HiveEnv,
|
||||
pub model_prices: crate::hive_stats::PriceTable,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod serve_config_tests {
|
||||
use super::ServeConfig;
|
||||
|
||||
#[test]
|
||||
fn deserialises_flat_with_per_field_defaults() {
|
||||
// The flattened HiveEnv fields + the local model_prices all live at
|
||||
// the JSON top level. Present fields are read; omitted ones fall
|
||||
// back to their canonical defaults (so a partial config is valid).
|
||||
let sc: ServeConfig = serde_json::from_str(
|
||||
r#"{
|
||||
"hyperhive_flake": "/x",
|
||||
"dashboard_port": 1234,
|
||||
"model_prices": {
|
||||
"opus": {"input": 5.0, "output": 25.0, "cache_read": 0.5, "cache_write": 10.0}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.expect("flat ServeConfig should deserialise");
|
||||
assert_eq!(sc.env.hyperhive_flake, "/x");
|
||||
assert_eq!(sc.env.dashboard_port, 1234);
|
||||
// omitted → HiveEnv::default()
|
||||
assert_eq!(sc.env.operator_pronouns, "she/her");
|
||||
assert_eq!(sc.env.agent_cpu_quota, "200%");
|
||||
assert_eq!(sc.env.context_window_tokens.get("sonnet"), Some(&1_000_000));
|
||||
assert!(sc.model_prices.contains_key("opus"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_object_is_all_defaults() {
|
||||
let sc: ServeConfig = serde_json::from_str("{}").expect("empty config valid");
|
||||
assert_eq!(sc.env.dashboard_port, 7000);
|
||||
assert_eq!(sc.env.hyperhive_flake, "/etc/hyperhive");
|
||||
assert!(sc.model_prices.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-agent filesystem paths that lifecycle operations write to.
|
||||
/// Assembled from `Coordinator`'s static path helpers so callers
|
||||
/// don't repeat the same `Coordinator::agent_*_dir(name)` calls.
|
||||
|
|
@ -271,24 +350,21 @@ impl TransientKind {
|
|||
}
|
||||
|
||||
impl Coordinator {
|
||||
#[allow(
|
||||
clippy::too_many_arguments,
|
||||
reason = "constructor wiring host-level config (flakes, ports, pronouns, \
|
||||
context-window + resource limits) into the coordinator; bundling \
|
||||
into a struct would just move the same fields one level out"
|
||||
)]
|
||||
pub fn open(
|
||||
db_path: &Path,
|
||||
hyperhive_flake: String,
|
||||
nixpkgs_flake: String,
|
||||
nixpkgs_unstable_flake: String,
|
||||
dashboard_port: u16,
|
||||
operator_pronouns: String,
|
||||
context_window_tokens: std::collections::HashMap<String, u64>,
|
||||
agent_cpu_quota: String,
|
||||
agent_memory_max: String,
|
||||
env: HiveEnv,
|
||||
model_prices: crate::hive_stats::PriceTable,
|
||||
) -> Result<Self> {
|
||||
let HiveEnv {
|
||||
hyperhive_flake,
|
||||
nixpkgs_flake,
|
||||
nixpkgs_unstable_flake,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
context_window_tokens,
|
||||
agent_cpu_quota,
|
||||
agent_memory_max,
|
||||
} = env;
|
||||
let broker = Broker::open(db_path).context("open broker")?;
|
||||
let approvals = Approvals::open(db_path).context("open approvals")?;
|
||||
let questions = OperatorQuestions::open(db_path).context("open operator_questions")?;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use hive_sh4re::{HostRequest, HostResponse};
|
|||
// tree — no per-binary duplication. Enumerated rather than wildcard
|
||||
// so clippy stays happy + the lib surface this bin consumes is
|
||||
// explicit (any new daemon entry point reads off the next add).
|
||||
use hive_c0re::coordinator::Coordinator;
|
||||
use hive_c0re::coordinator::{Coordinator, HiveEnv, ServeConfig};
|
||||
use hive_c0re::{
|
||||
agent_sockets, auto_update, bash_tasks_vacuum, broker, client, crash_watch, dashboard,
|
||||
dashboard_events, events_vacuum, forge, knowledge, manager_server, matrix, migrate,
|
||||
|
|
@ -32,69 +32,64 @@ struct Cli {
|
|||
enum Cmd {
|
||||
/// Run the coordinator daemon.
|
||||
Serve {
|
||||
/// URL of the hyperhive flake. Inlined into each per-agent
|
||||
/// `flake.nix` as the `hyperhive` input.
|
||||
#[arg(long, default_value = "/etc/hyperhive")]
|
||||
hyperhive_flake: String,
|
||||
/// Store-path URL of the nixpkgs to wire into the meta flake as
|
||||
/// `inputs.nixpkgs.url`. Set by the NixOS module to
|
||||
/// `"path:${pkgs.path}"` so the meta flake tracks exactly the
|
||||
/// nixpkgs the host was evaluated with (the host's own nixpkgs
|
||||
/// when `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` is
|
||||
/// set, otherwise hyperhive's pin). Empty = legacy
|
||||
/// `follows = "hyperhive/nixpkgs"` fallback.
|
||||
#[arg(long, default_value = "")]
|
||||
nixpkgs_flake: String,
|
||||
/// Store-path URL of the nixpkgs-unstable to wire into the meta
|
||||
/// flake as `inputs.nixpkgs-unstable.url`. Hyperhive's
|
||||
/// `inputs.nixpkgs-unstable` then follows this top-level input.
|
||||
/// Set by the NixOS module; defaults to the hyperhive flake's own
|
||||
/// nixpkgs-unstable store path. Empty = legacy
|
||||
/// `follows = "hyperhive/nixpkgs-unstable"` fallback.
|
||||
#[arg(long, default_value = "")]
|
||||
nixpkgs_unstable_flake: String,
|
||||
/// Path to a JSON config file holding the host-level daemon config
|
||||
/// (the [`ServeConfig`](hive_c0re::coordinator::ServeConfig) shape:
|
||||
/// the container-injected `HiveEnv` fields + the hive-c0re-local
|
||||
/// `model_prices` table). Used as the base; any per-flag override
|
||||
/// below wins over the file. Absent → start from the built-in
|
||||
/// defaults. The NixOS module passes a generated config here so the
|
||||
/// `ExecStart` stays short instead of carrying every setting — and
|
||||
/// the context-window / price-table JSON blobs — as flags.
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
/// Path to the sqlite message store.
|
||||
#[arg(long, default_value = hive_c0re::paths::BROKER_DB)]
|
||||
db: PathBuf,
|
||||
/// Dashboard HTTP port.
|
||||
#[arg(long, default_value_t = 7000)]
|
||||
dashboard_port: u16,
|
||||
/// Operator pronouns (free text). Threaded into each
|
||||
/// container's harness via `HIVE_OPERATOR_PRONOUNS` so the
|
||||
/// system prompt can mention them. Default: `she/her`.
|
||||
#[arg(long, default_value = "she/her")]
|
||||
operator_pronouns: String,
|
||||
/// Per-model context-window sizes, as JSON object mapping model-family
|
||||
/// short name to token count. Threaded into each container as
|
||||
/// `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env vars. Set via the
|
||||
/// `services.hive-c0re.contextWindowTokens` NixOS option.
|
||||
#[arg(
|
||||
long,
|
||||
default_value = r#"{"haiku":200000,"sonnet":1000000,"opus":1000000}"#
|
||||
)]
|
||||
context_window_tokens: String,
|
||||
/// systemd `CPUQuota=` applied to every agent container via a drop-in.
|
||||
/// Expressed as a percentage of one CPU core (e.g. `"200%"` = 2 cores).
|
||||
/// Set via `services.hyperhive.agentCpuQuota`.
|
||||
#[arg(long, default_value = "200%")]
|
||||
agent_cpu_quota: String,
|
||||
/// systemd `MemoryMax=` applied to every agent container.
|
||||
/// Set via `services.hyperhive.agentMemoryMax`.
|
||||
#[arg(long, default_value = "4G")]
|
||||
agent_memory_max: String,
|
||||
/// Per-model USD prices (per million tokens) for the hive-wide
|
||||
/// ST4TS cost estimate, as a JSON object mapping a model-family
|
||||
/// short name to `{input, output, cache_read, cache_write}`.
|
||||
/// Keys are matched case-insensitively as a substring of the
|
||||
/// model id; models not covered fall back to the built-in
|
||||
/// Override: URL of the hyperhive flake. Inlined into each
|
||||
/// per-agent `flake.nix` as the `hyperhive` input.
|
||||
#[arg(long)]
|
||||
hyperhive_flake: Option<String>,
|
||||
/// Override: store-path URL of the nixpkgs to wire into the meta
|
||||
/// flake as `inputs.nixpkgs.url`. Empty = legacy
|
||||
/// `follows = "hyperhive/nixpkgs"` fallback.
|
||||
#[arg(long)]
|
||||
nixpkgs_flake: Option<String>,
|
||||
/// Override: store-path URL of the nixpkgs-unstable to wire into
|
||||
/// the meta flake as `inputs.nixpkgs-unstable.url`. Empty = legacy
|
||||
/// `follows = "hyperhive/nixpkgs-unstable"` fallback.
|
||||
#[arg(long)]
|
||||
nixpkgs_unstable_flake: Option<String>,
|
||||
/// Override: dashboard HTTP port.
|
||||
#[arg(long)]
|
||||
dashboard_port: Option<u16>,
|
||||
/// Override: operator pronouns (free text). Threaded into each
|
||||
/// container's harness via `HIVE_OPERATOR_PRONOUNS`.
|
||||
#[arg(long)]
|
||||
operator_pronouns: Option<String>,
|
||||
/// Override: per-model context-window sizes, as a JSON object
|
||||
/// mapping model-family short name to token count. Threaded into
|
||||
/// each container as `HIVE_CONTEXT_WINDOW_TOKENS_<KEY_UPPER>` env
|
||||
/// vars. Set via the `services.hive-c0re.contextWindowTokens`
|
||||
/// NixOS option.
|
||||
#[arg(long)]
|
||||
context_window_tokens: Option<String>,
|
||||
/// Override: systemd `CPUQuota=` applied to every agent container
|
||||
/// via a drop-in (e.g. `"200%"` = 2 cores). Set via
|
||||
/// `services.hyperhive.agentCpuQuota`.
|
||||
#[arg(long)]
|
||||
agent_cpu_quota: Option<String>,
|
||||
/// Override: systemd `MemoryMax=` applied to every agent
|
||||
/// container. Set via `services.hyperhive.agentMemoryMax`.
|
||||
#[arg(long)]
|
||||
agent_memory_max: Option<String>,
|
||||
/// Override: per-model USD prices (per million tokens) for the
|
||||
/// hive-wide ST4TS cost estimate, as a JSON object mapping a
|
||||
/// model-family short name to `{input, output, cache_read,
|
||||
/// cache_write}`. Models not covered fall back to the built-in
|
||||
/// estimate. Set via the `services.hyperhive.modelPrices` NixOS
|
||||
/// option (whose default carries the full opus/sonnet/haiku
|
||||
/// table — kept in sync with the in-code `builtin_prices`).
|
||||
/// Defaults to `{}` here so a bare `hive-c0re serve` leans
|
||||
/// entirely on `builtin_prices`; the NixOS default (every real
|
||||
/// deployment) shadows it with the same operator-tunable table.
|
||||
#[arg(long, default_value = "{}")]
|
||||
model_prices: String,
|
||||
/// option.
|
||||
#[arg(long)]
|
||||
model_prices: Option<String>,
|
||||
},
|
||||
/// Spawn a new agent container directly (`hive-agent-<name>`). Bypasses
|
||||
/// the approval queue — use only as an operator on the host. For
|
||||
|
|
@ -156,10 +151,11 @@ async fn main() -> Result<()> {
|
|||
let cli = Cli::parse();
|
||||
match cli.cmd {
|
||||
Cmd::Serve {
|
||||
config,
|
||||
db,
|
||||
hyperhive_flake,
|
||||
nixpkgs_flake,
|
||||
nixpkgs_unstable_flake,
|
||||
db,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
context_window_tokens,
|
||||
|
|
@ -167,20 +163,48 @@ async fn main() -> Result<()> {
|
|||
agent_memory_max,
|
||||
model_prices,
|
||||
} => {
|
||||
cmd_serve(
|
||||
hyperhive_flake,
|
||||
nixpkgs_flake,
|
||||
nixpkgs_unstable_flake,
|
||||
db,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
context_window_tokens,
|
||||
agent_cpu_quota,
|
||||
agent_memory_max,
|
||||
model_prices,
|
||||
&cli.socket,
|
||||
)
|
||||
.await
|
||||
// Base config from the --config file (or the built-in
|
||||
// defaults), then apply any per-flag overrides — config
|
||||
// file is the base, explicit flags win.
|
||||
let mut sc = match &config {
|
||||
Some(p) => {
|
||||
let s = std::fs::read_to_string(p)
|
||||
.with_context(|| format!("read --config {}", p.display()))?;
|
||||
serde_json::from_str::<ServeConfig>(&s)
|
||||
.with_context(|| format!("parse --config {}", p.display()))?
|
||||
}
|
||||
None => ServeConfig::default(),
|
||||
};
|
||||
if let Some(v) = hyperhive_flake {
|
||||
sc.env.hyperhive_flake = v;
|
||||
}
|
||||
if let Some(v) = nixpkgs_flake {
|
||||
sc.env.nixpkgs_flake = v;
|
||||
}
|
||||
if let Some(v) = nixpkgs_unstable_flake {
|
||||
sc.env.nixpkgs_unstable_flake = v;
|
||||
}
|
||||
if let Some(v) = dashboard_port {
|
||||
sc.env.dashboard_port = v;
|
||||
}
|
||||
if let Some(v) = operator_pronouns {
|
||||
sc.env.operator_pronouns = v;
|
||||
}
|
||||
if let Some(v) = context_window_tokens {
|
||||
sc.env.context_window_tokens =
|
||||
serde_json::from_str(&v).context("--context-window-tokens: invalid JSON")?;
|
||||
}
|
||||
if let Some(v) = agent_cpu_quota {
|
||||
sc.env.agent_cpu_quota = v;
|
||||
}
|
||||
if let Some(v) = agent_memory_max {
|
||||
sc.env.agent_memory_max = v;
|
||||
}
|
||||
if let Some(v) = model_prices {
|
||||
sc.model_prices =
|
||||
serde_json::from_str(&v).context("--model-prices: invalid JSON")?;
|
||||
}
|
||||
cmd_serve(sc.env, sc.model_prices, db, &cli.socket).await
|
||||
}
|
||||
Cmd::Spawn { name } => {
|
||||
render(client::request(&cli.socket, HostRequest::Spawn { name }).await?)
|
||||
|
|
@ -220,23 +244,16 @@ async fn main() -> Result<()> {
|
|||
/// background tasks (auto-update, vacuums, crash-watcher, reminder-scheduler,
|
||||
/// dashboard), then serve the admin socket until a signal arrives.
|
||||
#[allow(
|
||||
clippy::too_many_arguments,
|
||||
clippy::too_many_lines,
|
||||
reason = "the `serve` subcommand's args are the host-level config the daemon \
|
||||
boots from (flakes, ports, pronouns, context-window + resource \
|
||||
limits); they flow straight through to Coordinator::open"
|
||||
reason = "startup orchestration: open the broker, run migrations, then spawn \
|
||||
the full set of background services (auto-update, vacuums, \
|
||||
crash-watch, schedulers, dashboard) before the serve loop — the \
|
||||
length is inherent to booting the daemon, not the arg list"
|
||||
)]
|
||||
async fn cmd_serve(
|
||||
hyperhive_flake: String,
|
||||
nixpkgs_flake: String,
|
||||
nixpkgs_unstable_flake: String,
|
||||
env: HiveEnv,
|
||||
model_prices: hive_c0re::hive_stats::PriceTable,
|
||||
db: std::path::PathBuf,
|
||||
dashboard_port: u16,
|
||||
operator_pronouns: String,
|
||||
context_window_tokens: String,
|
||||
agent_cpu_quota: String,
|
||||
agent_memory_max: String,
|
||||
model_prices: String,
|
||||
socket: &std::path::Path,
|
||||
) -> Result<()> {
|
||||
// Move any host-side state still at the legacy flat layout into its
|
||||
|
|
@ -244,22 +261,10 @@ async fn cmd_serve(
|
|||
// broker db — the broker + build-logs dbs are among the relocated
|
||||
// files. Idempotent; a no-op once migrated.
|
||||
hive_c0re::paths::relocate_legacy_state();
|
||||
let cwt: std::collections::HashMap<String, u64> = serde_json::from_str(&context_window_tokens)
|
||||
.context("--context-window-tokens: invalid JSON")?;
|
||||
let prices: hive_c0re::hive_stats::PriceTable =
|
||||
serde_json::from_str(&model_prices).context("--model-prices: invalid JSON")?;
|
||||
let coord = Arc::new(Coordinator::open(
|
||||
&db,
|
||||
hyperhive_flake,
|
||||
nixpkgs_flake,
|
||||
nixpkgs_unstable_flake,
|
||||
dashboard_port,
|
||||
operator_pronouns,
|
||||
cwt,
|
||||
agent_cpu_quota,
|
||||
agent_memory_max,
|
||||
prices,
|
||||
)?);
|
||||
// `dashboard_port` is consumed into the Coordinator below; capture the
|
||||
// Copy value first for the dashboard + knowledge-webhook tasks.
|
||||
let dashboard_port = env.dashboard_port;
|
||||
let coord = Arc::new(Coordinator::open(&db, env, model_prices)?);
|
||||
manager_server::start(coord.clone())?;
|
||||
// Idempotent pre-flight: rewrite pre-meta-layout applied
|
||||
// repos, ensure proposed repos carry the `applied`
|
||||
|
|
|
|||
|
|
@ -28,6 +28,28 @@ let
|
|||
[safe]
|
||||
directory = *
|
||||
'';
|
||||
|
||||
# The `hive-c0re serve` config, written to the store as JSON and passed
|
||||
# via a single `--config` flag so the systemd ExecStart line stays short
|
||||
# instead of carrying every host-level setting as its own flag (the
|
||||
# context-window + model-price maps alone were escaped JSON blobs on the
|
||||
# command line). Keys are snake_case to match the `ServeConfig` serde
|
||||
# shape the daemon deserialises (the container-injected HiveEnv fields,
|
||||
# flattened, plus the hive-c0re-local model_prices table); per-flag
|
||||
# overrides still work for ad-hoc invocations.
|
||||
serveConfig = pkgs.writeText "hive-c0re-serve.json" (
|
||||
builtins.toJSON {
|
||||
hyperhive_flake = cfg.hyperhiveFlake;
|
||||
nixpkgs_flake = cfg.nixpkgsFlake;
|
||||
nixpkgs_unstable_flake = cfg.nixpkgsUnstableFlake;
|
||||
dashboard_port = cfg.dashboardPort;
|
||||
operator_pronouns = cfg.operatorPronouns;
|
||||
context_window_tokens = cfg.contextWindowTokens;
|
||||
agent_cpu_quota = cfg.agentCpuQuota;
|
||||
agent_memory_max = cfg.agentMemoryMax;
|
||||
model_prices = cfg.modelPrices;
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
# The forge is part of the standard install — hive-c0re mirrors
|
||||
|
|
@ -736,7 +758,7 @@ in
|
|||
);
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --nixpkgs-flake ${cfg.nixpkgsFlake} --nixpkgs-unstable-flake ${cfg.nixpkgsUnstableFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)} --agent-cpu-quota ${lib.escapeShellArg cfg.agentCpuQuota} --agent-memory-max ${lib.escapeShellArg cfg.agentMemoryMax} --model-prices ${lib.escapeShellArg (builtins.toJSON cfg.modelPrices)}";
|
||||
ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --config ${serveConfig}";
|
||||
# Migrate hive-c0re's *own* state to the service user after an
|
||||
# upgrade from a root-run install (systemd's StateDirectory only
|
||||
# chowns the top-level dir, not pre-existing files inside it). The
|
||||
|
|
|
|||
Loading…
Reference in a new issue