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/
Features β’ Quick Start β’ SDK Integration β’ Architecture
Modern, dark-mode dashboard showing aggregated business metrics and system health.
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.
- π 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.
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
Display your SHM metrics in your README with embeddable SVG badges.
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.
Dashboard |
Details |
Graph |
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: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.sqldocker compose up -dAccess the dashboard at http://localhost:8080.
| 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 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.
Embed the telemetry client into your application.
go get github.com/btouchard/shm/sdkpackage 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
}Embed the telemetry client into your Node.js application.
npm install @btouchard/shm-sdkimport { 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.
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
jsonbcolumn. - UI: The server parses the JSON keys dynamically to build the table and graphs.
- 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.
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=trueThis allows end-users to opt-out of telemetry at the system level, regardless of the application's configuration.
| 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 |
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
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.