feat(manage): manage.sh overhaul + exe-relative pattern loading + desktop entry
manage.sh: - run: foreground execution with auto DISPLAY + RUST_LOG - start/stop/restart/status: background daemon lifecycle - logs: tail Robin log file - build-debug: Rust-only build (no Node/npm needed) - desktop-install/remove: system application menu entry - autostart-enable/disable: XDG autostart entry - install: build-debug + desktop-install + autostart-enable in one step - uninstall: reverses install cleanly patterns.rs: - Add exe-relative candidates: binary-dir/patterns/ and binary-dir/../../patterns/ This means the binary works from any working directory without requiring the user to cd into ~/robin/ first
This commit is contained in:
parent
8e28ac2624
commit
efec0a53ee
2 changed files with 225 additions and 64 deletions
262
manage.sh
Normal file → Executable file
262
manage.sh
Normal file → Executable file
|
|
@ -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" <<EOF
|
||||
[Unit]
|
||||
Description=Robin — Linux migration companion
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
ExecStart=$BINARY
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
Environment=DISPLAY=:0
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
desktop-install)
|
||||
# Install .desktop entry so Robin appears in the system app menu.
|
||||
bin="$(_require_binary)"
|
||||
APPS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
|
||||
mkdir -p "$APPS_DIR"
|
||||
cat > "$APPS_DIR/robin.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=Robin
|
||||
GenericName=Linux Migration Assistant
|
||||
Comment=Helps you find your feet on Linux
|
||||
Exec=$bin
|
||||
Icon=$ICON
|
||||
Categories=Utility;System;
|
||||
StartupNotify=false
|
||||
StartupWMClass=Robin
|
||||
Keywords=linux;migration;help;assistant;
|
||||
EOF
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable robin
|
||||
echo "Robin installed. Start with: systemctl --user start robin"
|
||||
update-desktop-database "$APPS_DIR" 2>/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" <<EOF
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=Robin
|
||||
Comment=Helps you find your feet on Linux
|
||||
Exec=$bin
|
||||
Icon=$ICON
|
||||
StartupNotify=false
|
||||
X-GNOME-Autostart-enabled=true
|
||||
Hidden=false
|
||||
NoDisplay=false
|
||||
EOF
|
||||
echo "Robin will start automatically at login."
|
||||
echo "Entry: $AUTOSTART_DIR/robin.desktop"
|
||||
;;
|
||||
|
||||
status)
|
||||
systemctl --user status robin
|
||||
autostart-disable)
|
||||
AUTOSTART="${XDG_CONFIG_HOME:-$HOME/.config}/autostart/robin.desktop"
|
||||
if [[ -f "$AUTOSTART" ]]; then
|
||||
rm "$AUTOSTART"
|
||||
echo "Robin autostart disabled."
|
||||
else
|
||||
echo "No autostart entry found at $AUTOSTART"
|
||||
fi
|
||||
;;
|
||||
|
||||
logs)
|
||||
journalctl --user -u robin -f
|
||||
install)
|
||||
# Full install: build-debug + desktop-install + autostart-enable
|
||||
echo "=== Robin install ==="
|
||||
"$0" build-debug
|
||||
"$0" desktop-install
|
||||
"$0" autostart-enable
|
||||
echo ""
|
||||
echo "Robin installed. It will start at next login."
|
||||
echo "Start it now: ./manage.sh start"
|
||||
;;
|
||||
|
||||
test)
|
||||
cd "$SCRIPT_DIR"
|
||||
cargo test --manifest-path src-tauri/Cargo.toml
|
||||
uninstall)
|
||||
"$0" stop || true
|
||||
"$0" desktop-remove || true
|
||||
"$0" autostart-disable || true
|
||||
echo "Robin uninstalled."
|
||||
;;
|
||||
|
||||
help|*)
|
||||
echo "Usage: ./manage.sh <command>"
|
||||
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 <command>
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -48,16 +48,29 @@ pub fn load(
|
|||
distro_family: &str,
|
||||
) -> Result<PatternFile> {
|
||||
let mut candidates: Vec<String> = 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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue