Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Secret Scan

on:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

jobs:
gitleaks:
name: Scan for secrets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 changes: 14 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,21 @@ config.yaml.bak
*.tmp
*.swp

# Secrets and credentials (global)
.env
.env.*
!.env.example
*.key
*.pem
*.p12
*.pfx
*.crt
credentials.*
secrets.*
secrets/
*.keystore

# Token Spy runtime
token-spy/.env
token-spy/data/
token-spy/*.db
token-spy/*.sqlite
5 changes: 5 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Gitleaks ignore file
# Add fingerprints of known false positives here (one per line)
# Get fingerprints from gitleaks output when a false positive is detected
#
# Example: abc123def456...
16 changes: 16 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Pre-commit hooks for secret scanning
# Install: pip install pre-commit && pre-commit install
# Run manually: pre-commit run --all-files

repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaks

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: detect-private-key
- id: check-added-large-files
args: ['--maxkb=500']
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ The most important four lines in the entire repo. Without them, OpenClaw sends p

These are already set in `configs/openclaw.json`. Just copy it and go.

### Gateway Config (Security Note)

The golden config includes gateway settings for LAN access:

```json
"gateway": {
"bind": "lan",
"controlUi": {
"allowInsecureAuth": true,
"dangerouslyDisableDeviceAuth": true
}
}
```

**`dangerouslyDisableDeviceAuth: true`** — Disables the device authorization flow that normally requires confirming new devices via the OpenClaw UI. Set to `true` here because local/headless setups (SSH, systemd) can't complete the interactive auth prompt. **If you expose your gateway to the internet, set this to `false`.**

**`allowInsecureAuth: true`** — Allows HTTP (non-HTTPS) auth on LAN. Safe for local networks, not for public-facing deployments.

---

## How It Works
Expand Down
82 changes: 58 additions & 24 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,48 @@ if ($Help) {
exit 0
}

# ── Parse YAML (minimal parser) ───────────────────────────────
# ── Parse YAML (section-aware parser) ─────────────────────────
# Usage: Parse-Yaml "section.key" "default" — reads key within a section
# Parse-Yaml "key" "default" — reads top-level key (legacy)
function Parse-Yaml {
param([string]$Key, [string]$Default)
param([string]$Input, [string]$Default)
if (-not (Test-Path $Config)) { return $Default }
$match = Select-String -Path $Config -Pattern "^\s*${Key}:" | Select-Object -First 1
if ($match) {
$value = ($match.Line -split ":\s*", 2)[1].Trim().Trim('"').Trim("'")
$value = ($value -split "\s*#")[0].Trim()
if ($value -and $value -ne '""' -and $value -ne "''") { return $value }

$section = ""
$key = $Input
if ($Input -match "^(.+)\.(.+)$") {
$section = $Matches[1]
$key = $Matches[2]
}

if ($section) {
$lines = Get-Content $Config
$inSection = $false
foreach ($line in $lines) {
if ($line -match "^${section}:") {
$inSection = $true
continue
}
if ($inSection -and $line -match "^[a-zA-Z_]") {
break
}
if ($inSection -and $line -match "^\s+${key}:") {
$value = ($line -split ":\s*", 2)[1].Trim().Trim('"').Trim("'")
$value = ($value -split "\s*#")[0].Trim()
if ($value -and $value -ne '""' -and $value -ne "''") { return $value }
return $Default
}
}
return $Default
} else {
$match = Select-String -Path $Config -Pattern "^\s*${key}:" | Select-Object -First 1
if ($match) {
$value = ($match.Line -split ":\s*", 2)[1].Trim().Trim('"').Trim("'")
$value = ($value -split "\s*#")[0].Trim()
if ($value -and $value -ne '""' -and $value -ne "''") { return $value }
}
return $Default
}
return $Default
}

# ── Load config ────────────────────────────────────────────────
Expand All @@ -73,26 +104,29 @@ if (-not (Test-Path $Config)) {

Info "Loading config from $Config"

$OpenClawDir = Parse-Yaml "openclaw_dir" "$env:USERPROFILE\.openclaw"
# Session cleanup settings
$OpenClawDir = Parse-Yaml "session_cleanup.openclaw_dir" "$env:USERPROFILE\.openclaw"
$OpenClawDir = $OpenClawDir -replace "^~", $env:USERPROFILE
$SessionsPath = Parse-Yaml "sessions_path" "agents\main\sessions"
$MaxSessionSize = Parse-Yaml "max_session_size" "256000"
$IntervalMinutes = Parse-Yaml "interval_minutes" "60"
$ProxyPort = Parse-Yaml "port" "8003"
$VllmUrl = Parse-Yaml "vllm_url" "http://localhost:8000"
$SessionsPath = Parse-Yaml "session_cleanup.sessions_path" "agents\main\sessions"
$MaxSessionSize = Parse-Yaml "session_cleanup.max_session_size" "256000"
$IntervalMinutes = Parse-Yaml "session_cleanup.interval_minutes" "60"

# Proxy settings
$ProxyPort = Parse-Yaml "tool_proxy.port" "8003"
$VllmUrl = Parse-Yaml "tool_proxy.vllm_url" "http://localhost:8000"

$SessionsDir = Join-Path $OpenClawDir $SessionsPath

# Token Spy settings
$TsEnabled = Parse-Yaml "enabled" "false"
$TsAgentName = Parse-Yaml "agent_name" "my-agent"
$TsPort = Parse-Yaml "port" "9110"
$TsHost = Parse-Yaml "host" "0.0.0.0"
$TsAnthropicUpstream = Parse-Yaml "anthropic_upstream" "https://api.anthropic.com"
$TsOpenaiUpstream = Parse-Yaml "openai_upstream" ""
$TsApiProvider = Parse-Yaml "api_provider" "anthropic"
$TsDbBackend = Parse-Yaml "db_backend" "sqlite"
$TsSessionCharLimit = Parse-Yaml "session_char_limit" "200000"
$TsEnabled = Parse-Yaml "token_spy.enabled" "false"
$TsAgentName = Parse-Yaml "token_spy.agent_name" "my-agent"
$TsPort = Parse-Yaml "token_spy.port" "9110"
$TsHost = Parse-Yaml "token_spy.host" "0.0.0.0"
$TsAnthropicUpstream = Parse-Yaml "token_spy.anthropic_upstream" "https://api.anthropic.com"
$TsOpenaiUpstream = Parse-Yaml "token_spy.openai_upstream" ""
$TsApiProvider = Parse-Yaml "token_spy.api_provider" "anthropic"
$TsDbBackend = Parse-Yaml "token_spy.db_backend" "sqlite"
$TsSessionCharLimit = Parse-Yaml "token_spy.session_char_limit" "200000"

Write-Host ""
Info "Configuration:"
Expand Down Expand Up @@ -259,7 +293,7 @@ Write-Output "[`$(Get-Date)] Cleanup complete: removed `$removedInactive inactiv
}

$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$CleanupScript`""
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes)
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes) -RepetitionDuration ([TimeSpan]::MaxValue)
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Limited

Expand Down
71 changes: 45 additions & 26 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,31 @@ echo -e "${CYAN} LightHeart OpenClaw - Installer${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
echo ""

# ── Parse config (basic YAML parser — no dependencies needed) ──
# ── Parse config (section-aware YAML parser — no dependencies needed) ──
# Usage: parse_yaml "section.key" "default" — reads key within a section
# parse_yaml "key" "default" — reads top-level key (legacy)
parse_yaml() {
local key="$1"
local input="$1"
local default="$2"
local value
value=$(grep -E "^\s*${key}:" "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*:\s*//' | sed 's/\s*#.*//' | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//" | xargs)
local section="" key="" value=""

if [[ "$input" == *.* ]]; then
section="${input%%.*}"
key="${input#*.}"
else
key="$input"
fi

if [ -n "$section" ]; then
# Extract lines between "section:" and the next top-level key (non-indented)
value=$(sed -n "/^${section}:/,/^[a-zA-Z_]/{/^${section}:/d;/^[a-zA-Z_]/d;p;}" "$CONFIG_FILE" \
| grep -E "^\s+${key}:" | head -1 \
| sed 's/.*:\s*//' | sed 's/\s*#.*//' | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//" | xargs)
else
value=$(grep -E "^\s*${key}:" "$CONFIG_FILE" 2>/dev/null | head -1 \
| sed 's/.*:\s*//' | sed 's/\s*#.*//' | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//" | xargs)
fi

if [ -z "$value" ] || [ "$value" = '""' ] || [ "$value" = "''" ]; then
echo "$default"
else
Expand All @@ -88,34 +107,34 @@ fi
info "Loading config from $CONFIG_FILE"

# Session cleanup settings
CLEANUP_ENABLED=$(parse_yaml "enabled" "true")
OPENCLAW_DIR=$(parse_yaml "openclaw_dir" "~/.openclaw")
CLEANUP_ENABLED=$(parse_yaml "session_cleanup.enabled" "true")
OPENCLAW_DIR=$(parse_yaml "session_cleanup.openclaw_dir" "~/.openclaw")
OPENCLAW_DIR="${OPENCLAW_DIR/#\~/$HOME}"
SESSIONS_PATH=$(parse_yaml "sessions_path" "agents/main/sessions")
MAX_SESSION_SIZE=$(parse_yaml "max_session_size" "256000")
INTERVAL_MINUTES=$(parse_yaml "interval_minutes" "60")
BOOT_DELAY=$(parse_yaml "boot_delay_minutes" "5")
SESSIONS_PATH=$(parse_yaml "session_cleanup.sessions_path" "agents/main/sessions")
MAX_SESSION_SIZE=$(parse_yaml "session_cleanup.max_session_size" "256000")
INTERVAL_MINUTES=$(parse_yaml "session_cleanup.interval_minutes" "60")
BOOT_DELAY=$(parse_yaml "session_cleanup.boot_delay_minutes" "5")

# Proxy settings
PROXY_ENABLED=$(parse_yaml "enabled" "true")
PROXY_PORT=$(parse_yaml "port" "8003")
PROXY_HOST=$(parse_yaml "host" "0.0.0.0")
VLLM_URL=$(parse_yaml "vllm_url" "http://localhost:8000")
LOG_FILE=$(parse_yaml "log_file" "~/vllm-proxy.log")
PROXY_ENABLED=$(parse_yaml "tool_proxy.enabled" "true")
PROXY_PORT=$(parse_yaml "tool_proxy.port" "8003")
PROXY_HOST=$(parse_yaml "tool_proxy.host" "0.0.0.0")
VLLM_URL=$(parse_yaml "tool_proxy.vllm_url" "http://localhost:8000")
LOG_FILE=$(parse_yaml "tool_proxy.log_file" "~/vllm-proxy.log")
LOG_FILE="${LOG_FILE/#\~/$HOME}"

# Token Spy settings
TS_ENABLED=$(parse_yaml "enabled" "false")
TS_AGENT_NAME=$(parse_yaml "agent_name" "my-agent")
TS_PORT=$(parse_yaml "port" "9110")
TS_HOST=$(parse_yaml "host" "0.0.0.0")
TS_ANTHROPIC_UPSTREAM=$(parse_yaml "anthropic_upstream" "https://api.anthropic.com")
TS_OPENAI_UPSTREAM=$(parse_yaml "openai_upstream" "")
TS_API_PROVIDER=$(parse_yaml "api_provider" "anthropic")
TS_DB_BACKEND=$(parse_yaml "db_backend" "sqlite")
TS_SESSION_CHAR_LIMIT=$(parse_yaml "session_char_limit" "200000")
TS_AGENT_SESSION_DIRS=$(parse_yaml "agent_session_dirs" "")
TS_LOCAL_MODEL_AGENTS=$(parse_yaml "local_model_agents" "")
TS_ENABLED=$(parse_yaml "token_spy.enabled" "false")
TS_AGENT_NAME=$(parse_yaml "token_spy.agent_name" "my-agent")
TS_PORT=$(parse_yaml "token_spy.port" "9110")
TS_HOST=$(parse_yaml "token_spy.host" "0.0.0.0")
TS_ANTHROPIC_UPSTREAM=$(parse_yaml "token_spy.anthropic_upstream" "https://api.anthropic.com")
TS_OPENAI_UPSTREAM=$(parse_yaml "token_spy.openai_upstream" "")
TS_API_PROVIDER=$(parse_yaml "token_spy.api_provider" "anthropic")
TS_DB_BACKEND=$(parse_yaml "token_spy.db_backend" "sqlite")
TS_SESSION_CHAR_LIMIT=$(parse_yaml "token_spy.session_char_limit" "200000")
TS_AGENT_SESSION_DIRS=$(parse_yaml "token_spy.agent_session_dirs" "")
TS_LOCAL_MODEL_AGENTS=$(parse_yaml "token_spy.local_model_agents" "")

# System user
SYSTEM_USER=$(parse_yaml "system_user" "")
Expand Down
Loading