Перейти к содержимому

Server Configuration

Это содержимое пока не доступно на вашем языке.

Every setting has three places it can live: the YAML config file, an environment variable, and a command-line flag. A flag you type wins over the environment, which wins over the file, which wins over the built-in default. A flag’s default never overrides the file.

For a service install, put everything in the file and let the unit be ExecStart=/usr/local/bin/woho -config /etc/woho/woho-server.yaml.


/etc/woho/woho-server.yaml
RunningIf the file is missing
Default pathFine — flags, environment and defaults apply
-config <path>Error. A path you named must exist

config init writes a fully commented file with every setting: the defaults, plus whatever the environment and the flags you pass say.

Terminal window
woho config init \
-tls-cert /etc/woho/tunnel.crt -tls-key /etc/woho/tunnel.key \
-rp-id woho.example.com -rp-origin https://woho.example.com

It refuses to overwrite an existing file. -config - prints to stdout.

The key is the flag name with underscores.

http: '0.0.0.0:8080' # admin panel listen address
tcp: '0.0.0.0:9000' # agent tunnel listen address
db: '/var/lib/woho/woho.db' # SQLite file — absolute
tls_cert: '/etc/woho/tunnel.crt' # tunnel certificate and key: both or neither
tls_key: '/etc/woho/tunnel.key'
tunnel_insecure: false # cleartext tunnel; exclusive with the pair above
rp_id: 'woho.example.com' # WebAuthn relying party: the panel's hostname
rp_origin: 'https://woho.example.com' # scheme://host[:port]; empty derives from http
proxy_domain: '' # wildcard parent domain for Web forwards
metrics: '127.0.0.1:9090' # Prometheus address; '' disables
tunnel_max_pending: 256 # unregistered tunnel connections, server-wide
tunnel_max_pending_per_ip: 32 # the same, per source address
retain_admin_activity: 90 # days; 0 keeps forever
retain_connection_history: 90
retain_login_sessions: 90
retain_sessions: 90
record_terminals: false # record terminal output for replay
retain_transcripts: 30 # days to keep recordings
transcript_max_mb: 10 # cap per recording, 1–100
vacuum_free_percent: 25 # VACUUM after a purge at this much free space
session_idle_timeout: 2h # 2h, 30m; 0 disables
proxy_idle_timeout: 4h
trusted_proxy_cidrs: [] # reverse proxies whose X-Forwarded-For is believed
storage_dir: '/var/lib/woho/storage' # admins' server-side file area — absolute
log_level: 'info' # debug, info, warn, error
log_format: 'json' # json or text

The file is strict. Each of these stops the server with an error naming the key, never a silent fallback:

  • an unknown or duplicate key, a second YAML document
  • a value of the wrong type, including a bare number where a duration is expected — 2 is not two hours
  • a relative path for db, tls_cert, tls_key or storage_dir
  • a listen address with no port, an rp_origin with a path, a proxy_domain that is a URL
  • tls_cert / tls_key next to tunnel_insecure: true, one of the pair without the other, or neither
  • a negative count or duration, vacuum_free_percent above 100, transcript_max_mb outside 1–100, an invalid CIDR, an unknown log level or format

The file is read once, at startup.

Terminal window
vi /etc/woho/woho-server.yaml
woho config check -config /etc/woho/woho-server.yaml
systemctl restart woho-server

config check resolves the settings exactly as a start would and prints each one with where its value came from. It exits 1 with the message the server would have logged, so a typo is caught before the restart.

The file holds no secret — tls_key is a path. config init writes it 0644 owned by root; the server warns if it is writable by group or others.


FlagDefaultDescription
-config/etc/woho/woho-server.yaml when it existsYAML config file
-http0.0.0.0:8080Admin panel bind address
-tcp0.0.0.0:9000Agent tunnel bind address
-dbwoho.dbSQLite database file
-storage-dirstorageAdmins’ server-side file area; each admin gets <dir>/files/<username>

The tunnel has no default transport; the server will not start without one of these.

FlagDefaultDescription
-tls-certenv WOHO_TLS_CERTPEM certificate for the tunnel
-tls-keyenv WOHO_TLS_KEYPEM private key
-tunnel-insecurefalseCleartext tunnel. Agents must use -insecure

On startup the server logs the certificate’s SPKI pin; agents need it only when the certificate is not publicly trusted. Certificates are read once — restart after renewing.

FlagDefaultDescription
-rp-idlocalhostRelying party ID — the panel’s hostname, no scheme or port
-rp-originderived from -httpFull origin, https://woho.example.com

-rp-id must be a domain name that matches, or is a parent of, the origin’s host. Passkeys need HTTPS, and stop working if rp_id changes.

FlagDefaultDescription
-proxy-domainnoneParent domain serving forwarded content, one hostname per forward — below
-proxy-idle-timeout4hClose a forward with no traffic for this long; 0 disables
FlagDefaultDescription
-tunnel-max-pending256Tunnel connections that have not yet registered, server-wide; 0 disables
-tunnel-max-pending-per-ip32The same, per source address

A connection counts only from accept until the agent registers. The caps bound registrations in flight, not the fleet: a thousand connected agents hold no slots. Refusals are counted as woho_tcp_connections_rejected_total{reason="global"|"per_ip"}.

FlagDefaultTable
-retain-admin-activity90 daysAdmin actions — the audit trail
-retain-connection-history90 daysAgent connection attempts
-retain-login-sessions90 daysAdmin logins
-retain-sessions90 daysEnded agent sessions. A live session is never purged
-retain-transcripts30 daysTerminal recordings
-vacuum-free-percent25Compact the database after the daily purge when at least this share of it is free (and 8 MiB reclaimable); 0 never

0 on any -retain-* flag keeps forever. The purge runs at startup and every 24 hours. A vacuum locks the database while it rewrites the file and needs free disk roughly equal to its size; the threshold is sized so it fires once, on the first upgrade of a long-running install, and then effectively never.

FlagDefaultDescription
-record-terminalsfalseRecord every terminal’s output for replay from the machine’s timeline
-retain-transcripts30Days to keep recordings; 0 forever
-transcript-max-mb10Largest recording, 1–100 MB. Past it the rest is not stored and the recording is marked truncated
FlagDefaultDescription
-session-idle-timeout2hLog an admin out after this much inactivity; 0 disables. Terminal traffic counts as activity. The absolute 24-hour lifetime applies regardless
-trusted-proxy-cidrsnoneComma-separated CIDRs of reverse proxies whose X-Forwarded-For / X-Real-IP are believed. Empty ignores the headers
-log-levelinfodebug, info, warn, error
-log-formatjsonjson or text
FlagDefaultDescription
-metrics127.0.0.1:9090Prometheus endpoint, or "" to disable
CommandDescription
woho config init [-config PATH] [flags]Write a commented config file. Refuses to overwrite; -config - prints to stdout
woho config check [-config PATH] [flags]Validate everything, load the certificate, print each setting with its source and the SPKI pin
woho -versionVersion, build time, commit
Terminal window
# Production, on the config file
woho -config /etc/woho/woho-server.yaml
# One setting for one run, on top of the file
woho -config /etc/woho/woho-server.yaml -log-level debug
# Local trial
woho -http :8080 -tcp :9000 -db /var/lib/woho/woho.db -tunnel-insecure

A typed flag beats them; they beat the file.

VariableFlag
WOHO_TLS_CERT, WOHO_TLS_KEY-tls-cert, -tls-key
WOHO_TUNNEL_INSECURE-tunnel-insecure (only the exact value true)
WOHO_PROXY_DOMAIN-proxy-domain
WOHO_PROXY_IDLE_TIMEOUT-proxy-idle-timeout
METRICS_ADDR-metrics
WOHO_TUNNEL_MAX_PENDING, WOHO_TUNNEL_MAX_PENDING_PER_IP-tunnel-max-pending, -tunnel-max-pending-per-ip
WOHO_RETAIN_ADMIN_ACTIVITY_DAYS-retain-admin-activity
WOHO_RETAIN_CONNECTION_HISTORY_DAYS-retain-connection-history
WOHO_RETAIN_LOGIN_SESSIONS_DAYS-retain-login-sessions
WOHO_RETAIN_SESSIONS_DAYS-retain-sessions
WOHO_RECORD_TERMINALS-record-terminals (only true)
WOHO_RETAIN_TRANSCRIPTS_DAYS-retain-transcripts
WOHO_TRANSCRIPT_MAX_MB-transcript-max-mb
WOHO_VACUUM_FREE_PERCENT-vacuum-free-percent
WOHO_SESSION_IDLE_TIMEOUT-session-idle-timeout
TRUSTED_PROXY_CIDRS-trusted-proxy-cidrs
WOHO_STORAGE_DIR-storage-dir
DEV_MODEno flag — see below

An empty environment value means unset. A file value shadowed by an environment variable is named in the startup log and by config check.

DEV_MODE=true is for development only: unbundled assets, cookies over HTTP, and debug / text as the logging defaults.


The admin panel’s HTTPS is handled by the reverse proxy; the tunnel on port 9000 is configured in WoHo itself.

Use a certificate that matches the hostname agents connect to — usually the panel’s own:

tls_cert: '/etc/letsencrypt/live/woho.example.com/fullchain.pem'
tls_key: '/etc/letsencrypt/live/woho.example.com/privkey.pem'

The woho user must be able to read the key. If it cannot, copy the pair:

Terminal window
install -o woho -g woho -m 0400 privkey.pem /etc/woho/tunnel.key
install -o woho -g woho -m 0444 fullchain.pem /etc/woho/tunnel.crt

Agents then need no TLS option: they verify against the system root store. For a server without public DNS, generate a self-signed certificate and let agents pin it:

Terminal window
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -days 3650 \
-keyout /etc/woho/tunnel.key -out /etc/woho/tunnel.crt \
-subj "/CN=woho.internal" -addext "subjectAltName=DNS:woho.internal,IP:10.0.0.5"

The server logs the SPKI pin at startup; agents pass it with -pin. Pinning checks the key only, so the certificate’s expiry does not matter. -pin takes several comma-separated values, so a key can be rotated with no outage: add the new pin on every agent, switch the server, remove the old one.

Confirm from outside:

Terminal window
openssl s_client -connect woho.example.com:9000 -tls1_3 </dev/null

Without proxy_domain, Web tabs are served from /proxy/ on the panel origin inside a sandbox with an opaque origin — isolated from the panel, but also unable to use their own cookies or storage. With it, every forward gets its own hostname:

https://<forward-id>.proxy.woho.example.com/

It must be hostnames, not ports: cookies are scoped by host and ignore the port, so a second port on the panel’s hostname would receive the panel’s session cookie.

You need:

  1. A wildcard DNS record — *.proxy.woho.example.com pointing at the server.
  2. A certificate covering the wildcard — a *.proxy.woho.example.com SAN, which means DNS-01 validation.
  3. The reverse proxy passing both names to the same WoHo port with the original Host intact — there is no second listener.
server {
server_name woho.example.com *.proxy.woho.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

Forward hostnames answer 404 for /api/…, and the panel host answers 404 for /proxy/…. proxy_domain must not cover the panel’s own hostname.


SQLite, created on first start with migrations applied and the bootstrap admin / admin account seeded. The file and its WAL sidecars are held at mode 0600.

Terminal window
# Backup while the server runs
sqlite3 /var/lib/woho/woho.db ".backup /backup/woho-$(date +%Y%m%d).db"
# Integrity
sqlite3 /var/lib/woho/woho.db "PRAGMA integrity_check;"

Migrations run forward only: back up before upgrading if you may need to roll back.


Preserves WebSockets, large file transfers, and the original host.

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream woho_http {
server 127.0.0.1:8080;
}
server {
listen 443 ssl;
server_name woho.example.com;
ssl_certificate /etc/letsencrypt/live/woho.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/woho.example.com/privkey.pem;
client_max_body_size 512m;
client_body_timeout 180s;
send_timeout 180s;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location = /ws {
proxy_pass http://woho_http;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location = /api/files/client-upload {
proxy_pass http://woho_http;
proxy_request_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location / {
proxy_pass http://woho_http;
proxy_read_timeout 360s;
proxy_send_timeout 360s;
proxy_buffering off;
proxy_request_buffering off;
}
}

Then in the config file:

trusted_proxy_cidrs: ['127.0.0.1/32'] # add '::1/128' if nginx connects over IPv6
rp_id: 'woho.example.com'
rp_origin: 'https://woho.example.com'

With trusted CIDRs set, the login rate limit and History use the forwarded address; X-Forwarded-For is read from the right, taking the first address that is not a trusted proxy. The agent tunnel port 9000 stays directly reachable; it does not go through an HTTP proxy.


  • Change the admin / admin password on first login, before the panel is reachable from anywhere untrusted
  • Delete the bootstrap admin once you have a personal account
  • Tunnel TLS with tls_cert / tls_key; tunnel_insecure only for a local trial
  • HTTPS for the panel, with trusted_proxy_cidrs, rp_id and rp_origin set
  • proxy_domain with wildcard DNS and certificate if admins use Web tabs
  • Keep Require Authentication Key on
  • A passkey for every admin
  • metrics on loopback, or ''
  • Port 8080 reachable only through the reverse proxy; port 9000 open to the managed machines
  • Decide knowingly about record_terminals