Replace stale bin/install.sh with a wrapper over sync.py restore
bin/install.sh still rsynced --delete from a backup path retired in July, so the documented Linux install both failed and could erase a working tree. Every step it claimed to do already lives in bin/sync.py, shared with Windows. Root ./install.sh is now a thin wrapper over `sync.py restore`, so deployment stays one bash command. It also registers ncp/promethean in ~/.bashrc, which was the only thing the old script uniquely did. Split restore-linux.sh into runtime (Ollama binary, shell aliases) and desktop (XFCE panel, theme, os-release). Only desktop writes outside the repo, it now auto-skips off XFCE, and `--no-desktop` skips it explicitly. Two tests keep the stage names in sync and the $HOME writes confined to the desktop stage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -72,8 +72,9 @@ does all inference over HTTP).
|
|||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Install deps into the Promethean venv (auto-selects AMD/NVIDIA/CPU)
|
# 1. Build everything: venv (auto-selects AMD/NVIDIA/CPU), web UI, memory DB,
|
||||||
./bin/install.sh
|
# system packages and the Ollama binary.
|
||||||
|
./install.sh
|
||||||
|
|
||||||
# 2. Launch (memory :8001, backend :8000 — backend also serves the built UI)
|
# 2. Launch (memory :8001, backend :8000 — backend also serves the built UI)
|
||||||
./launch_nexus.sh
|
./launch_nexus.sh
|
||||||
@@ -82,7 +83,12 @@ does all inference over HTTP).
|
|||||||
Python deps are layered: `requirements-base.txt` (GPU-agnostic core) plus one
|
Python deps are layered: `requirements-base.txt` (GPU-agnostic core) plus one
|
||||||
GPU overlay — `requirements-amd.txt` (ROCm) or `requirements-nvidia.txt` (CUDA).
|
GPU overlay — `requirements-amd.txt` (ROCm) or `requirements-nvidia.txt` (CUDA).
|
||||||
`requirements-wsl.txt` is the standalone CPU-only runtime (no base overlay).
|
`requirements-wsl.txt` is the standalone CPU-only runtime (no base overlay).
|
||||||
`bin/install.sh` picks the right one for the host.
|
`bin/sync.py` picks the right one for the host.
|
||||||
|
|
||||||
|
`./install.sh` is also the update path — re-run it any time to pull the latest
|
||||||
|
and rebuild. `--check` dry-runs it; `--no-desktop` skips the XFCE panel/theme
|
||||||
|
wiring (that stage is auto-skipped off XFCE anyway). It's a thin wrapper over
|
||||||
|
`bin/sync.py restore`, the same code the Windows box runs.
|
||||||
|
|
||||||
### Individual services
|
### Individual services
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ cd "$(dirname "$0")/.."
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
if [ ! -x Promethean/bin/python ]; then
|
if [ ! -x Promethean/bin/python ]; then
|
||||||
echo "!! no Promethean venv - run bin/install.sh first" >&2
|
echo "!! no Promethean venv - run ./install.sh first" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
-168
@@ -1,168 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# NexusOS installer — Linux side.
|
|
||||||
# Syncs the repo, builds the Python venv, installs frontend deps, registers ncp,
|
|
||||||
# and installs the Promethean Terminal + panel.
|
|
||||||
#
|
|
||||||
# ./install.sh Linux install (default)
|
|
||||||
# ./install.sh -w | --windows Hand off to the Windows installer (install-windows.ps1)
|
|
||||||
|
|
||||||
NEXUS_ROOT="$HOME/nexus-core"
|
|
||||||
ROUTER_BACKUP="router:/tmp/mnt/Wingdrive2/nexus-core/"
|
|
||||||
|
|
||||||
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<EOF
|
|
||||||
Usage: install.sh [-w|--windows] [-h|--help]
|
|
||||||
|
|
||||||
(no flags) Run the Linux install.
|
|
||||||
-w, --windows Print how to run the native-Windows installer
|
|
||||||
(install-windows.ps1 — winget-based, no WSL).
|
|
||||||
-h, --help Show this help.
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
detect_requirements() {
|
|
||||||
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null 2>&1; then
|
|
||||||
echo "requirements-nvidia.txt"
|
|
||||||
elif lspci 2>/dev/null | grep -qi nvidia; then
|
|
||||||
echo "requirements-nvidia.txt"
|
|
||||||
elif grep -qi microsoft /proc/version 2>/dev/null; then
|
|
||||||
echo "requirements-wsl.txt"
|
|
||||||
elif lspci 2>/dev/null | grep -qi amd; then
|
|
||||||
echo "requirements-amd.txt"
|
|
||||||
else
|
|
||||||
echo "requirements-wsl.txt"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
run_windows() {
|
|
||||||
local ps1="$NEXUS_ROOT/install-windows.ps1"
|
|
||||||
if [ ! -f "$ps1" ]; then
|
|
||||||
echo "Error: $ps1 not found." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# install-windows.ps1 is a native-Windows installer: it self-elevates to
|
|
||||||
# Administrator and uses winget (Python/Node/Ollama) directly. No WSL.
|
|
||||||
# It must run from Windows PowerShell, so just point the way.
|
|
||||||
cat <<EOF
|
|
||||||
The Windows installer must be run from Windows, not from this shell.
|
|
||||||
|
|
||||||
1. Open a PowerShell window in the nexus-core folder.
|
|
||||||
2. Run: powershell -ExecutionPolicy Bypass -File .\install-windows.ps1
|
|
||||||
|
|
||||||
It self-elevates, installs Python/Node/Ollama via winget, builds the venv and
|
|
||||||
web UI, and drops a desktop shortcut. NexusOS then runs single-process on :8000.
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# ─── Arg parsing ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
-w|--windows)
|
|
||||||
run_windows
|
|
||||||
exit $?
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
"")
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown option: $1" >&2
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# ─── Step 1: Sync from router ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
echo "Pulling Nexus from router..."
|
|
||||||
mkdir -p "$NEXUS_ROOT"
|
|
||||||
rsync -avz --delete \
|
|
||||||
--exclude='.git/' \
|
|
||||||
--exclude='Promethean/' \
|
|
||||||
--exclude='models/blobs/' \
|
|
||||||
--exclude='ollama/' \
|
|
||||||
--exclude='interface/web/node_modules/' \
|
|
||||||
--exclude='interface/web/dist/' \
|
|
||||||
--exclude='runtime/' \
|
|
||||||
--exclude='__pycache__/' \
|
|
||||||
--exclude='*.pyc' \
|
|
||||||
-e ssh \
|
|
||||||
"$ROUTER_BACKUP" "$NEXUS_ROOT/"
|
|
||||||
|
|
||||||
# ─── Step 2: Python venv ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Creating Python environment..."
|
|
||||||
python3 -m venv "$NEXUS_ROOT/Promethean"
|
|
||||||
|
|
||||||
# ─── Step 3: pip install ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Installing Python dependencies..."
|
|
||||||
req=$(detect_requirements)
|
|
||||||
echo "Detected: $req"
|
|
||||||
if [ -f "$NEXUS_ROOT/$req" ]; then
|
|
||||||
"$NEXUS_ROOT/Promethean/bin/pip" install --upgrade pip -q
|
|
||||||
"$NEXUS_ROOT/Promethean/bin/pip" install -r "$NEXUS_ROOT/$req"
|
|
||||||
else
|
|
||||||
echo "Warning: $req not found — skipping pip install."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ─── Step 4: npm install ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Installing frontend dependencies..."
|
|
||||||
export NVM_DIR="$HOME/.nvm"
|
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
||||||
if command -v npm &>/dev/null; then
|
|
||||||
cd "$NEXUS_ROOT/interface/web" && npm install
|
|
||||||
else
|
|
||||||
echo "npm not found — install nvm/node then run 'cd $NEXUS_ROOT/interface/web && npm install'."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ─── Step 5: Register ncp in ~/.bashrc ───────────────────────────────────────
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Registering ncp..."
|
|
||||||
if ! grep -q "nexus-core/management/nexus-cli.sh" "$HOME/.bashrc"; then
|
|
||||||
cat >> "$HOME/.bashrc" << 'EOF'
|
|
||||||
|
|
||||||
# Nexus
|
|
||||||
ncp() {
|
|
||||||
~/nexus-core/management/nexus-cli.sh "$@"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
echo "ncp registered in ~/.bashrc."
|
|
||||||
else
|
|
||||||
echo "ncp already in ~/.bashrc — skipping."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -qF "alias promethean='source ~/nexus-core/.promethean_bashrc'" "$HOME/.bashrc"; then
|
|
||||||
printf '\n# Promethean\nalias promethean='"'"'source ~/nexus-core/.promethean_bashrc'"'"'\n' >> "$HOME/.bashrc"
|
|
||||||
echo "promethean registered in ~/.bashrc."
|
|
||||||
else
|
|
||||||
echo "promethean already in ~/.bashrc — skipping."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ─── Step 6: Promethean Terminal + panel ─────────────────────────────────────
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Installing Promethean Terminal..."
|
|
||||||
bash "$NEXUS_ROOT/bin/promethean/install.sh" || \
|
|
||||||
echo "Warning: Promethean Terminal install failed — run bin/promethean/install.sh manually."
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Installing NexusOS panel applet..."
|
|
||||||
bash "$NEXUS_ROOT/bin/panel/install.sh" || \
|
|
||||||
echo "Warning: panel install failed — run bin/panel/install.sh manually."
|
|
||||||
|
|
||||||
# ─── Done ─────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Installation complete. Nexus is ready."
|
|
||||||
echo "Run 'ncp start' to launch Nexus."
|
|
||||||
+63
-18
@@ -3,17 +3,29 @@
|
|||||||
# The Linux-only half of a restore: system packages, the bundled Ollama binary,
|
# The Linux-only half of a restore: system packages, the bundled Ollama binary,
|
||||||
# and the XFCE desktop wiring (panel, wallpaper, theme, terminal, branding).
|
# and the XFCE desktop wiring (panel, wallpaper, theme, terminal, branding).
|
||||||
# None of it means anything on Windows, which is why it lives here instead of in
|
# None of it means anything on Windows, which is why it lives here instead of in
|
||||||
# bin/sync.py — sync.py owns the portable half and calls this in two stages:
|
# bin/sync.py — sync.py owns the portable half and calls this in three stages:
|
||||||
#
|
#
|
||||||
# restore-linux.sh prep Before the rebuild: system packages the build needs.
|
# restore-linux.sh prep Before the rebuild: system packages the build needs.
|
||||||
# restore-linux.sh desktop After the rebuild: Ollama binary + desktop wiring.
|
# restore-linux.sh runtime After: Ollama binary + shell wiring. Any Linux box
|
||||||
|
# needs these to actually run NexusOS.
|
||||||
|
# restore-linux.sh desktop After: the XFCE look (panel, wallpaper, theme,
|
||||||
|
# branding). Cosmetic, this box's setup, and the only
|
||||||
|
# stage that rewrites files outside the repo and $HOME.
|
||||||
|
# Skippable with `--no-desktop`; auto-skipped off XFCE.
|
||||||
#
|
#
|
||||||
# Not meant to be run by hand — use `ncp restore` (python bin/sync.py restore).
|
# Not meant to be run by hand — use `ncp restore` (python bin/sync.py restore).
|
||||||
|
|
||||||
# Set by bin/sync.py to the repo it was invoked from, so a clone in a scratch dir
|
# Set by bin/sync.py to the repo it was invoked from, so a clone in a scratch dir
|
||||||
# operates on itself instead of reaching into the real ~/nexus-core.
|
# operates on itself instead of reaching into the real ~/nexus-core.
|
||||||
NEXUS_ROOT="${NEXUS_ROOT:-$HOME/nexus-core}"
|
NEXUS_ROOT="${NEXUS_ROOT:-$HOME/nexus-core}"
|
||||||
stage="${1:?usage: restore-linux.sh prep|desktop}"
|
stage="${1:?usage: restore-linux.sh prep|runtime|desktop}"
|
||||||
|
# sync.py calls these by name with check=False, so a stage renamed on one side
|
||||||
|
# and not the other would silently do nothing. Fail loudly instead; a test keeps
|
||||||
|
# this list and sync.py's call sites in agreement.
|
||||||
|
case "$stage" in
|
||||||
|
prep|runtime|desktop) ;;
|
||||||
|
*) echo "unknown stage: $stage (expected prep|runtime|desktop)" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
cd "$NEXUS_ROOT" || { echo "No $NEXUS_ROOT"; exit 1; }
|
cd "$NEXUS_ROOT" || { echo "No $NEXUS_ROOT"; exit 1; }
|
||||||
|
|
||||||
if [ "$stage" = "prep" ]; then
|
if [ "$stage" = "prep" ]; then
|
||||||
@@ -29,14 +41,55 @@ if [ "$stage" = "prep" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
if [ "$stage" = "runtime" ]; then
|
||||||
echo "Ensuring Ollama binary..."
|
echo ""
|
||||||
# ollama/ is gitignored, so a fresh clone has no binary — fetch it. No-op if the
|
echo "Ensuring Ollama binary..."
|
||||||
# machine already has one (e.g. an in-place restore).
|
# ollama/ is gitignored, so a fresh clone has no binary — fetch it. No-op if the
|
||||||
if [ -x "$NEXUS_ROOT/bin/fetch-ollama.sh" ]; then
|
# machine already has one (e.g. an in-place restore).
|
||||||
|
if [ -x "$NEXUS_ROOT/bin/fetch-ollama.sh" ]; then
|
||||||
"$NEXUS_ROOT/bin/fetch-ollama.sh" "$NEXUS_ROOT" || echo "Warning: Ollama fetch failed — install it manually."
|
"$NEXUS_ROOT/bin/fetch-ollama.sh" "$NEXUS_ROOT" || echo "Warning: Ollama fetch failed — install it manually."
|
||||||
else
|
else
|
||||||
echo "Warning: bin/fetch-ollama.sh not found — skipping Ollama fetch."
|
echo "Warning: bin/fetch-ollama.sh not found — skipping Ollama fetch."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Shell wiring: the `ncp` function and the Promethean venv alias. This is the
|
||||||
|
# only place they get registered now that bin/install.sh is gone. Guarded by a
|
||||||
|
# grep so an in-place restore is a no-op and a scratch clone can't re-point an
|
||||||
|
# already-wired ~/.bashrc at itself.
|
||||||
|
if ! grep -q "management/nexus-cli.sh" "$HOME/.bashrc" 2>/dev/null; then
|
||||||
|
cat >> "$HOME/.bashrc" <<EOF
|
||||||
|
|
||||||
|
# Nexus
|
||||||
|
ncp() {
|
||||||
|
$NEXUS_ROOT/management/nexus-cli.sh "\$@"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
echo "Registered ncp in ~/.bashrc."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -qF "alias promethean=" "$HOME/.bashrc" 2>/dev/null; then
|
||||||
|
printf "\n# Promethean\nalias promethean='source %s/.promethean_bashrc'\n" \
|
||||||
|
"$NEXUS_ROOT" >> "$HOME/.bashrc"
|
||||||
|
echo "Registered promethean in ~/.bashrc."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The AMD box runs CPU-only (Vega 20, 4GB VRAM thrashes). An NVIDIA box should not.
|
||||||
|
if command -v nvidia-smi >/dev/null && command -v sqlite3 >/dev/null && \
|
||||||
|
[ "$(sqlite3 "$NEXUS_ROOT/synapse/memory/memory.db" \
|
||||||
|
"select value from settings where key='memory_gpu_offload'" 2>/dev/null)" = "0" ]; then
|
||||||
|
echo "Note: memory_gpu_offload=0 came from the AMD box (4GB VRAM). This machine has an"
|
||||||
|
echo " NVIDIA GPU — raise it in Settings to actually use the card."
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- desktop stage: XFCE only, and it writes outside the repo -----------------
|
||||||
|
# Everything below reconfigures the logged-in user's desktop. On any other DE it
|
||||||
|
# would be actively wrong (copying XFCE channel XMLs onto a GNOME box does
|
||||||
|
# nothing good), so bail rather than make a mess of someone else's machine.
|
||||||
|
if ! command -v xfconf-query >/dev/null; then
|
||||||
|
echo "Not an XFCE session (no xfconf-query) — skipping desktop wiring."
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Panel layout, wallpaper, compositing, keybindings and terminal profile all live
|
# Panel layout, wallpaper, compositing, keybindings and terminal profile all live
|
||||||
@@ -45,7 +98,7 @@ fi
|
|||||||
# it clobbers what we just copied; the next xfconf-query respawns it.
|
# it clobbers what we just copied; the next xfconf-query respawns it.
|
||||||
xml_snap="$NEXUS_ROOT/assets/themes/restore-snapshot/xfconf-xml"
|
xml_snap="$NEXUS_ROOT/assets/themes/restore-snapshot/xfconf-xml"
|
||||||
xml_dst="$HOME/.config/xfce4/xfconf/xfce-perchannel-xml"
|
xml_dst="$HOME/.config/xfce4/xfconf/xfce-perchannel-xml"
|
||||||
if [ -d "$xml_snap" ] && command -v xfconf-query >/dev/null; then
|
if [ -d "$xml_snap" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Restoring XFCE desktop settings (panel, wallpaper, effects)..."
|
echo "Restoring XFCE desktop settings (panel, wallpaper, effects)..."
|
||||||
mkdir -p "$xml_dst"
|
mkdir -p "$xml_dst"
|
||||||
@@ -110,11 +163,3 @@ if grep -q '^ID=linuxmint' /etc/os-release 2>/dev/null && ! grep -q 'NexusOS' /e
|
|||||||
sudo sed -i -e 's/^NAME=.*/NAME="NexusOS"/' -e 's/^PRETTY_NAME=.*/PRETTY_NAME="NexusOS 1.0"/' \
|
sudo sed -i -e 's/^NAME=.*/NAME="NexusOS"/' -e 's/^PRETTY_NAME=.*/PRETTY_NAME="NexusOS 1.0"/' \
|
||||||
/etc/os-release || echo "Warning: os-release branding skipped."
|
/etc/os-release || echo "Warning: os-release branding skipped."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The AMD box runs CPU-only (Vega 20, 4GB VRAM thrashes). An NVIDIA box should not.
|
|
||||||
if command -v nvidia-smi >/dev/null && \
|
|
||||||
[ "$(sqlite3 "$NEXUS_ROOT/synapse/memory/memory.db" \
|
|
||||||
"select value from settings where key='memory_gpu_offload'" 2>/dev/null)" = "0" ]; then
|
|
||||||
echo "Note: memory_gpu_offload=0 came from the AMD box (4GB VRAM). This machine has an"
|
|
||||||
echo " NVIDIA GPU — raise it in Settings to actually use the card."
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -237,6 +237,8 @@ def cmd_restore(args) -> int:
|
|||||||
return 1
|
return 1
|
||||||
restore_db()
|
restore_db()
|
||||||
rebuild_env()
|
rebuild_env()
|
||||||
|
linux_stage("restore-linux.sh", "runtime")
|
||||||
|
if not args.no_desktop:
|
||||||
linux_stage("restore-linux.sh", "desktop")
|
linux_stage("restore-linux.sh", "desktop")
|
||||||
print("\nRestore complete. Nexus is ready to start.")
|
print("\nRestore complete. Nexus is ready to start.")
|
||||||
print("Note: Ollama models are not in the backup - pull them with `ollama pull <model>`.")
|
print("Note: Ollama models are not in the backup - pull them with `ollama pull <model>`.")
|
||||||
@@ -323,6 +325,9 @@ def main() -> int:
|
|||||||
|
|
||||||
restore = sub.add_parser("restore", help="pull from Gitea, rebuild DB + venv + web UI")
|
restore = sub.add_parser("restore", help="pull from Gitea, rebuild DB + venv + web UI")
|
||||||
restore.add_argument("-c", "--check", action="store_true", help="dry run, change nothing")
|
restore.add_argument("-c", "--check", action="store_true", help="dry run, change nothing")
|
||||||
|
restore.add_argument("--no-desktop", action="store_true",
|
||||||
|
help="skip the XFCE desktop wiring (panel, theme, os-release). "
|
||||||
|
"Use for a test clone - that stage writes to $HOME, not the repo.")
|
||||||
restore.set_defaults(func=cmd_restore)
|
restore.set_defaults(func=cmd_restore)
|
||||||
|
|
||||||
backup = sub.add_parser("backup", help="dump DB, commit and push to Gitea")
|
backup = sub.add_parser("backup", help="dump DB, commit and push to Gitea")
|
||||||
|
|||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# NexusOS installer, Linux. One painless command:
|
||||||
|
#
|
||||||
|
# git clone <repo> nexus-core && cd nexus-core && ./install.sh
|
||||||
|
#
|
||||||
|
# ponytail: a wrapper, not an installer. Every step - git pull, venv, pip with
|
||||||
|
# the right GPU overlay, npm build, apt packages, Ollama binary, desktop wiring
|
||||||
|
# - lives in bin/sync.py, shared with native Windows. Duplicating any of it here
|
||||||
|
# means two installers drifting apart, which is exactly how the old
|
||||||
|
# bin/install.sh ended up rsyncing from a backup path retired months earlier.
|
||||||
|
# Re-run it any time to update; --check dry-runs it. Windows: install-windows.ps1.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
if ! command -v python3 >/dev/null; then
|
||||||
|
echo "python3 is required (it runs the installer). Install it, then re-run:" >&2
|
||||||
|
echo " sudo apt-get install -y python3 python3-venv" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prefer the venv interpreter once it exists, same as ncp does; python3 is the
|
||||||
|
# bootstrap case on a fresh clone. sync.py is stdlib-only either way.
|
||||||
|
py=Promethean/bin/python
|
||||||
|
[ -x "$py" ] || py=python3
|
||||||
|
|
||||||
|
exec "$py" bin/sync.py restore "$@"
|
||||||
@@ -209,3 +209,29 @@ def test_sync_compare_detects_direction(tmp_path):
|
|||||||
|
|
||||||
db.unlink()
|
db.unlink()
|
||||||
assert sync.compare(db, dump) == "no-live"
|
assert sync.compare(db, dump) == "no-live"
|
||||||
|
|
||||||
|
|
||||||
|
def test_restore_stages_match_the_script():
|
||||||
|
"""sync.py invokes restore-linux.sh stages by name with check=False, so a
|
||||||
|
rename on one side alone fails silently - and the runtime stage is what
|
||||||
|
installs Ollama and the ncp alias. Keep the two in agreement."""
|
||||||
|
import re
|
||||||
|
called = set(re.findall(
|
||||||
|
r'linux_stage\(\s*"restore-linux\.sh"\s*,\s*"(\w+)"',
|
||||||
|
(REPO_ROOT / "bin" / "sync.py").read_text()))
|
||||||
|
script = (REPO_ROOT / "bin" / "restore-linux.sh").read_text()
|
||||||
|
handled = set(re.search(r"case \"\$stage\" in\s*\n\s*([\w|]+)\)", script).group(1).split("|"))
|
||||||
|
assert called, "no restore-linux.sh stages found in sync.py"
|
||||||
|
assert called <= handled, f"sync.py calls unhandled stage(s): {called - handled}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_desktop_stage_is_the_only_one_touching_home():
|
||||||
|
"""--no-desktop is only a real safety valve if the $HOME writes all live in
|
||||||
|
the desktop stage. A test clone runs prep + runtime unconditionally."""
|
||||||
|
script = (REPO_ROOT / "bin" / "restore-linux.sh").read_text()
|
||||||
|
runtime = script.split('if [ "$stage" = "runtime" ]; then')[1].split("\n exit 0\nfi")[0]
|
||||||
|
# The ncp/promethean shell aliases are the one deliberate exception - they're
|
||||||
|
# how you launch NexusOS at all, and both are grep-guarded no-ops on re-run.
|
||||||
|
home_writes = [ln for ln in runtime.splitlines()
|
||||||
|
if "$HOME" in ln and ".bashrc" not in ln]
|
||||||
|
assert not home_writes, f"runtime stage writes to $HOME: {home_writes}"
|
||||||
|
|||||||
Reference in New Issue
Block a user