led

command module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 3 Imported by: 0

README

CI Release image

A self-hosted short link, mailbox, and DNS management service in a single Go binary with an embedded React dashboard. Inspired by wr.do and dub, rebuilt to ship as one binary / one Docker image.

Single-user today. The schema already carries an owner id on every row, so multi-user / multi-org (under a commercial license) drops in without a migration.

Features

  • Overview dashboard — the home page: total clicks / links / mailboxes / domains at a glance, a 30-day clicks chart, top links, device & country breakdowns, and recent mail.

  • Short links — custom or random slugs, host picked from your link-enabled domains, password protection, expiry and expired-URL fallback, click limits, advanced routing rules (by geo, device, os, language), tags + archive, a built-in UTM builder, one-click title fetch from the destination, QR codes, copy-to-clipboard, and basic click analytics (time series + referer / country / device / browser / bot detection).

  • Mailboxes — addresses built from your mail-enabled domains, receive mail via Cloudflare Email Routing, read it (with attachment list + raw .eml download), reply, mark-all-read, and send through multiple configured SMTP relays.

  • DNSone-click sync of every Cloudflare zone + records, full record CRUD with type/text filtering and subdomain presets (short-link / email), all behind a provider abstraction (Cloudflare and DNSPod today; Aliyun / Route53 slot in). Record notes map to the provider's native comment / remark field.

  • Open API tokens — issue bearer tokens for the JSON API; every data endpoint accepts either a session cookie or Authorization: Bearer led_…. Only a SHA-256 hash is stored, and the raw token is shown once at creation.

  • Notification channels — receive alerts via Telegram Bot or Webhook when new mail arrives, managed entirely from the dashboard.

  • One binary — pure-Go SQLite (no cgo), React dashboard embedded via go:embed. Postgres supported by flipping two env vars.

Quick start

cp .env.example .env          # set LED_SECRET_KEY + LED_ADMIN_PASSWORD
make release                  # build web + binary  (or: make all)
./led                         # serves dashboard + API + redirects on :8080

Or with Docker:

docker compose up --build     # reads .env, persists data in a volume

The default Dockerfile builds the dashboard from source (Node) and the binary (Go) into a distroless image. If you've already built the web assets (make web) — or build them in a separate CI step — use the binary-only Dockerfile for a tiny (~19 MB) scratch image with no Node stage:

docker build -f deploy/Dockerfile.binary -t led:latest .

Open http://localhost:8080 (redirects to /admin), sign in with LED_ADMIN_USER / LED_ADMIN_PASSWORD.

Architecture

            ┌──────────────────────────── led (single binary) ────────────────────────────┐
  browser → │  host router                                                                  │
            │   ├─ /api/*        → JSON API (auth, links, domains, mailboxes, emails)       │
            │   ├─ /admin/*      → embedded React dashboard (SPA)                            │
            │   └─ /{slug}       → 302 redirect + async click event (root = link namespace) │
            │                                                                                │
            │  GORM ──┬─ SQLite (pure-Go, default)   DNS ─ provider iface ─ Cloudflare API   │
            │         └─ Postgres (optional)         Mail ─ inbound webhook + SMTP sender     │
            └────────────────────────────────────────────────────────────────────────────────┘
                          ▲
   Cloudflare Email Routing → Worker → POST /api/email/inbound  (deploy/cloudflare-email-worker.js)
Routing
  • The dashboard lives under /admin so the entire root namespace belongs to short links — https://go.example.com/abc is never shadowed by a dashboard route.
  • / redirects to /admin/.
  • LED_ADMIN_HOST (e.g. admin.example.com), when set, restricts /admin to that host so pure link hosts don't expose the dashboard; unset = served anywhere.
  • Reserved slugs (admin, api, assets, plus any you configure in Settings) can't be used for short links.

Email receiving (Cloudflare)

led receives mail through Cloudflare Email Routing rather than running its own SMTP server, so no port 25 / MX / anti-spam ops are required:

  1. Enable Email Routing on your domain in Cloudflare.
  2. Deploy deploy/cloudflare-email-worker.js as a Worker; set LED_ENDPOINT and LED_TOKEN (must match the Inbound Token you configure in Settings).
  3. Point a catch-all route at the Worker.

Mark a domain as Accept email in the dashboard; if Catch-all is enabled in Settings, mail to any unknown address on it will automatically create a new mailbox.

Configuration

All configuration is via environment variables — see .env.example.

Development

# terminal 1: API on :8080
LED_SECRET_KEY=dev LED_ADMIN_PASSWORD=dev go run .
# terminal 2: Vite dev server with hot reload, proxies /api → :8080
make dev

Roadmap

  • P0 scaffold — auth, DB abstraction, embedded SPA, Docker
  • P1 short links + advanced routing (geo/device) + analytics + QR
  • P2 DNS management (Cloudflare, multi-provider interface)
  • P3 email — Cloudflare inbound webhook, inbox UI, multiple SMTP senders
  • P4 open API tokens (bearer auth), system notification channels (Telegram, Webhook)
  • P5 multi-tenant / multi-org (commercial license)

License

MIT

Documentation

Overview

Command led is a single-binary domain / short-link / email management service (link · email · domain). It serves an embedded React dashboard, a JSON API, and a short-link redirector from one process.

This is the open-core binary: it runs the app with no Pro plugins. The commercial build (private led-core module) reuses the same app package and registers additional plugins before Run — see the plugin package.

Directories

Path Synopsis
Package app is the public composition root for led.
Package app is the public composition root for led.
Package config loads runtime configuration from environment variables.
Package config loads runtime configuration from environment variables.
internal
api
Package api implements led's JSON HTTP API.
Package api implements led's JSON HTTP API.
auth
Package auth implements single-user session authentication using a signed cookie.
Package auth implements single-user session authentication using a signed cookie.
crypto
Package crypto provides AES-GCM encryption for secrets at rest and HMAC signing for session cookies.
Package crypto provides AES-GCM encryption for secrets at rest and HMAC signing for session cookies.
db
Package db opens the configured database (SQLite or Postgres) through GORM and runs migrations.
Package db opens the configured database (SQLite or Postgres) through GORM and runs migrations.
dnsprovider
Package dnsprovider abstracts DNS record management across providers.
Package dnsprovider abstracts DNS record management across providers.
geo
Package geo resolves a client IP into country/city (optional, via a MaxMind GeoLite2 mmdb) and parses a User-Agent into device/browser/os.
Package geo resolves a client IP into country/city (optional, via a MaxMind GeoLite2 mmdb) and parses a User-Agent into device/browser/os.
mail
Package mail parses inbound MIME messages and sends outbound mail via SMTP.
Package mail parses inbound MIME messages and sends outbound mail via SMTP.
models
Package models defines the GORM persistence schema for led.
Package models defines the GORM persistence schema for led.
notify
Package notify delivers best-effort notifications about led events.
Package notify delivers best-effort notifications about led events.
server
Package server wires the API, short-link redirector, and embedded SPA behind a single http.Handler.
Package server wires the API, short-link redirector, and embedded SPA behind a single http.Handler.
shortlink
Package shortlink resolves slugs to targets, records click events asynchronously, and renders the password gate when a link is protected.
Package shortlink resolves slugs to targets, records click events asynchronously, and renders the password gate when a link is protected.
Package plugin defines the contract a commercial (Pro) module implements to extend led without forking it.
Package plugin defines the contract a commercial (Pro) module implements to extend led without forking it.
Package webembed embeds the built React dashboard into the binary.
Package webembed embeds the built React dashboard into the binary.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL