From 4b801a635364713889a58e0964925ddcad53a562 Mon Sep 17 00:00:00 2001 From: lone-cloud Date: Thu, 22 Jan 2026 11:46:59 -0800 Subject: [PATCH] improving README, small refactoring updates --- README.md | 61 ++++++++++++++++++++++++++--------- assets/screenshots/1.webp | Bin 0 -> 10666 bytes assets/screenshots/2.webp | Bin 0 -> 26670 bytes assets/screenshots/3.webp | Bin 0 -> 12166 bytes assets/screenshots/4.webp | Bin 0 -> 14346 bytes server/.env.example | 9 +++--- server/modules/protonmail.ts | 29 ++++++++++++++--- server/modules/signal.ts | 5 ++- server/public/index.css | 58 +++++++++++++++++++++++++++++++++ server/public/index.html | 26 ++++++++------- server/routes/admin.ts | 21 +++++++----- server/utils/format.ts | 35 ++++++++++++++++++++ 12 files changed, 199 insertions(+), 45 deletions(-) create mode 100644 assets/screenshots/1.webp create mode 100644 assets/screenshots/2.webp create mode 100644 assets/screenshots/3.webp create mode 100644 assets/screenshots/4.webp create mode 100644 server/utils/format.ts diff --git a/README.md b/README.md index 5e1f101..00fd342 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ > ⚠️ **Early Alpha**: SUP is under rapid development. The Android app is being actively developed and the current version is not thoroughly tested. There are no stable releases yet. Use at your own risk. -SUP is a UnifiedPush distributor that routes push notifications through Signal, allowing you to receive app notifications without exposing unique network fingerprints to any network observers. All notification traffic appears as regular Signal messages. +SUP is a [UnifiedPush](https://unifiedpush.org/) server and distributor that routes push notifications through Signal, allowing you to receive app notifications without exposing unique network fingerprints to any network observers. All notification traffic appears as regular Signal messages. ## Why? @@ -24,6 +24,12 @@ SUP also includes an optional Proton Mail integration, allowing you to receive e Note that you'll need to run SUP on your own server (either at home or on a VPS) since it uses your personal Signal and Proton Mail credentials. A Raspberry Pi, which is a small and affordable micro-computer, works perfectly for this, using minimal power (3-5W) while running SUP 24/7. +## How? + +SUP functions as a UnifiedPush server to proxy http-based requests to Signal groups via [signal-cli](https://github.com/AsamK/signal-cli). + +For the optional ProtonMail integration, SUP requires a server that runs Proton's official [proton-bridge](https://github.com/ProtonMail/proton-bridge). SUP's docker compose process will run an image from [protonmail-bridge-docker](https://github.com/shenxn/protonmail-bridge-docker). Once authenticated, the communication between SUP and proton-bridge will be over IMAP. + ## Setup ### 1. Install Android App (Optional) @@ -55,20 +61,20 @@ curl -L -O https://raw.githubusercontent.com/lone-cloud/sup/master/docker-compos docker compose run --rm protonmail-bridge init ``` -2. **Login to Proton Mail Bridge**: +2.**Login to Proton Mail Bridge**: - At the `>>>` prompt, run: `login` - Enter your email - Enter your password - Enter your 2FA code -3. **Get IMAP credentials**: +3.**Get IMAP credentials**: - Run: `info` - Copy the Username and Password shown - Run: `exit` to quit -4. **Add credentials to .env**: +4.**Add credentials to .env**: ```bash # Add these to your .env file @@ -76,7 +82,7 @@ PROTON_IMAP_USERNAME=bridge-username-from-info-command PROTON_IMAP_PASSWORD=bridge-generated-password-from-info-command ``` -5. **Start all services with Proton Mail**: +5.**Start all services with Proton Mail**: ```bash docker compose --profile protonmail up -d @@ -84,7 +90,7 @@ docker compose --profile protonmail up -d Your phone will now receive Signal notifications when Proton Mail receives new emails. -Note that the bridge will first need to sync all of your old emails before you can start getting new email notifications. +Note that the bridge will first need to sync all of your old emails before you can start getting new email notifications which may take a while, but this is a one-time setup. ### 3. SUP Server integration @@ -102,10 +108,31 @@ nano .env # Start SUP server docker compose up -d -# Link your Signal account (one-time setup) -# Visit http://localhost:8080 and scan QR code with Signal app ``` +### 4. Configuration + +Link your Signal account (one-time setup) +Visit localhost:8080 and scan QR code with Signal app + +#### Authenticate with the API_KEY + + + +#### Scan the QR code from your Signal app by linking SUP as a device + + + +#### A healthy setup with a linked account + + + +#### A healthy setup with a linked account and the optional Proton Mail integration + + + + + ### Development For local development, install Bun and signal-cli: @@ -118,6 +145,8 @@ git clone https://github.com/lone-cloud/sup.git cd sup bun install +cd server +bun start ``` Then build and run with docker-compose.dev.yml: @@ -132,13 +161,6 @@ or just the proton-bridge: docker compose -f docker-compose.dev.yml up protonmail-bridge ``` -Or run services directly with Bun: - -```bash -bun install -bun --filter sup-server dev -``` - ## Real-World Examples ### Proton Mail Notifications @@ -173,7 +195,14 @@ Reboot your Home Assistant system and you'll then be able to send Signal notific ![SUP Architecture](assets/SUP%20Architecture.webp) -SUP consists of two services that **MUST run together on the same machine**: +SUP consists of two services that **MUST rSUP uses [basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) for authentication. un together on the same machin`API_KEY` as a password and the username can be ignored. + +For API-based monitoring, you can call `/api/health` which will return JSON health output like: + +```JSON +{"uptime":"3s","signal":{"daemon":"running","linked":true},"protonMail":"connected"} +``` +**: - **sup-server** (Bun): Receives webhooks, sends Signal messages via signal-cli. Optional: monitors Proton Mail IMAP - **protonmail-bridge** (Official Proton, optional): Decrypts Proton Mail emails, runs local IMAP server diff --git a/assets/screenshots/1.webp b/assets/screenshots/1.webp new file mode 100644 index 0000000000000000000000000000000000000000..18287ebca7a59b914fdc723ddee65313e8658db9 GIT binary patch literal 10666 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 literal 0 HcmV?d00001 diff --git a/assets/screenshots/2.webp b/assets/screenshots/2.webp new file mode 100644 index 0000000000000000000000000000000000000000..04ad05e3ca51c80321f44a6cec9c6ef85755e12f GIT binary patch literal 26670 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 literal 0 HcmV?d00001 diff --git a/assets/screenshots/3.webp b/assets/screenshots/3.webp new file mode 100644 index 0000000000000000000000000000000000000000..ae5f67a8a84b43465934ebadab9b342530c08340 GIT binary patch literal 12166 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

^chS-Y)`gX)=sH6?1S-2z{>V-tZAr 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 K6hMD3ME^gVR4I`F literal 0 HcmV?d00001 diff --git a/assets/screenshots/4.webp b/assets/screenshots/4.webp new file mode 100644 index 0000000000000000000000000000000000000000..cf3474cfd7ec7e7c9b2c04ed9a19bfb120f9b1bd GIT binary patch literal 14346 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 JufTss{}(XgI(7g6 literal 0 HcmV?d00001 diff --git a/server/.env.example b/server/.env.example index 15481e7..9cba50b 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,11 +1,10 @@ +# Required: API key for authentication +# API_KEY=your-secret-key-here + # Optional: Server port # Default: 8080 # PORT=8080 -# Optional: Protect endpoint registration with an API key (recommended for public deployments) -# Default: unset (no authentication required) -# API_KEY=your-secret-key-here - # Optional: Allow HTTP connections without HTTPS # Default: false # ALLOW_INSECURE_HTTP=true @@ -16,7 +15,7 @@ # Optional: Device name shown in Signal app's linked devices list # Default: SUP -# DEVICE_NAME=SUP dev +# DEVICE_NAME=What SUP # Optional: Enable verbose logging # Default: false diff --git a/server/modules/protonmail.ts b/server/modules/protonmail.ts index d5b8939..0104494 100644 --- a/server/modules/protonmail.ts +++ b/server/modules/protonmail.ts @@ -11,6 +11,7 @@ import { getOrCreateGroup } from '@/modules/store'; import { logError, logInfo, logSuccess, logVerbose, logWarn } from '@/utils/log'; let imapConnected = false; +let monitorStartTime = 0; export const isImapConnected = () => imapConnected; @@ -68,11 +69,18 @@ export async function startProtonMonitor() { return; } - imap.on('mail', async (numNewMsgs: number) => { - logVerbose(`${numNewMsgs} new message(s) received`); + if (!monitorStartTime) { + monitorStartTime = Date.now(); + logVerbose(`Inbox opened with ${box.messages.total} existing messages`); + } else { + logVerbose('Inbox reopened (reconnection)'); + } - const fetch = imap.seq.fetch(`${box.messages.total}:*`, { - bodies: 'HEADER.FIELDS (FROM SUBJECT)', + imap.on('mail', async (numNewMsgs: number) => { + logVerbose(`${numNewMsgs} new message(s) in mailbox`); + + const fetch = imap.seq.fetch(`${box.messages.total - numNewMsgs + 1}:*`, { + bodies: 'HEADER.FIELDS (FROM SUBJECT DATE)', struct: true, }); @@ -86,6 +94,19 @@ export async function startProtonMonitor() { const header = Imap.parseHeader(buffer); const rawFrom = header.from?.[0] || 'Unknown sender'; const subject = header.subject?.[0] || 'No subject'; + const dateStr = header.date?.[0]; + + if (dateStr) { + const messageDate = new Date(dateStr).getTime(); + if (messageDate < monitorStartTime) { + const formattedDate = new Date(dateStr).toLocaleString('en-US', { + dateStyle: 'medium', + timeStyle: 'short', + }); + logVerbose(`Skipping old email: ${subject} (${formattedDate})`); + return; + } + } const nameMatch = rawFrom.match(/^"?([^"<]+)"?\s* daemon?.kill(); + +export const getAccount = () => account; diff --git a/server/public/index.css b/server/public/index.css index f94220a..24a6c61 100644 --- a/server/public/index.css +++ b/server/public/index.css @@ -72,6 +72,46 @@ h2 { padding: 0.625rem 1rem; border-radius: 0.25rem; font-weight: 500; + position: relative; +} + +.status-item:has(.tooltip) { + cursor: help; +} + +.status-item .tooltip { + visibility: hidden; + opacity: 0; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + margin-bottom: 0.5rem; + background: #333; + color: white; + padding: 0.375rem 0.625rem; + border-radius: 0.25rem; + font-size: 0.875rem; + font-weight: 400; + white-space: nowrap; + transition: opacity 0.2s; + pointer-events: none; + z-index: 10; +} + +.status-item .tooltip::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border: 0.3125rem solid transparent; + border-top-color: #333; +} + +.status-item:hover .tooltip { + visibility: visible; + opacity: 1; } .status-ok { @@ -186,6 +226,24 @@ h2 { .loading { color: #666; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.spinner { + width: 1.25rem; + height: 1.25rem; + border: 0.125rem solid #e0e0e0; + border-top-color: #8159b8; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } } .loading-subtitle { diff --git a/server/public/index.html b/server/public/index.html index 1436185..fa37cb4 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -13,24 +13,27 @@

- Loading health status... + hx-indicator="#health-status"> +
+
+

Signal

-
Loading Signal status...
+
+
+
@@ -38,11 +41,12 @@

Endpoints

- Loading endpoints... + hx-indicator="#endpoints-list"> +
+
+
diff --git a/server/routes/admin.ts b/server/routes/admin.ts index 000b016..66e5f6d 100644 --- a/server/routes/admin.ts +++ b/server/routes/admin.ts @@ -6,11 +6,13 @@ import { checkSignalCli, finishLink, generateLinkQR, + getAccount, hasValidAccount, initSignal, } from '@/modules/signal'; import { getAllMappings, remove } from '@/modules/store'; import { verifyApiKey } from '@/utils/auth'; +import { formatPhoneNumber, formatUptime } from '@/utils/format'; let cachedQR: string | null = null; let qrCacheTime = 0; @@ -22,19 +24,22 @@ export const handleHealthFragment = async () => { const linked = signalOk && (await hasValidAccount()); const imap = isImapConnected(); const hasProtonConfig = PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD; + const accountNumber = getAccount(); const html = `
- Signal Daemon: ${signalOk ? 'Running' : 'Stopped'} + Signal Network: ${signalOk ? 'Connected' : 'Disconnected'}
Account: ${linked ? 'Linked' : 'Unlinked'} + ${linked && accountNumber ? `${formatPhoneNumber(accountNumber)}` : ''}
${ hasProtonConfig ? `
Proton Mail: ${imap ? 'Connected' : 'Disconnected'} + ${imap ? `${PROTON_IMAP_USERNAME}` : ''}
` : '' } @@ -83,7 +88,7 @@ export const handleEndpointsFragment = async () => {
@@ -157,7 +162,7 @@ admin.get('/api/health', async (c) => { const hasProtonConfig = PROTON_IMAP_USERNAME && PROTON_IMAP_PASSWORD; const result: Record = { - uptime: process.uptime(), + uptime: formatUptime(process.uptime()), signal: { daemon: signalOk ? 'running' : 'stopped', linked, @@ -171,18 +176,18 @@ admin.get('/api/health', async (c) => { return c.json(result); }); -admin.get('/health/fragment', async (c) => { +admin.get('/fragment/health', async (c) => { const { html } = await handleHealthFragment(); return c.html(html); }); -admin.get('/signal-info/fragment', async (c) => c.html(await handleSignalInfoFragment())); +admin.get('/fragment/signal-info', async (c) => c.html(await handleSignalInfoFragment())); -admin.get('/endpoints/fragment', async (c) => c.html(await handleEndpointsFragment())); +admin.get('/fragment/endpoints', async (c) => c.html(await handleEndpointsFragment())); -admin.get('/link/qr-section', async (c) => c.html(await handleQRSection())); +admin.get('/fragment/link-qr', async (c) => c.html(await handleQRSection())); -admin.delete('/endpoint/delete/:endpoint', async (c) => { +admin.delete('/action/delete-endpoint/:endpoint', async (c) => { const endpoint = decodeURIComponent(c.req.param('endpoint')); if (!endpoint) { diff --git a/server/utils/format.ts b/server/utils/format.ts new file mode 100644 index 0000000..b0d4407 --- /dev/null +++ b/server/utils/format.ts @@ -0,0 +1,35 @@ +export const formatUptime = (seconds: number): string => { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = Math.floor(seconds % 60); + + const parts = []; + if (days > 0) parts.push(`${days}d`); + if (hours > 0) parts.push(`${hours}h`); + if (minutes > 0) parts.push(`${minutes}m`); + if (secs > 0 || parts.length === 0) parts.push(`${secs}s`); + + return parts.join(' '); +}; + +export const formatPhoneNumber = (phone: string): string => { + if (!phone) return phone; + + // US/Canada: +1 (234) 567-8901 + if (phone.startsWith('+1') && phone.length === 12) { + return `+1 (${phone.slice(2, 5)}) ${phone.slice(5, 8)}-${phone.slice(8)}`; + } + + // International: +XX XXXX XXXX + if (phone.startsWith('+')) { + const match = phone.match(/^\+(\d{1,3})(\d{3,4})(\d+)$/); + if (match) { + const [, code, first, rest] = match; + const parts = rest?.match(/.{1,4}/g) || []; + return `+${code} ${first} ${parts.join(' ')}`; + } + } + + return phone; +};