Add config and service subcommands

config subcommand — show, path, edit ($EDITOR), and a non-interactive
set that takes --prefix / --compat-dir / --gameid. Lets users retarget
the Wine prefix without hand-editing TOML.

service subcommand — install / uninstall / status for a systemd --user
unit that autostarts the tray. install writes ~/.config/systemd/user/
battlenet-manager.service with ExecStart pointing at the current binary,
then daemon-reloads and enable --now's the unit. uninstall tears it back
down.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-16 17:25:48 -07:00
parent 8908c15974
commit f7738d215b
4 changed files with 231 additions and 11 deletions
+98
View File
@@ -0,0 +1,98 @@
use anyhow::{bail, Context, Result};
use std::path::PathBuf;
use std::process::Command;
const UNIT_NAME: &str = "battlenet-manager.service";
fn unit_path() -> Result<PathBuf> {
let home = std::env::var("HOME").context("$HOME is not set")?;
Ok(PathBuf::from(home)
.join(".config/systemd/user")
.join(UNIT_NAME))
}
fn render_unit(exe: &std::path::Path) -> String {
format!(
"[Unit]\n\
Description=Battle.net tray manager\n\
After=graphical-session.target\n\
PartOf=graphical-session.target\n\
\n\
[Service]\n\
ExecStart={exe}\n\
Restart=on-failure\n\
RestartSec=5\n\
\n\
[Install]\n\
WantedBy=graphical-session.target\n",
exe = exe.display(),
)
}
fn systemctl(args: &[&str]) -> Result<()> {
let status = Command::new("systemctl")
.arg("--user")
.args(args)
.status()
.context("Failed to invoke systemctl --user (is systemd installed?)")?;
if !status.success() {
bail!("systemctl --user {} exited non-zero", args.join(" "));
}
Ok(())
}
/// Write the unit, reload systemd, and enable+start the service.
pub fn install() -> Result<()> {
let exe = std::env::current_exe()
.context("Cannot determine path to own executable")?;
let path = unit_path()?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create {parent:?}"))?;
}
let contents = render_unit(&exe);
std::fs::write(&path, &contents)
.with_context(|| format!("Failed to write unit file {path:?}"))?;
println!("Wrote unit: {}", path.display());
println!("ExecStart: {}", exe.display());
println!();
systemctl(&["daemon-reload"])?;
systemctl(&["enable", "--now", UNIT_NAME])?;
println!();
println!("\x1b[1;32m✓\x1b[0m Service installed and started.");
println!(" Status: systemctl --user status {UNIT_NAME}");
println!(" Logs: journalctl --user -u {UNIT_NAME} -f");
Ok(())
}
/// Stop, disable, and remove the unit file.
pub fn uninstall() -> Result<()> {
let path = unit_path()?;
// Ignore failures: the unit may already be stopped or unknown to systemd.
let _ = systemctl(&["disable", "--now", UNIT_NAME]);
if path.exists() {
std::fs::remove_file(&path)
.with_context(|| format!("Failed to remove {path:?}"))?;
println!("Removed {}", path.display());
} else {
println!("No unit file at {}", path.display());
}
let _ = systemctl(&["daemon-reload"]);
println!("\x1b[1;32m✓\x1b[0m Service removed.");
Ok(())
}
/// Pass through `systemctl --user status`.
pub fn status() -> Result<()> {
let _ = Command::new("systemctl")
.args(["--user", "status", UNIT_NAME])
.status();
Ok(())
}