From 347bbb748b8d51431e6559da517f34f37b9a0a99 Mon Sep 17 00:00:00 2001 From: Henrik Jess Nielsen Date: Sat, 19 Sep 2026 06:52:45 +0200 Subject: [PATCH] Update copy to post-July-2026 reality and notify on signups - Rewrite copy from "this will happen" to "this has happened": direct registration closed 1 July 2026, transition ends 30 June 2028 - Fix timeline (30 June 2028, not 1 July), update FAQ + JSON-LD, add FAQ on registering new domains now - Send signup notification mail to henrik@i80.dk via smtp.i80.dk, with credentials from Consul KV - Fix CI health check URL (punktfri.dk) Co-Authored-By: Claude Opus 5 (1M context) --- .gitea/workflows/deploy.yml | 6 ++-- README.md | 5 +++ app.py | 48 ++++++++++++++++++++++++++ punktfri.nomad | 13 +++++++ static/sitemap.xml | 2 +- static/style.css | 5 +++ templates/index.html | 68 +++++++++++++++++++++++-------------- templates/tak.html | 2 +- tests/test_app.py | 27 +++++++++++++++ 9 files changed, 145 insertions(+), 31 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index cbee3af..d606205 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -63,10 +63,10 @@ jobs: - name: Health check run: | sleep 20 - curl -f https://punktfri.i80.dk/health || echo "Service not yet available" + curl -f https://punktfri.dk/health || echo "Service not yet available" - name: Notify deployment status run: | echo "Deployment completed!" - echo "Service: https://punktfri.i80.dk" - echo "Health: https://punktfri.i80.dk/health" + echo "Service: https://punktfri.dk" + echo "Health: https://punktfri.dk/health" diff --git a/README.md b/README.md index 94c8c65..3e515e0 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ Visit `http://localhost:5000` | `LOG_FILE` | `signups.log` | Signup log file path | | `ADMIN_USER` | `admin` | Admin username | | `ADMIN_PASS` | `punktfri2024` | Admin password | +| `NOTIFY_EMAIL` | `henrik@i80.dk` | Receives a mail on every signup | +| `SMTP_HOST` | *(empty)* | SMTP server; notifications are skipped when unset | +| `SMTP_PORT` | `587` | SMTP port (STARTTLS) | +| `SMTP_USER` / `SMTP_PASS` | *(empty)* | SMTP login (Consul KV `punktfri/smtp_user`, `punktfri/smtp_pass` in Nomad) | +| `SMTP_FROM` | `SMTP_USER` | Sender address (Consul KV `punktfri/smtp_from`) | Change `ADMIN_USER` and `ADMIN_PASS` before deploying publicly. diff --git a/app.py b/app.py index 5b756a9..9511356 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,9 @@ import os import logging +import smtplib import sqlite3 +import threading +from email.message import EmailMessage from functools import wraps from flask import Flask, render_template, request, redirect, url_for, g, Response, send_from_directory @@ -10,6 +13,12 @@ LOG_FILE = os.environ.get("LOG_FILE", "signups.log") ADMIN_USER = os.environ.get("ADMIN_USER", "admin") ADMIN_PASS = os.environ.get("ADMIN_PASS", "punktfri2024") PORT = int(os.environ.get("PORT", 5000)) +NOTIFY_EMAIL = os.environ.get("NOTIFY_EMAIL", "henrik@i80.dk") +SMTP_HOST = os.environ.get("SMTP_HOST", "") +SMTP_PORT = int(os.environ.get("SMTP_PORT", 587)) +SMTP_USER = os.environ.get("SMTP_USER", "") +SMTP_PASS = os.environ.get("SMTP_PASS", "") +SMTP_FROM = os.environ.get("SMTP_FROM", "") or SMTP_USER app = Flask(__name__) @@ -20,6 +29,41 @@ _fh.setFormatter(logging.Formatter("%(asctime)s %(message)s")) signup_logger.addHandler(_fh) +def build_notification(navn, email, domaener, egne_ns, kommentar): + msg = EmailMessage() + msg["Subject"] = f"PunktFri: ny tilmelding fra {navn}" + msg["From"] = SMTP_FROM + msg["To"] = NOTIFY_EMAIL + msg["Reply-To"] = email + msg.set_content( + f"Ny interessetilkendegivelse på punktfri.dk\n\n" + f"Navn: {navn}\n" + f"E-mail: {email}\n" + f"Antal domæner: {domaener}\n" + f"Egne navneservere: {egne_ns}\n" + f"Kommentar:\n{kommentar or '(ingen)'}\n" + ) + return msg + + +def _send(msg): + try: + with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=15) as s: + s.starttls() + if SMTP_USER: + s.login(SMTP_USER, SMTP_PASS) + s.send_message(msg) + except Exception: + app.logger.exception("Kunne ikke sende tilmeldingsnotifikation til %s", NOTIFY_EMAIL) + + +def notify_signup(**fields): + if not SMTP_HOST: + app.logger.warning("SMTP_HOST ikke sat — springer notifikation over") + return + threading.Thread(target=_send, args=(build_notification(**fields),), daemon=True).start() + + def get_db(): db = getattr(g, "_database", None) if db is None: @@ -105,6 +149,10 @@ def index(): f"SIGNUP navn={navn!r} email={email!r} domaener={domaener} " f"egne_ns={egne_ns} kommentar={kommentar!r}" ) + notify_signup( + navn=navn, email=email, domaener=domaener, + egne_ns=egne_ns, kommentar=kommentar, + ) return redirect(url_for("tak")) return render_template("index.html", error=error) diff --git a/punktfri.nomad b/punktfri.nomad index 7aaedee..0b7f0f4 100644 --- a/punktfri.nomad +++ b/punktfri.nomad @@ -104,6 +104,19 @@ job "punktfri" { DATABASE = "/app/instance/punktfri.db" LOG_FILE = "/app/instance/signups.log" TZ = "Europe/Copenhagen" + NOTIFY_EMAIL = "henrik@i80.dk" + SMTP_HOST = "smtp.i80.dk" + SMTP_PORT = "587" + } + + template { + data = < https://punktfri.dk/ - 2026-05-22 + 2026-09-19 weekly 1.0 diff --git a/static/style.css b/static/style.css index 8531fa9..eb8fc88 100644 --- a/static/style.css +++ b/static/style.css @@ -824,6 +824,11 @@ footer { box-shadow: 0 0 0 4px var(--primary-bg); } +.timeline-marker.is-past { + background: var(--border); + border-color: var(--border); +} + .timeline-marker.is-future { background: var(--white); border-color: var(--border); diff --git a/templates/index.html b/templates/index.html index 986cd3f..ccebfd7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -10,8 +10,9 @@

PunktFri — Non-profit registrator for .dk-domæner

Dit .dk-domæne. Din infrastruktur. Ingen forhandler.

- Punktum dk kræver fra 2026 en akkrediteret registrator for alle .dk-domæner — også dem du har - administreret selv i årtier uden at bede nogen om hjælp. 90-150 kr/domæne/år for noget du ikke har brug for. + Det er sket. Den 1. juli 2026 lukkede Punktum dk for direkte registrering af nye .dk-domæner, + og nedtællingen er i gang for dem, du har administreret selv i årtier uden at bede nogen om hjælp: + senest 30. juni 2028 skal de også have en registrator. 90–150 kr/domæne/år for noget du ikke har brug for. Du har ikke bedt om et mellemled. Din infrastruktur virker. PunktFri er et initiativ fra selvhostere, der heller ikke vil betale kommerciel pris for noget, de aldrig har haft brug for.

@@ -27,8 +28,8 @@ Du har kørt BIND, PowerDNS, Knot eller tilsvarende i årevis. Du har sat DNSSEC op selv — med DS-records, signering og det hele — og det har bare virket. Du har administreret dine domæner direkte hos Punktum dk uden nogensinde at have brug for hjælp fra dem til noget som helst. - Nu kræver de alligevel at du har en kommerciel forhandler som mellemmand. Intet ændrer sig teknisk. - Det er ren administration — og det skal du nu betale 90–150 kr/år for per domæne. Det er absurd, + Alligevel har de nu gjort en kommerciel forhandler til et krav. Teknisk er intet ændret. + Det er ren administration — og den koster nu 90–150 kr/år per domæne. Det er absurd, og det ved vi godt.

@@ -122,33 +123,33 @@

Punktum dk A/S er den danske registry — det centrale register for alle .dk-domæner. Lidt ligesom Tinglysningsretten er for ejendomme: de er registret, ikke en udbyder. - Indtil nu har tekniske brugere kunnet administrere domæner direkte hos Punktum dk uden mellemmand. - Det slutter nu. + Tekniske brugere har i årevis kunnet administrere deres domæner direkte hos Punktum dk uden mellemmand. + Det er slut: modellen er lukket for nye domæner, og for de eksisterende løber tiden ud.

+
+
+
+ Før 1. juli 2026 +

Direkte registrering hos Punktum dk

+

Som registrant kunne du oprette, betale og administrere dine .dk-domæner direkte hos Punktum dk — uden mellemmand.

+
+
- Nu — maj 2026 -

Direkte registrering virker stadig

-

Har du administreret dine domæner direkte hos Punktum dk, kører det fortsat. Men det ændrer sig meget snart.

+ 1. juli 2026 Gældende nu +

Direkte registrering er lukket for nye domæner

+

Punktum dk har lukket for oprettelse af nye domæner uden mellemled. Nye .dk-domæner oprettes nu kun via en registrator — uanset om du bruger deres services eller ej — og registrering og fornyelse sker ét år ad gangen. Dine eksisterende, direkte registrerede domæner kører videre, men kun indtil overgangsperioden udløber.

- 1. juli 2026 Snart -

Nye .dk-domæner kræver registrator

-

Direkte registrering af nye domæner hos Punktum dk udgår. Du skal have en akkrediteret registrator — uanset om du bruger deres services eller ej.

-
-
-
-
-
- 1. juli 2028 -

Alle eksisterende domæner skal overføres

-

Også domæner registreret direkte hos Punktum dk skal nu have en registrator som formelt mellemled. Intet ændrer sig teknisk — men det administrative led er obligatorisk.

+ 30. juni 2028 Næste frist +

Overgangsperioden udløber

+

Herefter skal alle .dk-domæner ligge hos en registrator — også dem, du i dag har direkte hos Punktum dk. Teknisk ændres intet, men mellemleddet er obligatorisk.

@@ -215,7 +216,14 @@
Hvornår er PunktFri klar?
-

Vi arbejder på det. Målet er at have forening stiftet og akkreditering ansøgt inden 1. juli 2026-deadlinen for nye domæner. Tilmeldingerne her er afgørende for, om vi har den kritiske masse til at gå videre — juridisk, teknisk og organisatorisk.

+

Vi arbejder på det. Den frist, der betyder mest for selvhostere med eksisterende domæner, er 30. juni 2028 — og målet er at være klar i god tid før. Tilmeldingerne her er afgørende for, om vi har den kritiske masse til at gå videre — juridisk, teknisk og organisatorisk.

+
+
+ +
+ Jeg skal bruge et nyt .dk-domæne nu — hvad gør jeg? +
+

Siden 1. juli 2026 skal nye domæner oprettes via en registrator. Indtil PunktFri er akkrediteret, må du bruge en eksisterende registrator. Når PunktFri er klar, kan du flytte domænet til os — dine navneservere og zoner er upåvirkede af en registratorflytning.

@@ -227,9 +235,9 @@
- Hvad sker der med mine domæner, hvis jeg intet gør inden 2028? + Hvad sker der med mine domæner, hvis jeg intet gør inden 30. juni 2028?
-

Punktum dk har endnu ikke offentliggjort præcis, hvad der sker — men de vil sandsynligvis tvinge en overflytning til en registrator, du ikke selv har valgt. Det er bedre at vælge sin registrator på egne præmisser og i god tid.

+

Punktum dk har meldt ud, at de selv vil afgøre, hvad der sker med domæner, som ikke er flyttet til en registrator inden 30. juni 2028. I værste fald risikerer du at miste kontrollen over domænet. Mange har desuden forældede kontaktoplysninger hos Punktum dk, hvilket kan trække en overførsel i langdrag — så vent ikke til sidste øjeblik, og vælg din registrator på egne præmisser.

@@ -284,7 +292,15 @@ "name": "Hvornår er PunktFri klar?", "acceptedAnswer": { "@type": "Answer", - "text": "Vi arbejder på det. Målet er at have forening stiftet og akkreditering ansøgt inden 1. juli 2026-deadlinen for nye domæner. Tilmeldingerne her er afgørende for, om vi har den kritiske masse til at gå videre — juridisk, teknisk og organisatorisk." + "text": "Vi arbejder på det. Den frist, der betyder mest for selvhostere med eksisterende domæner, er 30. juni 2028 — og målet er at være klar i god tid før. Tilmeldingerne her er afgørende for, om vi har den kritiske masse til at gå videre — juridisk, teknisk og organisatorisk." + } + }, + { + "@type": "Question", + "name": "Jeg skal bruge et nyt .dk-domæne nu — hvad gør jeg?", + "acceptedAnswer": { + "@type": "Answer", + "text": "Siden 1. juli 2026 skal nye domæner oprettes via en registrator. Indtil PunktFri er akkrediteret, må du bruge en eksisterende registrator. Når PunktFri er klar, kan du flytte domænet til os — dine navneservere og zoner er upåvirkede af en registratorflytning." } }, { @@ -297,10 +313,10 @@ }, { "@type": "Question", - "name": "Hvad sker der med mine domæner, hvis jeg intet gør inden 2028?", + "name": "Hvad sker der med mine domæner, hvis jeg intet gør inden 30. juni 2028?", "acceptedAnswer": { "@type": "Answer", - "text": "Punktum dk har endnu ikke offentliggjort præcis, hvad der sker — men de vil sandsynligvis tvinge en overflytning til en registrator, du ikke selv har valgt. Det er bedre at vælge sin registrator på egne præmisser og i god tid." + "text": "Punktum dk har meldt ud, at de selv vil afgøre, hvad der sker med domæner, som ikke er flyttet til en registrator inden 30. juni 2028. I værste fald risikerer du at miste kontrollen over domænet. Mange har desuden forældede kontaktoplysninger hos Punktum dk, hvilket kan trække en overførsel i langdrag — så vent ikke til sidste øjeblik, og vælg din registrator på egne præmisser." } }, { diff --git a/templates/tak.html b/templates/tak.html index cb85617..1fcb21a 100644 --- a/templates/tak.html +++ b/templates/tak.html @@ -19,7 +19,7 @@ Jo flere selvhostere der tilkendegiver interesse, jo bedre argumenterer vi overfor Punktum dk og potentielle medstiftere. Send dem linket.

- + Kopieret!
diff --git a/tests/test_app.py b/tests/test_app.py index 44c8e6e..0467d11 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -90,3 +90,30 @@ def test_signup_duplicate_email(client): r = client.post("/", data=data) assert r.status_code == 200 assert "allerede" in r.data.decode("utf-8") + + +def test_signup_sends_notification(client, monkeypatch): + sent = [] + monkeypatch.setattr(app_module, "SMTP_HOST", "smtp.example") + monkeypatch.setattr(app_module, "_send", sent.append) + monkeypatch.setattr(app_module.threading, "Thread", + lambda target, args, daemon: type("T", (), {"start": lambda self: target(*args)})()) + client.post("/", data={ + "navn": "Notif Test", "email": "notif@test.dk", + "domaener": "4", "egne_ns": "ja", "kommentar": "hej", + }) + assert len(sent) == 1 + msg = sent[0] + assert msg["To"] == "henrik@i80.dk" + assert msg["Reply-To"] == "notif@test.dk" + assert "Notif Test" in msg["Subject"] + assert "hej" in msg.get_content() + + +def test_signup_without_smtp_skips_notification(client, monkeypatch): + monkeypatch.setattr(app_module, "SMTP_HOST", "") + monkeypatch.setattr(app_module, "_send", lambda m: pytest.fail("should not send")) + r = client.post("/", data={ + "navn": "X", "email": "nosmtp@test.dk", "domaener": "1", "egne_ns": "nej", + }) + assert r.status_code == 302