Compare commits
4 Commits
84d57ad12d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 035ffd925d | |||
| eb3f882bcf | |||
| dafbf2974e | |||
| 5d633c1706 |
179
.gitea/workflows/release.yml
Normal file
179
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
name: build-binaries
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
tags: [ "v*" ]
|
||||||
|
|
||||||
|
# Change this Variables to your needs
|
||||||
|
# Set these Secrets in your Organisation-Settings
|
||||||
|
# AGENT_URL=https://agent.your-domain.xyz <-- No Tailing-Slash (/) at the end!!
|
||||||
|
# AGENT_TOKEN=
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: "1.25"
|
||||||
|
BINARY_NAME: pcinfoagent
|
||||||
|
|
||||||
|
# Do not edit following except you need to change build-options
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-fast
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- goos: windows
|
||||||
|
goarch: amd64
|
||||||
|
ext: ".exe"
|
||||||
|
|
||||||
|
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 }}" .
|
||||||
|
# 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:
|
||||||
|
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/**/${{ env.BINARY_NAME }}-*.tar.gz
|
||||||
|
dist/**/${{ env.BINARY_NAME }}-*.zip
|
||||||
|
|
||||||
|
publish-agent:
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
needs: release
|
||||||
|
runs-on: ubuntu-fast
|
||||||
|
env:
|
||||||
|
PRODUCT: ${{ env.BINARY_NAME }}
|
||||||
|
AGENT_URL: ${{ secrets.AGENT_URL }}
|
||||||
|
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}
|
||||||
|
SERVER_URL: ${{ github.server_url }}
|
||||||
|
REPOSITORY: ${{ github.repository }}
|
||||||
|
TAG: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: ./dist
|
||||||
|
- name: Publish release metadata to Version Agent
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ -z "${AGENT_URL:-}" || -z "${AGENT_TOKEN:-}" ]]; then
|
||||||
|
echo "Missing AGENT_URL or AGENT_TOKEN" >&2; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="${TAG#v}"
|
||||||
|
MAJOR="${VERSION%%.*}"
|
||||||
|
BRANCH="${MAJOR}.x"
|
||||||
|
|
||||||
|
CHANNEL="stable"
|
||||||
|
[[ "$VERSION" == *"-rc"* ]] && CHANNEL="rc"
|
||||||
|
[[ "$VERSION" == *"-beta"* ]] && CHANNEL="beta"
|
||||||
|
|
||||||
|
RELEASED_AT="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||||
|
NOTES_URL="${SERVER_URL}/${REPOSITORY}/releases/tag/${TAG}"
|
||||||
|
|
||||||
|
publish() { # args: OS ARCH FILE
|
||||||
|
local OS="$1" ARCH="$2" FILE="$3"
|
||||||
|
local BIT="64"; case "$ARCH" in 386|armv7) BIT="32";; esac
|
||||||
|
|
||||||
|
local FNAME="$(basename "$FILE")"
|
||||||
|
local URL="${SERVER_URL}/${REPOSITORY}/releases/download/${TAG}/${FNAME}"
|
||||||
|
|
||||||
|
local SHA256 SIZE
|
||||||
|
SHA256="$(sha256sum "$FILE" | awk '{print $1}')"
|
||||||
|
SIZE="$(stat -c%s "$FILE")"
|
||||||
|
|
||||||
|
jq -n \
|
||||||
|
--arg product "$PRODUCT" \
|
||||||
|
--arg branch "$BRANCH" \
|
||||||
|
--arg channel "$CHANNEL" \
|
||||||
|
--arg arch "$ARCH" \
|
||||||
|
--arg bit "$BIT" \
|
||||||
|
--arg os "$OS" \
|
||||||
|
--arg version "$VERSION" \
|
||||||
|
--arg released_at "$RELEASED_AT" \
|
||||||
|
--arg notes "$NOTES_URL" \
|
||||||
|
--arg url "$URL" \
|
||||||
|
--arg sha256 "$SHA256" \
|
||||||
|
--argjson size "$SIZE" \
|
||||||
|
'{
|
||||||
|
product:$product,
|
||||||
|
branch:$branch, channel:$channel, arch:$arch, bit:$bit, os:$os,
|
||||||
|
release:{
|
||||||
|
version:$version, released_at:$released_at, notes_url:$notes,
|
||||||
|
assets:[{url:$url, sha256:$sha256, size_bytes:$size}]
|
||||||
|
}
|
||||||
|
}' > payload.json
|
||||||
|
|
||||||
|
curl -fsS -H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer ${AGENT_TOKEN}" \
|
||||||
|
-d @payload.json "${AGENT_URL}/v1/publish"
|
||||||
|
}
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in dist/**/${PRODUCT}-windows-amd64.zip; do publish windows amd64 "$f"; done
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
# pcinfoagent
|
# pcinfoagent
|
||||||
|
|
||||||
|
curl.exe -X POST "http://127.0.0.1:24000/api/notify" -H "Content-Type: application/json" -H "X-Notify-Token: GEHEIM" -d "{\"title\":\"Wartung\",\"message\":\"Bitte PC nach Feierabend\",\"target_user\":\"\"}"
|
||||||
|
{"id":2,"created_at":"2025-12-16T22:15:22.0832541+01:00","title":"Wartung","message":"Bitte PC nach Feierabend","target_user":""}
|
||||||
193
main.go
Normal file
193
main.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"os/user"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const appUserModelID = "de.stadthilden.GoSysNotifyAgent"
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
TargetUser string `json:"target_user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
serviceURL = "http://127.0.0.1:24000"
|
||||||
|
pollInterval = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
if v := os.Getenv("SERVICE_URL"); v != "" {
|
||||||
|
serviceURL = strings.TrimRight(v, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benutzername bestimmen (z. B. "DOMAIN\\user" oder "user")
|
||||||
|
userName := currentUserName()
|
||||||
|
log.Printf("Notification-Agent gestartet für Benutzer: %s", userName)
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
var lastSeenID int64 = 0
|
||||||
|
|
||||||
|
ticker := time.NewTicker(pollInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// beim Start einmal sofort
|
||||||
|
if err := pollOnce(ctx, userName, &lastSeenID); err != nil {
|
||||||
|
log.Printf("initial poll error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Println("Agent beendet.")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := pollOnce(ctx, userName, &lastSeenID); err != nil {
|
||||||
|
log.Printf("poll error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func currentUserName() string {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err == nil && u.Username != "" {
|
||||||
|
return u.Username
|
||||||
|
}
|
||||||
|
if u := os.Getenv("USERNAME"); u != "" {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func pollOnce(ctx context.Context, userName string, lastSeenID *int64) error {
|
||||||
|
url := fmt.Sprintf("%s/api/notifications?user=%s&since_id=%d",
|
||||||
|
serviceURL,
|
||||||
|
urlQueryEscape(userName),
|
||||||
|
*lastSeenID,
|
||||||
|
)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("status %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var notifs []Notification
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(¬ifs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxID = *lastSeenID
|
||||||
|
for _, n := range notifs {
|
||||||
|
if n.ID > maxID {
|
||||||
|
maxID = n.ID
|
||||||
|
}
|
||||||
|
showErr := showToast(n.Title, n.Message)
|
||||||
|
if showErr != nil {
|
||||||
|
log.Printf("showToast error: %v", showErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxID > *lastSeenID {
|
||||||
|
*lastSeenID = maxID
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlQueryEscape(s string) string {
|
||||||
|
// minimal, reicht hier
|
||||||
|
return strings.ReplaceAll(s, " ", "%20")
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Toast ===
|
||||||
|
|
||||||
|
func escapeXML(s string) string {
|
||||||
|
replacer := strings.NewReplacer(
|
||||||
|
`&`, "&",
|
||||||
|
`<`, "<",
|
||||||
|
`>`, ">",
|
||||||
|
`"`, """,
|
||||||
|
`'`, "'",
|
||||||
|
)
|
||||||
|
return replacer.Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showToast(title, message string) error {
|
||||||
|
title = strings.TrimSpace(title)
|
||||||
|
message = strings.TrimSpace(message)
|
||||||
|
if title == "" {
|
||||||
|
title = "Benachrichtigung"
|
||||||
|
}
|
||||||
|
if message == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML escapen
|
||||||
|
title = escapeXML(title)
|
||||||
|
message = escapeXML(message)
|
||||||
|
|
||||||
|
psScript := `
|
||||||
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
|
||||||
|
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null
|
||||||
|
|
||||||
|
# bevorzugt Windows Terminal, sonst Windows PowerShell, sonst erste App
|
||||||
|
$appid = (Get-StartApps | Where-Object Name -eq 'Windows Terminal').AppId
|
||||||
|
if (-not $appid) {
|
||||||
|
$appid = (Get-StartApps | Where-Object Name -eq 'Windows PowerShell').AppId
|
||||||
|
}
|
||||||
|
if (-not $appid) {
|
||||||
|
$appid = (Get-StartApps | Select-Object -First 1).AppId
|
||||||
|
}
|
||||||
|
|
||||||
|
$xmlString = @"
|
||||||
|
<toast activationType="foreground">
|
||||||
|
<visual>
|
||||||
|
<binding template="ToastGeneric">
|
||||||
|
<text>` + title + `</text>
|
||||||
|
<text>` + message + `</text>
|
||||||
|
</binding>
|
||||||
|
</visual>
|
||||||
|
</toast>
|
||||||
|
"@
|
||||||
|
|
||||||
|
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
||||||
|
$xml.LoadXml($xmlString)
|
||||||
|
|
||||||
|
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appid)
|
||||||
|
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
|
||||||
|
$notifier.Show($toast)
|
||||||
|
`
|
||||||
|
|
||||||
|
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", psScript)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("powershell toast error: %v, output: %s", err, string(out))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user