diff --git a/README.md b/README.md index 17c6a3f..27c5b50 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,111 @@ -# xmrpay.link — Monero Invoice Generator +# xmrpay — Monero Invoice Generator -> Private. Self-hosted. No accounts. No tracking. No bullshit. +> Create Monero payment requests in seconds. No accounts. No tracking. No KYC. -**[Live: xmrpay.link](https://xmrpay.link)** · **[Tor: mc6wfe...zyd.onion](http://mc6wfeaqc7oijgdcudrr5zsotmwok3jzk3tu2uezzyjisn7nzzjjizyd.onion)** +**[Demo: xmrpay.link](https://xmrpay.link)** — for real payments, self-host your own instance. --- -## What is this? +## Self-Host in 60 Seconds -**xmrpay.link** is a client-side web app that lets anyone create a professional Monero payment request in under 30 seconds — no account registration, no KYC, no custodial services. +You need a VPS with a domain pointing to it. Then: -Enter your address, the amount, an optional description — and get a wallet-native `monero:` URI, QR code, and PDF invoice. Short links are optional. +```bash +curl -sL https://xmrpay.link/install.sh | sh -s your-domain.com +``` -### Architecture & Transparency +Done. HTTPS is automatic (via Caddy + Let's Encrypt). -xmrpay.link uses a **minimal backend** for the following specific purposes: +### Requirements -| Component | Where it runs | What the server sees | -|-----------|--------------|---------------------| -| QR code generation | Browser only | Nothing | -| PDF invoice | Browser only | Nothing | -| Payment (TX) verification | Browser only | Nothing | -| Fiat exchange rates | Server (CoinGecko proxy) | Your IP address | -| Short URL storage | Server | Invoice hash (address + amount + description), HMAC-signed | -| Payment proof storage | Server | TX hash + amount — **not** your XMR address | +| | Minimum | Recommended | +|---|---|---| +| **CPU** | 1 vCPU | 2 vCPU | +| **RAM** | 1 GB | 2 GB | +| **Disk** | 10 GB | 20 GB | +| **OS** | Any Linux with Docker | Ubuntu 22+, Debian 12+ | +| **Domain** | A-Record pointing to server IP | | +| **Cost** | ~3 EUR/month (Hetzner, Contabo, etc.) | | -**Self-hosting** eliminates trust in the public instance. -**No short links** (use wallet URI / long `/#...` URL / QR code) = no shortlink lookup dependency. +### Updates -### Trust Model (Important) +Watchtower runs alongside xmrpay and automatically pulls new images every 6 hours. No action needed. -- **Default mode:** wallet-native URI + QR (no shortlink lookup). -- **Short links are opt-in:** convenience feature with a trust trade-off. -- **Public instance caution:** if a server is fully compromised, first-access shortlink resolution can be manipulated. -- **Best security posture:** use wallet URI directly or self-host. +Manual update: -### Security Model +```bash +cd /opt/xmrpay && docker compose pull && docker compose up -d +``` -- **HMAC-signed short URLs:** Hashes are signed with a server-side secret. Clients verify the signature on load to detect tampering. -- **Address never stored:** Payment verification is cryptographic and runs client-side. The server never learns your XMR address. -- **Rate-limited APIs:** All write endpoints are rate-limited per IP. -- **Origin-restricted:** API endpoints reject cross-origin requests. -- **Clear scope:** HMAC improves integrity checks, but it is not a complete defense against a fully compromised server. +### Configuration ---- +After install, the config is at `/opt/xmrpay/.env`: -## Why? +```bash +DOMAIN=your-domain.com +XMRPAY_IMAGE=schmidt1024/xmrpay:latest +``` -| Solution | Problem | +### Docker Images + +| Registry | Pull command | |---|---| -| **BTCPay Server** | Requires own server, complex setup | -| **NOWPayments, Globee** | Custodial, KYC, fees, third-party dependency | -| **Cake Wallet Invoice** | Mobile-only, no sharing without app | -| **MoneroPay** | Backend daemon required, developer-only | -| **Wallet QR** | No amount, no description, no confirmation | +| Docker Hub | `docker pull schmidt1024/xmrpay:latest` | +| GitHub (GHCR) | `docker pull ghcr.io/schmidt1024/xmrpay:latest` | -**The gap:** There's no simple, privacy-respecting tool for freelancers, small merchants, and creators that works without setup and still allows payment confirmation. +### Manual Setup (without install script) + +```bash +mkdir -p /opt/xmrpay && cd /opt/xmrpay + +curl -fsSL https://raw.githubusercontent.com/schmidt1024/xmrpay/master/docker-compose.yml -o docker-compose.yml + +cat > .env <` payment widget -- [ ] Invoice history (LocalStorage, CSV export) -- [ ] "Pay Button" generator (HTML snippet) -- [x] Auto-cleanup: Lazy-delete invoices after deadline expires - --- ## License diff --git a/app.js b/app.js index 1b13672..b9b2e51 100644 --- a/app.js +++ b/app.js @@ -61,6 +61,21 @@ let pdfLoaded = false; let lastPaidData = null; + // --- Self-host Banner --- + (function () { + var PUBLIC_HOSTS = ['xmrpay.link', 'mc6wfeaqc7oijgdcudrr5zsotmwok3jzk3tu2uezzyjisn7nzzjjizyd.onion']; + var banner = $('#selfHostBanner'); + var dismiss = $('#dismissBanner'); + if (!banner) return; + if (PUBLIC_HOSTS.indexOf(location.hostname) === -1) return; + if (sessionStorage.getItem('banner_dismissed')) return; + banner.hidden = false; + dismiss.addEventListener('click', function () { + banner.hidden = true; + sessionStorage.setItem('banner_dismissed', '1'); + }); + })(); + // --- Currency Detection --- function detectCurrency() { var localeToCurrency = { diff --git a/i18n.js b/i18n.js index 81f8a5d..d0bc862 100644 --- a/i18n.js +++ b/i18n.js @@ -41,6 +41,7 @@ var I18n = (function () { pdf_scan_qr: 'Scan QR code to pay', pdf_footer: 'Created with xmrpay.link', qr_hint: 'Click QR to save', + self_host_banner: 'This is a public demo. For real payments, host your own instance — it takes 60 seconds.', footer: footer, aria_currency: 'Currency', label_share_link: 'Shareable link', @@ -92,6 +93,7 @@ var I18n = (function () { pdf_date: 'Datum', pdf_scan_qr: 'QR-Code scannen zum Bezahlen', pdf_footer: 'Erstellt mit xmrpay.link', + self_host_banner: 'Dies ist eine öffentliche Demo. Für echte Zahlungen eigene Instanz hosten — dauert 60 Sekunden.', qr_hint: 'Klick auf QR zum Speichern', footer: footer, aria_currency: 'Währung', @@ -144,6 +146,7 @@ var I18n = (function () { pdf_date: 'Date', pdf_scan_qr: 'Scanner le QR code pour payer', pdf_footer: 'Créé avec xmrpay.link', + self_host_banner: 'Ceci est une démo publique. Pour de vrais paiements, hébergez votre propre instance — ça prend 60 secondes.', qr_hint: 'Cliquez sur le QR pour enregistrer', footer: footer, aria_currency: 'Devise', @@ -196,6 +199,7 @@ var I18n = (function () { pdf_date: 'Data', pdf_scan_qr: 'Scansiona il QR per pagare', pdf_footer: 'Creato con xmrpay.link', + self_host_banner: 'Questa è una demo pubblica. Per pagamenti reali, ospita la tua istanza — ci vogliono 60 secondi.', qr_hint: 'Clicca sul QR per salvare', footer: footer, aria_currency: 'Valuta', @@ -248,6 +252,7 @@ var I18n = (function () { pdf_date: 'Fecha', pdf_scan_qr: 'Escanear QR para pagar', pdf_footer: 'Creado con xmrpay.link', + self_host_banner: 'Esta es una demo pública. Para pagos reales, aloja tu propia instancia — toma 60 segundos.', qr_hint: 'Clic en QR para guardar', footer: footer, aria_currency: 'Moneda', @@ -300,6 +305,7 @@ var I18n = (function () { pdf_date: 'Data', pdf_scan_qr: 'Digitalizar QR para pagar', pdf_footer: 'Criado com xmrpay.link', + self_host_banner: 'Esta é uma demo pública. Para pagamentos reais, hospede sua própria instância — leva 60 segundos.', qr_hint: 'Clique no QR para guardar', footer: footer, aria_currency: 'Moeda', @@ -352,6 +358,7 @@ var I18n = (function () { pdf_date: 'Дата', pdf_scan_qr: 'Сканируйте QR для оплаты', pdf_footer: 'Создано с помощью xmrpay.link', + self_host_banner: 'Это публичная демо-версия. Для реальных платежей разверните свой экземпляр — это займёт 60 секунд.', qr_hint: 'Нажмите на QR для сохранения', footer: footer, aria_currency: 'Валюта', diff --git a/index.html b/index.html index 0a66f1f..03cc137 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,11 @@ + +

xmrpay.link

Monero payment request in seconds

@@ -132,7 +137,7 @@
- + diff --git a/style.css b/style.css index 7873c98..3121b2a 100644 --- a/style.css +++ b/style.css @@ -58,6 +58,40 @@ body { align-items: center; } +.self-host-banner { + background: var(--accent); + color: #fff; + text-align: center; + padding: 0.6rem 2.5rem 0.6rem 1rem; + font-size: 0.85rem; + line-height: 1.4; + position: relative; +} + +.self-host-banner a { + color: #fff; + font-weight: 600; + text-decoration: underline; +} + +.self-host-banner button { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #fff; + font-size: 1.2rem; + cursor: pointer; + opacity: 0.7; + padding: 0.25rem 0.5rem; +} + +.self-host-banner button:hover { + opacity: 1; +} + header { text-align: center; padding: 2rem 1rem 1rem;