diff --git a/manage.sh b/manage.sh old mode 100644 new mode 100755 index 84c780e..cb24688 --- a/manage.sh +++ b/manage.sh @@ -5,24 +5,123 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_NAME="robin" +RELEASE_BIN="$SCRIPT_DIR/src-tauri/target/release/$APP_NAME" +DEBUG_BIN="$SCRIPT_DIR/src-tauri/target/debug/$APP_NAME" +ICON="$SCRIPT_DIR/src-tauri/icons/128x128.png" +LOG_FILE="${XDG_DATA_HOME:-$HOME/.local/share}/tech.circuitforge.robin/logs/Robin.log" cmd="${1:-help}" +# ── Helpers ─────────────────────────────────────────────────────────────────── + +_find_binary() { + if [[ -f "$RELEASE_BIN" ]]; then + echo "$RELEASE_BIN" + elif [[ -f "$DEBUG_BIN" ]]; then + echo "$DEBUG_BIN" + else + echo "" + fi +} + +_require_binary() { + local bin + bin="$(_find_binary)" + if [[ -z "$bin" ]]; then + echo "Robin binary not found. Run one of:" + echo " ./manage.sh build (release, requires Node + Tauri CLI)" + echo " ./manage.sh build-debug (debug, requires Rust only)" + exit 1 + fi + echo "$bin" +} + +# ── Commands ────────────────────────────────────────────────────────────────── + case "$cmd" in + + run) + # Run the binary in the foreground (logs to terminal). + # Use this for manual testing; Ctrl+C to quit. + bin="$(_require_binary)" + echo "Starting Robin ($bin)..." + export DISPLAY="${DISPLAY:-:0}" + export RUST_LOG="${RUST_LOG:-robin_lib=info,warn}" + exec "$bin" + ;; + + start) + # Start Robin in the background (daemonised via nohup). + if pgrep -x "$APP_NAME" > /dev/null 2>&1; then + echo "Robin is already running (PID $(pgrep -x "$APP_NAME"))" + exit 0 + fi + bin="$(_require_binary)" + echo "Starting Robin in background..." + export DISPLAY="${DISPLAY:-:0}" + export RUST_LOG="${RUST_LOG:-robin_lib=info,warn}" + mkdir -p "$(dirname "$LOG_FILE")" + nohup "$bin" >> "$LOG_FILE" 2>&1 & + echo "Started (PID $!). Logs: $LOG_FILE" + ;; + + stop) + if pgrep -x "$APP_NAME" > /dev/null 2>&1; then + pkill -x "$APP_NAME" + echo "Robin stopped." + else + echo "Robin is not running." + fi + ;; + + restart) + "$0" stop || true + sleep 1 + "$0" start + ;; + + status) + if pgrep -x "$APP_NAME" > /dev/null 2>&1; then + echo "Robin is running (PID $(pgrep -x "$APP_NAME"))" + else + echo "Robin is not running." + fi + ;; + + logs) + if [[ -f "$LOG_FILE" ]]; then + tail -f "$LOG_FILE" + else + echo "No log file yet at $LOG_FILE" + echo "Start Robin first: ./manage.sh start" + fi + ;; + dev) - echo "Starting Robin in dev mode..." + echo "Starting Robin in dev mode (hot-reload)..." cd "$SCRIPT_DIR" npm run tauri dev ;; build) - echo "Building Robin..." + echo "Building Robin release binary + installers..." cd "$SCRIPT_DIR" npm run tauri build ;; + build-debug) + echo "Building Robin debug binary (Rust only, no Node needed)..." + cargo build --manifest-path "$SCRIPT_DIR/src-tauri/Cargo.toml" + echo "Binary: $DEBUG_BIN" + ;; + + test) + echo "Running Rust tests..." + cargo test --manifest-path "$SCRIPT_DIR/src-tauri/Cargo.toml" --lib + ;; + install-deps) - echo "Installing system dependencies (Debian/Ubuntu)..." + echo "Installing system dependencies (Debian/Ubuntu/Mint)..." sudo apt-get install -y \ libwebkit2gtk-4.1-dev \ libayatana-appindicator3-dev \ @@ -30,15 +129,12 @@ case "$cmd" in libgtk-3-dev \ libssl-dev \ pkg-config - echo "Installing Node dependencies..." - cd "$SCRIPT_DIR" - npm install echo "Installing Rust dependencies..." cargo fetch --manifest-path "$SCRIPT_DIR/src-tauri/Cargo.toml" ;; install-deps-arch) - echo "Installing system dependencies (Arch/CachyOS)..." + echo "Installing system dependencies (Arch/Manjaro/CachyOS)..." paru -S --needed \ webkit2gtk-4.1 \ libayatana-appindicator \ @@ -46,72 +142,124 @@ case "$cmd" in gtk3 \ openssl \ pkg-config - cd "$SCRIPT_DIR" - npm install cargo fetch --manifest-path "$SCRIPT_DIR/src-tauri/Cargo.toml" ;; - install) - echo "Installing Robin as a systemd user service..." - SERVICE_DIR="$HOME/.config/systemd/user" - mkdir -p "$SERVICE_DIR" - BINARY="$SCRIPT_DIR/src-tauri/target/release/$APP_NAME" - if [[ ! -f "$BINARY" ]]; then - echo "Binary not found — run './manage.sh build' first" - exit 1 - fi - cat > "$SERVICE_DIR/robin.service" < "$APPS_DIR/robin.desktop" </dev/null || true + echo "Robin added to application menu." + echo "Entry: $APPS_DIR/robin.desktop" ;; - start) - systemctl --user start robin + desktop-remove) + DESKTOP="${XDG_DATA_HOME:-$HOME/.local/share}/applications/robin.desktop" + if [[ -f "$DESKTOP" ]]; then + rm "$DESKTOP" + update-desktop-database "$(dirname "$DESKTOP")" 2>/dev/null || true + echo "Robin removed from application menu." + else + echo "No desktop entry found at $DESKTOP" + fi ;; - stop) - systemctl --user stop robin + autostart-enable) + # Start Robin automatically when the desktop session begins. + bin="$(_require_binary)" + AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart" + mkdir -p "$AUTOSTART_DIR" + cat > "$AUTOSTART_DIR/robin.desktop" <" - echo "" - echo " dev Start in development mode (hot-reload)" - echo " build Build release binary + installers" - echo " install-deps Install system deps (Debian/Ubuntu)" - echo " install-deps-arch Install system deps (Arch/CachyOS)" - echo " install Install as systemd user service" - echo " start Start Robin service" - echo " stop Stop Robin service" - echo " status Show Robin service status" - echo " logs Tail Robin logs" - echo " test Run Rust tests" + cat <<'EOF' +Robin — Linux migration companion +Usage: ./manage.sh + +Running: + run Run in foreground (logs to terminal, Ctrl+C to quit) + start Start in background + stop Stop background instance + restart Stop then start + status Show whether Robin is running + logs Tail the Robin log file + +Building: + build-debug Build debug binary (Rust only, no Node/npm needed) + build Build release binary + .deb/.rpm/.AppImage (needs Node + Tauri CLI) + dev Start dev mode with hot-reload (needs Node + Tauri CLI) + test Run Rust unit tests + +Installation: + install build-debug + desktop-install + autostart-enable + uninstall Remove desktop entry, autostart, and stop Robin + desktop-install Add Robin to the system application menu + desktop-remove Remove Robin from the application menu + autostart-enable Start Robin automatically at login + autostart-disable Stop Robin from starting at login + +Dependencies: + install-deps Install system deps (Debian/Ubuntu/Mint) + install-deps-arch Install system deps (Arch/Manjaro/CachyOS) +EOF ;; esac diff --git a/src-tauri/src/patterns.rs b/src-tauri/src/patterns.rs index b78358e..b6a7ee7 100644 --- a/src-tauri/src/patterns.rs +++ b/src-tauri/src/patterns.rs @@ -48,16 +48,29 @@ pub fn load( distro_family: &str, ) -> Result { let mut candidates: Vec = Vec::new(); + + // Helper: push paths for a given filename into candidates. + let mut push_candidates = |filename: &str| { + // 1. Relative to binary: covers both bundled installs (patterns/ next to binary) + // and dev builds (target/debug/ → ../../patterns/ = src-tauri/patterns/). + if let Ok(exe) = std::env::current_exe() { + if let Some(exe_dir) = exe.parent() { + candidates.push(exe_dir.join("patterns").join(filename).display().to_string()); + candidates.push(exe_dir.join("../../patterns").join(filename).display().to_string()); + } + } + // 2. CWD-relative fallbacks (for manual / script invocations). + candidates.push(format!("patterns/{filename}")); + candidates.push(format!("src-tauri/patterns/{filename}")); + // 3. System install path. + candidates.push(format!("/usr/share/robin/patterns/{filename}")); + }; + if let Some(src_distro) = source_distro_family { - let specific = format!("{src_distro}-to-{distro_family}.toml"); - candidates.push(format!("patterns/{specific}")); - candidates.push(format!("src-tauri/patterns/{specific}")); - candidates.push(format!("/usr/share/robin/patterns/{specific}")); + push_candidates(&format!("{src_distro}-to-{distro_family}.toml")); } let generic = format!("{source_os}-to-{distro_family}.toml"); - candidates.push(format!("patterns/{generic}")); - candidates.push(format!("src-tauri/patterns/{generic}")); - candidates.push(format!("/usr/share/robin/patterns/{generic}")); + push_candidates(&generic); for path in &candidates { let content = match std::fs::read_to_string(path) { Ok(c) => c,