# Self-host (Node / standalone)

Run chmonitor on any server using Node.js or Bun. Best for bare VMs, dedicated servers, or any environment where you manage the process directly.

## Quick start

```bash
git clone https://github.com/duyet/clickhouse-monitoring.git
cd clickhouse-monitoring
bun install

## Set your ClickHouse credentials
export CLICKHOUSE_HOST='https://clickhouse.example.com:8443'
export CLICKHOUSE_USER='monitoring'
export CLICKHOUSE_PASSWORD='change-me'

## Build and start
bun run build
bun run start
```

Open `http://localhost:3000`.

## Using .env

Put env vars in `.env.local` in the project root (Next.js) or `.env` for the TanStack app. Never commit credentials.

```bash
cat <<'EOF' > .env.local
CLICKHOUSE_HOST=https://clickhouse.example.com:8443
CLICKHOUSE_USER=monitoring
CLICKHOUSE_PASSWORD=change-me
CLICKHOUSE_NAME=prod
CLICKHOUSE_MAX_EXECUTION_TIME=30
CLICKHOUSE_TZ=UTC
EOF

bun run start
```

## Configure

### ClickHouse connection

| Variable | Default | Description |
|---|---|---|
| `CLICKHOUSE_HOST` | — (required) | ClickHouse URL(s), comma-separated |
| `CLICKHOUSE_USER` | `default` | Username(s), same count as HOST |
| `CLICKHOUSE_PASSWORD` | `""` | Password(s), same count as HOST |
| `CLICKHOUSE_NAME` | — | Friendly label(s) for host switcher |

#### Multiple hosts

`CLICKHOUSE_HOST` defines the host count. `CLICKHOUSE_USER` and `CLICKHOUSE_PASSWORD` may be a single value (applied to all hosts) or one value per host position. `CLICKHOUSE_NAME` is optional:

```bash
export CLICKHOUSE_HOST='https://ch1:8443,https://ch2:8443'
export CLICKHOUSE_USER='monitoring,monitoring'
export CLICKHOUSE_PASSWORD='pass1,pass2'
export CLICKHOUSE_NAME='shard-1,shard-2'
```

### Query / pool tuning

| Variable | Default | Description |
|---|---|---|
| `CLICKHOUSE_MAX_EXECUTION_TIME` | `60` | Query timeout in seconds |
| `CLICKHOUSE_TZ` | — | Timezone for queries |
| `CLICKHOUSE_DATABASE` | `system` | Default database |
| `CLICKHOUSE_POOL_SIZE` | `10` | Connection pool size |
| `CLICKHOUSE_POOL_TIMEOUT` | `300000` | Pool acquire timeout (ms) |
| `CLICKHOUSE_POOL_CLEANUP_INTERVAL` | `60000` | Pool cleanup interval (ms) |

### Feature permissions

**Via env vars:**

```bash
export CHM_DISABLED_FEATURES='peerdb,actions'
export CHM_AUTH_REQUIRED_FEATURES='agent,settings,mcp'
export CHM_FEATURE_AGENT_ACCESS='authenticated'
export CHM_FEATURE_SETTINGS_ENABLED='false'
```

**Via a config file (TOML):**

```bash
export CHM_CONFIG_FILE='/etc/chmonitor/config.toml'
```

```toml
## /etc/chmonitor/config.toml
[features.agent]
access = "authenticated"

[features.settings]
enabled = false

[features.mcp]
access = "authenticated"
```

Feature ids: `overview`, `agent`, `insights`, `health`, `queries`, `tables`, `metrics`, `dashboard`, `security`, `logs`, `settings`, `cluster`, `operations`, `actions`, `mcp`, `docs`, `about`.

### Authentication

**None (default):**

```bash
export CHM_AUTH_PROVIDER='none'
```

**API key layer:**

```bash
export CHM_API_KEY_SECRET='a-long-random-secret'
```

**Clerk:**

```bash
export CHM_AUTH_PROVIDER='clerk'
export CLERK_SECRET_KEY='sk_live_...'
## VITE_* vars must be set at build time, not runtime:
## VITE_AUTH_PROVIDER=clerk VITE_CLERK_PUBLISHABLE_KEY=pk_live_... bun run build
```

**Proxy — Cloudflare Access:**

```bash
export CHM_AUTH_PROVIDER='proxy'
export CHM_CF_ACCESS_TEAM_DOMAIN='https://yourteam.cloudflareaccess.com'
export CHM_CF_ACCESS_AUD='<audience-tag>'
```

**Proxy — trusted header (nginx):**

Put chmonitor behind nginx. Configure nginx to set an auth header and pass the secret.

```bash
export CHM_AUTH_PROVIDER='proxy'
export CHM_PROXY_AUTH_HEADER='X-Forwarded-User'
export CHM_PROXY_AUTH_SECRET='a-long-random-secret'
```

Without `CHM_PROXY_AUTH_SECRET`, the trusted-header provider is disabled. See [Authentication](/authentication).

Example nginx config:

```nginx
upstream chmonitor {
    server 127.0.0.1:3000;
}

server {
    listen 443 ssl;
    server_name monitor.example.com;

    # Set after your auth module populates $remote_user
    proxy_set_header X-Forwarded-User $remote_user;
    proxy_set_header X-Chm-Proxy-Secret "$chm_proxy_secret";  # inject via env/secrets manager, not hardcoded

    location / {
        proxy_pass http://chmonitor;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
```

### AI agent

```bash
export LLM_API_KEY='sk-...'
export LLM_API_BASE='https://openrouter.ai/api/v1'
export LLM_MODEL='openrouter/free'
export AGENT_API_TOKEN='bearer-token-for-agent-api'
export AGENT_ENABLE_CONTROL_TOOLS='false'
```

Keep `LLM_API_KEY` server-side — never set it as a `VITE_*` variable.

### Conversation store

**Default:** browser localStorage. No server config needed.

Server-side persistence requires `VITE_FEATURE_CONVERSATION_DB=true` set at build time (a `VITE_*` build-time variable). Build a custom image or binary with this flag set. At runtime, select the backend:

**Postgres store (recommended for self-host):**

```bash
export CONVERSATION_STORE_BACKEND='postgres'
export DATABASE_URL='postgresql://user:pass@host:5432/dbname'
```

**AgentState store:**

```bash
export CONVERSATION_STORE_BACKEND='agentstate'
export AGENTSTATE_API_KEY='as_live_...'
```

D1 and Durable Object stores are Cloudflare-only.

### Health alerting

The health sweep runs at `GET /api/cron/health-sweep`. Call it from a cron job:

```bash
export HEALTH_ALERT_ENABLED='true'
export HEALTH_ALERT_WEBHOOK_URL='https://hooks.slack.com/services/...'
export HEALTH_ALERT_MIN_SEVERITY='warning'
export CRON_SECRET='a-random-secret'
```

Add a crontab entry:

```
*/5 * * * * curl -sf -H "Authorization: Bearer <CRON_SECRET>" http://localhost:3000/api/cron/health-sweep
```

### Branding

Branding vars are inlined at build time:

```bash
VITE_TITLE_SHORT=MyCluster VITE_LOGO=/logo.png bun run build
```

These are inlined at build time — set them before running `bun run build`.

## systemd unit

```ini
[Unit]
Description=chmonitor ClickHouse dashboard
After=network.target

[Service]
Type=simple
User=chmonitor
WorkingDirectory=/opt/chmonitor
EnvironmentFile=/etc/chmonitor/env
ExecStart=/usr/local/bin/bun run start
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
```

Create `/etc/chmonitor/env`:

```bash
CLICKHOUSE_HOST=https://clickhouse.example.com:8443
CLICKHOUSE_USER=monitoring
CLICKHOUSE_PASSWORD=change-me
CHM_AUTH_PROVIDER=none
```

Enable and start:

```bash
systemctl enable --now chmonitor
```

## Upgrading

```bash
cd /opt/chmonitor
git pull
bun install
bun run build
systemctl restart chmonitor
```

For breaking changes between major versions, see [Migrating to v0.3](/migrating/v0-3).
