feat(sidecar): make the sidecar installable — CLI, --print-config, anchored data paths
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 7m52s
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 7m52s
Phase 0.2 of the installer plan (docs/installer/PLAN.md §5). The installer has to drive this binary non-interactively, and today it cannot: the auth token is only readable by scraping the startup log, the config path can only be named through an environment variable, and a relative db path follows the process working directory — which a service manager, not the operator, chooses. - Add a four-flag CLI (cli.rs): --print-config, --config <PATH>, --version, --help. Hand-rolled; an argument-parsing dependency would be larger than the code it replaced. An unrecognized flag exits 2 rather than starting a sidecar that is not the one that was asked for. - --print-config resolves the configuration exactly as a normal start does — including writing a missing config file and generating a blank auth token — and prints it as JSON on stdout: versions, protocol, both bind addresses, ws path, resolved db path, and the token. config_created / token_generated let a re-run tell "read an existing install" from "provisioned a new one". The log subscriber is deliberately not started in this mode, so the document is the whole output. - Anchor a relative [store].path to the config file's directory instead of the CWD, and report resolved absolute paths. A unit pinning UOLINK_CONFIG now keeps its database beside its config rather than in %SystemRoot%\System32 or a VirtualStore redirect. Development is unaffected: under cargo run the two directories are the same. :memory: and file: URIs are left alone. - Hand the db path to sqlx as a filesystem path instead of formatting it into a sqlite:// URL, which percent-decodes it and splits it on '?'. An installed path containing %20 previously opened a different file; verified it now does not. - Create the config's and the database's parent directories when missing, so a service can name /var/lib/runicgateway on a host where nothing made it yet. - 22 unit tests covering argument parsing, path anchoring, token persistence, the generated config template, and the --print-config document. No protocol change: PROTOCOL_VERSION stays 3. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
//! instead of round-tripping the shard. Links and profiles are written from the REST reply paths
|
||||
//! (`link.ok`, `char.profile`), which are RPC replies and never hit the broadcast stream.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::path::Path;
|
||||
|
||||
use serde_json::Value;
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||
@@ -20,9 +20,24 @@ pub struct Store {
|
||||
|
||||
impl Store {
|
||||
/// Opens (creating if absent) the SQLite database and ensures the schema exists.
|
||||
///
|
||||
/// `path` is a filesystem path, handed to sqlx as one. It is deliberately **not** formatted
|
||||
/// into a `sqlite://` URL first: that spelling is parsed as a URL, so it percent-decodes the
|
||||
/// path and splits it on `?`. Under an installed layout the path is absolute and chosen by the
|
||||
/// operator — `C:\ProgramData\RunicGateway\uo-link.db`, or something under a home directory
|
||||
/// with a `%` or `#` in it — and a URL round-trip silently opens a *different* file.
|
||||
pub async fn open(path: &str) -> anyhow::Result<Self> {
|
||||
let opts =
|
||||
SqliteConnectOptions::from_str(&format!("sqlite://{path}"))?.create_if_missing(true);
|
||||
// A service unit can name a data directory that does not exist yet; creating it here means
|
||||
// one less way for a fresh install to fail on first start.
|
||||
if let Some(dir) = Path::new(path).parent() {
|
||||
if !dir.as_os_str().is_empty() && !dir.exists() {
|
||||
std::fs::create_dir_all(dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
let opts = SqliteConnectOptions::new()
|
||||
.filename(path)
|
||||
.create_if_missing(true);
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(4)
|
||||
|
||||
Reference in New Issue
Block a user