Privacy-first, Agnostic Telemetry for Self-Hosted Software. Collect usage stats, verify active instances, and understand your user base without spying on them.
Website : https://self-hosted-metrics.com/
🖼️ 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/kolapsis/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/kolapsis/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/kolapsis/shm/main/migrations/001_init.sql -o migrations/001_init.sql curl -sL https://raw.githubusercontent.com/kolapsis/shm/main/migrations/002_applications.sql -o migrations/002_applications.sql
3. Start the services
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/kolapsis/shm/sdk
Implementation Example
package main import ( shm "github.com/kolapsis/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{} { // Replace with your actual metrics return map[string]interface{}{ "documents_created": 42, // e.g. db.CountDocs() "users_active": 10, // e.g. db.CountActive() "jobs_processed": 100, // e.g. worker.TotalJobs() } }) // 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 @kolapsis/shm-sdk
Implementation Example
import { SHMClient } from '@kolapsis/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(() => ({ // Replace with your actual metrics documents_created: 42, // e.g. db.countDocs() users_active: 10, // e.g. db.countActive() jobs_processed: 100, // e.g. worker.totalJobs() })); // 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
shm_identity.json. - Protocol: Sends a Heartbeat/Snapshot signed with the private key.
- Storage: PostgreSQL stores the raw JSON payload in a
jsonbcolumn. - UI: The server parses the JSON keys dynamically to build the table and graphs.
Identity File
The SDK automatically generates shm_identity.json on first run. This file contains:
{
"instance_id": "550e8400-e29b-41d4-a716-446655440000",
"private_key": "hex-encoded-ed25519-private-key",
"public_key": "hex-encoded-ed25519-public-key"
}Important for Docker/Kubernetes: The identity file must be persisted across container restarts. Mount a volume for the DataDir (Go) or dataDir (Node.js) configuration:
services: my-app: volumes: - ./data:/app/data # Persist identity file environment: - SHM_DATA_DIR=/app/data
If the identity file is lost, the instance will generate a new identity and the server will return 401 Unauthorized for requests signed with the old key.
🛡️ 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: falseconfig.
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.