---
name: self-hosted-services
description: "Deploy, configure, and troubleshoot self-hosted web services (Node.js, Python, Docker). Covers npm global installs, CLI workarounds, SQLite config, API-based setup, and proxy configuration."
version: 1.0.0
author: Hermes Agent
license: MIT
metadata:
  hermes:
    tags: [devops, deployment, nodejs, self-hosted, proxy]
    related_skills: [database-in-docker, docker-install]
---

# Self-Hosted Services

Deploy, configure, and troubleshoot self-hosted web services. Covers common patterns across Node.js, Python, and Docker-based services.

## When to Use

- Deploying npm/pip global packages as services
- Troubleshooting CLI tools that fail silently or hang
- Managing SQLite-based application configuration
- Configuring outbound proxies for services
- Resetting passwords in self-hosted applications

## General Deployment Patterns

### npm Global Install + Direct Execution

Many npm packages ship a CLI wrapper that may not work in all environments. The fallback is running the actual server directly:

```bash
# 1. Install globally
npm install -g <package-name>

# 2. Try CLI first
<package-name> --help

# 3. If CLI hangs/fails, find the actual entry point
ls -la $(npm root -g)/<package-name>/
# Look for server.js, index.js, or app.js

# 4. Run directly with node
cd $(npm root -g)/<package-name>/app  # or appropriate dir
PORT=3000 HOSTNAME=0.0.0.0 node server.js
```

**Why CLI wrappers fail:**
- Spawn child processes with detached TTY
- Auto-update checks that hang in non-interactive environments
- System tray mode detection fails on headless servers
- Missing SQLite native deps (need runtime bootstrap)

### SQLite-Based Configuration

Many self-hosted apps store config in SQLite. Common patterns:

```python
import sqlite3
import json

db_path = "~/.<app>/db/data.sqlite"
conn = sqlite3.connect(db_path)
cursor = conn.cursor()

# List tables
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")

# Read settings
cursor.execute("SELECT * FROM settings;")
rows = cursor.fetchall()
for row in rows:
    data = json.loads(row[1])  # Usually JSON in second column
    print(json.dumps(data, indent=2))
```

### Password Reset for bcrypt-Protected Apps

When you don't know the password and can't access the UI:

```python
import sqlite3
import bcrypt
import json

db_path = "~/.<app>/db/data.sqlite"
conn = sqlite3.connect(db_path)
cursor = conn.cursor()

# Generate new hash
new_password = "newpassword"
password_hash = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode()

# Update settings table
cursor.execute("UPDATE settings SET data = ? WHERE id = 1;",
               (json.dumps({"password": password_hash}),))
conn.commit()
```

**Requires:** `pip install bcrypt` or `uv pip install bcrypt`

## Proxy Configuration

### Environment Variables (Process-Level)

```bash
HTTP_PROXY=http://127.0.0.1:7890 \
HTTPS_PROXY=http://127.0.0.1:7890 \
ALL_PROXY=socks5://127.0.0.1:7891 \
node server.js
```

### Application-Level (API-Based)

Many services expose proxy settings via REST API. Common pattern:

```bash
# Login first
curl -s -X POST http://localhost:<port>/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"password":"<password>"}' -c cookies.txt

# Update proxy settings (PATCH often works better than PUT)
curl -s -X PATCH -b cookies.txt http://localhost:<port>/api/settings \
  -H "Content-Type: application/json" \
  -d '{"outboundProxyEnabled":true,"outboundProxyUrl":"http://127.0.0.1:7890"}'
```

**Pitfall:** Some APIs use PATCH for partial updates, not PUT. PUT may require all fields.

### mihomo/Clash Proxy Node Management

```bash
# List available proxies
curl -s http://127.0.0.1:9090/proxies

# Switch active node in a proxy group
curl -s -X PUT http://127.0.0.1:9090/proxies/<group-name> \
  -H "Content-Type: application/json" \
  -d '{"name":"<node-name>"}'

# Check node status (URL-encode the node name)
curl -s "http://127.0.0.1:9090/proxies/<url-encoded-name>"
# Look for "alive": true/false
```

**Pitfall:** Node names with emojis/special chars must be URL-encoded. Use Python's `urllib.parse.quote()` or browser dev tools.

## Pitfalls

### npm CLI tools hanging silently
- Symptom: `9router --help` produces no output, times out
- Cause: CLI wrapper spawns child process, checks for updates, or waits for TTY
- Fix: Find and run the actual server entry point directly

### SQLite database locked
- Symptom: `database is locked` error
- Cause: Another process has the DB open (the running service)
- Fix: Stop the service before modifying the DB

### bcrypt not installed
- Symptom: `ModuleNotFoundError: No module named 'bcrypt'`
- Fix: `uv pip install bcrypt` (use `uv` if `pip` not in PATH)

### API returns "Unauthorized"
- Most self-hosted services require authentication
- Check for default credentials (admin/admin, admin/password, admin/123456)
- If password unknown, reset via SQLite (see above)

### Proxy nodes showing "alive": false
- Node may be temporarily down or blocked
- Switch to another node in the same group
- Check mihomo logs: `~/.config/mihomo/mihomo.log`

## Reference Files

- `references/9router-deployment.md` — Complete 9router deployment walkthrough

## Quick Checklist

When deploying a new self-hosted service:

1. [ ] Install via package manager (npm/pip/docker)
2. [ ] Test CLI command — if hangs, find actual entry point
3. [ ] Check for config files in `~/.<app>/`
4. [ ] Determine auth mechanism (password, token, API key)
5. [ ] Configure proxy if external API calls needed
6. [ ] Test service accessibility on expected port
7. [ ] Document any non-obvious workarounds
