Open-Source Cold Email & Warmup Platform
Warmbly 是开源的冷邮件平台:温暖您的邮箱、发送活动、并对每条回复进行分流。可在我们的云端运行,也可自行托管整套系统。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证A modern, high-performance, self-hosted URL shortener written in Go, backed by PostgreSQL or SQLite with optional Redis. Ships as a single static binary with custom QR codes, link rotation, local MaxMind GeoIP analytics, automatic phishing screening, and strict security hardening.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗start_at and end_at parameters, or automatic time-to-live (TTL) limits.Every link request undergoes security screening and database optimization before resolving:
graph TD
A[User requests short code /ABC123] --> B{Code exists?}
B -- No --> C[404 Not Found]
B -- Yes --> D{Enabled and within its schedule?}
D -- Disabled / expired / outside window --> E[410 Gone]
D -- Active --> F{Password protected?}
F -- Yes, not yet unlocked --> G[Prompt for the link password]
G -- Invalid --> G
G -- Valid --> H{Device-specific target?}
F -- No --> H
H -- iOS / Android --> I[Use the matching device URL]
H -- Neither --> J{Rotation targets set?}
J -- Yes --> K[Pick a rotation target at random]
J -- No --> L[Use the main URL]
I --> M{Destination on the blocklist?}
K --> M
L --> M
M -- Blocked --> N[403 Forbidden]
M -- Safe --> O[Record click: country, browser, platform, referrer]
O --> P{Preview mode?}
P -- Yes --> Q[Preview page: confirm before leaving]
P -- No --> R[Interstitial with a 5s countdown, then navigate]
The destination is re-checked against the blocklist on every request, not only at creation time, so a link whose target is added to a phishing feed later stops resolving immediately.
net/http routing and html/template rendering — no web frameworkpgx (robust relational store), Redis (shared rate limiting & geo cache), SQLite via the pure-Go modernc.org/sqlite driver (resilient local fallback, no cgo)/metrics (protect it with METRICS_TOKEN or a firewall if the app port is reachable)cmd/redrx/ Entrypoint, logging, background blocklist refresh
internal/config/ Environment configuration
internal/store/ Database access; schema matches the previous SQLAlchemy models
internal/security/ Werkzeug-compatible password hashing
internal/session/ Signed cookie sessions and CSRF tokens
internal/ratelimit/ Flask-Limiter-compatible limit parsing, memory and Redis backends
internal/safety/ Blocked-domain and phishing-feed enforcement
internal/geo/ MaxMind lookups and IP anonymisation
internal/qr/ QR rendering with colours and logo overlay
internal/shortcode/ Short code generation and validation
internal/web/ HTTP handlers, middleware, templates and static assets
Ensure safe execution by loading our production-ready image verified with strict security hashes:
docker-compose.ghcr.yml as your template:
services:
app:
image: ghcr.io/arumes31/redrx:latest@sha256:0e2de80198eda6ed1b5b601d4def4dff4ce5e47c6051a81876a4ed7b5addcbc5
ports:
- "5000:5000"
environment:
- SECRET_KEY=${SECRET_KEY:?generate with openssl rand -hex 32}
- DATABASE_URL=postgresql://redrx:${POSTGRES_PASSWORD:?}@db:5432/redrx_db
- BASE_DOMAIN=short.yourdomain.com
docker-compose up -d
docker-compose up --build
The application will boot and expose itself at http://localhost:5000.
The application container runs as UID/GID 10001:10001; it does not support
PUID or PGID overrides. A bind mount hides the ownership prepared in the
image, so existing host directories mounted at /app/data or /app/db must be
writable by that UID before the container starts:
Upgrades must also migrate an existing blocklist_data named volume once,
before starting the new container. Refreshes create and rename temporary files
in /app/data, so the mounted directory must be owned by UID/GID 10001:10001:
docker compose run --rm --no-deps --user 0:0 --cap-add CHOWN \
--entrypoint sh web -c 'chown -R 10001:10001 /app/data'
sudo chown -R 10001:10001 /srv/redrx/data /srv/redrx/db
sudo find /srv/redrx/data /srv/redrx/db -type d -exec chmod 0750 {} \;
sudo find /srv/redrx/data /srv/redrx/db -type f -exec chmod 0640 {} \;
Use only the paths you actually bind mount; /app/db is needed for SQLite, not
the PostgreSQL setup in the supplied Compose files. Create new directories with
the correct ownership so Docker does not create them as root:
sudo install -d -o 10001 -g 10001 -m 0750 /srv/redrx/data /srv/redrx/db
The GeoIP mount is read-only in the application container. If it is also a host
bind mount, its directories need execute/read permission and its database files
need read permission for UID 10001; keep ownership with the GeoIP updater
instead of changing it to the application user.
Requires Go 1.26.5 or newer, the minimum declared by the go directive in go.mod. Dependencies are pinned in go.mod and checksum-verified through go.sum.
go mod download
go build ./cmd/redrx
The server defaults to SQLite at db/shortener.db, so no external services are needed for development:
# Windows PowerShell
$env:SECRET_KEY = "dev-secret"
$env:REDRX_DEBUG = "true"
$env:BASE_DOMAIN = "localhost:5000"
go run ./cmd/redrx
# Linux / macOS
SECRET_KEY=dev-secret REDRX_DEBUG=true BASE_DOMAIN=localhost:5000 go run ./cmd/redrx
The application boots at http://localhost:5000. Setting REDRX_DEBUG (or the legacy FLASK_DEBUG) relaxes the canonical-domain redirect, allows a fallback SECRET_KEY, and drops the Secure flag from session cookies so plain HTTP works locally.
go test ./... # full suite
go test -race ./... # with the race detector
go vet ./... # static analysis
The suite includes a committed SQLite fixture written by the previous Python application (internal/store/testdata/legacy_python.db). Tests read and write it to prove that existing databases, password hashes and timestamp formats remain compatible.
| Category | Variable | Default | Description |
|---|---|---|---|
| Core | SECRET_KEY | - | Strong cryptographic key for session signing and hashing. Enforced in production; documented placeholder values are refused, and keys under 32 characters log a warning. Rotating it logs everyone out and invalidates stored TOTP secrets and recovery codes. |
| Core | ALLOW_WEAK_SECRET_KEY | false | Escape hatch that lets an existing deployment boot with a placeholder SECRET_KEY while it plans a rotation. |
| Domain | BASE_DOMAIN | short.example.com | Base host string used when formatting shortened URLs. |
| GeoIP | MAXMIND_LICENSE_KEY | - | Required to download the GeoIP dataset and update in background. |
| Phishing | ENABLE_PHISHING_CHECK | true | Enables domain protection against real-time blacklists. |
| Phishing | ENABLE_AUTO_REMOVE_PHISHING | false | Automatically removes links that redirect to verified phishing domains. |
| Database | DATABASE_URL | - | PostgreSQL URI (e.g. postgresql://user:pass@host:5432/db). Defaults to SQLite local file fallback. |
| Limits | RATELIMIT_STORAGE_URL | redis://redis:6379 | Rate limiting backend. Can fall back to local storage memory:// in dev. The compose files build it from REDIS_PASSWORD. |
| Limits | REDIS_PASSWORD | - | Compose only, required: the password the bundled Redis is started with (--requirepass). Keep it URL-safe. |
| Limits | RATELIMIT_LOGIN_ACCOUNT | 20 per hour | Per-account budget for the password and TOTP login steps, in addition to the per-IP . |
Rate limiting, analytics and the abuse controls all key off the client IP, so
redrx has to be told who is allowed to tell it what that IP is. By default it
trusts nobody and uses the peer address, which is correct for a directly
exposed service: a client that can set its own X-Forwarded-For can otherwise
pick its own rate-limit bucket and bypass every limit, including the one in
front of login.
For the common Cloudflare → nginx → redrx chain:
TRUSTED_PROXIES=172.18.0.0/16 # nginx, as redrx sees it
USE_CLOUDFLARE=true
USE_CLOUDFLARE matters more than it looks. nginx appends the address it
received from to X-Forwarded-For, so the header arrives as
<visitor>, <cloudflare-edge> — the last entry is a Cloudflare server, not
your user. CF-Connecting-IP names the real visitor, so redrx prefers it. The
alternative is to add Cloudflare's published ranges to TRUSTED_PROXIES, which
lets redrx walk back past them; without one of the two, every visitor on the
internet shares the handful of buckets belonging to the edge servers.
Rate limits use the same syntax as before ("200 per day;50 per hour", "10 per minute", "5/hour"). RATELIMIT_STORAGE_URL accepts memory:// or a redis:// URL; when Redis is configured it also backs the GeoIP lookup cache. If Redis is unreachable at boot the service logs a warning and falls back to in-memory limiting rather than refusing to start, and if it becomes unreachable later each request is counted in memory on that replica, so the limits stay in force (per replica) instead of switching off.
For the full detailed documentation including parameter tables and system metrics, visit the /api-docs page on your running Redrx instance.
Include your personal API key in request headers. Generate it from the dashboard: only a hash is stored, so the key is shown once, when it is generated. Keys created before this change keep working.
X-API-KEY: your_api_key_here
POST /api/v1/shorten
Payload:
{
"long_url": "https://example.com/my-long-link",
"custom_code": "my-code",
"code_length": 6,
"preview_mode": true,
"stats_enabled": true,
"rotate_targets": ["https://alt1.com", "https://alt2.com"],
"ios_target_url": "https://apps.apple.com/app/id123",
"android_target_url": "https://play.google.com/store/apps/details?id=com.example",
"password": "secret-password",
"expiry_hours": 24,
"start_at": "2026-06-05T22:00:00Z",
"end_at": "2026-06-30T23:59:59Z"
}
Response (201 Created):
{
"short_code": "my-code",
"short_url": "https://short.example.com/my-code",
"long_url": "https://example.com/my-long-link",
"rotate_targets": ["https://alt1.com", "https://alt2.com"],
"ios_target_url": "https://apps.apple.com/app/id123",
"android_target_url": "https://play.google.com/store/apps/details?id=com.example",
"expires_at": "2026-06-06T22:00:00+00:00",
"start_at": "2026-06-05T22:00:00+00:00",
"end_at": "2026-06-30T23:59:59+00:00",
"password_protected": true,
"preview_mode": true,
"stats_enabled": true
}
GET /api/v1/<short_code>
Response (200 OK):
{
"short_code": "my-code",
"short_url": "https://short.example.com/my-code",
"long_url": "https://example.com/my-long-link",
"rotate_targets": ["https://alt1.com", "https://alt2.com"],
"ios_target_url": "https://apps.apple.com/app/id123",
"android_target_url": "https://play.google.com/store/apps/details?id=com.example",
"preview_mode": true,
"stats_enabled": true,
"clicks_count": 42,
"clicks": 42,
"created_at": "2026-06-05T22:00:00+00:00",
"expires_at": "2026-06-06T22:00:00+00:00",
"start_at": "2026-06-05T22:00:00+00:00",
"end_at": "2026-06-30T23:59:59+00:00",
"active": true
}
gosec, govulncheck and CodeQL run on every push and nightly.go.sum pins a checksum for every dependency; CI runs go mod verify, and Trivy and Gitleaks scan the tree.HttpOnly, SameSite=Lax, and Secure outside debug. CSRF tokens are per-session, constant-time compared, and rotated on login.ANONYMIZE_LOGS masks them in log output.This project is licensed under the MIT License. See LICENSE for details.
RATELIMIT_LOGIN| Access | DISABLE_ANONYMOUS_CREATE | false | When true, only authenticated users can shorten links. |
| Access | DISABLE_REGISTRATION | false | When true, public registration routes are disabled. |
| Abuse prevention | ANONYMOUS_POW_DIFFICULTY | 16 | Proof-of-work difficulty for anonymous link creation; 0 disables it, maximum 28. |
| Privacy | ENABLE_CONSENT_BANNER | false | Ask visitors for consent before recording anonymous click analytics. |
| Privacy | HONOR_DO_NOT_TRACK | true | Skip click analytics whenever the browser sends DNT: 1. |
| Proxy | TRUSTED_PROXIES | - | Peers whose X-Forwarded-* / CF-* headers are believed (IPs or CIDRs, comma separated; * for any). Empty means the headers are ignored. |
| Proxy | USE_CLOUDFLARE | false | Trust CF-Connecting-IP from a trusted proxy. Required for correct client IPs behind Cloudflare. |
| Server | LISTEN_ADDR | :5000 | Address the HTTP server binds to. |
| Server | METRICS_TOKEN | - | When set, /metrics requires Authorization: Bearer <token>. Unset leaves it public. |
| Server | REDRX_DEBUG | false | Development mode. Relaxes the canonical-domain redirect and allows a fallback SECRET_KEY. |