mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-27 21:05:34 +00:00
feat(windows): Phase-1 native install + scheduled-task service (#438)
Closes Phase-1 of #298 (Native Windows Support RFC). Reverses the long-standing "native Windows is not supported" stance. PowerShell installer at `deploy/windows/install.ps1` (published to https://open-jarvis.github.io/OpenJarvis/install.ps1) plus a `jarvis-service.ps1` scheduled-task helper that mirrors `deploy/systemd/openjarvis.service` and `deploy/launchd/com.openjarvis.plist`. Loopback default (127.0.0.1, no API key) — same as launchd. One-liner install: irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex Adversarial review caught and fixed two real bugs pre-commit: 1. `irm | iex` drops `param()` flags — added env-var fallbacks (OPENJARVIS_SKIP_SERVICE / OPENJARVIS_SERVICE / OPENJARVIS_FORCE). 2. Scheduled tasks don't inherit the registering session's env — the LAN-exposed `OPENJARVIS_API_KEY` path now persists the key to User scope so the task's logon environment can read it. Supersedes #434 (the guidance-only "use WSL2" install.ps1). Massive thanks to @SeCuReDmE-main-dev for the careful RFC #298 — the three-phase decomposition (install / service / shared-memory bridge) is exactly the right framing. This PR ships Phase-1 and Phase-2 of the RFC fused into one release; Phase-3 (shared memory bridge) remains future work. Thanks also to @KadenBordeaux for raising #334 ("'bash' is not recognized as the name of a cmdlet"). That report is what made this whole Windows-support cluster a priority — without your bug report the unsupported stance would still be in the README. The friction you hit motivated #432 (numpy/python cap), #433 (CLI startup resilience), #436 (python discovery helpers), #437 (desktop launcher fix), and this PR. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
b6a8280d68
commit
a79cd6f5e9
@@ -39,13 +39,18 @@ curl -fsSL https://open-jarvis.github.io/OpenJarvis/install.sh | bash
|
||||
|
||||
The installer handles everything for you — including [uv](https://docs.astral.sh/uv/), the Python venv, Ollama, and a small starter model. You don't need to install anything first.
|
||||
|
||||
**Windows:** the installer is a `bash` script and won't run in PowerShell or `cmd`. Pick one of:
|
||||
**Windows:** the bash installer won't run in PowerShell or `cmd`. Pick one of:
|
||||
|
||||
- **WSL2 (recommended for the CLI / Python SDK)** — one-time setup in an admin PowerShell, then run the same `curl … | bash` inside Ubuntu:
|
||||
```powershell
|
||||
wsl --install -d Ubuntu-24.04
|
||||
```
|
||||
Open the Ubuntu shell that gets installed, then follow [WSL2 install instructions](https://open-jarvis.github.io/OpenJarvis/getting-started/wsl2/).
|
||||
- **Native PowerShell (advanced)** — Phase-1 of the [native Windows RFC (#298)](https://github.com/open-jarvis/OpenJarvis/issues/298). Same `uv sync --extra server`, no WSL2 / no Docker:
|
||||
```powershell
|
||||
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
|
||||
```
|
||||
See [native Windows install guide](https://open-jarvis.github.io/OpenJarvis/getting-started/windows-native/).
|
||||
- **Desktop app** — download the [Windows installer (`.exe`)](https://github.com/open-jarvis/OpenJarvis/releases/download/desktop-v1.0.2/OpenJarvis_1.0.1_x64-setup.exe) from the latest [desktop release](https://github.com/open-jarvis/OpenJarvis/releases/tag/desktop-v1.0.2) (macOS `.dmg` and Linux `.deb`/`.rpm`/`.AppImage` are there too) for the GUI experience, no terminal required. **Prerequisite:** the desktop app expects [uv](https://docs.astral.sh/uv/) to be installed already — if it isn't, install it first in PowerShell, then launch the app:
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
@@ -59,7 +64,7 @@ jarvis
|
||||
|
||||
The Rust extension and bigger models continue downloading in the background while you chat. Run `jarvis doctor` to see status.
|
||||
|
||||
**Platforms:** macOS (Intel + Apple Silicon), Linux, WSL2 on Windows. Native Windows is not supported — use WSL2 or the desktop binary.
|
||||
**Platforms:** macOS (Intel + Apple Silicon), Linux, WSL2 on Windows, and Phase-1 native Windows (PowerShell install + scheduled-task service — see [#298](https://github.com/open-jarvis/OpenJarvis/issues/298)).
|
||||
|
||||
**Manual install / contributors:** see [docs/getting-started/install.md](docs/getting-started/install.md).
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# OpenJarvis on native Windows
|
||||
|
||||
Phase-1 of the native-Windows-support RFC (#298). Mirrors the Linux
|
||||
(`deploy/systemd/`) and macOS (`deploy/launchd/`) deployments — but for
|
||||
PowerShell, without WSL2 or Docker.
|
||||
|
||||
## One-liner install
|
||||
|
||||
In an elevated-or-regular PowerShell:
|
||||
|
||||
```powershell
|
||||
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
1. Refuses non-Windows hosts and Windows < 10 1809.
|
||||
2. Checks Python 3.10 – 3.13 (3.14 has no numpy wheels yet — see #432).
|
||||
3. Checks `git` on PATH.
|
||||
4. Installs `uv` (https://astral.sh/uv) if absent.
|
||||
5. Clones the OpenJarvis repository to `%LOCALAPPDATA%\OpenJarvis`
|
||||
(override with `$env:OPENJARVIS_HOME`).
|
||||
6. Runs `uv sync --extra server` so the FastAPI server entry point is
|
||||
importable.
|
||||
7. Optionally prompts to register a scheduled task that auto-starts the
|
||||
server at logon.
|
||||
|
||||
Flags (when invoked directly rather than via `irm | iex`):
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `-Service` | Register the scheduled task without prompting |
|
||||
| `-SkipService` | Don't prompt; don't register |
|
||||
| `-Force` | Re-run all steps even if already done |
|
||||
|
||||
`irm | iex` can't pass `param()` args into a piped script string, so
|
||||
the same knobs are honored via env vars when the corresponding flag is
|
||||
absent:
|
||||
|
||||
```powershell
|
||||
$env:OPENJARVIS_SKIP_SERVICE = '1'
|
||||
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
|
||||
```
|
||||
|
||||
The available env vars: `OPENJARVIS_SKIP_SERVICE`, `OPENJARVIS_SERVICE`,
|
||||
`OPENJARVIS_FORCE`. If you need richer control, save the script first
|
||||
(`irm ... -OutFile install.ps1; .\install.ps1 -Force`).
|
||||
|
||||
## Manual scheduled-task setup
|
||||
|
||||
If you skipped the prompt during install, you can register / inspect /
|
||||
remove the task with `jarvis-service.ps1`:
|
||||
|
||||
```powershell
|
||||
$srv = "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1"
|
||||
|
||||
# install (idempotent — replaces existing)
|
||||
powershell -ExecutionPolicy Bypass -File $srv install
|
||||
|
||||
# status
|
||||
powershell -ExecutionPolicy Bypass -File $srv status
|
||||
|
||||
# remove
|
||||
powershell -ExecutionPolicy Bypass -File $srv uninstall
|
||||
```
|
||||
|
||||
The task runs as the current user with `LogonType=Interactive` and
|
||||
`RunLevel=Limited`. It restarts up to 3 times on failure (1-minute
|
||||
gap), has no execution-time limit, and starts when available (catches
|
||||
up if missed).
|
||||
|
||||
## Loopback vs LAN-exposed
|
||||
|
||||
By default the scheduled task binds `127.0.0.1` — reachable only from
|
||||
this machine, no API key required. This matches launchd parity (see
|
||||
`deploy/launchd/com.openjarvis.plist`).
|
||||
|
||||
To expose on your LAN:
|
||||
|
||||
```powershell
|
||||
# 1. Generate an API key. The server REFUSES to bind 0.0.0.0 without one.
|
||||
$env:OPENJARVIS_API_KEY = (uv run jarvis auth generate-key)
|
||||
|
||||
# 2. Re-register the task with -ListenHost 0.0.0.0.
|
||||
powershell -ExecutionPolicy Bypass -File $srv install -ListenHost 0.0.0.0
|
||||
```
|
||||
|
||||
`jarvis-service.ps1 install` refuses `-ListenHost 0.0.0.0` if
|
||||
`$env:OPENJARVIS_API_KEY` is unset — same guard as the systemd unit's
|
||||
`EnvironmentFile=/etc/openjarvis/env`.
|
||||
|
||||
## Parity table
|
||||
|
||||
| Concern | systemd | launchd | Windows |
|
||||
|---------|---------|---------|---------|
|
||||
| Service definition | `deploy/systemd/openjarvis.service` | `deploy/launchd/com.openjarvis.plist` | `deploy/windows/jarvis-service.ps1` (cmdlet-driven) |
|
||||
| Default bind | `0.0.0.0` (with API key) | `127.0.0.1` (no API key) | `127.0.0.1` (no API key) |
|
||||
| Restart on failure | `Restart=on-failure RestartSec=5` | `KeepAlive=true` | `RestartCount=3 RestartInterval=PT1M` |
|
||||
| Auto-start | `multi-user.target` | `RunAtLoad=true` | `AtLogOn` trigger |
|
||||
|
||||
## Updating
|
||||
|
||||
To pull the latest:
|
||||
|
||||
```powershell
|
||||
cd "$env:LOCALAPPDATA\OpenJarvis\src"
|
||||
git pull --ff-only
|
||||
uv sync --extra server
|
||||
```
|
||||
|
||||
Or re-run the installer with `-Force`:
|
||||
|
||||
```powershell
|
||||
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
|
||||
# (then re-run with the file directly, passing -Force)
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1" uninstall
|
||||
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\OpenJarvis"
|
||||
```
|
||||
|
||||
Uninstalling does NOT remove `uv` (it's a separate tool — you may have
|
||||
other Python projects using it).
|
||||
@@ -0,0 +1,299 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
OpenJarvis native Windows installer.
|
||||
|
||||
.DESCRIPTION
|
||||
Phase-1 of the native-Windows-support RFC (#298). Mirrors the
|
||||
behavior of scripts/install/install.sh (the curl-pipe-bash installer
|
||||
for Linux/WSL2/macOS) but for native Windows PowerShell — no WSL,
|
||||
no Docker, no MSYS2.
|
||||
|
||||
Steps:
|
||||
1. Refuse non-Windows / Windows < 10.
|
||||
2. Check Python 3.10 — 3.13 on PATH (3.14 has no numpy wheels yet,
|
||||
see #432).
|
||||
3. Check git on PATH.
|
||||
4. Install uv (https://astral.sh/uv) if absent.
|
||||
5. Clone the OpenJarvis repository to $env:LOCALAPPDATA\OpenJarvis
|
||||
(override with $env:OPENJARVIS_HOME).
|
||||
6. Run `uv sync --extra server` so the FastAPI server entry point
|
||||
is importable.
|
||||
7. Optionally register the scheduled-task service (see
|
||||
deploy/windows/jarvis-service.ps1).
|
||||
|
||||
Usage (one-liner):
|
||||
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
|
||||
|
||||
Usage (file invocation, supports flags):
|
||||
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 -OutFile install.ps1
|
||||
.\install.ps1 -SkipService
|
||||
|
||||
Flags (when running the file directly):
|
||||
-SkipService Don't prompt for / install the scheduled task.
|
||||
-Service Install the scheduled task without prompting.
|
||||
-Force Re-run all steps even if already done.
|
||||
|
||||
Under `irm | iex` the param block is unreachable (Invoke-Expression
|
||||
can't pass named args into a piped script string), so the same knobs
|
||||
are honored via env vars when the corresponding flag is absent:
|
||||
$env:OPENJARVIS_SKIP_SERVICE = '1'
|
||||
$env:OPENJARVIS_SERVICE = '1'
|
||||
$env:OPENJARVIS_FORCE = '1'
|
||||
|
||||
.NOTES
|
||||
Loopback default: the scheduled-task service binds 127.0.0.1, so no
|
||||
API key is needed. To expose on the LAN, edit the registered task to
|
||||
pass `--host 0.0.0.0` AND set $env:OPENJARVIS_API_KEY (an
|
||||
unauthenticated 0.0.0.0 server refuses to start). See
|
||||
deploy/windows/README.md.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch] $SkipService,
|
||||
[switch] $Service,
|
||||
[switch] $Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Env-var fallback for the `irm | iex` path, where the param block is
|
||||
# unreachable (see header comment). Any explicit -switch wins; env vars
|
||||
# only fill in the gaps.
|
||||
if (-not $SkipService -and $env:OPENJARVIS_SKIP_SERVICE) { $SkipService = $true }
|
||||
if (-not $Service -and $env:OPENJARVIS_SERVICE) { $Service = $true }
|
||||
if (-not $Force -and $env:OPENJARVIS_FORCE) { $Force = $true }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Output helpers — coloured but plain enough for Constrained Language Mode.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Write-Info ($msg) { Write-Host "[info] $msg" -ForegroundColor Cyan }
|
||||
function Write-Ok ($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
|
||||
function Write-Warn2 ($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
|
||||
function Write-Fail ($msg) {
|
||||
Write-Host "[fail] $msg" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. OS check
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Info "Checking OS..."
|
||||
if ($PSVersionTable.Platform -and $PSVersionTable.Platform -ne 'Win32NT') {
|
||||
Write-Fail "install.ps1 is for native Windows. On Linux/macOS use install.sh."
|
||||
}
|
||||
|
||||
# Build number 17763 = Windows 10 1809 (the oldest LTS we test against).
|
||||
$build = [System.Environment]::OSVersion.Version.Build
|
||||
if ($build -lt 17763) {
|
||||
Write-Fail "Windows 10 1809 (build 17763) or newer is required. Detected build $build."
|
||||
}
|
||||
Write-Ok "Windows build $build"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Python check
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-PythonCommand {
|
||||
# Prefer `python3` (matches our cross-platform helper convention),
|
||||
# fall back to `python` (the Windows store / python.org default).
|
||||
foreach ($name in @('python3', 'python')) {
|
||||
$cmd = Get-Command $name -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return $cmd.Source }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
Write-Info "Checking Python (3.10 - 3.13)..."
|
||||
$pythonExe = Get-PythonCommand
|
||||
if (-not $pythonExe) {
|
||||
Write-Fail @"
|
||||
Python 3.10 - 3.13 not found on PATH.
|
||||
|
||||
Install from https://python.org (check 'Add python.exe to PATH' during
|
||||
install) or via winget:
|
||||
|
||||
winget install Python.Python.3.13
|
||||
|
||||
Then re-run this installer.
|
||||
"@
|
||||
}
|
||||
|
||||
$verRaw = & $pythonExe --version 2>&1
|
||||
$verMatch = [regex]::Match($verRaw, '(\d+)\.(\d+)\.(\d+)')
|
||||
if (-not $verMatch.Success) {
|
||||
Write-Fail "Could not parse Python version from: $verRaw"
|
||||
}
|
||||
$pyMajor = [int]$verMatch.Groups[1].Value
|
||||
$pyMinor = [int]$verMatch.Groups[2].Value
|
||||
if ($pyMajor -ne 3 -or $pyMinor -lt 10 -or $pyMinor -gt 13) {
|
||||
Write-Fail @"
|
||||
Found Python $pyMajor.$pyMinor at $pythonExe, but OpenJarvis requires
|
||||
3.10 - 3.13. Python 3.14 has no numpy Windows wheels yet (#432, will
|
||||
re-open once numpy ships cp314).
|
||||
"@
|
||||
}
|
||||
Write-Ok "Python $pyMajor.$pyMinor ($pythonExe)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. git check
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Info "Checking git..."
|
||||
$gitExe = (Get-Command git -ErrorAction SilentlyContinue).Source
|
||||
if (-not $gitExe) {
|
||||
Write-Fail @"
|
||||
git not found on PATH.
|
||||
|
||||
Install via winget:
|
||||
|
||||
winget install Git.Git
|
||||
|
||||
or download from https://git-scm.com, then re-run this installer.
|
||||
"@
|
||||
}
|
||||
Write-Ok "git ($gitExe)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. uv check / install
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Info "Checking uv..."
|
||||
$uvExe = (Get-Command uv -ErrorAction SilentlyContinue).Source
|
||||
if (-not $uvExe) {
|
||||
Write-Info "Installing uv via astral.sh/uv (official PowerShell installer)..."
|
||||
try {
|
||||
Invoke-RestMethod -Uri 'https://astral.sh/uv/install.ps1' -UseBasicParsing | Invoke-Expression
|
||||
} catch {
|
||||
Write-Fail "uv install failed: $($_.Exception.Message)"
|
||||
}
|
||||
# The astral installer puts uv at %USERPROFILE%\.local\bin\uv.exe and
|
||||
# adds that dir to the User PATH. The current process's PATH isn't
|
||||
# refreshed automatically — prepend the install dir so the rest of
|
||||
# this script picks it up.
|
||||
$uvDir = Join-Path $env:USERPROFILE '.local\bin'
|
||||
if (Test-Path (Join-Path $uvDir 'uv.exe')) {
|
||||
$env:Path = "$uvDir;$env:Path"
|
||||
}
|
||||
$uvExe = (Get-Command uv -ErrorAction SilentlyContinue).Source
|
||||
if (-not $uvExe) {
|
||||
Write-Fail "uv installed but isn't on PATH. Re-open a fresh PowerShell and re-run."
|
||||
}
|
||||
}
|
||||
Write-Ok "uv ($uvExe)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Clone the repo
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
$installRoot = if ($env:OPENJARVIS_HOME) {
|
||||
$env:OPENJARVIS_HOME
|
||||
} else {
|
||||
Join-Path $env:LOCALAPPDATA 'OpenJarvis'
|
||||
}
|
||||
$srcDir = Join-Path $installRoot 'src'
|
||||
|
||||
Write-Info "Install root: $installRoot"
|
||||
|
||||
if (-not (Test-Path $installRoot)) {
|
||||
New-Item -ItemType Directory -Path $installRoot | Out-Null
|
||||
}
|
||||
|
||||
$repoUrl = if ($env:OPENJARVIS_REPO_URL) {
|
||||
$env:OPENJARVIS_REPO_URL
|
||||
} else {
|
||||
'https://github.com/open-jarvis/OpenJarvis.git'
|
||||
}
|
||||
|
||||
if (Test-Path (Join-Path $srcDir '.git')) {
|
||||
if ($Force) {
|
||||
Write-Info "Force: pulling latest from $repoUrl..."
|
||||
& $gitExe -C $srcDir pull --ff-only
|
||||
if ($LASTEXITCODE -ne 0) { Write-Fail "git pull failed" }
|
||||
} else {
|
||||
Write-Ok "Repository already cloned (use -Force to update)"
|
||||
}
|
||||
} else {
|
||||
Write-Info "Cloning $repoUrl..."
|
||||
& $gitExe clone --depth 1 $repoUrl $srcDir
|
||||
if ($LASTEXITCODE -ne 0) { Write-Fail "git clone failed" }
|
||||
Write-Ok "Cloned to $srcDir"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. uv sync --extra server
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Info "Running 'uv sync --extra server' in $srcDir (this can take a few minutes)..."
|
||||
Push-Location $srcDir
|
||||
try {
|
||||
& $uvExe sync --extra server
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Fail "uv sync failed with exit code $LASTEXITCODE. Check the output above."
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
Write-Ok "Dependencies installed"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. Optional: register the scheduled-task service
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
$serviceScript = Join-Path $srcDir 'deploy\windows\jarvis-service.ps1'
|
||||
$shouldInstallService = $false
|
||||
|
||||
if ($Service) {
|
||||
$shouldInstallService = $true
|
||||
} elseif ($SkipService) {
|
||||
$shouldInstallService = $false
|
||||
} else {
|
||||
# Interactive prompt only when there's a real user at the keyboard
|
||||
# AND stdin isn't piped. [Environment]::UserInteractive is the
|
||||
# canonical PowerShell idiom for "is this a user session" (false for
|
||||
# services, scheduled tasks, etc); we additionally guard against the
|
||||
# `irm | iex` case where stdin is redirected.
|
||||
$isInteractive = [Environment]::UserInteractive `
|
||||
-and -not [System.Console]::IsInputRedirected
|
||||
if ($isInteractive) {
|
||||
$reply = Read-Host "Register OpenJarvis as a Windows scheduled task (auto-start at logon, loopback only)? [y/N]"
|
||||
$shouldInstallService = ($reply -match '^[yY]')
|
||||
} else {
|
||||
Write-Warn2 "Non-interactive install — skipping scheduled-task setup."
|
||||
Write-Warn2 "To register the service later, run:"
|
||||
Write-Warn2 " powershell -ExecutionPolicy Bypass -File `"$serviceScript`" install"
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldInstallService) {
|
||||
if (-not (Test-Path $serviceScript)) {
|
||||
Write-Fail "Service script not found at $serviceScript (the clone may be missing files; try -Force)."
|
||||
}
|
||||
Write-Info "Installing scheduled task..."
|
||||
& powershell -ExecutionPolicy Bypass -File $serviceScript install -InstallRoot $installRoot
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Fail "Scheduled task setup failed."
|
||||
}
|
||||
Write-Ok "Scheduled task 'OpenJarvis' registered (loopback default)."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 8. Final message
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " ┌──────────────────────────────────┐" -ForegroundColor Green
|
||||
Write-Host " │ OpenJarvis install complete │" -ForegroundColor Green
|
||||
Write-Host " └──────────────────────────────────┘" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host " Repo: $srcDir"
|
||||
Write-Host " Run it: cd `"$srcDir`"; uv run jarvis serve"
|
||||
if ($shouldInstallService) {
|
||||
Write-Host " Service: schtasks /Query /TN OpenJarvis (status)"
|
||||
Write-Host " powershell -File `"$serviceScript`" uninstall (remove)"
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host " Docs: https://open-jarvis.github.io/OpenJarvis/"
|
||||
Write-Host ""
|
||||
@@ -0,0 +1,208 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Register / unregister the OpenJarvis Windows scheduled task.
|
||||
|
||||
.DESCRIPTION
|
||||
The Windows equivalent of deploy/systemd/openjarvis.service and
|
||||
deploy/launchd/com.openjarvis.plist.
|
||||
|
||||
Registers a per-user scheduled task named "OpenJarvis" that starts
|
||||
`jarvis serve` at logon and restarts on failure. Loopback default
|
||||
(127.0.0.1) so no API key is required — matches launchd parity.
|
||||
|
||||
Subcommands:
|
||||
install — create or replace the task
|
||||
uninstall — remove the task
|
||||
status — show task state
|
||||
|
||||
Arguments (install only):
|
||||
-InstallRoot <path> default: %LOCALAPPDATA%\OpenJarvis (matches
|
||||
install.ps1's default)
|
||||
-ListenHost <addr> default: 127.0.0.1 (loopback). Set to 0.0.0.0
|
||||
ONLY if you also set $env:OPENJARVIS_API_KEY
|
||||
— the server refuses to start unauthenticated
|
||||
on a non-loopback bind.
|
||||
-ListenPort <int> default: 8000
|
||||
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 install
|
||||
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 uninstall
|
||||
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 status
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Position = 0)]
|
||||
[ValidateSet('install', 'uninstall', 'status')]
|
||||
[string] $Command = 'status',
|
||||
|
||||
[string] $InstallRoot,
|
||||
[string] $ListenHost = '127.0.0.1',
|
||||
[int] $ListenPort = 8000
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$TaskName = 'OpenJarvis'
|
||||
|
||||
function Write-Info ($msg) { Write-Host "[info] $msg" -ForegroundColor Cyan }
|
||||
function Write-Ok ($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
|
||||
function Write-Warn2 ($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
|
||||
function Write-Fail ($msg) {
|
||||
Write-Host "[fail] $msg" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Get-DefaultInstallRoot {
|
||||
# Use $script: prefix so this is robust to being called from any
|
||||
# function scope (PowerShell's default dynamic lookup would also
|
||||
# work today, but $script: is the explicit contract).
|
||||
if ($script:InstallRoot) { return $script:InstallRoot }
|
||||
if ($env:OPENJARVIS_HOME) { return $env:OPENJARVIS_HOME }
|
||||
return (Join-Path $env:LOCALAPPDATA 'OpenJarvis')
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# install
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Install-Task {
|
||||
$root = Get-DefaultInstallRoot
|
||||
$srcDir = Join-Path $root 'src'
|
||||
if (-not (Test-Path $srcDir)) {
|
||||
Write-Fail "OpenJarvis source not found at $srcDir. Run install.ps1 first."
|
||||
}
|
||||
|
||||
$uvCmd = Get-Command uv -ErrorAction SilentlyContinue
|
||||
if (-not $uvCmd) {
|
||||
$uvFallback = Join-Path $env:USERPROFILE '.local\bin\uv.exe'
|
||||
if (Test-Path $uvFallback) {
|
||||
$uvPath = $uvFallback
|
||||
} else {
|
||||
Write-Fail "uv.exe not found on PATH or at $uvFallback. Re-run install.ps1."
|
||||
}
|
||||
} else {
|
||||
$uvPath = $uvCmd.Source
|
||||
}
|
||||
|
||||
# Safety: refuse to register a non-loopback bind without an API key.
|
||||
# Mirrors deploy/systemd/openjarvis.service's EnvironmentFile guard.
|
||||
$isLoopback = ($ListenHost -eq '127.0.0.1' -or $ListenHost -eq 'localhost')
|
||||
if (-not $isLoopback -and -not $env:OPENJARVIS_API_KEY) {
|
||||
Write-Fail @"
|
||||
ListenHost is $ListenHost (non-loopback) but `$env:OPENJARVIS_API_KEY is
|
||||
not set. An unauthenticated non-loopback bind is refused by jarvis serve
|
||||
and would also create a security hole. Set the env var first:
|
||||
|
||||
`$env:OPENJARVIS_API_KEY = (uv run jarvis auth generate-key)
|
||||
|
||||
then re-run with -ListenHost 0.0.0.0.
|
||||
"@
|
||||
}
|
||||
|
||||
# CRITICAL: scheduled tasks do NOT inherit the registering session's
|
||||
# environment. If we registered the task now and stopped here, the
|
||||
# task would launch at logon with a clean env, find no API key, and
|
||||
# `jarvis serve` would refuse to bind 0.0.0.0 — failing silently every
|
||||
# logon. Persist the key to the User env scope so the task's logon
|
||||
# session picks it up. (Loopback path doesn't need the key, so this
|
||||
# only runs for the explicit LAN-exposed case.)
|
||||
if (-not $isLoopback) {
|
||||
Write-Info "Persisting OPENJARVIS_API_KEY to User environment so the scheduled task can read it at logon."
|
||||
[System.Environment]::SetEnvironmentVariable(
|
||||
'OPENJARVIS_API_KEY',
|
||||
$env:OPENJARVIS_API_KEY,
|
||||
'User'
|
||||
)
|
||||
}
|
||||
|
||||
Write-Info "Registering scheduled task '$TaskName'..."
|
||||
Write-Info " Working dir : $srcDir"
|
||||
Write-Info " Listen : $ListenHost`:$ListenPort"
|
||||
Write-Info " User : $env:USERNAME"
|
||||
|
||||
# If a previous task exists, remove it first (idempotent install).
|
||||
$existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if ($existing) {
|
||||
Write-Info "Existing task found — replacing."
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
$action = New-ScheduledTaskAction `
|
||||
-Execute $uvPath `
|
||||
-Argument "run jarvis serve --host $ListenHost --port $ListenPort" `
|
||||
-WorkingDirectory $srcDir
|
||||
|
||||
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
|
||||
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable `
|
||||
-RestartCount 3 `
|
||||
-RestartInterval (New-TimeSpan -Minutes 1) `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Seconds 0)
|
||||
|
||||
$principal = New-ScheduledTaskPrincipal `
|
||||
-UserId $env:USERNAME `
|
||||
-LogonType Interactive `
|
||||
-RunLevel Limited
|
||||
|
||||
Register-ScheduledTask `
|
||||
-TaskName $TaskName `
|
||||
-Action $action `
|
||||
-Trigger $trigger `
|
||||
-Settings $settings `
|
||||
-Principal $principal `
|
||||
-Description 'OpenJarvis API server (loopback default — see deploy/windows/README.md)' | Out-Null
|
||||
|
||||
Write-Ok "Task '$TaskName' registered."
|
||||
Write-Info "It will start automatically at next logon."
|
||||
Write-Info "To start it now: Start-ScheduledTask -TaskName $TaskName"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# uninstall
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Uninstall-Task {
|
||||
$existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $existing) {
|
||||
Write-Warn2 "Task '$TaskName' is not registered — nothing to remove."
|
||||
return
|
||||
}
|
||||
Write-Info "Stopping '$TaskName' (if running)..."
|
||||
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
Write-Info "Unregistering '$TaskName'..."
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
Write-Ok "Task '$TaskName' removed."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# status
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Show-Status {
|
||||
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if (-not $task) {
|
||||
Write-Host "Task '$TaskName' is not registered."
|
||||
Write-Host "Install it with:"
|
||||
Write-Host " powershell -ExecutionPolicy Bypass -File `"$PSCommandPath`" install"
|
||||
return
|
||||
}
|
||||
$info = Get-ScheduledTaskInfo -TaskName $TaskName
|
||||
Write-Host "Task : $TaskName"
|
||||
Write-Host "State : $($task.State)"
|
||||
Write-Host "LastRun : $($info.LastRunTime)"
|
||||
Write-Host "LastRes : 0x$('{0:X8}' -f $info.LastTaskResult)"
|
||||
Write-Host "NextRun : $($info.NextRunTime)"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
switch ($Command) {
|
||||
'install' { Install-Task }
|
||||
'uninstall' { Uninstall-Task }
|
||||
'status' { Show-Status }
|
||||
}
|
||||
@@ -1,21 +1,30 @@
|
||||
"""Publish the canonical install.sh into the docs site.
|
||||
"""Publish the canonical install scripts into the docs site.
|
||||
|
||||
Serves the installers at::
|
||||
|
||||
https://open-jarvis.github.io/OpenJarvis/install.sh (Linux / macOS / WSL2)
|
||||
https://open-jarvis.github.io/OpenJarvis/install.ps1 (native Windows)
|
||||
|
||||
Serves the installer at ``https://open-jarvis.github.io/OpenJarvis/install.sh``
|
||||
so users have an HTTPS-valid, project-controlled install URL that does not
|
||||
depend on the externally-hosted ``openjarvis.ai`` domain — whose TLS config
|
||||
broke and which the project does not control (issue #337).
|
||||
|
||||
Single source of truth: the script lives at ``scripts/install/install.sh``
|
||||
(also bundled into the wheel as ``_install_scripts/``). This copies it
|
||||
verbatim into the built site at ``install.sh`` on every ``mkdocs build``,
|
||||
so the published copy can never drift from the canonical one.
|
||||
Single source of truth: the scripts live under ``scripts/install/`` and
|
||||
``deploy/windows/`` (also bundled into the wheel as ``_install_scripts/``).
|
||||
This copies them verbatim into the built site on every ``mkdocs build``,
|
||||
so the published copies can never drift from the canonical ones.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import mkdocs_gen_files
|
||||
|
||||
_SRC = Path("scripts/install/install.sh")
|
||||
# (source path, published URL path)
|
||||
_SCRIPTS = [
|
||||
(Path("scripts/install/install.sh"), "install.sh"),
|
||||
(Path("deploy/windows/install.ps1"), "install.ps1"),
|
||||
]
|
||||
|
||||
with mkdocs_gen_files.open("install.sh", "wb") as dst:
|
||||
dst.write(_SRC.read_bytes())
|
||||
for src, dest in _SCRIPTS:
|
||||
with mkdocs_gen_files.open(dest, "wb") as out:
|
||||
out.write(src.read_bytes())
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Native Windows (advanced)
|
||||
|
||||
Phase-1 of the native-Windows-support RFC (#298). Mirrors the Linux
|
||||
(systemd) and macOS (launchd) deployments — but for PowerShell, without
|
||||
WSL2 or Docker. Choose this over [WSL2](wsl2.md) only if you want to
|
||||
avoid a Linux VM; WSL2 remains the smoother experience for most users.
|
||||
|
||||
## What you get
|
||||
|
||||
- A PowerShell installer that probes prerequisites, installs `uv`,
|
||||
clones the repo, and runs `uv sync --extra server`.
|
||||
- An optional Windows scheduled-task service equivalent to the systemd
|
||||
unit and launchd plist.
|
||||
- Loopback default — the service binds `127.0.0.1` so no API key is
|
||||
required.
|
||||
|
||||
## What you need
|
||||
|
||||
- Windows 10 1809+ or Windows 11.
|
||||
- Python 3.10 – 3.13 (Python 3.14 has no numpy Windows wheels yet —
|
||||
see [#432](https://github.com/open-jarvis/OpenJarvis/issues/432)).
|
||||
- `git` on PATH.
|
||||
- ~5 GB free disk on `%LOCALAPPDATA%`.
|
||||
|
||||
## Install
|
||||
|
||||
In any PowerShell:
|
||||
|
||||
```powershell
|
||||
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
|
||||
```
|
||||
|
||||
The installer will:
|
||||
|
||||
1. Refuse non-Windows hosts and old Windows builds.
|
||||
2. Confirm Python 3.10 – 3.13.
|
||||
3. Confirm `git`.
|
||||
4. Install `uv` if absent (via the official `astral.sh/uv` PowerShell
|
||||
installer).
|
||||
5. Clone the repo to `%LOCALAPPDATA%\OpenJarvis\src`.
|
||||
6. Run `uv sync --extra server`.
|
||||
7. Prompt to register the scheduled-task service (skip with
|
||||
`-SkipService`).
|
||||
|
||||
## Run it
|
||||
|
||||
```powershell
|
||||
cd "$env:LOCALAPPDATA\OpenJarvis\src"
|
||||
uv run jarvis serve
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:8000/health` to verify.
|
||||
|
||||
## Scheduled-task service
|
||||
|
||||
If you skipped the prompt during install, register the auto-start task
|
||||
manually:
|
||||
|
||||
```powershell
|
||||
$srv = "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1"
|
||||
powershell -ExecutionPolicy Bypass -File $srv install
|
||||
```
|
||||
|
||||
State:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File $srv status
|
||||
```
|
||||
|
||||
Remove:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File $srv uninstall
|
||||
```
|
||||
|
||||
See [`deploy/windows/README.md`](https://github.com/open-jarvis/OpenJarvis/blob/main/deploy/windows/README.md)
|
||||
for the LAN-exposed configuration and the parity table against
|
||||
systemd / launchd.
|
||||
|
||||
## See also
|
||||
|
||||
- [WSL2 install](wsl2.md) — the recommended Windows path.
|
||||
- [Full installer reference](install.md).
|
||||
@@ -1,6 +1,9 @@
|
||||
# WSL2 Install
|
||||
|
||||
OpenJarvis runs in WSL2 on Windows. Native Windows is not supported.
|
||||
OpenJarvis on Windows installs two ways: **WSL2** (this page — the
|
||||
recommended path; identical to native Linux) or **[native Windows
|
||||
(advanced)](windows-native.md)** (Phase-1; PowerShell installer, no
|
||||
WSL2 / no Docker). Pick WSL2 for the smoothest experience.
|
||||
|
||||
## One-time WSL setup
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ packages = ["src/openjarvis"]
|
||||
"src/openjarvis/agents/claude_code_runner" = "_node_modules/claude_code_runner"
|
||||
"src/openjarvis/channels/whatsapp_baileys_bridge" = "_node_modules/whatsapp_baileys_bridge"
|
||||
"scripts/install" = "_install_scripts"
|
||||
"deploy/windows" = "_deploy/windows"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
|
||||
Reference in New Issue
Block a user