diff --git a/README.md b/README.md index 0d0306f..c7029fa 100644 --- a/README.md +++ b/README.md @@ -227,12 +227,24 @@ Open **http://localhost:49737** in your browser. Your dweb OS is running. ```powershell # Import the dweb OS distro -wsl --import dweb C:\dweb dweb-distro.tar.gz --version 2 +wsl --import dweb C:\dweb .\dweb-wsl-rootfs.tar.gz --version 2 -# Start dweb OS +# Start dweb OS — you'll see the dweb MOTD banner wsl -d dweb +``` + +**What you get:** On every login, a dweb MOTD shows server status and system info. Use the `dweb` CLI to manage everything: + +```bash +dweb status # Server health, PID, memory, disk +dweb logs -f # Tail server logs +dweb restart # Restart the server +dweb update # Check for newer GitHub releases +dweb help # All commands +``` -# Or run the import script +Or use the import script: +```powershell .\packaging\wsl\import-dweb-wsl.ps1 ``` @@ -387,6 +399,34 @@ Host a service on your dweb OS node and share it directly with other dweb users. ### 4. Self-Hosted Websites & Portfolios Deploy static sites, blogs, and portfolios on your own machine with a `.dweb` domain. Accessible via P2P network or local LAN. +### 5. WSL Distro — Full Linux Dev Environment on Windows +The dweb WSL distro is more than just a server — it's a complete **dweb OS experience** inside WSL: + +``` +╔══════════════════════════════════════════════╗ +║ dweb OS v0.1.0 ║ +║ Self-Hosted Dev Portal + P2P Network ║ +╚══════════════════════════════════════════════╝ + +🌐 Server: http://localhost:49737 +📡 Status: ✅ Running +🟢 Node.js: v22.14.0 +💻 Memory: 42M / 1982M +⏱️ Uptime: up 2 hours +📊 Load: 0.15 / 0.20 / 0.25 + +Commands: dweb status dweb logs dweb restart + dweb update dweb help +``` + +| Feature | What it does | +|---------|-------------| +| **MOTD Banner** | Server status, system info on every login | +| **dweb CLI** | `dweb status`, `logs`, `start`, `stop`, `restart`, `ping`, `update` | +| **Auto-Update** | Checks GitHub releases, downloads & applies updates | +| **Node.js musl** | Proper Alpine compatibility (not glibc) | +| **OpenRC Init** | Auto-starts dweb-server on boot | + ### 5. Developer Demo & Portfolio Environment Showcase projects to clients or collaborators by giving them access to your dweb OS portal. Each project gets its own service, domain, and AI-assisted build pipeline. @@ -456,7 +496,11 @@ dweb/ │ └── connectivity-test.cjs ├── packaging/ # Distribution packages │ ├── wsl/ # WSL distro builder (Alpine Linux) -│ │ ├── build-wsl-distro.sh +│ │ ├── build-wsl-distro.sh # Build script +│ │ ├── overlay/ # dweb OS overlays (MOTD, CLI, update) +│ │ │ ├── etc/profile.d/dweb-motd.sh +│ │ │ ├── usr/bin/dweb +│ │ │ └── opt/dweb/tools/dweb-update.sh │ │ ├── import-dweb-wsl.ps1 │ │ └── Dockerfile │ └── win32/ # Windows packaging diff --git a/WINDOWS-TESTING-P2P.md b/WINDOWS-TESTING-P2P.md new file mode 100644 index 0000000..31c633d --- /dev/null +++ b/WINDOWS-TESTING-P2P.md @@ -0,0 +1,248 @@ +# WSL ↔ Windows P2P Connectivity Test + +Test **peer-to-peer connectivity** between two dweb instances — one running inside WSL, one running natively on Windows. + +## Architecture + +``` +┌──────────────────────────┐ WebSocket Signaling ┌──────────────────────────┐ +│ WSL (dweb distro) │◄────────────────────────────►│ Windows Native │ +│ │ port 49736 (relay) │ │ +│ dweb-relay.cjs :49736 │ │ dweb-server.cjs :49739 │ +│ dweb-server.cjs :49737 │◄────── WebRTC P2P Data ─────►│ RELAY_ADDR=localhost │ +│ (one process starts │ │ (portable distribution) │ +│ both automatically) │ │ │ +└──────────────────────────┘ └──────────────────────────┘ +``` + +### Ports used + +| Port | Service | Where | +|------|---------|-------| +| 49736 | dweb-relay (bootstrap + signaling) | WSL | +| 49737 | dweb-server (Instance A — frontend + API) | WSL | +| 49739 | dweb-server (Instance B — frontend + API) | Windows native | + +--- + +## Prerequisites + +- [ ] **WSL distro installed** — `wsl --import dweb C:\dweb-wsl dweb-wsl-rootfs.tar.gz --version 2` +- [ ] **Windows portable extracted** — `C:\dweb-portable` from `dweb-windows-portable.tar.gz` +- [ ] **Node.js 18+ on Windows** — https://nodejs.org (required for the portable server) +- [ ] Both machines on the **same local network** (both are actually the same Windows PC) + +> **Note:** WSL and Windows native share the same network stack on one PC. +> WSL can reach Windows via `localhost`. Windows can reach WSL via `localhost`. + +--- + +## Test 1: Start the Relay + Server in WSL + +```powershell +# Enter the WSL distro +wsl -d dweb + +# Start everything (relay + server) +dweb start +``` + +Verify the server is running: + +```bash +curl http://localhost:49737/ping +# Expected: {"status":"ok","server":{"name":"dweb-desktop-server",...}} +``` + +Check the relay status: + +```bash +curl http://localhost:49736/ping +# Expected: {"status":"ok","server":{"type":"dweb-relay",...}} +``` + +--- + +## Test 2: Start a Second Server on Windows Native + +Open a **new PowerShell window** (not inside WSL). + +```powershell +# Navigate to portable distribution +cd C:\dweb-portable + +# Start a second dweb server that connects to the WSL relay +$env:RELAY_ADDR = "localhost:49736" +$env:PORT = "49739" +node tools\dweb-server.cjs +``` + +You should see output like: +``` + [relay] Registered as dweb- (1 peers online) + [relay] Connection state: disconnected -> connected +``` + +--- + +## Test 3: Verify Peer Discovery + +### From WSL — check for the Windows peer + +```powershell +# In WSL: +curl http://localhost:49737/relay/peers +``` + +Expected output — you should see 1 peer (the Windows instance): +```json +{ + "status": "ok", + "count": 1, + "peers": [ + { + "id": "dweb-", + "platform": "win32", + "port": 49739, + "mode": "p2p-visible" + } + ] +} +``` + +### From Windows — check for the WSL peer + +```powershell +# In Windows PowerShell (use curl.exe, not curl): +curl.exe http://localhost:49739/relay/peers +``` + +Expected output — you should see 1 peer (the WSL instance): +```json +{ + "status": "ok", + "count": 1, + "peers": [ + { + "id": "dweb-", + "platform": "linux", + "port": 49737, + "mode": "p2p-visible" + } + ] +} +``` + +--- + +## Test 4: Cross-Instance Health Check + +### From Windows, ping the WSL server + +```powershell +curl.exe http://localhost:49737/ping +# Expected: {"status":"ok",...} +``` + +### From WSL, ping the Windows server + +```bash +curl http://host.docker.internal:49739/ping +# OR using the WSL gateway IP: +curl http://172.x.x.x:49739/ping +# Expected: {"status":"ok",...} +``` + +> **Tip:** Run `ip route show default` in WSL to find the gateway IP (Windows host). + +--- + +## Test 5: Frontend Dashboard Verification + +Open in your browser and verify both instances: + +| Instance | URL | What to check | +|----------|-----|---------------| +| WSL | http://localhost:49737 | Full dweb dashboard loads | +| Windows | http://localhost:49739 | Full dweb dashboard loads | +| Relay | http://localhost:49736/status | Shows 2 peers online | + +--- + +## Test 6: WebRTC Peer Connection (Advanced) + +The frontend at http://localhost:49737 includes P2P relay features. +Open the **Network / P2P** section in the dashboard and you should see: + +- Your **Peer ID** (e.g., `dweb-desktop-abc123`) +- **Relay status**: Connected +- **Peers online**: 1 +- Option to initiate a WebRTC connection + +If WebRTC connects successfully, the two instances are communicating +**directly** (P2P) without going through the relay. + +--- + +## Troubleshooting + +### Peer not showing up in `/relay/peers` + +```bash +# Check relay status on both sides: +curl http://localhost:49736/status # relay daemon +curl http://localhost:49737/relay/status # WSL server +curl http://localhost:49739/relay/status # Windows server +``` + +Look for: +- `relayConnected: true` on both servers +- `peersOnline: > 0` on the relay + +### Relay connection failed + +```bash +# In WSL, check if relay is running: +curl http://localhost:49736/ping + +# Restart if needed: +dweb restart +``` + +### Windows can't reach WSL relay + +WSL's `localhost` is automatically forwarded to Windows. +Make sure the WSL distro is running (`wsl -d dweb`) and the relay +is listening on `0.0.0.0:49736` (it does by default). + +### PowerShell curl issues + +PowerShell aliases `curl` to `Invoke-WebRequest`. +Use `curl.exe` for real curl, or use: +```powershell +Invoke-RestMethod -Uri http://localhost:49737/ping +``` + +--- + +## Test Checklist + +- [ ] **Test 1:** WSL relay + server start successfully +- [ ] **Test 2:** Windows native server connects to WSL relay +- [ ] **Test 3:** Both peers discover each other via `/relay/peers` +- [ ] **Test 4:** Cross-instance health check (WSL -> Windows) +- [ ] **Test 5:** Both frontends load in browser +- [ ] **Test 6:** WebRTC P2P connection establishes (optional) +- [ ] **PR #11 decision:** All tests pass -> merge `feat/wsl-enhancements` -> `main` + +--- + +## Reporting Issues + +Open an issue at https://github.com/Awaiswilll/dweb/issues with: + +- Test number that failed +- Output of `curl http://localhost:49736/status` +- Output of `curl http://localhost:49737/dweb-status` +- Windows version (`winver.exe`) +- WSL version (`wsl --version`) diff --git a/packaging/wsl/README.md b/packaging/wsl/README.md index e23b038..578aa53 100644 --- a/packaging/wsl/README.md +++ b/packaging/wsl/README.md @@ -1,6 +1,6 @@ # dweb WSL Distro -**dweb** packaged as an Alpine Linux-based WSL (Windows Subsystem for Linux) distribution. Includes dweb-server, opencode CLI, and Ollama for local AI — all pre-configured and ready to use. +**dweb** packaged as an Alpine Linux-based WSL (Windows Subsystem for Linux) distribution. Includes dweb-server, dweb CLI, MOTD (welcome banner), auto-update, and Ollama for local AI — all pre-configured and ready to use. ## Requirements @@ -64,21 +64,56 @@ http://localhost:49737 | Component | Description | |-----------|-------------| | **dweb-server** | P2P Dev + Hosting Platform — serves the React frontend on port 49737 | -| **opencode CLI** | AI-assisted coding in your terminal | +| **dweb CLI** | `dweb status`, `logs`, `start`, `stop`, `restart`, `ping`, `update`, `help` | +| **MOTD Banner** | Welcome screen with server status, Node.js version, memory, uptime on login | +| **Auto-Update** | Checks GitHub releases for newer distro versions, applies in-place with backup | | **Ollama** | Local AI model runner (download models with `ollama pull `) | -| **Alpine Linux** | Lightweight, secure base operating system | +| **Alpine Linux** | Lightweight, secure base operating system (46MB compressed) | ## Usage +### dweb CLI (Primary Interface) + +```bash +dweb status # Server health, PID, memory, disk usage +dweb logs -f # Tail server logs +dweb start # Start dweb-server +dweb stop # Stop dweb-server +dweb restart # Restart the server +dweb ping # Quick health check +dweb update # Check GitHub for newer releases, apply update +dweb help # Show all commands +``` + +### MOTD Banner + +On every login, you'll see the dweb welcome screen: +``` +╔══════════════════════════════════════════════╗ +║ dweb OS v0.1.0 ║ +║ Self-Hosted Dev Portal + P2P Network ║ +╚══════════════════════════════════════════════╝ + +🌐 Server: http://localhost:49737 +📡 Status: ✅ Running +🟢 Node.js: v22.14.0 +💻 Memory: 42M / 1982M +⏱️ Uptime: up 2 hours +📊 Load: 0.15 / 0.20 / 0.25 + +Commands: dweb status dweb logs dweb restart + dweb update dweb help +``` + ### Starting/Stopping dweb ```bash -# Inside WSL: -dweb-start # Start dweb-server -dweb-stop # Stop dweb-server -dweb-restart # Restart dweb-server -dweb-status # Check if running -dweb-logs # View server logs +# Using the dweb CLI: +dweb start # Start dweb-server +dweb stop # Stop dweb-server +dweb restart # Restart dweb-server +dweb status # Check if running +dweb logs -f # View server logs ``` Or using service commands directly: @@ -120,6 +155,9 @@ ollama list | `/opt/dweb/` | dweb code (server + frontend) | | `/var/log/dweb.log` | Server logs | | `/etc/init.d/dweb` | OpenRC service script | +| `/etc/profile.d/dweb-motd.sh` | MOTD banner script | +| `/usr/bin/dweb` | dweb CLI | +| `/opt/dweb/tools/dweb-update.sh` | Auto-update script | | `/home/dweb/` | Default user home | ## Building the Distro Yourself @@ -174,13 +212,13 @@ PORT=49738 node /opt/dweb/dweb.cjs ```bash # Check logs -dweb-logs +dweb logs # Check if Node.js is installed node --version # Restart the service -dweb-restart +dweb restart ``` ### WSL import fails @@ -224,12 +262,16 @@ wsl -d dweb ## Updating ```bash +# Automatic update (checks GitHub releases): +dweb update + +# Manual update (from source): # Inside WSL: cd /opt/dweb git pull npm install npm run build -dweb-restart +dweb restart ``` ## Uninstall diff --git a/packaging/wsl/build-wsl-distro.sh b/packaging/wsl/build-wsl-distro.sh index 1522b2f..4b1b39e 100755 --- a/packaging/wsl/build-wsl-distro.sh +++ b/packaging/wsl/build-wsl-distro.sh @@ -3,11 +3,14 @@ # build-wsl-distro.sh — Build a WSL-ready dweb distro tarball # # Creates a minimal Alpine Linux rootfs with: -# - Node.js + npm +# - Node.js + npm (musl-linked for Alpine compatibility) # - dweb-server (pre-built frontend + tools) # - opencode CLI (global npm install) # - Ollama (local AI, downloaded but not running during build) # - WSL init scripts for auto-start +# - dweb CLI (/usr/bin/dweb — convenience tool) +# - dweb MOTD (welcome banner on login) +# - Auto-update script (checks GitHub releases) # # Output: dweb-wsl-rootfs.tar.gz (importable with `wsl --import`) # ═══════════════════════════════════════════════════════════════════════════════ @@ -366,6 +369,48 @@ enabled = true appendWindowsPath = true WSLCONF +# ═══════════════════════════════════════════════════════════════════════════════ +# STEP 7.5: Install dweb overlays (MOTD, CLI, auto-update) +# ═══════════════════════════════════════════════════════════════════════════════ + +info "Installing dweb OS overlays..." + +# ── MOTD (Message of the Day) ───────────────────────────────────── +OVERLAY_DIR="$(cd "$(dirname "$0")" && pwd)/overlay" +if [ -d "$OVERLAY_DIR" ]; then + info "Copying overlay files from $OVERLAY_DIR..." + + # Copy MOTD + if [ -f "$OVERLAY_DIR/etc/profile.d/dweb-motd.sh" ]; then + mkdir -p "$ROOTFS/etc/profile.d" + cp "$OVERLAY_DIR/etc/profile.d/dweb-motd.sh" "$ROOTFS/etc/profile.d/dweb-motd.sh" + chmod 755 "$ROOTFS/etc/profile.d/dweb-motd.sh" + info " MOTD installed: /etc/profile.d/dweb-motd.sh" + fi + + # Copy dweb CLI + if [ -f "$OVERLAY_DIR/usr/bin/dweb" ]; then + mkdir -p "$ROOTFS/usr/bin" + cp "$OVERLAY_DIR/usr/bin/dweb" "$ROOTFS/usr/bin/dweb" + chmod 755 "$ROOTFS/usr/bin/dweb" + info " CLI installed: /usr/bin/dweb" + fi + + # Copy auto-update script + if [ -f "$OVERLAY_DIR/opt/dweb/tools/dweb-update.sh" ]; then + mkdir -p "$ROOTFS/opt/dweb/tools" + cp "$OVERLAY_DIR/opt/dweb/tools/dweb-update.sh" "$ROOTFS/opt/dweb/tools/dweb-update.sh" + chmod 755 "$ROOTFS/opt/dweb/tools/dweb-update.sh" + info " Auto-update installed: /opt/dweb/tools/dweb-update.sh" + fi +else + warn "Overlay directory not found at $OVERLAY_DIR — skipping overlay installation" + warn "Create overlay files at packaging/wsl/overlay/ for future builds" +fi + +# Ensure proper ownership +chroot "$ROOTFS" /bin/sh -c 'chown -R dweb:dweb /opt/dweb 2>/dev/null || true' + # ═══════════════════════════════════════════════════════════════════════════════ # STEP 8: Enable services to start on boot # ═══════════════════════════════════════════════════════════════════════════════ @@ -432,26 +477,31 @@ clean_rootfs() { rm -rf "$rootfs/var/cache/misc/"* rm -rf "$rootfs/tmp/"* - # Remove documentation and man pages + # Remove documentation, man pages, locales, timezones rm -rf "$rootfs/usr/share/doc/"* rm -rf "$rootfs/usr/share/man/"* rm -rf "$rootfs/usr/share/info/"* rm -rf "$rootfs/usr/share/gtk-doc/"* - - # Remove unnecessary locales (keep en_US) - find "$rootfs/usr/share/locale" -maxdepth 1 -mindepth 1 -type d \ - ! -name "en_US" ! -name "locale.alias" -exec rm -rf {} + 2>/dev/null || true + rm -rf "$rootfs/usr/share/locale/" + rm -rf "$rootfs/usr/share/zoneinfo/" + rm -rf "$rootfs/usr/share/i18n/" # Remove logs find "$rootfs/var/log" -type f -delete 2>/dev/null || true - # Remove npm cache - rm -rf "$rootfs/root/.npm/"* - rm -rf "$rootfs/home/dweb/.npm/"* 2>/dev/null || true - rm -rf "$rootfs/root/.cache/"* 2>/dev/null || true + # Remove npm/node caches + rm -rf "$rootfs/root/.npm/"* "$rootfs/root/.cache/"* "$rootfs/root/.node-gyp/"* + rm -rf "$rootfs/home/dweb/.npm/"* "$rootfs/home/dweb/.cache/"* 2>/dev/null || true + + # Remove C headers, static libs, libtool archives + rm -rf "$rootfs/usr/include/" + find "$rootfs/usr/lib" -name "*.a" -delete 2>/dev/null || true + find "$rootfs/usr/lib" -name "*.la" -delete 2>/dev/null || true + find "$rootfs/usr/lib" -name "*.h" -delete 2>/dev/null || true - # Remove node-gyp cache - rm -rf "$rootfs/root/.node-gyp/"* 2>/dev/null || true + # Remove Python cache files + find "$rootfs" -name "*.pyc" -delete 2>/dev/null || true + find "$rootfs" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true # Strip binaries where possible (skip if not on Alpine) if command -v strip &>/dev/null; then diff --git a/packaging/wsl/dweb-wsl-rootfs.tar.gz b/packaging/wsl/dweb-wsl-rootfs.tar.gz index 4370676..34cb2c0 100644 Binary files a/packaging/wsl/dweb-wsl-rootfs.tar.gz and b/packaging/wsl/dweb-wsl-rootfs.tar.gz differ diff --git a/packaging/wsl/import-dweb-wsl.ps1 b/packaging/wsl/import-dweb-wsl.ps1 index f8053f9..cbb1641 100755 --- a/packaging/wsl/import-dweb-wsl.ps1 +++ b/packaging/wsl/import-dweb-wsl.ps1 @@ -17,7 +17,7 @@ #> param( - [string]$TarballUrl = "https://github.com/dweb/dweb/releases/latest/download/dweb-wsl-rootfs.tar.gz", + [string]$TarballUrl = "https://github.com/Awaiswilll/dweb/releases/download/v0.1.0/dweb-wsl-rootfs.tar.gz", [string]$DistroName = "dweb", [string]$InstallDir = "./dweb-wsl", [string]$TarballPath = "" @@ -220,10 +220,11 @@ Write-Host " wsl -d $DistroName -- bash # Open a shell" -ForegroundColor Gra Write-Host " wsl --terminate $DistroName # Stop the distro" -ForegroundColor Gray Write-Host "" Write-Host " Inside WSL:" -ForegroundColor White -Write-Host " dweb-logs # View server logs" -ForegroundColor Gray -Write-Host " dweb-status # Check server status" -ForegroundColor Gray -Write-Host " dweb-restart # Restart dweb-server" -ForegroundColor Gray -Write-Host " opencode --help # Use opencode CLI" -ForegroundColor Gray +Write-Host " dweb status # Check server status (MOTD banner)" -ForegroundColor Gray +Write-Host " dweb logs -f # View server logs" -ForegroundColor Gray +Write-Host " dweb restart # Restart dweb-server" -ForegroundColor Gray +Write-Host " dweb update # Check for dweb OS updates" -ForegroundColor Gray +Write-Host " dweb help # Show all CLI commands" -ForegroundColor Gray Write-Host "" -Write-Host " Need help? https://github.com/dweb/dweb/issues" -ForegroundColor Yellow +Write-Host " Need help? https://github.com/Awaiswilll/dweb/issues" -ForegroundColor Yellow Write-Host "" diff --git a/packaging/wsl/overlay/etc/profile.d/dweb-motd.sh b/packaging/wsl/overlay/etc/profile.d/dweb-motd.sh new file mode 100755 index 0000000..aa104b5 --- /dev/null +++ b/packaging/wsl/overlay/etc/profile.d/dweb-motd.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# dweb OS — Message of the Day +# Displayed on interactive shell login + +# Only show on interactive login, not scp/sftp +if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ] || [ -t 0 ]; then + # Get server status + DWEB_STATUS=$(curl -sf http://localhost:49737/ping 2>/dev/null || echo "offline") + DWEB_OK=$(echo "$DWEB_STATUS" | grep -c '"ok"' 2>/dev/null || true) + + # System info + UPTIME=$(uptime -p 2>/dev/null | sed 's/up //' || echo "unknown") + LOAD=$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null || echo "?") + MEM=$(free -m 2>/dev/null | awk '/Mem:/ {print $3 "M / " $2 "M"}' || echo "?") + NODE_VER=$(node --version 2>/dev/null || echo "not installed") + IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "unknown") + + echo "" + echo " ╔══════════════════════════════════════════════╗" + echo " ║ dweb OS v0.1.0 ║" + echo " ║ Self-Hosted Dev Portal + P2P Network ║" + echo " ╚══════════════════════════════════════════════╝" + echo "" + echo " 🌐 Server: http://localhost:49737" + echo " 📡 Status: $([ "$DWEB_OK" -gt 0 ] && echo "✅ Running" || echo "⏳ Starting...")" + echo " 🟢 Node.js: $NODE_VER" + echo " 💻 Memory: $MEM" + echo " ⏱️ Uptime: $UPTIME" + echo " 📊 Load: $LOAD" + echo " 🖥️ IP: $IP" + echo "" + echo " Commands: dweb status dweb logs dweb restart" + echo " dweb update dweb help" + echo "" +fi diff --git a/packaging/wsl/overlay/opt/dweb/tools/dweb-update.sh b/packaging/wsl/overlay/opt/dweb/tools/dweb-update.sh new file mode 100755 index 0000000..10a9337 --- /dev/null +++ b/packaging/wsl/overlay/opt/dweb/tools/dweb-update.sh @@ -0,0 +1,173 @@ +#!/bin/sh +# dweb-update — Auto-update for dweb WSL distro +# Checks GitHub releases and can download/apply updates +# Usage: dweb update (or run directly: /opt/dweb/tools/dweb-update.sh) + +set -e + +VERSION="0.1.0" +OWNER="Awaiswilll" +REPO="dweb" +API="https://api.github.com/repos/$OWNER/$REPO/releases/latest" + +info() { echo " [INFO] $1"; } +warn() { echo " [WARN] $1"; } +error() { echo " [ERROR] $1"; exit 1; } + +# ── Check for updates ───────────────────────────────────────────── + +check() { + info "Checking for dweb OS updates..." + + local release_data + release_data=$(curl -sf "$API" 2>/dev/null || echo "") + + if [ -z "$release_data" ]; then + warn "Could not reach GitHub API. Check your network connection." + return 1 + fi + + local latest_tag + latest_tag=$(echo "$release_data" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) + print(d.get('tag_name', 'unknown')) +except: + print('unknown') +" 2>/dev/null || echo "unknown") + + local published_at + published_at=$(echo "$release_data" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) + print(d.get('published_at', '')) +except: + print('') +" 2>/dev/null || echo "") + + echo "" + echo " Current version: v$VERSION" + echo " Latest release: $latest_tag" + echo " Published: $(echo "$published_at" | cut -dT -f1 2>/dev/null || echo '?')" + echo "" + + if [ "$latest_tag" = "v$VERSION" ]; then + echo " ✅ You are running the latest version." + return 0 + elif [ "$latest_tag" = "unknown" ] || [ -z "$latest_tag" ]; then + warn "Could not determine latest version." + return 1 + else + echo " ⬆️ Update available: $latest_tag" + return 2 + fi +} + +# ── Download latest tarball ─────────────────────────────────────── + +download() { + local release_data + release_data=$(curl -sf "$API" 2>/dev/null || echo "") + + local tarball_url + tarball_url=$(echo "$release_data" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) + for a in d.get('assets', []): + if 'dweb-wsl-rootfs' in a['name']: + print(a['browser_download_url']) + break +except: + pass +" 2>/dev/null || echo "") + + if [ -z "$tarball_url" ]; then + error "No WSL tarball found in latest release." + fi + + local dest="/tmp/dweb-update.tar.gz" + info "Downloading: $tarball_url" + curl -L -o "$dest" "$tarball_url" 2>&1 | tail -1 + + if [ -f "$dest" ]; then + local size + size=$(du -h "$dest" | cut -f1) + info "Downloaded: $size" + echo "$dest" + else + error "Download failed." + fi +} + +# ── Apply update ────────────────────────────────────────────────── + +apply() { + local tarball="${1:-/tmp/dweb-update.tar.gz}" + + if [ ! -f "$tarball" ]; then + error "Tarball not found: $tarball" + fi + + warn "Applying update from: $tarball" + warn "This will replace the current dweb installation." + + # Backup current config + local backup_dir="/tmp/dweb-backup-$(date +%Y%m%d-%H%M%S)" + mkdir -p "$backup_dir" + cp -r /opt/dweb "$backup_dir/" 2>/dev/null || true + info "Backup saved to: $backup_dir" + + # Apply new rootfs — only replace /opt/dweb and /usr/bin/dweb + local temp_dir + temp_dir=$(mktemp -d) + tar xzf "$tarball" -C "$temp_dir" + + if [ -f "$temp_dir/usr/bin/dweb" ]; then + cp "$temp_dir/usr/bin/dweb" /usr/bin/dweb + chmod +x /usr/bin/dweb + info "Updated: /usr/bin/dweb" + fi + + if [ -d "$temp_dir/opt/dweb" ]; then + cp -r "$temp_dir/opt/dweb/"* /opt/dweb/ + info "Updated: /opt/dweb/" + fi + + # Restart server + dweb restart 2>/dev/null || true + + rm -rf "$temp_dir" + info "Update applied successfully!" + info "Backup at: $backup_dir" +} + +# ── Main ────────────────────────────────────────────────────────── + +case "${1:-check}" in + check) + check + ;; + download) + download + ;; + apply) + apply "$2" + ;; + upgrade|update) + check + local rc=$? + if [ $rc -eq 2 ]; then + echo "" + local tarball + tarball=$(download) + apply "$tarball" + fi + ;; + *) + echo "Usage: $0 {check|download|apply|upgrade}" + exit 1 + ;; +esac diff --git a/packaging/wsl/overlay/usr/bin/dweb b/packaging/wsl/overlay/usr/bin/dweb new file mode 100755 index 0000000..8a454fc --- /dev/null +++ b/packaging/wsl/overlay/usr/bin/dweb @@ -0,0 +1,219 @@ +#!/bin/sh +# dweb — dweb OS CLI +# Convenience tool for managing the dweb server and system + +set -e + +DWEB_PORT="${DWEB_PORT:-49737}" +DWEB_SERVER="${DWEB_SERVER:-http://localhost:$DWEB_PORT}" +VERSION="0.1.0" + +usage() { + cat < [options] + +Commands: + status Show dweb server status and system info + logs [-f] Show server logs (tail mode with -f) + start Start dweb server if not running + stop Stop dweb server + restart Restart dweb server + ping Quick health check + update Check for updates on GitHub + help Show this help message + +Examples: + dweb status + dweb logs -f + dweb update + +Environment: + DWEB_PORT Server port (default: 49737) +USAGE +} + +status() { + echo "═══ dweb OS Status ═══════════════════════════" + echo "" + + # Server health check + local ping_result + ping_result=$(curl -sf "$DWEB_SERVER/ping" 2>/dev/null || echo "offline") + local ok=$(echo "$ping_result" | grep -c '"ok"' 2>/dev/null || true) + + if [ "$ok" -gt 0 ]; then + echo " Server: ✅ Running on $DWEB_SERVER" + echo " Response: $ping_result" + else + echo " Server: ❌ Not running or unreachable" + fi + + # Process info + local node_pid + node_pid=$(pgrep -f "node.*dweb" 2>/dev/null | head -1 || true) + if [ -n "$node_pid" ]; then + local node_uptime + node_uptime=$(ps -o etime= -p "$node_pid" 2>/dev/null | tr -d ' ' || echo "?") + echo " PID: $node_pid (up $node_uptime)" + fi + + echo "" + + # System info + echo " Node.js: $(node --version 2>/dev/null || echo 'not installed')" + echo " Uptime: $(uptime -p 2>/dev/null | sed 's/up //' || echo '?')" + echo " Memory: $(free -h 2>/dev/null | awk '/Mem:/ {print $3 " / " $2}' || echo '?')" + echo " Disk: $(df -h / 2>/dev/null | awk 'NR==2{print $3 " / " $2 " (" $5 ")"}' || echo '?')" + + # Ollama status + if command -v ollama &>/dev/null; then + echo " Ollama: $(pgrep -x ollama >/dev/null && echo 'running' || echo 'stopped')" + fi + + echo "" + echo "═══════════════════════════════════════════════" +} + +logs() { + local logfile="/var/log/dweb.log" + if [ ! -f "$logfile" ]; then + echo "Log file not found: $logfile" + echo "The server may not have been started yet." + exit 1 + fi + + if [ "$1" = "-f" ]; then + tail -f "$logfile" + else + tail -50 "$logfile" + fi +} + +start_server() { + if curl -sf "$DWEB_SERVER/ping" &>/dev/null; then + echo "dweb server is already running on $DWEB_SERVER" + exit 0 + fi + + echo "Starting dweb server..." + mkdir -p /var/log + nohup node /opt/dweb/dweb.cjs &>/var/log/dweb.log & + echo "dweb server started (PID $!)" + echo "Logs: tail -f /var/log/dweb.log" +} + +stop_server() { + local pid + pid=$(pgrep -f "node.*dweb" 2>/dev/null || true) + if [ -n "$pid" ]; then + echo "Stopping dweb server (PID $pid)..." + kill "$pid" 2>/dev/null || true + sleep 1 + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + echo "Force stopped." + else + echo "Stopped." + fi + else + echo "dweb server is not running." + fi +} + +restart_server() { + stop_server + sleep 1 + start_server +} + +ping() { + local result + result=$(curl -sf "$DWEB_SERVER/ping" 2>/dev/null || echo '{"status":"error","message":"unreachable"}') + echo "$result" | python3 -m json.tool 2>/dev/null || echo "$result" +} + +check_update() { + echo "Checking for updates..." + local api_url="https://api.github.com/repos/Awaiswilll/dweb/releases/latest" + + local release_data + release_data=$(curl -sf "$api_url" 2>/dev/null || echo "") + + if [ -z "$release_data" ]; then + echo " Unable to check for updates (no network or GitHub unreachable)" + exit 1 + fi + + local latest_tag + latest_tag=$(echo "$release_data" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) + print(d.get('tag_name', 'unknown')) +except: + print('unknown') +" 2>/dev/null || echo "unknown") + + local latest_url + latest_url=$(echo "$release_data" | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) + for a in d.get('assets', []): + if 'dweb-wsl-rootfs' in a['name']: + print(a['browser_download_url']) + break +except: + pass +" 2>/dev/null || echo "") + + echo "" + echo " Current version: v$VERSION" + echo " Latest release: $latest_tag" + + if [ "$latest_tag" = "v$VERSION" ] || [ "$latest_tag" = "unknown" ]; then + echo " Status: ✅ Up to date" + else + echo " Status: ⬆️ Update available" + if [ -n "$latest_url" ]; then + echo " Download: $latest_url" + fi + fi + echo "" +} + +# ── Main ────────────────────────────────────────────────────────── + +case "${1:-help}" in + status|stat) + status + ;; + logs|log) + logs "$2" + ;; + start) + start_server + ;; + stop) + stop_server + ;; + restart) + restart_server + ;; + ping) + ping + ;; + update|upgrade|check) + check_update + ;; + help|--help|-h) + usage + ;; + *) + echo "Unknown command: $1" + echo "Run 'dweb help' for usage." + exit 1 + ;; +esac