diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efe0ee0..1d72aa6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,3 +52,17 @@ jobs: - name: Rust build run: cargo build --manifest-path src-tauri/Cargo.toml + + macos: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci || npm install + - run: npm run build + - run: cargo test --manifest-path src-tauri/Cargo.toml + - run: cargo build --manifest-path src-tauri/Cargo.toml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d213617..ccb7787 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,3 +53,30 @@ jobs: RemoteOpsX-x86_64.AppImage src-tauri/target/release/bundle/deb/*.deb src-tauri/target/release/bundle/rpm/*.rpm + + macos: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install JS dependencies + run: npm ci || npm install + + - name: Build macOS bundles + run: npm run app:build + + - name: Upload macOS release assets + uses: softprops/action-gh-release@v2 + with: + files: | + src-tauri/target/release/bundle/dmg/*.dmg + src-tauri/target/release/bundle/macos/*.app.tar.gz diff --git a/README.md b/README.md index 1c047ac..907da3e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # RemoteOpsX -**A unified Linux remote-operations workspace — not just another terminal.** +**A unified Linux and macOS remote-operations workspace — not just another terminal.** RemoteOpsX is *MobaXterm + Remmina + a Netdata-lite + a server runbook engine*, -built for Linux operators. It combines remote access (SSH / SFTP / FTP / RDP / VNC), +built for Linux and macOS operators. It combines remote access (SSH / SFTP / FTP / RDP / VNC), **agentless** live server-health monitoring, systemd diagnostics, log tooling, SSH tunnels and **executable runbooks** into one keyboard-friendly desktop app. -> Working name: **RemoteOpsX**. Linux-first (Arch, Ubuntu, Debian, Fedora). +> Working name: **RemoteOpsX**. Supports macOS and Linux (Arch, Ubuntu, Debian, Fedora, openSUSE). --- @@ -190,7 +190,7 @@ sudo dnf install openssh-clients sshpass freerdp tigervnc gnome-keyring Prerequisites: **Rust** (stable, via rustup), **Node 18+**, and the Tauri Linux system deps (`webkit2gtk-4.1`, `libappindicator`, etc). -On Arch, Debian/Ubuntu, Fedora, and openSUSE, install all runtime and build +On macOS, Arch, Debian/Ubuntu, Fedora, and openSUSE, install all runtime and build dependencies interactively with: ```bash diff --git a/docs/distribution.md b/docs/distribution.md index 54c9961..b257ce2 100644 --- a/docs/distribution.md +++ b/docs/distribution.md @@ -1,6 +1,6 @@ # Distribution -RemoteOpsX ships as a native Linux desktop app. Docker is not required. +RemoteOpsX ships as a native Linux and macOS desktop app. Docker is not required. ## GitHub Releases @@ -16,6 +16,25 @@ Expected release assets: - `RemoteOpsX-x86_64.AppImage` - Debian package (`.deb`) - RPM package (`.rpm`) +- macOS disk image (`.dmg`) + +## macOS + +The macOS build uses the native Keychain for secrets and the built-in Screen +Sharing application for VNC. FreeRDP is installed with Homebrew for RDP. + +Install build dependencies: + +```bash +npm run deps:build +``` + +For an unpacked application bundle, the installer prepares runtime dependencies +before copying RemoteOpsX into `~/Applications`: + +```bash +bash packaging/macos/install-app.sh path/to/RemoteOpsX.app +``` ## Local AppImage Install diff --git a/package.json b/package.json index d08fd56..8df0784 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "app:dev": "tauri dev", "app:build": "tauri build", "app:build:arch": "NO_STRIP=1 PKG_CONFIG_PATH=\"$PWD/scripts/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}\" tauri build", - "deps:runtime": "bash packaging/linux/bootstrap-dependencies.sh --runtime", - "deps:build": "bash packaging/linux/bootstrap-dependencies.sh --build" + "deps:runtime": "bash scripts/bootstrap-dependencies.sh --runtime", + "deps:build": "bash scripts/bootstrap-dependencies.sh --build" }, "dependencies": { "@tauri-apps/api": "^2.1.1", diff --git a/packaging/macos/bootstrap-dependencies.sh b/packaging/macos/bootstrap-dependencies.sh new file mode 100644 index 0000000..e66fba6 --- /dev/null +++ b/packaging/macos/bootstrap-dependencies.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail + +MODE="runtime" +ASSUME_YES=0 +DRY_RUN=0 + +for argument in "$@"; do + case "${argument}" in + --runtime) MODE="runtime" ;; + --build) MODE="build" ;; + --yes|-y) ASSUME_YES=1 ;; + --dry-run) DRY_RUN=1 ;; + --help|-h) + echo "Usage: $0 [--runtime|--build] [--yes] [--dry-run]" + exit 0 + ;; + *) echo "Unknown option: ${argument}" >&2; exit 2 ;; + esac +done + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "This installer is for macOS. Use packaging/linux on Linux." >&2 + exit 1 +fi + +if ! command -v brew >/dev/null 2>&1; then + echo "Homebrew is required to manage RemoteOpsX native dependencies." >&2 + echo "Install it from https://brew.sh and run this command again." >&2 + exit 1 +fi + +# OpenSSH, curl, Keychain and the built-in VNC Screen Sharing client ship with macOS. +formulae=(freerdp sshpass) +casks=() +if [[ "${MODE}" == "build" ]]; then + formulae+=(rust node pkg-config cmake llvm libvncserver) +fi + +echo "RemoteOpsX will install ${MODE} dependencies with Homebrew:" +printf ' formula: %s\n' "${formulae[@]}" +if [[ "${#casks[@]}" -gt 0 ]]; then + printf ' cask: %s\n' "${casks[@]}" +fi + +if [[ "${DRY_RUN}" -eq 1 ]]; then + exit 0 +fi +if [[ "${ASSUME_YES}" -ne 1 ]]; then + read -r -p "Continue? [y/N] " reply + [[ "${reply}" =~ ^[Yy]$ ]] || { echo "Cancelled."; exit 0; } +fi + +brew install "${formulae[@]}" +if [[ "${#casks[@]}" -gt 0 ]]; then + brew install --cask "${casks[@]}" +fi +echo "RemoteOpsX ${MODE} dependencies are installed." diff --git a/packaging/macos/install-app.sh b/packaging/macos/install-app.sh new file mode 100644 index 0000000..dc54cf8 --- /dev/null +++ b/packaging/macos/install-app.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_PATH="${1:-RemoteOpsX.app}" +if [[ ! -d "${APP_PATH}" || "${APP_PATH}" != *.app ]]; then + echo "Application bundle not found: ${APP_PATH}" >&2 + echo "Usage: $0 path/to/RemoteOpsX.app" >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +if [[ "${REMOTEOPSX_SKIP_DEPENDENCIES:-0}" != "1" ]]; then + bash "${SCRIPT_DIR}/bootstrap-dependencies.sh" --runtime +fi + +mkdir -p "${HOME}/Applications" +destination="${HOME}/Applications/RemoteOpsX.app" +if [[ -e "${destination}" ]]; then + read -r -p "Replace the existing ${destination}? [y/N] " reply + [[ "${reply}" =~ ^[Yy]$ ]] || { echo "Cancelled."; exit 0; } + rm -rf "${destination}" +fi +cp -R "${APP_PATH}" "${destination}" +echo "Installed RemoteOpsX to ${destination}" diff --git a/scripts/bootstrap-dependencies.sh b/scripts/bootstrap-dependencies.sh new file mode 100644 index 0000000..419989f --- /dev/null +++ b/scripts/bootstrap-dependencies.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +case "$(uname -s)" in + Linux) exec bash "${ROOT_DIR}/packaging/linux/bootstrap-dependencies.sh" "$@" ;; + Darwin) exec bash "${ROOT_DIR}/packaging/macos/bootstrap-dependencies.sh" "$@" ;; + *) echo "RemoteOpsX dependency bootstrap supports Linux and macOS." >&2; exit 1 ;; +esac diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f403640..2177405 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "remoteopsx" version = "0.1.0" -description = "RemoteOpsX - unified Linux remote operations workspace" +description = "RemoteOpsX - unified remote operations workspace" authors = ["RemoteOpsX"] edition = "2021" rust-version = "1.77" @@ -24,7 +24,7 @@ serde_yaml = "0.9" # Bundled SQLite so users need no system libsqlite to run the app. rusqlite = { version = "0.32", features = ["bundled"] } # OS Secret Service (keyring) so secrets never touch SQLite in plaintext. -keyring = { version = "3", features = ["linux-native"] } +keyring = { version = "3", features = ["linux-native", "apple-native"] } # System-PTY driver for the interactive SSH terminal (system `ssh` binary). portable-pty = "0.8" anyhow = "1" diff --git a/src-tauri/src/vnc_adapter.rs b/src-tauri/src/vnc_adapter.rs index 77df867..5faee5d 100644 --- a/src-tauri/src/vnc_adapter.rs +++ b/src-tauri/src/vnc_adapter.rs @@ -31,6 +31,18 @@ fn vnc_bin() -> Option<&'static str> { /// Launch an external VNC viewer for the given server. pub fn launch(server: &Server, opts: &VncOptions) -> Result<()> { + #[cfg(target_os = "macos")] + { + let target = format!("vnc://{}:{}", server.host, server.vnc_port()); + Command::new("open") + .arg(&target) + .spawn() + .map_err(|e| anyhow!("failed to launch macOS Screen Sharing: {e}"))?; + return Ok(()); + } + + #[cfg(not(target_os = "macos"))] + { let bin = vnc_bin().ok_or_else(|| { anyhow!("No VNC viewer found. Install one (e.g. `pacman -S tigervnc` / `apt install tigervnc-viewer`).") })?; @@ -57,4 +69,5 @@ pub fn launch(server: &Server, opts: &VncOptions) -> Result<()> { cmd.spawn() .map_err(|e| anyhow!("failed to launch {bin}: {e}"))?; Ok(()) + } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 1f6bd73..823d59d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -35,10 +35,10 @@ }, "bundle": { "active": true, - "targets": ["appimage", "deb", "rpm"], + "targets": "all", "category": "DeveloperTool", "shortDescription": "Unified Linux remote operations workspace", - "longDescription": "RemoteOpsX is a Linux-first remote operations desktop app: SSH/SFTP/RDP/VNC access, live agentless server health monitoring, systemd diagnostics, logs, tunnels and executable runbooks.", + "longDescription": "RemoteOpsX is a Linux and macOS remote operations desktop app: SSH/SFTP/RDP/VNC access, live agentless server health monitoring, systemd diagnostics, logs, tunnels and executable runbooks.", "icon": [ "icons/32x32.png", "icons/128x128.png",