fix: reduced app size by a good margin
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -72,6 +73,8 @@ pub enum Command {
|
||||
#[command(subcommand)]
|
||||
command: ProjectCommand,
|
||||
},
|
||||
/// Start the authenticated headless SSH server.
|
||||
Server(ServerArgs),
|
||||
/// Start interactive TUI mode (normally intercepted by the packaged launcher).
|
||||
Tui,
|
||||
/// Run an enabled utility Lua script from the active project.
|
||||
@@ -84,6 +87,22 @@ pub enum Command {
|
||||
Install,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args, Default)]
|
||||
pub struct ServerArgs {
|
||||
/// SSH listen address. Defaults to loopback; external access must be explicit.
|
||||
#[arg(long)]
|
||||
pub bind: Option<IpAddr>,
|
||||
/// SSH listen port.
|
||||
#[arg(long)]
|
||||
pub port: Option<u16>,
|
||||
/// Application database path.
|
||||
#[arg(long)]
|
||||
pub database: Option<PathBuf>,
|
||||
/// Private application data directory containing SSH key material.
|
||||
#[arg(long)]
|
||||
pub data_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum RepairPart {
|
||||
PostLinks,
|
||||
@@ -257,10 +276,8 @@ fn execute(command: Command, context: &RunContext, airplane: bool) -> Result<Com
|
||||
if matches!(command, Command::Install) {
|
||||
return install_launcher(context);
|
||||
}
|
||||
if matches!(command, Command::Tui) {
|
||||
bail!(
|
||||
"the packaged bds-cli launcher starts TUI mode directly; a direct CLI invocation cannot own the terminal"
|
||||
);
|
||||
if matches!(command, Command::Tui | Command::Server(_)) {
|
||||
bail!("server and TUI modes are started directly by the native bds-cli entry point");
|
||||
}
|
||||
|
||||
let db = open_database(&context.database_path)?;
|
||||
@@ -277,7 +294,7 @@ fn execute(command: Command, context: &RunContext, airplane: bool) -> Result<Com
|
||||
Command::Config { command } => config(&db, command),
|
||||
Command::Project { command } => project(&db, command),
|
||||
Command::Lua { script, args } => run_lua(&db, &script, &args, airplane),
|
||||
Command::Tui | Command::Install => unreachable!(),
|
||||
Command::Server(_) | Command::Tui | Command::Install => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1143,6 +1160,7 @@ fn command_name(command: &Command) -> &'static str {
|
||||
Command::Gallery(_) => "gallery",
|
||||
Command::Config { .. } => "config",
|
||||
Command::Project { .. } => "project",
|
||||
Command::Server(_) => "server",
|
||||
Command::Tui => "tui",
|
||||
Command::Lua { .. } => "lua",
|
||||
Command::Install => "install",
|
||||
@@ -1223,10 +1241,24 @@ mod tests {
|
||||
let help = String::from_utf8(help).unwrap();
|
||||
for command in [
|
||||
"rebuild", "repair", "render", "upload", "push", "pull", "post", "media", "gallery",
|
||||
"config", "project", "tui", "lua", "install",
|
||||
"config", "project", "server", "tui", "lua", "install",
|
||||
] {
|
||||
assert!(help.contains(command), "missing {command} from help");
|
||||
}
|
||||
let server = Cli::try_parse_from([
|
||||
"bds-cli",
|
||||
"server",
|
||||
"--bind",
|
||||
"127.0.0.2",
|
||||
"--port",
|
||||
"2233",
|
||||
"--database",
|
||||
"/tmp/bds.db",
|
||||
"--data-dir",
|
||||
"/tmp/bds",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(matches!(server.command, Command::Server(_)));
|
||||
assert!(Cli::try_parse_from(["bds-cli", "unknown"]).is_err());
|
||||
assert!(Cli::try_parse_from(["bds-cli", "render", "--incremental", "--force"]).is_err());
|
||||
assert!(Cli::try_parse_from(["bds-cli", "repair", "unknown"]).is_err());
|
||||
@@ -1511,9 +1543,11 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let target = fixture.home_dir.join(".local/bin/bds-cli");
|
||||
assert_eq!(
|
||||
target.canonicalize().unwrap(),
|
||||
executable.canonicalize().unwrap()
|
||||
assert!(!target.is_symlink());
|
||||
assert!(
|
||||
std::fs::read_to_string(target)
|
||||
.unwrap()
|
||||
.contains(executable.canonicalize().unwrap().to_str().unwrap())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,33 @@ fn main() -> ExitCode {
|
||||
}
|
||||
};
|
||||
|
||||
if let bds_cli::Command::Server(args) = &cli.command {
|
||||
let data_root = args
|
||||
.data_dir
|
||||
.clone()
|
||||
.unwrap_or_else(bds_core::util::application_data_dir);
|
||||
let database_path = args
|
||||
.database
|
||||
.clone()
|
||||
.unwrap_or_else(|| data_root.join("bds.db"));
|
||||
let config = match bds_server::ServerConfig::from_environment(database_path, data_root) {
|
||||
Ok(mut config) => {
|
||||
if let Some(bind) = args.bind {
|
||||
config.bind = bind;
|
||||
}
|
||||
if let Some(port) = args.port {
|
||||
config.port = port;
|
||||
}
|
||||
config
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("Error: {error:#}");
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
return run_headless(bds_server::boot::BootMode::Server, config);
|
||||
}
|
||||
|
||||
if matches!(cli.command, bds_cli::Command::Tui) {
|
||||
let data_root = bds_core::util::application_data_dir();
|
||||
let config = match bds_server::ServerConfig::from_environment(
|
||||
@@ -32,13 +59,7 @@ fn main() -> ExitCode {
|
||||
return ExitCode::from(1);
|
||||
}
|
||||
};
|
||||
return match bds_server::run_headless(bds_server::boot::BootMode::Tui, config) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
eprintln!("Error: {error:#}");
|
||||
ExitCode::from(1)
|
||||
}
|
||||
};
|
||||
return run_headless(bds_server::boot::BootMode::Tui, config);
|
||||
}
|
||||
|
||||
let json = cli.json;
|
||||
@@ -71,3 +92,13 @@ fn main() -> ExitCode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_headless(mode: bds_server::boot::BootMode, config: bds_server::ServerConfig) -> ExitCode {
|
||||
match bds_server::run_headless(mode, config) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
eprintln!("Error: {error:#}");
|
||||
ExitCode::from(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ edition.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
uuid = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -14,14 +14,18 @@ pub fn install_launcher(executable: &Path, home_dir: &Path) -> EngineResult<Path
|
||||
let bin_dir = home_dir.join(".local/bin");
|
||||
std::fs::create_dir_all(&bin_dir)?;
|
||||
let target = bin_dir.join(if cfg!(windows) {
|
||||
"bds-cli.exe"
|
||||
"bds-cli.cmd"
|
||||
} else {
|
||||
"bds-cli"
|
||||
});
|
||||
let source = executable.canonicalize()?;
|
||||
let launcher = launcher_contents(&source);
|
||||
#[cfg(unix)]
|
||||
if target.is_symlink() && target.canonicalize().ok().as_ref() == Some(&source) {
|
||||
std::fs::remove_file(&target)?;
|
||||
}
|
||||
if target.exists() {
|
||||
let existing = target.canonicalize().ok();
|
||||
let source = executable.canonicalize()?;
|
||||
if existing.as_ref() != Some(&source) {
|
||||
if std::fs::read(&target).ok().as_deref() != Some(launcher.as_bytes()) {
|
||||
return Err(EngineError::Conflict(format!(
|
||||
"refusing to overwrite existing launcher at {}",
|
||||
target.display()
|
||||
@@ -30,13 +34,29 @@ pub fn install_launcher(executable: &Path, home_dir: &Path) -> EngineResult<Path
|
||||
return Ok(target);
|
||||
}
|
||||
|
||||
std::fs::write(&target, launcher)?;
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::symlink(executable.canonicalize()?, &target)?;
|
||||
#[cfg(windows)]
|
||||
std::fs::copy(executable, &target)?;
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
let mut permissions = std::fs::metadata(&target)?.permissions();
|
||||
permissions.set_mode(0o755);
|
||||
std::fs::set_permissions(&target, permissions)?;
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn launcher_contents(executable: &Path) -> String {
|
||||
let quoted = executable.to_string_lossy().replace('\'', "'\"'\"'");
|
||||
format!("#!/bin/sh\nexec '{quoted}' \"$@\"\n")
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn launcher_contents(executable: &Path) -> String {
|
||||
let escaped = executable.to_string_lossy().replace('%', "%%");
|
||||
format!("@echo off\r\n\"{escaped}\" %*\r\n")
|
||||
}
|
||||
|
||||
/// Resolve the CLI shipped beside the desktop executable and install it.
|
||||
pub fn install_packaged_launcher(home_dir: &Path) -> EngineResult<PathBuf> {
|
||||
let app = std::env::current_exe()?;
|
||||
@@ -59,10 +79,9 @@ mod tests {
|
||||
std::fs::write(&executable, b"binary").unwrap();
|
||||
let home = root.path().join("home");
|
||||
let target = install_launcher(&executable, &home).unwrap();
|
||||
assert_eq!(
|
||||
target.canonicalize().unwrap(),
|
||||
executable.canonicalize().unwrap()
|
||||
);
|
||||
assert!(!target.is_symlink());
|
||||
let launcher = std::fs::read_to_string(&target).unwrap();
|
||||
assert!(launcher.contains(executable.canonicalize().unwrap().to_str().unwrap()));
|
||||
assert_eq!(install_launcher(&executable, &home).unwrap(), target);
|
||||
|
||||
std::fs::remove_file(&target).unwrap();
|
||||
@@ -70,4 +89,24 @@ mod tests {
|
||||
assert!(install_launcher(&executable, &home).is_err());
|
||||
assert_eq!(std::fs::read(&target).unwrap(), b"mine");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn replaces_the_previous_installer_symlink_with_a_forwarding_launcher() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let executable = root.path().join("packaged-bds-cli");
|
||||
std::fs::write(&executable, b"binary").unwrap();
|
||||
let home = root.path().join("home");
|
||||
let target = home.join(".local/bin/bds-cli");
|
||||
std::fs::create_dir_all(target.parent().unwrap()).unwrap();
|
||||
std::os::unix::fs::symlink(&executable, &target).unwrap();
|
||||
|
||||
assert_eq!(install_launcher(&executable, &home).unwrap(), target);
|
||||
assert!(!target.is_symlink());
|
||||
assert!(
|
||||
std::fs::read_to_string(target)
|
||||
.unwrap()
|
||||
.starts_with("#!/bin/sh")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1599,7 +1599,7 @@ mod tests {
|
||||
let engine = GitEngine::with_executable_and_timeouts(
|
||||
dir.path(),
|
||||
executable,
|
||||
Duration::from_secs(1),
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(1),
|
||||
);
|
||||
|
||||
|
||||
6
crates/bds-package-runtime/Cargo.toml
Normal file
6
crates/bds-package-runtime/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "bds-package-runtime"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
231
crates/bds-package-runtime/src/main.rs
Normal file
231
crates/bds-package-runtime/src/main.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
if env::args_os().nth(1).as_deref() == Some(OsStr::new("sign-app-for-dmg")) {
|
||||
return sign_app_for_dmg(&workspace);
|
||||
}
|
||||
run(
|
||||
Command::new(env::var_os("CARGO").unwrap_or_else(|| "cargo".into()))
|
||||
.current_dir(&workspace)
|
||||
.args([
|
||||
"build",
|
||||
"--release",
|
||||
"-p",
|
||||
"bds-ui",
|
||||
"-p",
|
||||
"bds-cli",
|
||||
"-p",
|
||||
"bds-mcp",
|
||||
]),
|
||||
"release build",
|
||||
)?;
|
||||
|
||||
let target = env::var_os("CARGO_TARGET_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| workspace.join("target"));
|
||||
let release = target.join("release");
|
||||
let staging = release.join("bds-runtime");
|
||||
if staging.exists() {
|
||||
fs::remove_dir_all(&staging)?;
|
||||
}
|
||||
fs::create_dir_all(&staging)?;
|
||||
|
||||
let shared_libraries = shared_library_names().map(|name| release.join(name));
|
||||
for library in &shared_libraries {
|
||||
copy_runtime(library, &staging)?;
|
||||
}
|
||||
|
||||
let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
|
||||
let output = Command::new(rustc)
|
||||
.args(["--print", "target-libdir"])
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err("rustc --print target-libdir failed".into());
|
||||
}
|
||||
let rust_lib_dir = PathBuf::from(String::from_utf8(output.stdout)?.trim());
|
||||
let mut std_libraries = fs::read_dir(&rust_lib_dir)?
|
||||
.filter_map(Result::ok)
|
||||
.map(|entry| entry.path())
|
||||
.filter(|path| is_dynamic_std(path))
|
||||
.collect::<Vec<_>>();
|
||||
std_libraries.sort();
|
||||
if std_libraries.is_empty() {
|
||||
return Err(format!(
|
||||
"no dynamic Rust standard library found in {}",
|
||||
rust_lib_dir.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
for library in &std_libraries {
|
||||
copy_runtime(&library, &staging)?;
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
rewrite_macos_install_names(&release, &staging, &shared_libraries, &std_libraries)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign_app_for_dmg(workspace: &Path) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(target_os = "macos")]
|
||||
if env::var_os("CARGO_PACKAGER_FORMAT").as_deref() == Some(OsStr::new("dmg")) {
|
||||
let target = env::var_os("CARGO_TARGET_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| workspace.join("target"));
|
||||
run(
|
||||
Command::new("codesign")
|
||||
.args(["--force", "--sign", "-"])
|
||||
.arg(target.join("release/Blogging Desktop Server.app")),
|
||||
"macOS app ad-hoc signing",
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(command: &mut Command, description: &str) -> Result<(), Box<dyn Error>> {
|
||||
if command.status()?.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("{description} failed").into())
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_runtime(source: &Path, staging: &Path) -> Result<(), Box<dyn Error>> {
|
||||
let name = source
|
||||
.file_name()
|
||||
.ok_or_else(|| format!("runtime path has no filename: {}", source.display()))?;
|
||||
let destination = staging.join(name);
|
||||
fs::copy(source, &destination)?;
|
||||
let mut permissions = fs::metadata(&destination)?.permissions();
|
||||
permissions.set_readonly(false);
|
||||
fs::set_permissions(&destination, permissions)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
fs::set_permissions(&destination, fs::Permissions::from_mode(0o755))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn shared_library_names() -> [&'static str; 2] {
|
||||
if cfg!(target_os = "windows") {
|
||||
["bds_core.dll", "bds_server.dll"]
|
||||
} else if cfg!(target_os = "macos") {
|
||||
["libbds_core.dylib", "libbds_server.dylib"]
|
||||
} else {
|
||||
["libbds_core.so", "libbds_server.so"]
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dynamic_std(path: &Path) -> bool {
|
||||
let name = path.file_name().and_then(OsStr::to_str).unwrap_or_default();
|
||||
if cfg!(target_os = "windows") {
|
||||
name.starts_with("std-") && name.ends_with(".dll")
|
||||
} else if cfg!(target_os = "macos") {
|
||||
name.starts_with("libstd-") && name.ends_with(".dylib")
|
||||
} else {
|
||||
name.starts_with("libstd-") && name.ends_with(".so")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn rewrite_macos_install_names(
|
||||
release: &Path,
|
||||
staging: &Path,
|
||||
shared_libraries: &[PathBuf],
|
||||
std_libraries: &[PathBuf],
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut replacements = Vec::new();
|
||||
for source in shared_libraries.iter().chain(std_libraries) {
|
||||
let old_name = macho_install_name(source)?;
|
||||
let file_name = source
|
||||
.file_name()
|
||||
.and_then(OsStr::to_str)
|
||||
.ok_or_else(|| format!("invalid dylib filename: {}", source.display()))?;
|
||||
replacements.push((old_name, format!("@rpath/{file_name}")));
|
||||
}
|
||||
|
||||
let mut targets = ["bds-ui", "bds-cli", "bds-mcp"]
|
||||
.map(|name| release.join(name))
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
targets.extend(
|
||||
shared_libraries
|
||||
.iter()
|
||||
.filter_map(|source| source.file_name().map(|name| staging.join(name))),
|
||||
);
|
||||
targets.extend(
|
||||
std_libraries
|
||||
.iter()
|
||||
.filter_map(|source| source.file_name().map(|name| staging.join(name))),
|
||||
);
|
||||
|
||||
for target in &targets {
|
||||
for (old_name, new_name) in &replacements {
|
||||
run(
|
||||
Command::new("install_name_tool")
|
||||
.args(["-change", old_name, new_name])
|
||||
.arg(target),
|
||||
"Mach-O dependency rewrite",
|
||||
)?;
|
||||
}
|
||||
}
|
||||
for (_, new_name) in &replacements {
|
||||
let file_name = new_name.trim_start_matches("@rpath/");
|
||||
run(
|
||||
Command::new("install_name_tool")
|
||||
.args(["-id", new_name])
|
||||
.arg(staging.join(file_name)),
|
||||
"Mach-O install-name rewrite",
|
||||
)?;
|
||||
}
|
||||
for target in targets {
|
||||
run(
|
||||
Command::new("codesign")
|
||||
.args(["--force", "--sign", "-"])
|
||||
.arg(target),
|
||||
"Mach-O ad-hoc signing",
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn macho_install_name(library: &Path) -> Result<String, Box<dyn Error>> {
|
||||
let output = Command::new("otool").arg("-D").arg(library).output()?;
|
||||
if !output.status.success() {
|
||||
return Err(format!("otool -D failed for {}", library.display()).into());
|
||||
}
|
||||
String::from_utf8(output.stdout)?
|
||||
.lines()
|
||||
.nth(1)
|
||||
.map(str::to_owned)
|
||||
.ok_or_else(|| format!("no Mach-O install name in {}", library.display()).into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn recognizes_only_the_platform_dynamic_standard_library() {
|
||||
let valid = if cfg!(target_os = "windows") {
|
||||
"std-123.dll"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"libstd-123.dylib"
|
||||
} else {
|
||||
"libstd-123.so"
|
||||
};
|
||||
assert!(is_dynamic_std(Path::new(valid)));
|
||||
assert!(!is_dynamic_std(Path::new("libstd-123.rlib")));
|
||||
for library in shared_library_names() {
|
||||
assert!(!is_dynamic_std(Path::new(library)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ edition.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
bds-core = { workspace = true }
|
||||
bds-editor = { workspace = true }
|
||||
|
||||
9
crates/bds-server/build.rs
Normal file
9
crates/bds-server/build.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
fn main() {
|
||||
// syntect and fastembed both reach onig_sys across separate Rust dylibs.
|
||||
// Keep the vendored static archive on this dylib's final native link line.
|
||||
if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc") {
|
||||
println!("cargo:rustc-link-arg=onig.lib");
|
||||
} else {
|
||||
println!("cargo:rustc-link-arg=-lonig");
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bds_server::boot::BootMode;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "bds-server",
|
||||
about = "Headless RuDS engine host over authenticated SSH"
|
||||
)]
|
||||
struct Args {
|
||||
/// SSH listen address. Defaults to loopback; external access must be explicit.
|
||||
#[arg(long)]
|
||||
bind: Option<IpAddr>,
|
||||
/// SSH listen port.
|
||||
#[arg(long)]
|
||||
port: Option<u16>,
|
||||
/// Application database path.
|
||||
#[arg(long)]
|
||||
database: Option<PathBuf>,
|
||||
/// Private application data directory containing SSH key material.
|
||||
#[arg(long)]
|
||||
data_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let data_root = args
|
||||
.data_dir
|
||||
.unwrap_or_else(bds_core::util::application_data_dir);
|
||||
let database_path = args.database.unwrap_or_else(|| data_root.join("bds.db"));
|
||||
let mut config = bds_server::ServerConfig::from_environment(database_path, data_root)?;
|
||||
if let Some(bind) = args.bind {
|
||||
config.bind = bind;
|
||||
}
|
||||
if let Some(port) = args.port {
|
||||
config.port = port;
|
||||
}
|
||||
bds_server::run_headless(BootMode::Server, config)
|
||||
}
|
||||
@@ -39,12 +39,15 @@ winresource = "0.1"
|
||||
product-name = "Blogging Desktop Server"
|
||||
identifier = "de.rfc1437.ruds"
|
||||
description = "A desktop application for writing and publishing static blogs."
|
||||
before-packaging-command = "cargo build --release -p bds-ui -p bds-cli -p bds-mcp -p bds-server"
|
||||
before-packaging-command = "cargo run --quiet -p bds-package-runtime"
|
||||
before-each-package-command = "cargo run --quiet -p bds-package-runtime -- sign-app-for-dmg"
|
||||
binaries = [
|
||||
{ path = "bds-ui", main = true },
|
||||
{ path = "bds-cli" },
|
||||
{ path = "bds-mcp" },
|
||||
{ path = "bds-server" },
|
||||
]
|
||||
resources = [
|
||||
{ src = "../../target/release/bds-runtime/*", target = "." },
|
||||
]
|
||||
deep-link-protocols = [{ schemes = ["ruds"] }]
|
||||
icons = [
|
||||
@@ -54,8 +57,7 @@ icons = [
|
||||
]
|
||||
|
||||
[package.metadata.packager.macos]
|
||||
minimum-system-version = "13.0"
|
||||
signing-identity = "-"
|
||||
minimum-system-version = "26.0"
|
||||
|
||||
[package.metadata.packager.nsis]
|
||||
installer-icon = "assets/app-icons/bds.ico"
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=assets/app-icons/bds.ico");
|
||||
|
||||
// Wry and bds-core both reach objc2's native exception helper across
|
||||
// separate Rust dylibs, so retain the static helper on the UI link line.
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
|
||||
println!("cargo:rustc-link-arg=-lobjc2_exception_helper_0_1");
|
||||
}
|
||||
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
|
||||
winresource::WindowsResource::new()
|
||||
.set_icon("assets/app-icons/bds.ico")
|
||||
|
||||
@@ -24,22 +24,55 @@ fn desktop_packages_have_native_icons_and_cargo_commands() {
|
||||
"assets/app-icons/bds.icns",
|
||||
"identifier = \"de.rfc1437.ruds\"",
|
||||
"deep-link-protocols = [{ schemes = [\"ruds\"] }]",
|
||||
"signing-identity = \"-\"",
|
||||
"cargo build --release -p bds-ui -p bds-cli -p bds-mcp",
|
||||
"minimum-system-version = \"26.0\"",
|
||||
"../../target/release/bds-runtime/*",
|
||||
"cargo run --quiet -p bds-package-runtime",
|
||||
"cargo run --quiet -p bds-package-runtime -- sign-app-for-dmg",
|
||||
"{ path = \"bds-mcp\" }",
|
||||
] {
|
||||
assert!(manifest.contains(required), "missing {required}");
|
||||
}
|
||||
assert!(!manifest.contains("signing-identity"));
|
||||
|
||||
let cargo_config = fs::read_to_string(Path::new(CRATE_DIR).join("../../.cargo/config.toml"))
|
||||
.expect("Cargo packaging aliases");
|
||||
for alias in ["bundle-macos", "bundle-windows", "bundle-linux"] {
|
||||
assert!(cargo_config.contains(alias), "missing cargo {alias}");
|
||||
}
|
||||
assert!(cargo_config.contains("prefer-dynamic"));
|
||||
assert!(cargo_config.contains("-mmacosx-version-min=26.0"));
|
||||
assert!(cargo_config.contains("@executable_path/../Resources"));
|
||||
assert!(cargo_config.contains("$ORIGIN/../lib/bds-ui"));
|
||||
|
||||
let main = fs::read_to_string(Path::new(CRATE_DIR).join("src/main.rs")).unwrap();
|
||||
assert!(main.contains("assets/app-icons/bds.png"));
|
||||
|
||||
let build = fs::read_to_string(Path::new(CRATE_DIR).join("build.rs")).unwrap();
|
||||
assert!(build.contains("set_icon(\"assets/app-icons/bds.ico\")"));
|
||||
|
||||
let workspace_manifest =
|
||||
fs::read_to_string(Path::new(CRATE_DIR).join("../../Cargo.toml")).unwrap();
|
||||
assert!(workspace_manifest.contains("strip = \"symbols\""));
|
||||
assert!(workspace_manifest.contains("ort-download-binaries-rustls-tls"));
|
||||
|
||||
let core_manifest =
|
||||
fs::read_to_string(Path::new(CRATE_DIR).join("../bds-core/Cargo.toml")).unwrap();
|
||||
assert!(core_manifest.contains("crate-type = [\"dylib\"]"));
|
||||
let server_manifest =
|
||||
fs::read_to_string(Path::new(CRATE_DIR).join("../bds-server/Cargo.toml")).unwrap();
|
||||
assert!(server_manifest.contains("crate-type = [\"dylib\"]"));
|
||||
assert!(!manifest.contains("{ path = \"bds-server\" }"));
|
||||
assert!(!manifest.contains("-p bds-server"));
|
||||
assert!(
|
||||
!Path::new(CRATE_DIR)
|
||||
.join("../bds-server/src/main.rs")
|
||||
.exists()
|
||||
);
|
||||
|
||||
let packager =
|
||||
fs::read_to_string(Path::new(CRATE_DIR).join("../bds-package-runtime/src/main.rs"))
|
||||
.unwrap();
|
||||
assert!(packager.contains("Command::new(\"codesign\")"));
|
||||
assert!(packager.contains("CARGO_PACKAGER_FORMAT"));
|
||||
assert!(!packager.contains("--options"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user