v1.0.0
Some checks failed
release-tag / release-image (push) Successful in 4m8s
build-binaries / build (, amd64, linux) (push) Failing after 40s
build-binaries / build (, arm, 7, linux) (push) Failing after 37s
build-binaries / build (, arm64, linux) (push) Failing after 37s
build-binaries / build (.exe, amd64, windows) (push) Failing after 36s
build-binaries / release (push) Has been skipped

This commit is contained in:
2025-10-05 10:01:28 +02:00
parent eff64482a1
commit c094b36af9
5 changed files with 513 additions and 18 deletions

View File

@@ -0,0 +1,51 @@
name: release-tag
on:
push:
branches:
- 'main'
jobs:
release-image:
runs-on: ubuntu-fast
env:
DOCKER_ORG: ${{ vars.DOCKER_ORG }}
DOCKER_LATEST: latest
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
with: # replace it with your local IP
config-inline: |
[registry."${{ vars.DOCKER_REGISTRY }}"]
http = true
insecure = true
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ vars.DOCKER_REGISTRY }} # replace it with your local IP
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
push: true
tags: | # replace it with your local IP and tags
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}

View File

@@ -0,0 +1,124 @@
# Git(tea) Actions workflow: Build and publish standalone binaries **plus** bundled `static/` assets
# ────────────────────────────────────────────────────────────────────
# ✧ Builds the Gobased WoL server for four targets **and** packt das Verzeichnis
# `static` zusammen mit der Binary, sodass es relativ zur ausführbaren Datei
# liegt (wichtig für die eingebauten BootstrapAssets & favicon).
#
# • linux/amd64 → wol-server-linux-amd64.tar.gz
# • linux/arm64 → wol-server-linux-arm64.tar.gz
# • linux/arm/v7 → wol-server-linux-armv7.tar.gz
# • windows/amd64 → wol-server-windows-amd64.zip
#
# ✧ Artefakte landen im Workflow und bei TagPush (vX.Y.Z) als ReleaseAssets.
#
# Secrets/variables:
# GITEA_TOKEN optional, falls default token keine ReleaseRechte hat.
# ────────────────────────────────────────────────────────────────────
name: build-binaries
on:
push:
branches: [ "main" ]
tags: [ "v*" ]
jobs:
build:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-fast
strategy:
matrix:
include:
- goos: linux
goarch: amd64
ext: ""
- goos: linux
goarch: arm64
ext: ""
- goos: linux
goarch: arm
goarm: "7"
ext: ""
- goos: windows
goarch: amd64
ext: ".exe"
env:
GO_VERSION: "1.24"
BINARY_NAME: advocacy-watchlist
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Build ${{ matrix.goos }}/${{ matrix.goarch }}${{ matrix.goarm && format('/v{0}', matrix.goarm) || '' }}
shell: bash
run: |
set -e
mkdir -p dist/package
if [ -n "${{ matrix.goarm }}" ]; then export GOARM=${{ matrix.goarm }}; fi
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -trimpath -ldflags "-s -w" \
-o "dist/package/${BINARY_NAME}${{ matrix.ext }}" .
# Assets: statisches Verzeichnis beilegen
cp -r static dist/package/
- name: Package archive with static assets
shell: bash
run: |
set -e
cd dist
if [ "${{ matrix.goos }}" == "windows" ]; then
ZIP_NAME="${BINARY_NAME}-windows-amd64.zip"
(cd package && zip -r "../$ZIP_NAME" .)
else
ARCH_SUFFIX="${{ matrix.goarch }}"
if [ "${{ matrix.goarch }}" == "arm" ]; then ARCH_SUFFIX="armv${{ matrix.goarm }}"; fi
TAR_NAME="${BINARY_NAME}-${{ matrix.goos }}-${ARCH_SUFFIX}.tar.gz"
tar -czf "$TAR_NAME" -C package .
fi
- name: Upload workflow artifact
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.goarm && format('v{0}', matrix.goarm) || '' }}
path: dist/*.tar.gz
if-no-files-found: ignore
- uses: actions/upload-artifact@v3
with:
name: windows-amd64
path: dist/*.zip
if-no-files-found: ignore
# Release Schritt für TagPushes
release:
if: startsWith(github.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-fast
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
path: ./dist
- name: Create / Update release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}
with:
name: "Release ${{ github.ref_name }}"
tag_name: ${{ github.ref_name }}
draft: false
prerelease: false
files: |
dist/**/advocacy-watchlist-*.tar.gz
dist/**/advocacy-watchlist-*.zip

8
Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM golang:1.24.4
WORKDIR /app
COPY go.mod ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /goprg
EXPOSE 8080
CMD ["/goprg"]

231
data.json
View File

@@ -9,30 +9,239 @@
{
"username": "jbergner",
"password_hash": "xO0T3HChR2wCqr2/EdGBh5DImelVl1WQZ7pji+MFW84=:yhjVxufYiqdzq0XQZBcRtQ==",
"is_admin": false,
"is_admin": true,
"created_at": 1759593689
},
{
"username": "B1tK1ll3r",
"password_hash": "y+KukuKzR+/5XKQdiUyvHYcwgbTJanzSwnZ/YTFB0XM=:bquVapMq6at57Ta5z2UCKw==",
"is_admin": false,
"created_at": 1759611544
}
],
"reports": [
{
"id": 3,
"name": "B1tK1ll3r",
"id": 5,
"name": "SOLHELFROST",
"status": "Bestätigt",
"reported_by": "admin",
"confirmed_by": "jbergner",
"created_at": 1759593661,
"confirmed_at": 1759593901,
"confirmed_by": "B1tK1ll3r",
"created_at": 1759612138,
"confirmed_at": 1759612167,
"reasons": [
{
"id": 4,
"text": "Trottel",
"id": 5,
"text": "Griefing mit Polaris",
"added_by": "admin",
"added_at": 1759593661
"added_at": 1759612138
}
]
}
],
"audit": [
{
"time": 1759651126,
"actor": "admin",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759614492,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759614266,
"actor": "jbergner",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759613631,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759613314,
"actor": "admin",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759612170,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759612167,
"actor": "B1tK1ll3r",
"action": "confirm",
"details": "ID 5 · SOLHELFROST",
"ip": "127.0.0.1"
},
{
"time": 1759612164,
"actor": "B1tK1ll3r",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759612150,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759612138,
"actor": "admin",
"action": "report",
"details": "ID 5 · SOLHELFROST",
"ip": "127.0.0.1"
},
{
"time": 1759612060,
"actor": "admin",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759612020,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759611994,
"actor": "admin",
"action": "delete",
"details": "ID 4 · Jan Bergner",
"ip": "127.0.0.1"
},
{
"time": 1759611990,
"actor": "admin",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759611575,
"actor": "jbergner",
"action": "delete",
"details": "ID 3 · B1tK1ll3r",
"ip": "127.0.0.1"
},
{
"time": 1759611564,
"actor": "jbergner",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759611559,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759611553,
"actor": "B1tK1ll3r",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759611547,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759611544,
"actor": "jbergner",
"action": "user_create",
"details": "B1tK1ll3r",
"ip": "127.0.0.1"
},
{
"time": 1759594548,
"actor": "jbergner",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759594542,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759594537,
"actor": "admin",
"action": "user_toggle_admin",
"details": "jbergner",
"ip": "127.0.0.1"
},
{
"time": 1759594531,
"actor": "admin",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759594526,
"actor": "",
"action": "logout",
"details": "",
"ip": "127.0.0.1"
},
{
"time": 1759594412,
"actor": "jbergner",
"action": "confirm",
"details": "ID 4 · Jan Bergner",
"ip": "127.0.0.1"
},
{
"time": 1759594402,
"actor": "jbergner",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759593988,
"actor": "admin",
"action": "report",
"details": "ID 4 · Jan Bergner",
"ip": "127.0.0.1"
},
{
"time": 1759593979,
"actor": "admin",
"action": "login",
"details": "Erfolg",
"ip": "127.0.0.1"
},
{
"time": 1759593901,
"actor": "jbergner",
@@ -335,6 +544,6 @@
"ip": "127.0.0.1"
}
],
"next_id": 4,
"next_rid": 5
"next_id": 6,
"next_rid": 6
}

117
main.go
View File

@@ -245,12 +245,12 @@ var baseTpl = `{{define "base"}}
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{block "title" .}}Meldesystem{{end}}</title>
<title>{{block "title" .}}Advocacy Watchlist{{end}}</title>
<style>
:root { --bg:#0f172a; --card:#111827; --muted:#9ca3af; --text:#e5e7eb; --acc:#06b6d4; --ok:#10b981; --warn:#f59e0b; --err:#ef4444; }
*{box-sizing:border-box} body{margin:0;background:linear-gradient(180deg,#0b1220,#0f172a);color:var(--text);font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
a{color:var(--acc);text-decoration:none} a:hover{text-decoration:underline}
.container{max-width:980px;margin:40px auto;padding:0 16px}
.container{max-width:1200px;margin:40px auto;padding:0 16px}
header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}
.brand{font-weight:800;font-size:20px;letter-spacing:.2px}
.card{background:var(--card);border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:20px;margin-bottom:16px}
@@ -278,7 +278,7 @@ var baseTpl = `{{define "base"}}
<body>
<div class="container">
<header>
<div class="brand">Meldesystem</div>
<div class="brand">Advocacy Watchlist</div>
<div class="flex">
{{if .IsAdmin}}<a class="badge" href="/users">👥 Nutzer</a>{{end}}
<a class="badge" href="/audit">🪪 Audit</a>
@@ -288,7 +288,7 @@ var baseTpl = `{{define "base"}}
</div>
</header>
{{template "content" .}}
<footer>Ohne externe Assets · Zeit: {{.Now}}</footer>
<footer>Advocacy Watchlist · Zeit: {{.Now}}</footer>
</div>
</body>
</html>
@@ -300,12 +300,24 @@ var loginTpl = `{{define "login"}}
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Login · Meldesystem</title>
<style>*{box-sizing:border-box} body{margin:0;display:grid;place-items:center;height:100vh;background:linear-gradient(180deg,#0b1220,#0f172a);color:#e5e7eb;font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial} .card{background:#111827;border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:24px;width:min(420px,94vw)} input{width:100%;max-width:100%;display:block;margin:0;border:1px solid #273244;border-radius:12px;background:#0b1220;color:#e5e7eb;padding:12px 14px} label{display:block;margin:8px 0 6px;color:#9ca3af} button{width:100%;margin-top:14px;padding:12px;border:0;border-radius:12px;background:#06b6d4;color:#001018;font-weight:800;cursor:pointer} .muted{color:#9ca3af;font-size:12px;text-align:center;margin-top:10px}</style>
<title>Login · Advocacy Watchlist</title>
<style>
*{box-sizing:border-box}
body{margin:0;display:grid;place-items:center;height:100vh;background:linear-gradient(180deg,#0b1220,#0f172a);color:#e5e7eb;font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
.card{background:#111827;border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:24px;width:min(420px,94vw)}
input{width:100%;max-width:100%;display:block;margin:0;border:1px solid #273244;border-radius:12px;background:#0b1220;color:#e5e7eb;padding:12px 14px}
label{display:block;margin:8px 0 6px;color:#9ca3af}
button{width:100%;margin-top:14px;padding:12px;border:0;border-radius:12px;background:#06b6d4;color:#001018;font-weight:800;cursor:pointer}
.badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;background:#0b1220;border:1px solid #1f2937;color:#9ca3af;font-size:12px} <!-- NEU -->
.muted{color:#9ca3af;font-size:12px;text-align:center;margin-top:10px}
</style>
</head>
<body>
<div class="card">
<h2 style="margin:0 0 8px">Anmeldung</h2>
<div style="margin-top:12px;text-align:center">
<a class="badge" href="/public">🌐 View public Entries</a>
</div>
<form method="POST" action="/login">
<label>Nutzername</label>
<input name="username" required />
@@ -422,7 +434,7 @@ var dashboardTpl = `{{define "dashboard"}}{{template "base" .}}{{end}}
<button type="submit">Bestätigen</button>
</form>
{{else}}
<span class="badge">⏳ Wartet auf Bestätigung durch andere</span>
<span class="badge">⏳ Wartet auf Bestätigung</span>
{{end}}
{{else}}
<span class="badge">✔ Bereits bestätigt</span>
@@ -443,6 +455,65 @@ var dashboardTpl = `{{define "dashboard"}}{{template "base" .}}{{end}}
</div>
{{end}}`
var publicTpl = `{{define "public_page"}}
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Advocacy Watchlist - Public</title>
<style>
:root { --bg:#0f172a; --card:#111827; --muted:#9ca3af; --text:#e5e7eb; --acc:#06b6d4; }
*{box-sizing:border-box} body{margin:0;background:linear-gradient(180deg,#0b1220,#0f172a);color:var(--text);font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
.container{max-width:880px;margin:40px auto;padding:0 16px}
header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}
.card{background:#111827;border:1px solid #1f2937;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:20px;margin-bottom:16px}
.row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
input[type=text]{flex:1 1 260px;padding:12px 14px;border:1px solid #273244;border-radius:12px;background:#0b1220;color:#e5e7eb}
button{padding:10px 14px;border:0;border-radius:12px;background:#06b6d4;color:#001018;font-weight:700;cursor:pointer}
table{width:100%;border-collapse:separate;border-spacing:0}
th,td{padding:12px 10px;border-bottom:1px solid #1f2937;font-size:14px}
th{text-align:left;color:#9ca3af;font-weight:600}
.muted{color:#9ca3af}
</style>
</head>
<body>
<div class="container">
<header>
<div class="brand">Advocacy Watchlist</div>
<div class="flex">
<a class="badge" href="/">🔒 Login</a>
</div>
</header>
<div class="card">
<h2 style="margin:0 0 10px">Public list of confirmed entries</h2>
<form class="row" method="GET" action="/public">
<input name="q" placeholder="Search…" value="{{.Q}}" />
<button type="submit">Filter</button>
</form>
<div class="muted" style="margin-top:8px">Only <b>confirmed</b> entries are displayed.</div>
</div>
<div class="card">
<table>
<thead><tr><th>Benutzername</th><th>Confirmed on</th></tr></thead>
<tbody>
{{range .Rows}}
<tr>
<td>{{.Name}}</td>
<td class="muted">{{.Since}}</td>
</tr>
{{else}}
<tr><td colspan="2" class="muted">No matching entries</td></tr>
{{end}}
</tbody>
</table>
</div>
</div>
</body>
</html>
{{end}}`
var auditTpl = `{{define "audit_page"}}
<!doctype html>
<html lang="de">
@@ -625,8 +696,10 @@ func main() {
template.Must(tpl.New("audit_bundle").Parse(auditTpl))
template.Must(tpl.New("users_bundle").Parse(usersTpl))
template.Must(tpl.New("me_bundle").Parse(meTpl))
template.Must(tpl.New("public_bundle").Parse(publicTpl))
http.HandleFunc("/", withAuth(handleIndex))
http.HandleFunc("/public", handlePublic)
http.HandleFunc("/reasons/add", withAuth(handleReasonAdd))
http.HandleFunc("/reasons/delete", withAuth(handleReasonDelete))
http.HandleFunc("/report", withAuth(handleReport))
@@ -895,6 +968,36 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
_ = tpl.ExecuteTemplate(w, "dashboard", data)
}
func handlePublic(w http.ResponseWriter, r *http.Request) {
q := strings.TrimSpace(r.URL.Query().Get("q"))
ql := strings.ToLower(q)
mu.Lock()
reports := make([]Report, 0, len(db.Reports))
for _, rep := range db.Reports {
if rep.Status != StatusBestaetigt {
continue // nur bestätigte öffentlich anzeigen
}
if q == "" || strings.Contains(strings.ToLower(rep.Name), ql) {
reports = append(reports, rep)
}
}
mu.Unlock()
rows := make([]map[string]any, 0, len(reports))
for _, rep := range reports {
rows = append(rows, map[string]any{
"Name": template.HTMLEscapeString(rep.Name),
"Since": time.Unix(rep.ConfirmedAt, 0).Format("02.01.2006 15:04"),
})
}
_ = tpl.ExecuteTemplate(w, "public_page", map[string]any{
"Q": q,
"Rows": rows,
})
}
func handleReport(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)