feat(maestro): installer drops optional maestro-web systemd unit (NAP-249)
ci / rust (push) Failing after 7m18s
ci / rust (push) Failing after 7m18s
New helpers in maestro-installer::systemd: * `render_web_unit` — same shape as the daemon unit, ordered after `maestrod.service` so the proxy doesn't 502 on startup. * `user_web_unit_path` / `default_maestro_web_path` — under `~/.config/` and `~/.cargo/bin/` respectively. New wizard step (NAP-241 deferred this; phone-dogfood on 2026-05-26 surfaced it): after the daemon+systemd flow, optionally `cargo install maestro-web` and lay down `~/.config/systemd/user/maestro-web.service`. Re-runs detect the existing unit and refresh + restart silently. New flags: * `--no-web` — skip the prompt entirely (headless hosts). * `--web-exec-start <path>` — override the unit's ExecStart. Non-interactive (`--yes` / piped) skips the web step unless an existing unit is being refreshed — operators opt in by re-running once interactively, then subsequent unattended re-runs refresh automatically. docs/INSTALL.md updated with the new flow + flags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,4 +14,7 @@ pub mod install;
|
|||||||
pub mod systemd;
|
pub mod systemd;
|
||||||
|
|
||||||
pub use install::{cargo_install_args, systemctl_user_args, CargoSource, SystemctlAction};
|
pub use install::{cargo_install_args, systemctl_user_args, CargoSource, SystemctlAction};
|
||||||
pub use systemd::{is_systemd_host, render_user_unit, user_unit_path, UNIT_FILE_NAME};
|
pub use systemd::{
|
||||||
|
is_systemd_host, render_user_unit, render_web_unit, user_unit_path, user_unit_path_for,
|
||||||
|
user_web_unit_path, UNIT_FILE_NAME, WEB_UNIT_FILE_NAME,
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,13 +16,15 @@ use std::process::{Command, ExitCode};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use maestro_installer::install::cargo_bin;
|
use maestro_installer::install::cargo_bin;
|
||||||
use maestro_installer::{
|
use maestro_installer::{
|
||||||
cargo_install_args, is_systemd_host, render_user_unit, systemctl_user_args, user_unit_path,
|
cargo_install_args, is_systemd_host, render_user_unit, render_web_unit, systemctl_user_args,
|
||||||
CargoSource, SystemctlAction, UNIT_FILE_NAME,
|
user_unit_path, user_web_unit_path, CargoSource, SystemctlAction, UNIT_FILE_NAME,
|
||||||
|
WEB_UNIT_FILE_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_GIT_URL: &str = "https://git.naps.pt/naps62/maestro";
|
const DEFAULT_GIT_URL: &str = "https://git.naps.pt/naps62/maestro";
|
||||||
const DAEMON_CRATE: &str = "maestro-daemon";
|
const DAEMON_CRATE: &str = "maestro-daemon";
|
||||||
const CLI_CRATE: &str = "maestro-cli";
|
const CLI_CRATE: &str = "maestro-cli";
|
||||||
|
const WEB_CRATE: &str = "maestro-web";
|
||||||
const SYSTEMD_PROBE: &str = "/run/systemd/system";
|
const SYSTEMD_PROBE: &str = "/run/systemd/system";
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
@@ -65,6 +67,16 @@ struct Args {
|
|||||||
/// Default: `~/.cargo/bin/maestrod`.
|
/// Default: `~/.cargo/bin/maestrod`.
|
||||||
#[arg(long, value_name = "PATH")]
|
#[arg(long, value_name = "PATH")]
|
||||||
exec_start: Option<PathBuf>,
|
exec_start: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Skip the optional `maestro-web` (browser UI) install step.
|
||||||
|
/// Useful for headless hosts.
|
||||||
|
#[arg(long)]
|
||||||
|
no_web: bool,
|
||||||
|
|
||||||
|
/// Override the ExecStart path for the `maestro-web` systemd unit.
|
||||||
|
/// Default: `~/.cargo/bin/maestro-web`.
|
||||||
|
#[arg(long, value_name = "PATH")]
|
||||||
|
web_exec_start: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
@@ -126,7 +138,15 @@ fn run(args: Args) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
handle_systemd(&args, interactive, configured)?;
|
handle_systemd(&args, interactive, configured)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
print_summary(configured, systemd_detected && !args.no_systemd);
|
// 4. Optional: maestro-web (NAP-249). Re-runs auto-detect an existing
|
||||||
|
// install and refresh rather than re-prompting, matching the daemon flow.
|
||||||
|
let web_installed = handle_web(&args, interactive, systemd_detected)?;
|
||||||
|
|
||||||
|
print_summary(
|
||||||
|
configured,
|
||||||
|
systemd_detected && !args.no_systemd,
|
||||||
|
web_installed,
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +168,7 @@ fn print_banner(source: &CargoSource) {
|
|||||||
eprintln!(" 1. cargo install both `{DAEMON_CRATE}` and `{CLI_CRATE}`");
|
eprintln!(" 1. cargo install both `{DAEMON_CRATE}` and `{CLI_CRATE}`");
|
||||||
eprintln!(" 2. (optional) run `maestrod configure` to bootstrap the config file");
|
eprintln!(" 2. (optional) run `maestrod configure` to bootstrap the config file");
|
||||||
eprintln!(" 3. (optional) install a systemd user unit and enable it");
|
eprintln!(" 3. (optional) install a systemd user unit and enable it");
|
||||||
|
eprintln!(" 4. (optional) install `{WEB_CRATE}` + its systemd unit (browser UI)");
|
||||||
eprintln!();
|
eprintln!();
|
||||||
match source {
|
match source {
|
||||||
CargoSource::Git { url, branch } => {
|
CargoSource::Git { url, branch } => {
|
||||||
@@ -318,6 +339,128 @@ fn default_exec_start() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
|||||||
.ok_or_else(|| "could not resolve $HOME (needed for ExecStart)".into())
|
.ok_or_else(|| "could not resolve $HOME (needed for ExecStart)".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_web_exec_start() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
|
maestro_installer::systemd::default_maestro_web_path()
|
||||||
|
.ok_or_else(|| "could not resolve $HOME (needed for web ExecStart)".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional step: install `maestro-web` + its systemd user unit (NAP-249).
|
||||||
|
/// Returns `true` iff the binary was installed (with or without systemd).
|
||||||
|
fn handle_web(
|
||||||
|
args: &Args,
|
||||||
|
interactive: bool,
|
||||||
|
systemd_detected: bool,
|
||||||
|
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
if args.no_web {
|
||||||
|
eprintln!("\nSkipping web UI step (--no-web).");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let web_unit_path = user_web_unit_path();
|
||||||
|
let unit_present = web_unit_path.as_ref().map(|p| p.exists()).unwrap_or(false);
|
||||||
|
|
||||||
|
// On re-runs the unit is already there — refresh silently. On first run
|
||||||
|
// ask interactively (default yes), or skip on --yes / non-TTY unless the
|
||||||
|
// unit is present (refresh path).
|
||||||
|
let want_web = if unit_present {
|
||||||
|
eprintln!(
|
||||||
|
"\nDetected existing maestro-web unit at {} — refreshing.",
|
||||||
|
web_unit_path.as_ref().unwrap().display()
|
||||||
|
);
|
||||||
|
true
|
||||||
|
} else if !interactive {
|
||||||
|
// Non-interactive (--yes or piped) with no existing unit: skip by
|
||||||
|
// default. Operators opt in by re-running once interactively.
|
||||||
|
eprintln!(
|
||||||
|
"\nSkipping maestro-web install (non-interactive). \
|
||||||
|
Re-run without --yes to set it up, or pass --no-web to silence."
|
||||||
|
);
|
||||||
|
return Ok(false);
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"\nOptional: install the browser UI (`maestro-web`)?\n \
|
||||||
|
Reverse-proxies a local maestrod and serves the SPA at \
|
||||||
|
http://127.0.0.1:8080/ by default.\n \
|
||||||
|
Edit [web].bind_addr in ~/.config/maestro/config.toml \
|
||||||
|
to expose it to your perimeter (e.g. WireGuard IP)."
|
||||||
|
);
|
||||||
|
confirm("Install maestro-web?", true)?
|
||||||
|
};
|
||||||
|
if !want_web {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the binary. Even if cargo-install fails we keep going so the
|
||||||
|
// operator can fix the cause and re-run; the existing maestrod work
|
||||||
|
// doesn't need to be undone.
|
||||||
|
let source = build_source(args);
|
||||||
|
let locked = !args.no_locked;
|
||||||
|
if let Err(e) = install_crate(&source, WEB_CRATE, locked) {
|
||||||
|
eprintln!("maestro-web install failed: {e}");
|
||||||
|
eprintln!("(skipping systemd unit; re-run after fixing the build.)");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.no_systemd || !systemd_detected {
|
||||||
|
eprintln!(
|
||||||
|
"\nmaestro-web installed. Start it manually with `maestro-web` \
|
||||||
|
(no systemd unit written)."
|
||||||
|
);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let unit_path = web_unit_path.expect("user_web_unit_path resolves under config_dir");
|
||||||
|
let exec_start = match &args.web_exec_start {
|
||||||
|
Some(p) => p.clone(),
|
||||||
|
None => default_web_exec_start()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(parent) = unit_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
let body = render_web_unit(&exec_start);
|
||||||
|
std::fs::write(&unit_path, body)?;
|
||||||
|
eprintln!("wrote {}", unit_path.display());
|
||||||
|
|
||||||
|
run_systemctl(SystemctlAction::DaemonReload, WEB_UNIT_FILE_NAME)?;
|
||||||
|
|
||||||
|
if !unit_present {
|
||||||
|
let want_enable = if !interactive {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
confirm("Enable maestro-web.service (start on login)?", true)?
|
||||||
|
};
|
||||||
|
if want_enable {
|
||||||
|
run_systemctl(SystemctlAction::Enable, WEB_UNIT_FILE_NAME)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update path → restart; first-install → start (always safe; web only
|
||||||
|
// needs the daemon's HTTP surface, which exists from NAP-243).
|
||||||
|
let (action, want) = if unit_present {
|
||||||
|
let want = if !interactive {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
confirm(
|
||||||
|
"Restart maestro-web.service to pick up the new binary?",
|
||||||
|
true,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
(SystemctlAction::Restart, want)
|
||||||
|
} else {
|
||||||
|
let want = if !interactive {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
confirm("Start maestro-web.service now?", true)?
|
||||||
|
};
|
||||||
|
(SystemctlAction::Start, want)
|
||||||
|
};
|
||||||
|
if want {
|
||||||
|
run_systemctl(action, WEB_UNIT_FILE_NAME)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn run_systemctl(action: SystemctlAction, unit: &str) -> Result<(), Box<dyn std::error::Error>> {
|
fn run_systemctl(action: SystemctlAction, unit: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args = systemctl_user_args(action, unit);
|
let args = systemctl_user_args(action, unit);
|
||||||
eprintln!("\n==> systemctl {}", args.join(" "));
|
eprintln!("\n==> systemctl {}", args.join(" "));
|
||||||
@@ -342,7 +485,7 @@ fn confirm(prompt: &str, default_yes: bool) -> io::Result<bool> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_summary(configured: bool, systemd_enabled: bool) {
|
fn print_summary(configured: bool, systemd_enabled: bool, web_installed: bool) {
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!("Done.");
|
eprintln!("Done.");
|
||||||
eprintln!();
|
eprintln!();
|
||||||
@@ -357,4 +500,16 @@ fn print_summary(configured: bool, systemd_enabled: bool) {
|
|||||||
eprintln!(" • Start the daemon: `maestrod`");
|
eprintln!(" • Start the daemon: `maestrod`");
|
||||||
}
|
}
|
||||||
eprintln!(" • Set the CLI's registry: `maestro set-registry <URL>`");
|
eprintln!(" • Set the CLI's registry: `maestro set-registry <URL>`");
|
||||||
|
if web_installed {
|
||||||
|
if systemd_enabled {
|
||||||
|
eprintln!(
|
||||||
|
" • Browser UI: open http://127.0.0.1:8080/ \
|
||||||
|
(or your [web].bind_addr from config.toml)"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
" • Browser UI: start with `maestro-web` and open http://127.0.0.1:8080/"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,24 +8,38 @@ use std::path::{Path, PathBuf};
|
|||||||
/// Standard probe path. Present on hosts booted under systemd.
|
/// Standard probe path. Present on hosts booted under systemd.
|
||||||
pub const SYSTEMD_PROBE_PATH: &str = "/run/systemd/system";
|
pub const SYSTEMD_PROBE_PATH: &str = "/run/systemd/system";
|
||||||
|
|
||||||
/// Filename written under `~/.config/systemd/user/`.
|
/// Filename written under `~/.config/systemd/user/` for the daemon.
|
||||||
pub const UNIT_FILE_NAME: &str = "maestrod.service";
|
pub const UNIT_FILE_NAME: &str = "maestrod.service";
|
||||||
|
|
||||||
|
/// Filename for the optional `maestro-web` browser UI unit (NAP-249).
|
||||||
|
pub const WEB_UNIT_FILE_NAME: &str = "maestro-web.service";
|
||||||
|
|
||||||
/// `true` when the given path exists. Production callers should pass
|
/// `true` when the given path exists. Production callers should pass
|
||||||
/// [`SYSTEMD_PROBE_PATH`]; tests inject a temp path so they're hermetic.
|
/// [`SYSTEMD_PROBE_PATH`]; tests inject a temp path so they're hermetic.
|
||||||
pub fn is_systemd_host(probe: &Path) -> bool {
|
pub fn is_systemd_host(probe: &Path) -> bool {
|
||||||
probe.exists()
|
probe.exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `~/.config/systemd/user/maestrod.service`. `None` if `$HOME` / config dir
|
/// `~/.config/systemd/user/<unit>`. `None` if `$HOME` / config dir is
|
||||||
/// is unresolvable.
|
/// unresolvable.
|
||||||
pub fn user_unit_path() -> Option<PathBuf> {
|
pub fn user_unit_path_for(unit_name: &str) -> Option<PathBuf> {
|
||||||
let base = dirs::config_dir()?;
|
let base = dirs::config_dir()?;
|
||||||
Some(base.join("systemd").join("user").join(UNIT_FILE_NAME))
|
Some(base.join("systemd").join("user").join(unit_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the unit file body. `exec_start` is the absolute path to `maestrod`
|
/// Convenience: path for `maestrod.service`. Kept as a free function so
|
||||||
/// — we default to `~/.cargo/bin/maestrod` because that's where `cargo install`
|
/// existing callers don't need to thread the unit name around.
|
||||||
|
pub fn user_unit_path() -> Option<PathBuf> {
|
||||||
|
user_unit_path_for(UNIT_FILE_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience: path for `maestro-web.service` (NAP-249).
|
||||||
|
pub fn user_web_unit_path() -> Option<PathBuf> {
|
||||||
|
user_unit_path_for(WEB_UNIT_FILE_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the daemon unit. `exec_start` is the absolute path to `maestrod` —
|
||||||
|
/// we default to `~/.cargo/bin/maestrod` because that's where `cargo install`
|
||||||
/// lands binaries; systemd doesn't inherit the user's PATH, so we can't rely
|
/// lands binaries; systemd doesn't inherit the user's PATH, so we can't rely
|
||||||
/// on a bare `maestrod`.
|
/// on a bare `maestrod`.
|
||||||
pub fn render_user_unit(exec_start: &Path) -> String {
|
pub fn render_user_unit(exec_start: &Path) -> String {
|
||||||
@@ -47,6 +61,30 @@ pub fn render_user_unit(exec_start: &Path) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render the `maestro-web` user unit (NAP-249). Orders after `maestrod`
|
||||||
|
/// so the proxy doesn't return 502s on startup; `Wants=` keeps it best-effort
|
||||||
|
/// — operators with daemon-less hosts can still install web alone if they
|
||||||
|
/// later remove the dependency.
|
||||||
|
pub fn render_web_unit(exec_start: &Path) -> String {
|
||||||
|
format!(
|
||||||
|
"[Unit]\n\
|
||||||
|
Description=Maestro web UI (maestro-web)\n\
|
||||||
|
Documentation=https://git.naps.pt/naps62/maestro\n\
|
||||||
|
After=network-online.target maestrod.service\n\
|
||||||
|
Wants=maestrod.service\n\
|
||||||
|
\n\
|
||||||
|
[Service]\n\
|
||||||
|
Type=simple\n\
|
||||||
|
ExecStart={exec}\n\
|
||||||
|
Restart=on-failure\n\
|
||||||
|
RestartSec=5\n\
|
||||||
|
\n\
|
||||||
|
[Install]\n\
|
||||||
|
WantedBy=default.target\n",
|
||||||
|
exec = exec_start.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Default `ExecStart` target: `~/.cargo/bin/maestrod`. `None` if `$HOME` is
|
/// Default `ExecStart` target: `~/.cargo/bin/maestrod`. `None` if `$HOME` is
|
||||||
/// unresolvable.
|
/// unresolvable.
|
||||||
pub fn default_maestrod_path() -> Option<PathBuf> {
|
pub fn default_maestrod_path() -> Option<PathBuf> {
|
||||||
@@ -58,6 +96,16 @@ pub fn default_maestrod_path() -> Option<PathBuf> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Default `ExecStart` target for the web unit: `~/.cargo/bin/maestro-web`.
|
||||||
|
pub fn default_maestro_web_path() -> Option<PathBuf> {
|
||||||
|
Some(
|
||||||
|
dirs::home_dir()?
|
||||||
|
.join(".cargo")
|
||||||
|
.join("bin")
|
||||||
|
.join("maestro-web"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -99,4 +147,26 @@ mod tests {
|
|||||||
let p = user_unit_path().expect("config dir resolvable in test env");
|
let p = user_unit_path().expect("config dir resolvable in test env");
|
||||||
assert!(p.ends_with("systemd/user/maestrod.service"), "got {p:?}");
|
assert!(p.ends_with("systemd/user/maestrod.service"), "got {p:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn user_web_unit_path_points_under_config_dir() {
|
||||||
|
let p = user_web_unit_path().expect("config dir resolvable in test env");
|
||||||
|
assert!(p.ends_with("systemd/user/maestro-web.service"), "got {p:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn web_unit_renders_with_dependency_on_daemon() {
|
||||||
|
let body = render_web_unit(Path::new("/home/alice/.cargo/bin/maestro-web"));
|
||||||
|
assert!(body.contains("ExecStart=/home/alice/.cargo/bin/maestro-web"));
|
||||||
|
assert!(body.contains("After=network-online.target maestrod.service"));
|
||||||
|
assert!(body.contains("Wants=maestrod.service"));
|
||||||
|
assert!(body.contains("WantedBy=default.target"));
|
||||||
|
assert!(body.ends_with('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_maestro_web_path_under_cargo_bin() {
|
||||||
|
let p = default_maestro_web_path().expect("HOME resolvable in test env");
|
||||||
|
assert!(p.ends_with(".cargo/bin/maestro-web"), "got {p:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-12
@@ -51,26 +51,48 @@ Both binaries land in `~/.cargo/bin` — make sure that's on your `$PATH`.
|
|||||||
## Adding the browser UI (optional)
|
## Adding the browser UI (optional)
|
||||||
|
|
||||||
`maestro-web` is a separate binary that serves the SPA bundle and reverse-
|
`maestro-web` is a separate binary that serves the SPA bundle and reverse-
|
||||||
proxies API calls to a co-located `maestrod`. Install it where you want a
|
proxies API calls to a co-located `maestrod`.
|
||||||
browser UI:
|
|
||||||
|
**Interactive install (default path).** The wizard now offers it as an
|
||||||
|
optional step right after the daemon flow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Optional: install the browser UI (`maestro-web`)?
|
||||||
|
Install maestro-web? [Y/n]
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `Y` and it `cargo install`s `maestro-web`, drops a
|
||||||
|
`~/.config/systemd/user/maestro-web.service` unit (ordered after
|
||||||
|
`maestrod.service`), reloads systemd, and offers to enable + start it. Re-runs
|
||||||
|
of `./install.sh` detect the existing unit and refresh + restart silently.
|
||||||
|
|
||||||
|
Pass `--no-web` to skip the prompt entirely (headless hosts), or `--yes`
|
||||||
|
with `--no-web` for non-interactive daemon-only installs. (Bare `--yes`
|
||||||
|
without `--no-web` *skips* the web step too — opt in by re-running once
|
||||||
|
interactively.)
|
||||||
|
|
||||||
|
**Frontend build prerequisite.** `maestro-web` embeds the SPA bundle at
|
||||||
|
compile time, so before running the installer:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm -C web install --frozen-lockfile
|
pnpm -C web install --frozen-lockfile
|
||||||
pnpm -C web build
|
pnpm -C web build
|
||||||
cargo install --git https://git.naps.pt/naps62/maestro --locked maestro-web
|
|
||||||
```
|
```
|
||||||
|
|
||||||
…or, from a checkout, `just web` then `cargo install --path crates/maestro-web --locked`.
|
Or `just web` from a workspace checkout.
|
||||||
|
|
||||||
It defaults to binding `127.0.0.1:8080` and proxying to `http://127.0.0.1:7070`
|
**Defaults.** `bind_addr = 127.0.0.1:8080`, `daemon_url =
|
||||||
(the daemon's default `bind_addr`). The `[web]` section of
|
http://127.0.0.1:7070`. The `[web]` section of
|
||||||
`~/.config/maestro/config.toml` overrides both — see
|
`~/.config/maestro/config.toml` overrides both — see
|
||||||
`crates/maestro-web/README.md`. **Trust model:** anyone who can reach the bind
|
`crates/maestro-web/README.md`.
|
||||||
address can use it; pick `127.0.0.1` for local-only, a WireGuard/LAN address
|
|
||||||
for one-segment access, never a public IP without adding auth first.
|
|
||||||
|
|
||||||
Headless hosts (servers, dev VMs) install only `maestrod`. Hosts where you
|
**Trust model.** Anyone who can reach the bind address can use it. Pick
|
||||||
want browser access install both.
|
`127.0.0.1` for local-only, a WireGuard/LAN address for one-segment access,
|
||||||
|
never a public IP without adding auth first.
|
||||||
|
|
||||||
|
Headless hosts (servers, dev VMs) skip the prompt with `--no-web`. Hosts
|
||||||
|
where you want browser access answer yes — both binaries end up in
|
||||||
|
`~/.cargo/bin`.
|
||||||
|
|
||||||
## Flags
|
## Flags
|
||||||
|
|
||||||
@@ -83,7 +105,9 @@ want browser access install both.
|
|||||||
| `--exec-start <path>` | Override the systemd unit's `ExecStart` (default `~/.cargo/bin/maestrod`). |
|
| `--exec-start <path>` | Override the systemd unit's `ExecStart` (default `~/.cargo/bin/maestrod`). |
|
||||||
| `--no-systemd` | Skip the systemd step even if detected. |
|
| `--no-systemd` | Skip the systemd step even if detected. |
|
||||||
| `--no-configure` | Skip the `maestrod configure` step. |
|
| `--no-configure` | Skip the `maestrod configure` step. |
|
||||||
| `-y`, `--yes` | Non-interactive: install binaries + drop systemd unit + reload + enable, but **don't** auto-run `configure` or auto-start the unit (those need real config). |
|
| `--no-web` | Skip the optional `maestro-web` install step. |
|
||||||
|
| `--web-exec-start <path>` | Override the web unit's `ExecStart` (default `~/.cargo/bin/maestro-web`). |
|
||||||
|
| `-y`, `--yes` | Non-interactive: install binaries + drop systemd unit + reload + enable, but **don't** auto-run `configure` or auto-start the unit (those need real config). Also skips the web step unless an existing `maestro-web.service` is being refreshed. |
|
||||||
|
|
||||||
## What the systemd unit looks like
|
## What the systemd unit looks like
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user