• Bygg en egen ringklocka med ntfy (HTTP, HTTPS och realtidsnotiser)

    Genom att sätta upp en lapp med en QR-kod på dörren kan en besökare enkelt skanna koden med sin mobil och göra dig uppmärksam på att någon vill komma in. Med hjälp av ntfy – en enkel och självhostad notifikationstjänst – kan detta knapptryck omedelbart ge både ljud och visuell signal. Genom att återanvända en gammal, uttjänt dator som ringklockskonsol får du full kontroll över när någon ringer på dörren. För den som föredrar en mer traditionell lösning går det dessutom att bygga en fysisk ringknapp, till exempel med en Raspberry Pi Pico. Resultatet är en modern, flexibel och helt egen ringklocka, byggd med öppna webbtekniker och utan beroende av proprietära molntjänster.

    ntfy är en enkel, öppen och självhostad publish–subscribe-tjänst som låter dig skicka notiser via vanliga HTTP-anrop. Ursprungligen är den tänkt för script, servrar och automation – men den lämpar sig utmärkt för fysiska projekt, till exempel en egen ringklocka.

    I den här artikeln visar vi hur man kan använda ntfy för att bygga en modern ringklocka där:

    • Besökaren trycker på en knapp på en webbsida (eller skannar en QR-kod)
    • Ett ringljud spelas lokalt i webbläsaren
    • En notis skickas via ntfy
    • En dator (t.ex. en gammal Ubuntu-laptop) lyssnar och visar tydligt att någon ringt, samt spelar upp ljud

    Allt bygger på öppna standarder: HTML, JavaScript, HTTPS och curl.

    Översikt: hur lösningen fungerar

    Arkitekturen ser ut så här:

    Besökare (mobil/webb)
       ↓ HTTPS
    Ringknapp (HTML + JS)
       ↓ HTTPS POST
    ntfy-server (publik)
       ↓ HTTPS stream
    Konsol (webbsida eller script)
    

    Viktiga principer:

    • ntfy körs självhostat
    • All extern trafik går via HTTPS
    • ntfy lyssnar endast lokalt (t.ex. på 127.0.0.1:8080)
    • En webbserver (Apache/Nginx) fungerar som reverse proxy

    Förutsättningar för att köra ntfy

    Innan du börjar är det viktigt att tänka på följande:

    1. Publik IP eller publik domän

    För att besökare ska kunna nå ringknappen och för att klienter ska kunna prenumerera krävs att ntfy är nåbar från internet.

    • Antingen: server med publik IP
    • Eller: VPS / server hos leverantör
    • Eller: portforwarding + dynamisk DNS (mindre stabilt)

    2. HTTPS är ett krav

    Moderna webbläsare kräver HTTPS för:

    • JavaScript fetch()
    • ljuduppspelning
    • push/notiser
    • EventSource (SSE)

    Rekommenderad lösning:
    Låt Apache eller Nginx hantera HTTPS med Let’s Encrypt, och proxy:a vidare till ntfy.

    3. Säkerhet

    • Använd långa, svårgissade topics
    • Exponera inte ntfy:s interna port (8080)
    • Kör ntfy bakom proxy (behind-proxy: true)

    Exempel: ntfy-konfiguration (server.yml)

    base-url: https://ntfy.exempel.se
    listen-http: 127.0.0.1:8080
    behind-proxy: true
    cache-file: /var/cache/ntfy/cache.db
    

    ntfy startas som systemtjänst och är endast åtkomlig lokalt – Apache tar hand om port 80/443.

    Ringknappen – webbsida för besökare

    Den här sidan:

    • spelar upp ett ringljud lokalt (ring.mp3)
    • skickar ett meddelande till ntfy
    • tillåter även att man skriver ett kort meddelande

    Placering

    /var/www/html/ring.html
    /var/www/html/sounds/ring.mp3
    

    Komplett kod: ring.html

    <!doctype html>
    <html lang="sv">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Ring på</title>
      <style>
        body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
        .box { max-width: 520px; margin: 0 auto; }
        .btn { width: 100%; font-size: 22px; padding: 16px; border: 0; border-radius: 16px; }
        textarea { width: 100%; min-height: 90px; }
        .note { margin-top: 12px; opacity: 0.85; }
      </style>
    </head>
    <body>
      <div class="box">
        <h1>Ring på dörren</h1>
    
        <button class="btn" id="ring">🔔 RING</button>
    
        <p>
          <textarea id="msg" placeholder="Skriv ett meddelande (valfritt)"></textarea>
          <button class="btn" id="send">✉️ SKICKA MEDDELANDE</button>
        </p>
    
        <div class="note" id="status"></div>
      </div>
    
      <audio id="ringSound" preload="auto">
        <source src="/sounds/ring.mp3" type="audio/mpeg">
      </audio>
    
      <script>
        const NTFY_URL = "https://ntfy.exempel.se/ringklocka-Ringer";
        const ringSound = document.getElementById("ringSound");
        const status = document.getElementById("status");
    
        function playSound() {
          ringSound.currentTime = 0;
          ringSound.play().catch(() => {});
        }
    
        async function send(msg, title) {
          const r = await fetch(NTFY_URL, {
            method: "POST",
            headers: { "Title": title, "Priority": "5" },
            body: msg
          });
          if (!r.ok) throw new Error();
        }
    
        document.getElementById("ring").onclick = async () => {
          playSound();
          try {
            await send("🔔 Någon ringde på!", "Ringklocka");
            status.textContent = "✅ Ringt!";
          } catch {
            status.textContent = "❌ Kunde inte ringa";
          }
        };
    
        document.getElementById("send").onclick = async () => {
          const text = document.getElementById("msg").value.trim();
          if (!text) return;
          try {
            await send("✉️ " + text, "Meddelande vid dörren");
            status.textContent = "✅ Skickat!";
          } catch {
            status.textContent = "❌ Kunde inte skicka";
          }
        };
      </script>
    </body>
    </html>
    

    Konsol: visa ringningar på en dator

    En gammal laptop med webläsare fungerar utmärkt som ringklockskonsol.

    Lyssna via terminal (script)

    curl -sN -H "Accept: application/json" \
      https://ntfy.exempel.se/ringklocka-Ringer/json \
    | jq -r 'select(.event=="message") | .message' \
    | while read -r msg; do
        notify-send "🔔 Det ringer" "$msg"
        paplay /usr/share/sounds/freedesktop/stereo/message-new-instant.oga
      done
    

    Detta ger:

    • popup-notis på skärmen
    • ljud varje gång någon ringer

    Scriptet kan köras som systemd user service för autostart.

    Webkonsol

    <!doctype html>
    <html lang="sv">
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>Ringklocka-konsol</title>
      <style>
        body { font-family: system-ui, sans-serif; margin: 0; padding: 18px; }
        .wrap { max-width: 900px; margin: 0 auto; }
        .card { border: 1px solid #ddd; border-radius: 16px; padding: 16px; margin: 12px 0; }
        .big { font-size: 36px; font-weight: 800; }
        .muted { opacity: 0.7; }
        button { font-size: 16px; padding: 10px 14px; border-radius: 12px; border: 1px solid #bbb; background: #fff; }
        ul { margin: 0; padding-left: 18px; }
        li { margin: 6px 0; }
        .ok { color: #0a7; font-weight: 700; }
        .bad { color: #c00; font-weight: 700; }
      </style>
    </head>
    <body>
      <div class="wrap">
        <h1>🔔 Ringklocka-konsol</h1>
    
        <div class="card">
          <div class="muted">Status</div>
          <div id="status" class="bad">Inte ansluten</div>
    
          <div class="muted" style="margin-top:10px">
            Webbläsare kräver ofta ett klick för att ljud ska tillåtas.
          </div>
    
          <div style="margin-top:10px">
            <button id="enableSound">Aktivera ljud</button>
            <span id="soundState" class="muted">Ljud ej aktiverat</span>
          </div>
        </div>
    
        <div class="card">
          <div class="muted">Senaste</div>
          <div id="latest" class="big">—</div>
          <div id="latestTime" class="muted">—</div>
        </div>
    
        <div class="card">
          <div class="muted">Historik (senaste 20)</div>
          <ul id="log"></ul>
        </div>
      </div>
    
      <!-- Lägg ring.mp3 på /sounds/ring.mp3 -->
      <audio id="ringSound" preload="auto">
        <source src="/sounds/ring.mp3" type="audio/mpeg">
      </audio>
    
      <script>
        /* ========= KONFIGURATION ========= */
        const NTFY_BASE = "https://ntfy.example.org";   // ← ändra till din ntfy-domän
        const TOPIC = "ringklocka-Ringer";              // ← välj eget topic
        /* ================================= */
    
        const SSE_URL = `${NTFY_BASE}/${TOPIC}/sse`;
    
        const statusEl = document.getElementById("status");
        const latestEl = document.getElementById("latest");
        const latestTimeEl = document.getElementById("latestTime");
        const logEl = document.getElementById("log");
    
        const ringSound = document.getElementById("ringSound");
        let soundEnabled = false;
    
        function playRing() {
          if (!soundEnabled) return;
          ringSound.currentTime = 0;
          ringSound.play().catch(() => {});
        }
    
        function addLogLine(text, when) {
          const li = document.createElement("li");
          li.textContent = `${when} — ${text}`;
          logEl.insertBefore(li, logEl.firstChild);
          while (logEl.children.length > 20) logEl.removeChild(logEl.lastChild);
        }
    
        function setConnected(ok) {
          statusEl.textContent = ok ? "Ansluten ✅" : "Inte ansluten";
          statusEl.className = ok ? "ok" : "bad";
        }
    
        document.getElementById("enableSound").addEventListener("click", async () => {
          try {
            await ringSound.play();
            ringSound.pause();
            ringSound.currentTime = 0;
            soundEnabled = true;
            document.getElementById("soundState").textContent = "Ljud aktiverat ✅";
          } catch {
            document.getElementById("soundState").textContent = "Kunde inte aktivera ljud ❌";
          }
        });
    
        const es = new EventSource(SSE_URL);
    
        es.addEventListener("open", () => setConnected(true));
        es.addEventListener("error", () => setConnected(false));
    
        es.onmessage = (evt) => {
          try {
            const data = JSON.parse(evt.data);
            if (data.event !== "message") return;
    
            const msg = data.message || "(tomt)";
            const when = new Date((data.time || Date.now()/1000) * 1000);
            const whenStr = when.toLocaleString();
    
            latestEl.textContent = msg;
            latestTimeEl.textContent = whenStr;
            addLogLine(msg, whenStr);
            playRing();
          } catch (_) {}
        };
      </script>
    </body>
    </html>

    Fördelar med ntfy som ringklockelösning

    • ✔ Kräver inga appar (webb + curl räcker)
    • ✔ Öppen källkod
    • ✔ Körs på egen server
    • ✔ Fungerar med mobil, dator, Raspberry Pi
    • ✔ Lätt att bygga vidare (kamera, logg, relä, LED)

    Saker att tänka på i drift

    • Använd HTTPS överallt
    • Håll ntfy uppdaterad
    • Använd hemliga topic-namn
    • Begränsa exponering (lyssna lokalt)
    • Säkerhetskopiera cache/databas vid behov

    Avslutning

    Artikeln ovan ska ses som inspiration till vad man kan göra. Med hjälp av AI-tjänster, som till exempel ChatGPT, kan du experimentera och laborera fram kod som passar dig och dina behov. Framför allt visar den vad man kan göra med datorer som andra tycker är skrot.

    Fakta: Ringklocka byggd med ntfy
    Denna lösning visar hur ntfy, en självhostad HTTP-baserad pub/sub-tjänst, kan användas för att bygga en modern ringklocka med öppna webbtekniker. En besökare skannar en QR-kod eller trycker på en webbaserad knapp, vilket skickar en notis i realtid till valfria lyssnare.
    • Ringknapp (HTML och JavaScript) som spelar lokalt ljud och skickar notiser via HTTPS.
    • ntfy körs självhostat och tar emot HTTP-anrop samt strömmar händelser i realtid.
    • HTTPS hanteras via reverse proxy (Apache eller Nginx) med Let’s Encrypt.
    • Webbkonsol i HTML som lyssnar via Server-Sent Events och visar ringningar i realtid.
    • Scriptbaserad lyssning via curl och JSON för automation och vidare integration.
    Arkitekturmässigt lyssnar ntfy endast internt, till exempel på 127.0.0.1:8080. All extern kommunikation sker över HTTPS (port 443) via en reverse proxy.
    • Säkerhet: använd långa och svårgissade topic-namn samt exponera inte interna portar.
    • Drift: lösningen kräver publik IP eller publik domän och korrekt TLS-konfiguration.
    • Återbruk: äldre datorer med Linux lämpar sig väl som permanenta ringklockskonsoler.
    • Utbyggbarhet: lösningen kan kompletteras med fysisk knapp, kamera, loggning eller relä.
    Artikeln och exemplen ska ses som inspiration. Med hjälp av AI-tjänster, till exempel ChatGPT, kan du själv experimentera och laborera fram kod som passar just dig, din miljö och dina behov.

Bygg en egen ringklocka med ntfy (HTTP, HTTPS och realtidsnotiser)

Genom att sätta upp en lapp med en QR-kod på dörren kan en besökare enkelt skanna koden med sin mobil och göra dig uppmärksam på att någon vill komma in. Med hjälp av ntfy – en enkel och självhostad notifikationstjänst – kan detta knapptryck omedelbart ge både ljud och visuell signal. Genom att återanvända en gammal, uttjänt dator som ringklockskonsol får du full kontroll över när någon ringer på dörren. För den som föredrar en mer traditionell lösning går det dessutom att bygga en fysisk ringknapp, till exempel med en Raspberry Pi Pico. Resultatet är en modern, flexibel och helt egen ringklocka, byggd med öppna webbtekniker och utan beroende av proprietära molntjänster.

ntfy är en enkel, öppen och självhostad publish–subscribe-tjänst som låter dig skicka notiser via vanliga HTTP-anrop. Ursprungligen är den tänkt för script, servrar och automation – men den lämpar sig utmärkt för fysiska projekt, till exempel en egen ringklocka.

I den här artikeln visar vi hur man kan använda ntfy för att bygga en modern ringklocka där:

  • Besökaren trycker på en knapp på en webbsida (eller skannar en QR-kod)
  • Ett ringljud spelas lokalt i webbläsaren
  • En notis skickas via ntfy
  • En dator (t.ex. en gammal Ubuntu-laptop) lyssnar och visar tydligt att någon ringt, samt spelar upp ljud

Allt bygger på öppna standarder: HTML, JavaScript, HTTPS och curl.

Översikt: hur lösningen fungerar

Arkitekturen ser ut så här:

Besökare (mobil/webb)
   ↓ HTTPS
Ringknapp (HTML + JS)
   ↓ HTTPS POST
ntfy-server (publik)
   ↓ HTTPS stream
Konsol (webbsida eller script)

Viktiga principer:

  • ntfy körs självhostat
  • All extern trafik går via HTTPS
  • ntfy lyssnar endast lokalt (t.ex. på 127.0.0.1:8080)
  • En webbserver (Apache/Nginx) fungerar som reverse proxy

Förutsättningar för att köra ntfy

Innan du börjar är det viktigt att tänka på följande:

1. Publik IP eller publik domän

För att besökare ska kunna nå ringknappen och för att klienter ska kunna prenumerera krävs att ntfy är nåbar från internet.

  • Antingen: server med publik IP
  • Eller: VPS / server hos leverantör
  • Eller: portforwarding + dynamisk DNS (mindre stabilt)

2. HTTPS är ett krav

Moderna webbläsare kräver HTTPS för:

  • JavaScript fetch()
  • ljuduppspelning
  • push/notiser
  • EventSource (SSE)

Rekommenderad lösning:
Låt Apache eller Nginx hantera HTTPS med Let’s Encrypt, och proxy:a vidare till ntfy.

3. Säkerhet

  • Använd långa, svårgissade topics
  • Exponera inte ntfy:s interna port (8080)
  • Kör ntfy bakom proxy (behind-proxy: true)

Exempel: ntfy-konfiguration (server.yml)

base-url: https://ntfy.exempel.se
listen-http: 127.0.0.1:8080
behind-proxy: true
cache-file: /var/cache/ntfy/cache.db

ntfy startas som systemtjänst och är endast åtkomlig lokalt – Apache tar hand om port 80/443.

Ringknappen – webbsida för besökare

Den här sidan:

  • spelar upp ett ringljud lokalt (ring.mp3)
  • skickar ett meddelande till ntfy
  • tillåter även att man skriver ett kort meddelande

Placering

/var/www/html/ring.html
/var/www/html/sounds/ring.mp3

Komplett kod: ring.html

<!doctype html>
<html lang="sv">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Ring på</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
    .box { max-width: 520px; margin: 0 auto; }
    .btn { width: 100%; font-size: 22px; padding: 16px; border: 0; border-radius: 16px; }
    textarea { width: 100%; min-height: 90px; }
    .note { margin-top: 12px; opacity: 0.85; }
  </style>
</head>
<body>
  <div class="box">
    <h1>Ring på dörren</h1>

    <button class="btn" id="ring">🔔 RING</button>

    <p>
      <textarea id="msg" placeholder="Skriv ett meddelande (valfritt)"></textarea>
      <button class="btn" id="send">✉️ SKICKA MEDDELANDE</button>
    </p>

    <div class="note" id="status"></div>
  </div>

  <audio id="ringSound" preload="auto">
    <source src="/sounds/ring.mp3" type="audio/mpeg">
  </audio>

  <script>
    const NTFY_URL = "https://ntfy.exempel.se/ringklocka-Ringer";
    const ringSound = document.getElementById("ringSound");
    const status = document.getElementById("status");

    function playSound() {
      ringSound.currentTime = 0;
      ringSound.play().catch(() => {});
    }

    async function send(msg, title) {
      const r = await fetch(NTFY_URL, {
        method: "POST",
        headers: { "Title": title, "Priority": "5" },
        body: msg
      });
      if (!r.ok) throw new Error();
    }

    document.getElementById("ring").onclick = async () => {
      playSound();
      try {
        await send("🔔 Någon ringde på!", "Ringklocka");
        status.textContent = "✅ Ringt!";
      } catch {
        status.textContent = "❌ Kunde inte ringa";
      }
    };

    document.getElementById("send").onclick = async () => {
      const text = document.getElementById("msg").value.trim();
      if (!text) return;
      try {
        await send("✉️ " + text, "Meddelande vid dörren");
        status.textContent = "✅ Skickat!";
      } catch {
        status.textContent = "❌ Kunde inte skicka";
      }
    };
  </script>
</body>
</html>

Konsol: visa ringningar på en dator

En gammal laptop med webläsare fungerar utmärkt som ringklockskonsol.

Lyssna via terminal (script)

curl -sN -H "Accept: application/json" \
  https://ntfy.exempel.se/ringklocka-Ringer/json \
| jq -r 'select(.event=="message") | .message' \
| while read -r msg; do
    notify-send "🔔 Det ringer" "$msg"
    paplay /usr/share/sounds/freedesktop/stereo/message-new-instant.oga
  done

Detta ger:

  • popup-notis på skärmen
  • ljud varje gång någon ringer

Scriptet kan köras som systemd user service för autostart.

Webkonsol

<!doctype html>
<html lang="sv">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Ringklocka-konsol</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 0; padding: 18px; }
    .wrap { max-width: 900px; margin: 0 auto; }
    .card { border: 1px solid #ddd; border-radius: 16px; padding: 16px; margin: 12px 0; }
    .big { font-size: 36px; font-weight: 800; }
    .muted { opacity: 0.7; }
    button { font-size: 16px; padding: 10px 14px; border-radius: 12px; border: 1px solid #bbb; background: #fff; }
    ul { margin: 0; padding-left: 18px; }
    li { margin: 6px 0; }
    .ok { color: #0a7; font-weight: 700; }
    .bad { color: #c00; font-weight: 700; }
  </style>
</head>
<body>
  <div class="wrap">
    <h1>🔔 Ringklocka-konsol</h1>

    <div class="card">
      <div class="muted">Status</div>
      <div id="status" class="bad">Inte ansluten</div>

      <div class="muted" style="margin-top:10px">
        Webbläsare kräver ofta ett klick för att ljud ska tillåtas.
      </div>

      <div style="margin-top:10px">
        <button id="enableSound">Aktivera ljud</button>
        <span id="soundState" class="muted">Ljud ej aktiverat</span>
      </div>
    </div>

    <div class="card">
      <div class="muted">Senaste</div>
      <div id="latest" class="big">—</div>
      <div id="latestTime" class="muted">—</div>
    </div>

    <div class="card">
      <div class="muted">Historik (senaste 20)</div>
      <ul id="log"></ul>
    </div>
  </div>

  <!-- Lägg ring.mp3 på /sounds/ring.mp3 -->
  <audio id="ringSound" preload="auto">
    <source src="/sounds/ring.mp3" type="audio/mpeg">
  </audio>

  <script>
    /* ========= KONFIGURATION ========= */
    const NTFY_BASE = "https://ntfy.example.org";   // ← ändra till din ntfy-domän
    const TOPIC = "ringklocka-Ringer";              // ← välj eget topic
    /* ================================= */

    const SSE_URL = `${NTFY_BASE}/${TOPIC}/sse`;

    const statusEl = document.getElementById("status");
    const latestEl = document.getElementById("latest");
    const latestTimeEl = document.getElementById("latestTime");
    const logEl = document.getElementById("log");

    const ringSound = document.getElementById("ringSound");
    let soundEnabled = false;

    function playRing() {
      if (!soundEnabled) return;
      ringSound.currentTime = 0;
      ringSound.play().catch(() => {});
    }

    function addLogLine(text, when) {
      const li = document.createElement("li");
      li.textContent = `${when} — ${text}`;
      logEl.insertBefore(li, logEl.firstChild);
      while (logEl.children.length > 20) logEl.removeChild(logEl.lastChild);
    }

    function setConnected(ok) {
      statusEl.textContent = ok ? "Ansluten ✅" : "Inte ansluten";
      statusEl.className = ok ? "ok" : "bad";
    }

    document.getElementById("enableSound").addEventListener("click", async () => {
      try {
        await ringSound.play();
        ringSound.pause();
        ringSound.currentTime = 0;
        soundEnabled = true;
        document.getElementById("soundState").textContent = "Ljud aktiverat ✅";
      } catch {
        document.getElementById("soundState").textContent = "Kunde inte aktivera ljud ❌";
      }
    });

    const es = new EventSource(SSE_URL);

    es.addEventListener("open", () => setConnected(true));
    es.addEventListener("error", () => setConnected(false));

    es.onmessage = (evt) => {
      try {
        const data = JSON.parse(evt.data);
        if (data.event !== "message") return;

        const msg = data.message || "(tomt)";
        const when = new Date((data.time || Date.now()/1000) * 1000);
        const whenStr = when.toLocaleString();

        latestEl.textContent = msg;
        latestTimeEl.textContent = whenStr;
        addLogLine(msg, whenStr);
        playRing();
      } catch (_) {}
    };
  </script>
</body>
</html>

Fördelar med ntfy som ringklockelösning

  • ✔ Kräver inga appar (webb + curl räcker)
  • ✔ Öppen källkod
  • ✔ Körs på egen server
  • ✔ Fungerar med mobil, dator, Raspberry Pi
  • ✔ Lätt att bygga vidare (kamera, logg, relä, LED)

Saker att tänka på i drift

  • Använd HTTPS överallt
  • Håll ntfy uppdaterad
  • Använd hemliga topic-namn
  • Begränsa exponering (lyssna lokalt)
  • Säkerhetskopiera cache/databas vid behov

Avslutning

Artikeln ovan ska ses som inspiration till vad man kan göra. Med hjälp av AI-tjänster, som till exempel ChatGPT, kan du experimentera och laborera fram kod som passar dig och dina behov. Framför allt visar den vad man kan göra med datorer som andra tycker är skrot.

Fakta: Ringklocka byggd med ntfy
Denna lösning visar hur ntfy, en självhostad HTTP-baserad pub/sub-tjänst, kan användas för att bygga en modern ringklocka med öppna webbtekniker. En besökare skannar en QR-kod eller trycker på en webbaserad knapp, vilket skickar en notis i realtid till valfria lyssnare.
  • Ringknapp (HTML och JavaScript) som spelar lokalt ljud och skickar notiser via HTTPS.
  • ntfy körs självhostat och tar emot HTTP-anrop samt strömmar händelser i realtid.
  • HTTPS hanteras via reverse proxy (Apache eller Nginx) med Let’s Encrypt.
  • Webbkonsol i HTML som lyssnar via Server-Sent Events och visar ringningar i realtid.
  • Scriptbaserad lyssning via curl och JSON för automation och vidare integration.
Arkitekturmässigt lyssnar ntfy endast internt, till exempel på 127.0.0.1:8080. All extern kommunikation sker över HTTPS (port 443) via en reverse proxy.
  • Säkerhet: använd långa och svårgissade topic-namn samt exponera inte interna portar.
  • Drift: lösningen kräver publik IP eller publik domän och korrekt TLS-konfiguration.
  • Återbruk: äldre datorer med Linux lämpar sig väl som permanenta ringklockskonsoler.
  • Utbyggbarhet: lösningen kan kompletteras med fysisk knapp, kamera, loggning eller relä.
Artikeln och exemplen ska ses som inspiration. Med hjälp av AI-tjänster, till exempel ChatGPT, kan du själv experimentera och laborera fram kod som passar just dig, din miljö och dina behov.