Compare commits

...

4 Commits

Author SHA1 Message Date
1c0d16c2c1 updated to golang 1.26
All checks were successful
release-tag / release-image (push) Successful in 1m46s
2026-02-20 08:15:01 +01:00
ce9086d314 bugfix
All checks were successful
release-tag / release-image (push) Successful in 2m12s
2026-02-19 21:32:44 +01:00
7c5d288ca6 added autoreport from Generator-App
All checks were successful
release-tag / release-image (push) Successful in 2m29s
2026-02-19 13:35:14 +01:00
553d216a42 Update für bessere Kompatibilität 2026-02-19 13:23:54 +01:00
9 changed files with 373 additions and 196 deletions

View File

@@ -1,4 +1,4 @@
FROM golang:1.25-alpine AS build
FROM golang:1.26-alpine AS build
WORKDIR /app
# Optional: git + certs for private modules; cache modules and build cache
@@ -22,4 +22,5 @@ WORKDIR /app
COPY --from=build /out/pgpdashboard /app/pgpdashboard
EXPOSE 8080
USER nonroot:nonroot
#KEYSERVER_API_TOKEN='supersecret'
ENTRYPOINT ["/app/pgpdashboard"]

View File

@@ -1,18 +1,18 @@
[
{
"id": "max_at_send.nrw--D88C7FA9A544ECF8BCCEC6EB8F0B3E5851F2C8CC",
"name": "max",
"email": "max@send.nrw",
"fingerprint": "D88C7FA9A544ECF8BCCEC6EB8F0B3E5851F2C8CC",
"filename": "public.asc",
"created_at": "2025-09-22T20:48:08.8860389+02:00"
"id": "j.bergner.hilden_at_gmail.com--EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291",
"name": "Jan Bergner",
"email": "j.bergner.hilden@gmail.com",
"fingerprint": "EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291",
"filename": "EA8F1C0B6A456CD4C1474AEC8162092BE1C1B291.asc",
"created_at": "2026-02-19T20:53:37.1668347+01:00"
},
{
"id": "jan.bergner_at_gmail.com--77588003EACB3CFF76B3C1B1A1E557B03E42CC77",
"name": "Jan Bergner",
"email": "jan.bergner@gmail.com",
"fingerprint": "77588003EACB3CFF76B3C1B1A1E557B03E42CC77",
"filename": "0x3E42CC77-pub.asc",
"created_at": "2025-09-22T20:02:50.1116136+02:00"
"id": "jan.bergner_at_hilden.de--660D6A7E3F56BF0BADABA140EFE349B5719CEFC4",
"name": "Jan Bergner (Stadt Hilden)",
"email": "jan.bergner@hilden.de",
"fingerprint": "660D6A7E3F56BF0BADABA140EFE349B5719CEFC4",
"filename": "660D6A7E3F56BF0BADABA140EFE349B5719CEFC4.asc",
"created_at": "2026-02-19T20:52:54.5792736+01:00"
}
]

View File

@@ -1,98 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: Keybase OpenPGP v1.0.0
Comment: https://keybase.io/crypto
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
S6vU
=hcF1
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,56 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: https://gopenpgp.org
Version: GopenPGP 2.9.0
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
xx/h8PnQbTFYuJSENfy42Dg=
=VnbW
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -0,0 +1,56 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: https://gopenpgp.org
Version: GopenPGP 2.9.0
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
JKIVC+HVIU6b
=drfJ
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -1,55 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: https://gopenpgp.org
Version: GopenPGP 2.9.0
xsFNBGjRma8BEACuJeyPs7T+VP3LDbJe/FyvojsRTQlGWpBthEKhDpU5VYyKg38P
Q/V+402jTqJkqBLJa+S1q7hN/PQ9It/+88MZFmh5o6rSA1lOfXV3lG/RjwNMOkl6
cgFE90wQmQHIGG+gvZcXShLItTtl8j9Y0/ag+6WIeBS5sVvs8EiYxJVWXMy/BYoR
SMVU1nrvvGM1RISqeXCX7RTxdsRgBmFjpxFtclIEqIwxLgUOgxr+HvB8TcjvB3gP
oCPLhp9k4riurISrKGxMy7USB+5+EFHSBP5ICP0CNODsWL+Yd5npeVfs8oA0d5HQ
diS9qh84pcWAbzoZaqZCj8+9B07Bt8hNqtx6PCbUGymm4Ef2rtV84jhkKDV5aqJx
M2hnrS+FzhWvaoBpDTes3GHWo3BO0InKbrqwFCp5Ds4Ekq7Tiw5vx3LmHXjgA/up
ad+km1JQ1/aMMKAqDUVIUgpdGWu8mKeB+7nbfWM9QMLABllrSH0H0f0j7Fba5N7W
LmaA5cmc46l0jsXtgYMnTJOrMPbRklk6pO9T64P85E60fX+mz8D09Zjrhu3u2tY3
FwEfyN6ZhHmKiIZXuFf++pdSu+nVa+BXpnqMWej802QOVcQgEDZEHQtWasm9EtEo
IHyLfDsC81RZYpqhcrtbFAl8/MJzLJu+U29FcjsCydca0jsyaB3NrXF3EwARAQAB
zRJtYXggPG1heEBzZW5kLm5ydz7Cwb0EEwEIAHEFgmjRma8DCwkHCRCPCz5YUfLI
zDUUAAAAAAAcABBzYWx0QG5vdGF0aW9ucy5vcGVucGdwanMub3JnhXnoDRWvzhAp
QPRcXRETYgIVCAMWAAICGQECmwMCHgEWIQTYjH+ppUTs+LzOxuuPCz5YUfLIzAAA
JUoP/0GRyu4kdLqAxCHrWaOpBzHFpZrbjrVWuhpNElEsDH3Jthhwd9vsOEQBhoTG
Fj3R/l+BuWWI6VeaPboEtMCqSaoXnYWKyzpJvfkS0EEc+yiN4HXPcai4NBLBnSJI
9JnSVaTg6+xEJhQ1kCbKviGzxcOKMn55ygBz5cECblaOHdZFzA4XB0bK6tiVwMi/
M2rhq5Nk+X4LW8El3L1QKrt3Mo1iiiqmZ4XJTevZh9w3cUiKKaIwXxIO7T9KkgLe
BFv+1jg7IemnNMfnHzeSX8JPX3odfRXsRWLfYteuO7u4awTadZjF2okH3FkhDoXF
K2Orl1q69kbHUYRYRHzoPFURZR9wbCbdZAAn9qT6Ihaiyg8taAz58Z25ldgWDIP8
ql8+vlCnxF102f1ZY8XTxLQfNsTYPfFZ4Kspq0g31GuY03IIpdS9RcyjrHXJ7NDx
JXFfjQDcKRBDbzVknpEPOLYG86BMFlgxPntOoR55KHttNzzjdGN2nW/TOcwkArwF
1xvUImIuSgeV+p6LAUDRdMXVBLVZ+1HDOcz+L/9lamZfgMq1wfma0XcxeaHJNsL2
f6BUGr/52MdOEuEQ53VRNX3g3fEZ5qHtDu8Au6avQisJdwTQ7jJdxAvXNfRWe5Cx
3n0uap//c/IkDHhRONdiY/Ph1aVCOJk6y/tGh0VKki4Bl/KZzsFNBGjRma8BEADX
iVUBaa3BxGrv6tvXRfDSl6q3JQojDH6pPKmJVT3dnyPb4XIcJ3xh0lsroeme4p01
oMNn//TNqnH1mQMf53bZtMsJ3DGfaZ6l+zRKACCnNZVuiAHzUUHua8W3bfMTkm32
iRvpI0/6Ch19znUPANdjxrANtuDqVtZB6NLr9nvQmtZDcLfSPw0xhrE/JYaLhpWM
ULMl99rJgpGivBLMwjtUaN42pd3yt/HiY0xh9WqHQUGCKSFxmbiuriDaR/65XNul
OzX0LypAqP8etvfSD90dMqOFIJ9jsjGAt9qGIvasnvS8ONvcVu/hvx3C8JUxQNfx
mi0nTR5EAUO3NXTp2wOi0tWLzRNW15NgWlEMuG6zAJPhnFPUnd3dEivbSqKEyRtO
b9kdTruIsGMFxn+QN/IGIabTIwekCpaPg0RJQqbzk22tBRS/yJOGtX7DKbC0cU4K
cVGyW2vOj/yHFQ84pEWZ/myk8+zdCWamxkFnmCBl7TdTGg5/VmQxsw2j44R2U6Ax
VkHl9SRJolGCMJYJlatvKybsuAyH0kCn6dbyxtpc/A4GFkIsMToVnsDr6hA1mPEe
lJaPRQ0mUUc0RA2BDXvmv5g62bNzNnNYlUGKvFAZotj88C9zYHKhmn4nkM9JfQIG
f6G8xlIKhMw+XZizZbi1r2MazbUZJ7TSvoFN25G/wwARAQABwsGsBBgBCABgBYJo
0ZmvCRCPCz5YUfLIzDUUAAAAAAAcABBzYWx0QG5vdGF0aW9ucy5vcGVucGdwanMu
b3JnUEPsuZ872vRsropTvyLVZQKbDBYhBNiMf6mlROz4vM7G648LPlhR8sjMAAD0
Kw/6ApWnJyDH6885a9XuP5BvwDXhLAhYtid+AgKqJI0BgC/LoseUIxed8F/W/qXs
BO0xSge3w4XDCf/eMQrJonQvGR3CJWeD7kdxnDizMRn4xYNqnpZVJpKF+Esvym+M
PxQoqG7uOTdAkpDwvb1d8nmFPf3+9LattSw6vTSTTE8yHL4RTG8xDBTp9d71LyCQ
vw0C0kHCkDVAH1AOLrUkN4qCERk9i1LCWqFY8dz+i7/yKCYcZgFH5VX5nOOEoht2
tt24XbQ8GOU7s6zhekY71UqCEr7Z5HuOmtjBtle4odT8fnogWsH3H1fIB6qNzKgz
dlOjn09ZyFV/xJv9lqMgTURFuRO28U8BOuC8LERgX3zWjoN8b/A5ni1Pq68JHrWY
xe0cErwnXINCMcAzp7vboAztZxnKfQ9WDJzVHFkgVAHqVNUw4qkrka0Cwp+m1y/H
UsTVJnNNwqwzrWfgMgDxuqXc5dYRW3CPjh7691Y1Gn+J2e7m8cRAdz2hFddI0+cl
1BblQytXfSseQ3fr/ahXjo6qDV3Y9MCjPCu+2J7zQdiDLgZzShZIOSUBISbw1dOK
FoZCrDqG3kUis06VHKlQcQsDJvdfhAgbFkymksQKE7EPDmyIuV1EqWxI/oC5ROOT
gaBFe8PcFGd8vUtDlyvUk3vDlQMcHEKAPIrzULgZud0Wl5Y=
=0v61
-----END PGP PUBLIC KEY BLOCK-----

273
main.go
View File

@@ -13,7 +13,7 @@
//
// Notes
// - This server publishes PUBLIC keys only.
// - WKD uses z-base-32(SHA1(strings.ToLower(addr-spec))) per spec.
// - WKD uses z-base-32(SHA1(strings.ToLower(local-part))) per spec.
// - HKP here is minimal: /pks/lookup?op=get|index&search=<term> (email/fpr/substring).
// - Protect /upload behind auth in production.
package main
@@ -69,6 +69,35 @@ type store struct {
byID map[string]KeyRecord
}
type apiUploadReq struct {
Name string `json:"name"`
Email string `json:"email"`
Fingerprint string `json:"fingerprint"` // optional, wird sonst aus Key geparsed
PublicArmored string `json:"public_armored"` // ASCII armored public key
Filename string `json:"filename"` // optional
}
type apiUploadResp struct {
ID string `json:"id"`
Email string `json:"email"`
Fingerprint string `json:"fingerprint"`
WKDHash string `json:"wkd_hash"`
Domain string `json:"domain"`
Local string `json:"local"`
}
func bearerOK(r *http.Request, token string) bool {
if token == "" {
return false
}
h := r.Header.Get("Authorization")
const p = "Bearer "
if !strings.HasPrefix(h, p) {
return false
}
return strings.TrimSpace(strings.TrimPrefix(h, p)) == token
}
func newStore() *store { return &store{byID: make(map[string]KeyRecord)} }
// persistSnapshot schreibt eine bereits kopierte Liste auf Disk.
@@ -271,16 +300,38 @@ func zbase32Encode(b []byte) string {
return string(out)
}
// wkdHash: z-base-32(SHA1(strings.ToLower(addr-spec))) + domain
func wkdHash(email string) (hash string, domain string) {
email = strings.ToLower(strings.TrimSpace(email))
// wkdHash: z-base-32(SHA1(strings.ToLower(local-part))) + domain
func wkdHash(email string) (hash string, domain string, local string) {
email = strings.TrimSpace(email)
parts := strings.Split(email, "@")
if len(parts) != 2 {
return "", ""
return "", "", ""
}
domain = parts[1]
s := sha1.Sum([]byte(email))
return zbase32Encode(s[:]), domain
local = parts[0] // keep original for ?l=
domain = strings.ToLower(parts[1])
lp := strings.ToLower(local)
s := sha1.Sum([]byte(lp))
return zbase32Encode(s[:]), domain, local
}
func armoredToBinary(arm []byte) ([]byte, error) {
ents, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(arm))
if err != nil {
return nil, err
}
if len(ents) == 0 {
return nil, fmt.Errorf("no keys in armored data")
}
var out bytes.Buffer
for _, e := range ents {
// Serialize schreibt binary OpenPGP packets (kein Armor)
if err := e.Serialize(&out); err != nil {
return nil, err
}
}
return out.Bytes(), nil
}
func main() {
@@ -311,6 +362,109 @@ func main() {
http.Error(w, err.Error(), 500)
}
})
apiToken := getenv("KEYSERVER_API_TOKEN", "12345678")
mux.HandleFunc("/api/v1/keys", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if !bearerOK(r, apiToken) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// Limit body size
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
defer r.Body.Close()
var req apiUploadReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
req.Name = strings.TrimSpace(req.Name)
req.Email = strings.TrimSpace(req.Email)
req.Fingerprint = strings.TrimSpace(req.Fingerprint)
req.PublicArmored = strings.TrimSpace(req.PublicArmored)
if req.Email == "" || !strings.Contains(req.Email, "@") {
http.Error(w, "missing/invalid email", http.StatusBadRequest)
return
}
if req.PublicArmored == "" || !strings.Contains(req.PublicArmored, "-----BEGIN PGP PUBLIC KEY BLOCK-----") {
http.Error(w, "public_armored must be ASCII-armored PGP public key", http.StatusBadRequest)
return
}
b := []byte(req.PublicArmored)
// Parse fingerprint (authoritative)
autoFPR, err := parseFingerprintFromASCII(b)
if err != nil || autoFPR == "" {
http.Error(w, "could not parse fingerprint from key", http.StatusBadRequest)
return
}
fpr := strings.ToUpper(strings.ReplaceAll(autoFPR, " ", ""))
// Optional: wenn req.Fingerprint gesetzt ist, validieren wir nur (kein override)
if req.Fingerprint != "" && normalizeFPR(req.Fingerprint) != normalizeFPR(fpr) {
http.Error(w, "fingerprint mismatch", http.StatusBadRequest)
return
}
// filename: prefer fingerprint (unique) -> <FPR>.asc
fn := normalizeFPR(fpr)
if fn == "" {
fn = sanitizeFilename(req.Email) // fallback (shouldn't happen)
} else {
fn = fn + ".asc"
}
fn = sanitizeFilename(fn) // keeps it safe, but doesn't mangle hex
path := filepath.Join(keysDir, fn)
oldID := genID(req.Email, fpr)
if old, ok := st.get(oldID); ok {
if old.Filename != "" && old.Filename != fn {
_ = os.Remove(filepath.Join(keysDir, old.Filename))
}
}
if err := os.WriteFile(path, b, 0o644); err != nil {
http.Error(w, "save error", http.StatusInternalServerError)
return
}
rec := KeyRecord{
ID: oldID,
Name: req.Name,
Email: req.Email,
Fingerprint: fpr,
Filename: fn,
CreatedAt: time.Now(),
}
if err := st.upsert(rec); err != nil {
http.Error(w, "index error", http.StatusInternalServerError)
return
}
// WKD meta (wenn du die bereits korrigierte wkdHash(local-part) nutzt)
h, d, l := wkdHash(rec.Email) // falls deine Signatur noch (hash,domain) ist
// Wenn du die neue wkdHash(email) -> (hash,domain,local) nutzt, dann: h,d,_ := wkdHash(rec.Email)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(apiUploadResp{
ID: rec.ID,
Email: rec.Email,
Fingerprint: rec.Fingerprint,
WKDHash: h,
Domain: d,
Local: l,
})
})
// Live search (HTMX)
mux.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
@@ -322,7 +476,7 @@ func main() {
})
// Upload with automatic fingerprint parsing
mux.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
if enabled("WRITEACCESS", false) {
if enabled("WRITEACCESS", true) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -334,7 +488,7 @@ func main() {
name := strings.TrimSpace(r.FormValue("name"))
email := strings.TrimSpace(r.FormValue("email"))
userFPR := strings.TrimSpace(r.FormValue("fingerprint")) // optional override
file, hdr, err := r.FormFile("file")
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, "missing file", http.StatusBadRequest)
return
@@ -363,10 +517,8 @@ func main() {
}
fpr = strings.ToUpper(strings.ReplaceAll(fpr, " ", ""))
base := sanitizeFilename(hdr.Filename)
if base == ".asc" || base == "" {
base = sanitizeFilename(email)
}
base := normalizeFPR(fpr) + ".asc"
base = sanitizeFilename(base)
path := filepath.Join(keysDir, base)
if err := os.WriteFile(path, b, 0o644); err != nil {
http.Error(w, "save error", 500)
@@ -428,16 +580,26 @@ func main() {
mux.HandleFunc("/.well-known/openpgpkey/policy", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("# WKD policy"))
_, _ = w.Write([]byte("# WKD policy\n"))
})
// direct method: /.well-known/openpgpkey/hu/<hash>
mux.HandleFunc("/.well-known/openpgpkey/hu/", func(w http.ResponseWriter, r *http.Request) {
pathHash := strings.TrimPrefix(r.URL.Path, "/.well-known/openpgpkey/hu/")
hash := strings.TrimPrefix(r.URL.Path, "/.well-known/openpgpkey/hu/")
hash = strings.Trim(hash, "/")
// Optional: ?l=... (unverändert, percent-encoded) — nur als Hint/Check
lParam := r.URL.Query().Get("l")
var match *KeyRecord
var matchLocal string
for _, it := range st.all() {
h, _ := wkdHash(it.Email)
if h == pathHash {
h, _, local := wkdHash(it.Email)
if h == hash {
if lParam != "" && lParam != local {
continue
}
match = &it
matchLocal = local
break
}
}
@@ -445,27 +607,70 @@ func main() {
http.NotFound(w, r)
return
}
data, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
arm, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/pgp-keys")
bin, err := armoredToBinary(arm)
if err != nil {
http.Error(w, "invalid stored key data", http.StatusInternalServerError)
return
}
// WKD responses are typically application/octet-stream
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Access-Control-Allow-Origin", "*") // optional, hilft manchen Tools
if r.Method == http.MethodHead {
w.WriteHeader(http.StatusOK)
return
}
_ = matchLocal // falls du später Logging möchtest
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
_, _ = w.Write(bin)
})
// advanced method: /openpgpkey/<domain>/hu/<hash>
mux.HandleFunc("/openpgpkey/", func(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/openpgpkey/"), "/")
mux.HandleFunc("/.well-known/openpgpkey/", func(w http.ResponseWriter, r *http.Request) {
// Nur advanced bedienen, wenn Host = openpgpkey.<domain>
host := r.Host
if i := strings.IndexByte(host, ':'); i >= 0 {
host = host[:i]
}
if !strings.HasPrefix(strings.ToLower(host), "openpgpkey.") {
// Für die Hauptdomain sind policy + /hu/ bereits separat gemappt
http.NotFound(w, r)
return
}
// Erwartet:
// /.well-known/openpgpkey/<domain>/hu/<hash>
// oder: /.well-known/openpgpkey/<domain>/policy
rest := strings.TrimPrefix(r.URL.Path, "/.well-known/openpgpkey/")
parts := strings.Split(strings.Trim(rest, "/"), "/")
if len(parts) == 2 && parts[1] == "policy" {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("# WKD policy\n"))
return
}
if len(parts) != 3 || parts[1] != "hu" {
http.NotFound(w, r)
return
}
domain, hash := parts[0], parts[2]
domain := strings.ToLower(parts[0])
hash := parts[2]
lParam := r.URL.Query().Get("l")
var match *KeyRecord
for _, it := range st.all() {
h, d := wkdHash(it.Email)
h, d, local := wkdHash(it.Email)
if h == hash && strings.EqualFold(d, domain) {
if lParam != "" && lParam != local {
continue
}
match = &it
break
}
@@ -474,14 +679,26 @@ func main() {
http.NotFound(w, r)
return
}
data, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
arm, err := os.ReadFile(filepath.Join(keysDir, match.Filename))
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/pgp-keys")
bin, err := armoredToBinary(arm)
if err != nil {
http.Error(w, "invalid stored key data", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == http.MethodHead {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
_, _ = w.Write(bin)
})
// --- Minimal HKP-compatible lookup ---

View File

@@ -22,7 +22,7 @@
<h1 class="brand h3 mb-0">🔐 PGP Key Server</h1>
<div class="muted">Unsere öffentlichen OpenPGP-Schlüssel</div>
</div>
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#uploadModal">+ Upload</button>
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#uploadModal">+ Hochladen</button>
</div>
{{template "content" .}}

View File

@@ -9,7 +9,7 @@
<td><code>{{.Fingerprint}}</code></td>
<td class="text-end">
<div class="btn-group">
<a class="btn btn-sm btn-outline-primary" href="/view/{{.ID}}">View</a>
<a class="btn btn-sm btn-outline-primary" href="/view/{{.ID}}">Ansehen</a>
<a class="btn btn-sm btn-primary" href="/keys/{{.ID}}">Download</a>
</div>
</td>