Self-Hosted Metrics (SHM)
πΌοΈ Dashboard Preview
Modern, dark-mode dashboard showing aggregated business metrics and system health.
π Why SHM?
When you distribute self-hosted software (on-premise), you fly blind. You don't know how many instances are running, which versions are active, or if your features are actually used.
SHM solves this with a lightweight, secure approach:
- Privacy First: Collects aggregate counters, never user content.
- Agnostic: Send any JSON payload. The dashboard adapts automatically.
- Secure: Every request is signed with an Ed25519 keypair generated on the client.
- Zero-Config Dashboard: Single Go binary with embedded UI. No frontend build required.
β¨ Features
- π Cryptographic Identity: Instances generate a unique ID and keypair. No spoofing possible.
- π¦ Multi-App Support: Track multiple software products on a single SHM server.
- β GitHub Stars: Automatically fetch and display GitHub repository stars for your applications.
- π¨ Dynamic Dashboard: Send
{"pizzas_eaten": 10} and SHM automatically creates the KPI cards and table columns.
- βοΈ Ops vs Business Separation: Automatically distinguishes between business metrics (KPIs) and system metrics (CPU, RAM, OS).
- π³ Docker Native: Runs anywhere with a simple
docker-compose.
π£οΈ Feedback & Real-World Usage
SHM is young and evolving.
If you are using it (even in dev), your feedback is extremely valuable.
π Share your experience here:
https://github.com/btouchard/shm/discussions
What matters most:
- real use cases
- missing metrics
- integration friction
- what you'd expect before using it in production
ποΈ Public Badges
Display your SHM metrics in your README with embeddable SVG badges.
Available Badges
Active Instances


Most Used Version


Aggregated Metrics


Combined Stats


Customization
All badges support query parameters for customization:
?color=00D084 - Custom hex color (without #)
?label=custom - Custom label text
Example:

Note: Replace your-shm-server.example.com with your actual SHM server URL and your-app with your application slug.
β‘ Quick Start (Server)
1. Create the configuration
Create a compose.yml file:
name: shm
services:
db:
image: postgres:15-alpine
container_name: shm-db
restart: unless-stopped
environment:
POSTGRES_USER: shm
POSTGRES_PASSWORD: ${DB_PASSWORD:-change-me-in-production}
POSTGRES_DB: metrics
volumes:
- postgres_data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shm -d metrics"]
interval: 10s
timeout: 5s
retries: 5
app:
image: ghcr.io/btouchard/shm:latest
# Or build from source:
# build:
# context: .
# dockerfile: Dockerfile
container_name: shm-app
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
SHM_DB_DSN: "postgres://shm:${DB_PASSWORD:-change-me-in-production}@db:5432/metrics?sslmode=disable"
PORT: "8080"
ports:
- "8080:8080"
volumes:
postgres_data:
2. Download migrations
mkdir -p migrations
curl -sL https://raw.githubusercontent.com/btouchard/shm/main/migrations/001_init.sql -o migrations/001_init.sql
curl -sL https://raw.githubusercontent.com/btouchard/shm/main/migrations/002_applications.sql -o migrations/002_applications.sql
3. Start the services
docker compose up -d
Access the dashboard at http://localhost:8080.
Environment Variables
Core Settings
| Variable |
Default |
Description |
SHM_DB_DSN |
(required) |
PostgreSQL connection string |
PORT |
8080 |
HTTP server port |
GITHUB_TOKEN |
- |
GitHub Personal Access Token for higher API rate limits (5000 req/h instead of 60 req/h) |
Rate Limiting
Rate limiting is enabled by default to protect against abuse.
| Variable |
Default |
Description |
SHM_RATELIMIT_ENABLED |
true |
Enable/disable rate limiting |
SHM_RATELIMIT_CLEANUP_INTERVAL |
10m |
Interval for cleaning up expired limiters |
SHM_RATELIMIT_REGISTER_REQUESTS |
5 |
Max requests per period for /v1/register and /v1/activate |
SHM_RATELIMIT_REGISTER_PERIOD |
1m |
Time window for register endpoints |
SHM_RATELIMIT_REGISTER_BURST |
2 |
Burst allowance for register endpoints |
SHM_RATELIMIT_SNAPSHOT_REQUESTS |
1 |
Max requests per period for /v1/snapshot (per instance) |
SHM_RATELIMIT_SNAPSHOT_PERIOD |
1m |
Time window for snapshot endpoint |
SHM_RATELIMIT_SNAPSHOT_BURST |
2 |
Burst allowance for snapshot endpoint |
SHM_RATELIMIT_ADMIN_REQUESTS |
30 |
Max requests per period for /api/v1/admin/* |
SHM_RATELIMIT_ADMIN_PERIOD |
1m |
Time window for admin endpoints |
SHM_RATELIMIT_ADMIN_BURST |
10 |
Burst allowance for admin endpoints |
SHM_RATELIMIT_BRUTEFORCE_THRESHOLD |
5 |
Failed auth attempts before IP ban |
SHM_RATELIMIT_BRUTEFORCE_BAN |
15m |
Duration of IP ban after brute-force detection |
See docs/DEPLOYMENT.md for deployment examples and security configuration.
π¦ SDK Integration (Go)
Embed the telemetry client into your application.
go get github.com/btouchard/shm/sdk
Implementation Example
package main
import (
shm "github.com/btouchard/shm/sdk/golang"
)
func main() {
// 1. Configure the client
telemetry, err := shm.New(shm.Config{
ServerURL: "https://metrics.your-domain.com",
AppName: "MyAwesomeApp",
AppVersion: "1.0.0",
Environment: "production",
Enabled: true,
})
if err != nil {
panic(err)
}
// 2. Define your metrics (Callback)
// This runs every hour (configurable)
telemetry.SetProvider(func() map[string]interface{} {
// Fetch your DB stats here
return map[string]interface{}{
"documents_created": db.CountDocs(), // Business Metric
"users_active": db.CountActive(), // Business Metric
"jobs_processed": worker.TotalJobs(), // Business Metric
}
})
// 3. Start in background
// SHM automatically adds System metrics (CPU, RAM, OS, Arch...)
go telemetry.Start(context.Background())
// ... run your app
}
π¦ SDK Integration (Node.js / TypeScript)
Embed the telemetry client into your Node.js application.

npm install @btouchard/shm-sdk
Implementation Example
import { SHMClient } from '@btouchard/shm-sdk';
// 1. Configure the client
const telemetry = new SHMClient({
serverUrl: 'https://metrics.your-domain.com',
appName: 'MyAwesomeApp',
appVersion: '1.0.0',
environment: 'production',
enabled: true,
});
// 2. Define your metrics (Callback)
// This runs every hour (configurable)
telemetry.setProvider(() => ({
documents_created: db.countDocs(), // Business Metric
users_active: db.countActive(), // Business Metric
jobs_processed: worker.totalJobs(), // Business Metric
}));
// 3. Start in background
// SHM automatically adds System metrics (CPU, RAM, OS, Arch...)
const controller = telemetry.start();
// To stop later:
// controller.abort();
Note: Requires Node.js >= 22 LTS. Zero external dependencies.
ποΈ Architecture
The system is designed to be as simple as possible to maintain.
graph LR
A[Your App Instance] -- Signed JSON (HTTPS) --> B[SHM Server]
B -- Store JSONB --> C[(PostgreSQL)]
D[Admin Dashboard] -- Read API --> B
- Client: Generates Ed25519 keys on first run. Stores identity in
metrics_identity.json.
- Protocol: Sends a Heartbeat/Snapshot signed with the private key.
- Storage: PostgreSQL stores the raw JSON payload in a
jsonb column.
- UI: The server parses the JSON keys dynamically to build the table and graphs.
π‘οΈ Security & Privacy
- No PII: We do not collect IP addresses (unless you configure your reverse proxy to log them), hostnames, or usernames.
- Authentication: The server uses a "Trust on First Use" (TOFU) or explicit activation model. Once an ID is registered with a Public Key, only that key can sign updates.
- Transparency: You should always inform your users that telemetry is active and allow them to opt-out via the
Enabled: false config.
Respecting User Privacy with DO_NOT_TRACK
All SHM clients (Go, Node.js, and future SDKs) respect the standard DO_NOT_TRACK environment variable. When set to true or 1, all telemetry is completely disabled β no data is sent to the server.
# Disable all telemetry
export DO_NOT_TRACK=true
This allows end-users to opt-out of telemetry at the system level, regardless of the application's configuration.
Environment Variables (Client-side)
| Variable |
Effect |
DO_NOT_TRACK=true or 1 |
Completely disables telemetry (no network requests) |
SHM_COLLECT_SYSTEM_METRICS=false or 0 |
Disables automatic system metrics collection (OS, CPU, memory) while still sending custom metrics |
π€ Contributing
Contributions are welcome! Please read the contributing guidelines first.
- Fork it
- Create your feature branch (
git checkout -b feature/amazing-feature)
- Commit your changes (
git commit -m 'Add some amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
π License
Distributed under the AGPLv3 License. See LICENSE for more information.
The SDK (in the /sdk subdirectory) is distributed under the MIT License for easier integration into your projects.
Built with β€οΈ by btouchard using Go, AlpineJS & Tailwind.