2026-08-03 10:58 Serverzertifikate werden in build.sh erzeugt
This commit is contained in:
parent
cf44131e18
commit
140412ff78
9 changed files with 52 additions and 348 deletions
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"web_port": ":8080",
|
||||
"i2c_bus": "1",
|
||||
"ads1115_address_1": 72,
|
||||
"ads1115_address_2": 73,
|
||||
"name_adc1_ch0": "23cm",
|
||||
"factor_adc1_ch0": 2.0,
|
||||
"name_adc1_ch1": "13 cm",
|
||||
"factor_adc1_ch1": 2.0,
|
||||
"name_adc1_ch2": "9 cm",
|
||||
"factor_adc1_ch2": 2.0,
|
||||
"name_adc1_ch3": "6 cm",
|
||||
"factor_adc1_ch3": 2.0,
|
||||
"name_adc2_ch0": "3 cm",
|
||||
"factor_adc2_ch0": 2.0,
|
||||
"relais_pins": [
|
||||
{"id": 1, "name": "Relais 1 (230V Hauptschalter)", "gpio": "GPIO12"},
|
||||
{"id": 2, "name": "USB-Relais 1 (Reserve)", "gpio": "USB_CH1"},
|
||||
{"id": 3, "name": "USB-Relais 2 23 cm", "gpio": "USB_CH2"},
|
||||
{"id": 4, "name": "USB-Relais 3 13 cm", "gpio": "USB_CH3"},
|
||||
{"id": 5, "name": "USB-Relais 4 9 cm", "gpio": "USB_CH4"},
|
||||
{"id": 6, "name": "USB-Relais 5 6 cm", "gpio": "USB_CH5"},
|
||||
{"id": 7, "name": "USB-Relais 6 3 cm", "gpio": "USB_CH6"},
|
||||
{"id": 8, "name": "USB-Relais 7 (Reserve)", "gpio": "USB_CH7"},
|
||||
{"id": 9, "name": "USB-Relais 8 (Reserve)", "gpio": "USB_CH8"}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,33 +0,0 @@
|
|||
# ==============================================================================
|
||||
# KURZANLEITUNG ZUR INSTALLATION UND VERWALTUNG
|
||||
# ==============================================================================
|
||||
# 1. Datei kopieren nach: /etc/systemd/system/spannung.service
|
||||
# 2. Systemd neu laden: sudo systemctl daemon-reload
|
||||
# 3. Autostart aktivieren: sudo systemctl enable spannung.service
|
||||
# 4. Dienst starten: sudo systemctl start spannung.service
|
||||
# ==============================================================================
|
||||
|
||||
[Unit]
|
||||
Description=Spannung Control Service
|
||||
After=network-online.target i2c.service
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/bin/bash -c 'for i in {1..10}; do i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b48\b" && i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b49\b" && exit 0; echo "warte auf ADS1115 Module..."; sleep 2; done; exit 1'
|
||||
|
||||
# Erkennt automatisch ob 32- oder 64-Bit OS vorliegt und startet die passende Version
|
||||
ExecStart=/bin/bash -c 'if [ "$(uname -m)" = "aarch64" ]; then exec %h/spannung/app/spannung-arm64; else exec %h/spannung/app/spannung-arm32_7; fi'
|
||||
WorkingDirectory=%h/spannung/app
|
||||
User=%u
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
StartLimitIntervalSec=60s
|
||||
StartLimitBurst=3
|
||||
|
||||
StandardOutput=append:%h/spannung/app/spannung.log
|
||||
StandardError=append:%h/spannung/app/spannung.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"version": "0.2",
|
||||
"date": "02.08.2026 um 09:34",
|
||||
"description": "Messung von zwei Spannung und Steuerung von Relais",
|
||||
"features": [
|
||||
"ADS1115-Abfrage über go-i2c",
|
||||
"Weboberfläche mit Steuerbuttons für Reilais",
|
||||
"Definiton der GPIO in der config.json"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type RelaisData struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
State bool `json:"state"`
|
||||
}
|
||||
|
||||
type StatusResponse struct {
|
||||
V1 float64 `json:"v1"`
|
||||
V2 float64 `json:"v2"`
|
||||
V3 float64 `json:"v3"`
|
||||
V4 float64 `json:"v4"`
|
||||
V5 float64 `json:"v5"`
|
||||
Names []string `json:"names"` // Neu: Sendet die Namen als Array mit
|
||||
Relais []RelaisData `json:"relais"`
|
||||
Version string `json:"version"`
|
||||
BuildDate string `json:"build_date"`
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
port string
|
||||
readVoltFunc func(channel int) float64
|
||||
toggleFunc func(id int)
|
||||
getRelais func() []RelaisData
|
||||
versionFunc func() (string, string)
|
||||
getNamesFunc func() []string // Neu hinzugefügt
|
||||
}
|
||||
|
||||
func NewServer(port string, readVolt func(int) float64, toggle func(int), getRelais func() []RelaisData, getVersion func() (string, string), getNames func() []string) *Server {
|
||||
return &Server{
|
||||
port: port,
|
||||
readVoltFunc: readVolt,
|
||||
toggleFunc: toggle,
|
||||
getRelais: getRelais,
|
||||
versionFunc: getVersion,
|
||||
getNamesFunc: getNames, // Neu hinzugefügt
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
http.HandleFunc("/", s.handleIndex)
|
||||
http.HandleFunc("/api/status", s.handleAPIStatus)
|
||||
http.HandleFunc("/api/toggle", s.handleAPIToggle)
|
||||
return http.ListenAndServe(s.port, nil)
|
||||
}
|
||||
|
||||
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
ver, date := s.versionFunc()
|
||||
tmpl, err := template.ParseFiles("web/static/index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Template nicht gefunden: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := StatusResponse{
|
||||
Names: s.getNamesFunc(), // Namen für das erste Rendern bereitstellen
|
||||
Relais: s.getRelais(),
|
||||
Version: ver,
|
||||
BuildDate: date,
|
||||
}
|
||||
tmpl.Execute(w, data)
|
||||
}
|
||||
|
||||
func (s *Server) handleAPIStatus(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ver, date := s.versionFunc()
|
||||
resp := StatusResponse{
|
||||
V1: s.readVoltFunc(0),
|
||||
V2: s.readVoltFunc(1),
|
||||
V3: s.readVoltFunc(2),
|
||||
V4: s.readVoltFunc(3),
|
||||
V5: s.readVoltFunc(4),
|
||||
Names: s.getNamesFunc(),
|
||||
Relais: s.getRelais(),
|
||||
Version: ver,
|
||||
BuildDate: date,
|
||||
}
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
func (s *Server) handleAPIToggle(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.URL.Query().Get("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err == nil {
|
||||
s.toggleFunc(id)
|
||||
}
|
||||
s.handleAPIStatus(w, r)
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Funkstation Steuerung</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Arial, sans-serif; text-align: center; background: #0f172a; color: #f8fafc; padding: 20px; }
|
||||
/* Maximale Breite erhöht, um Platz für das zweispaltige Layout zu bieten */
|
||||
.container { max-width: 1000px; margin: 0 auto; }
|
||||
|
||||
/* Das neue Haupt-Layout: Links Relais, rechts Spannungen */
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Spalten-Container für korrekte Ausrichtung */
|
||||
.layout-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card { background: #1e293b; padding: 20px; margin: 15px 0; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.3); border: 1px solid #334155; text-align: center; }
|
||||
.val { font-size: 2.5em; color: #38bdf8; font-weight: bold; font-family: monospace; }
|
||||
.grid { display: grid; grid-template-columns: 1fr; gap: 12px; margin-top: 15px; }
|
||||
.btn { width: 100%; padding: 14px; font-size: 1.1em; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; transition: background 0.2s, transform 0.1s; color: white; }
|
||||
.btn:active { transform: scale(0.98); }
|
||||
.btn-on { background: #ef4444; }
|
||||
.btn-on:hover { background: #dc2626; }
|
||||
.btn-off { background: #10b981; }
|
||||
.btn-off:hover { background: #059669; }
|
||||
.footer-nav { margin-top: 25px; font-size: 0.9em; color: #64748b; clear: both; }
|
||||
|
||||
/* Responsive Anpassung: Schaltet auf Smartphones automatisch untereinander um */
|
||||
@media (max-width: 768px) {
|
||||
.main-layout { grid-template-columns: 1fr; gap: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Funkstations-Monitor</h1>
|
||||
|
||||
<div class="main-layout">
|
||||
<!-- LINKE SPALTE: Relaissteuerung -->
|
||||
<div class="layout-col">
|
||||
<div class="card">
|
||||
<h3>Relaissteuerung</h3>
|
||||
<div class="grid">
|
||||
{{range .Relais}}
|
||||
<button class="btn btn-off" id="relais-btn-{{.ID}}" onclick="toggleRelais({{.ID}})">
|
||||
{{.Name}}: Laden...
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RECHTE SPALTE: Spannungsanzeigen -->
|
||||
<div class="layout-col">
|
||||
<div class="card">
|
||||
<h3>{{index .Names 0}}</h3>
|
||||
<div class="val" id="v1">--.-- V</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{index .Names 1}}</h3>
|
||||
<div class="val" id="v2">--.-- V</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{index .Names 2}}</h3>
|
||||
<div class="val" id="v3">--.-- V</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{index .Names 3}}</h3>
|
||||
<div class="val" id="v4">--.-- V</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{index .Names 4}}</h3>
|
||||
<div class="val" id="v5">--.-- V</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-nav">
|
||||
Live-Aktualisierung aktiv <br>
|
||||
<span style="font-size: 0.8em; color: #475569; margin-top: 5px; display: block;">
|
||||
Version v{{.Version}} ({{.BuildDate}})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateUI(data) {
|
||||
document.getElementById('v1').innerText = data.v1.toFixed(2) + ' V';
|
||||
document.getElementById('v2').innerText = data.v2.toFixed(2) + ' V';
|
||||
document.getElementById('v3').innerText = data.v3.toFixed(2) + ' V';
|
||||
document.getElementById('v4').innerText = data.v4.toFixed(2) + ' V';
|
||||
document.getElementById('v5').innerText = data.v5.toFixed(2) + ' V';
|
||||
|
||||
data.relais.forEach(r => {
|
||||
const btn = document.getElementById(`relais-btn-${r.id}`);
|
||||
if (btn) {
|
||||
if (r.name && r.name.toLowerCase().includes('reserve')) {
|
||||
btn.className = 'btn btn-off';
|
||||
btn.innerText = `${r.name}: GESPERRT`;
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.3';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
return;
|
||||
}
|
||||
|
||||
if (r.state) {
|
||||
btn.className = 'btn btn-on';
|
||||
btn.innerText = `${r.name}: AKTIV (Ausschalten)`;
|
||||
} else {
|
||||
btn.className = 'btn btn-off';
|
||||
btn.innerText = `${r.name}: INAKTIV (Einschalten)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadStatus() {
|
||||
fetch('/api/status')
|
||||
.then(response => response.json())
|
||||
.then(data => updateUI(data))
|
||||
.catch(err => console.error('Fehler beim Laden des Status:', err));
|
||||
}
|
||||
|
||||
function toggleRelais(id) {
|
||||
fetch(`/api/toggle?id=${id}`)
|
||||
.then(response => response.json())
|
||||
.then(data => updateUI(data))
|
||||
.catch(err => console.error('Fehler beim Schalten:', err));
|
||||
}
|
||||
|
||||
loadStatus();
|
||||
setInterval(loadStatus, 2000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
15
build.sh
15
build.sh
|
|
@ -54,7 +54,6 @@ cat << 'EOF' > ./app/spannung.service
|
|||
# 3. Autostart aktivieren: sudo systemctl enable spannung.service
|
||||
# 4. Dienst starten: sudo systemctl start spannung.service
|
||||
# ==============================================================================
|
||||
|
||||
[Unit]
|
||||
Description=Spannung Control Service
|
||||
After=network-online.target i2c.service
|
||||
|
|
@ -63,19 +62,19 @@ StartLimitIntervalSec=0
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/bin/bash -c 'for i in {1..10}; do i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b48\b" && i2cdetect -y 1 | grep -E "^40:" | grep -q -E "\b49\b" && exit 0; echo "warte auf ADS1115 Module..."; sleep 2; done; exit 1'
|
||||
ExecStartPre=/bin/bash -c 'for i in {1..10}; do i2cdetect -y 1 | grep -E "40:" | grep -q -E "\b48\b" && i2cdetect -y 1 | grep -E "40:" | grep -q -E "\b49\b" && exit 0; echo "warte auf ADS1115 Module..."; sleep 2; done; exit 1'
|
||||
|
||||
# Erkennt automatisch ob 32- oder 64-Bit OS vorliegt und startet die passende Version
|
||||
ExecStart=/bin/bash -c 'if [ "$(uname -m)" = "aarch64" ]; then exec %h/spannung/app/spannung-arm64; else exec %h/spannung/app/spannung-arm32_7; fi'
|
||||
WorkingDirectory=%h/spannung/app
|
||||
User=%u
|
||||
# Direkt der Pfad zur nativ gebauten Binärdatei ohne Bash-Weiche
|
||||
ExecStart=TARGET_DIR/spannung-aktiv
|
||||
WorkingDirectory=TARGET_DIR
|
||||
User=TARGET_USER
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
StartLimitIntervalSec=60s
|
||||
StartLimitBurst=3
|
||||
|
||||
StandardOutput=append:%h/spannung/app/spannung.log
|
||||
StandardError=append:%h/spannung/app/spannung.log
|
||||
StandardOutput=append:TARGET_DIR/spannung.log
|
||||
StandardError=append:TARGET_DIR/spannung.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
|||
70
deploy.sh
Normal file → Executable file
70
deploy.sh
Normal file → Executable file
|
|
@ -1,36 +1,56 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Prüfen, ob eine IP oder ein Hostname übergeben wurde
|
||||
if [ -z "$1" ]; then
|
||||
echo "❌ Fehler: Keine Ziel-Adresse angegeben!"
|
||||
echo "💡 Nutzung: ./deploy.sh <IP-oder-Hostname-des-Pi> [Benutzername]"
|
||||
echo "📌 Beispiel: ./deploy.sh 192.168.178.50"
|
||||
echo "📌 Beispiel: ./deploy.sh raspi-4.local pi"
|
||||
# --- PARAMETER-PRÜFUNG ---
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "❌ Fehler: Fehlende Argumente."
|
||||
echo "Verwendung: $0 <user> <host>"
|
||||
echo "Beispiel: $0 afu 100.64.0.4"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ziel-Konfiguration festlegen
|
||||
TARGET_HOST="$1"
|
||||
TARGET_USER="${2:-$USER}" # Nutzt den aktuellen Ubuntu-User, falls kein User übergeben wird
|
||||
TARGET_DIR="~/spannung"
|
||||
PI_USER="$1"
|
||||
PI_HOST="$2"
|
||||
TARGET_DIR="spannung/app"
|
||||
SERVICE_NAME="spannung.service"
|
||||
|
||||
echo "🚀 Starte Deployment auf $TARGET_USER@$TARGET_HOST..."
|
||||
echo "🚀 Starte Deployment auf $PI_USER@$PI_HOST..."
|
||||
|
||||
# 1. Prüfen, ob der lokale 'app'-Ordner existiert
|
||||
if [ ! -d "app" ]; then
|
||||
echo "❌ Fehler: Der Ordner 'app' existiert nicht."
|
||||
echo "🛠️ Bitte führen Sie zuerst ./build.sh aus!"
|
||||
exit 1
|
||||
fi
|
||||
# 1. Zielverzeichnis auf dem Pi erstellen
|
||||
ssh "$PI_USER@$PI_HOST" "mkdir -p \"\$HOME/$TARGET_DIR\""
|
||||
|
||||
# 2. Zielverzeichnis auf dem Raspberry Pi vorbereiten (alte App-Dateien löschen)
|
||||
echo "🧹 Bereinige altes App-Verzeichnis auf dem Raspberry Pi..."
|
||||
ssh "$TARGET_USER@$TARGET_HOST" "mkdir -p $TARGET_DIR && rm -rf $TARGET_DIR/app"
|
||||
# 2. Quellcode, Webseiten und Service-Datei zum Pi kopieren
|
||||
scp -r * "$PI_USER@$PI_HOST:$TARGET_DIR/"
|
||||
|
||||
# 3. Den neuen 'app'-Ordner via SCP auf den Pi kopieren
|
||||
echo "📦 Kopiere 'app'-Ordner auf den Raspberry Pi..."
|
||||
scp -r ./app "$TARGET_USER@$TARGET_HOST:$TARGET_DIR/"
|
||||
# 3. Build und Systemd-Setup direkt auf dem Pi ausführen
|
||||
ssh -t "$PI_USER@$PI_HOST" "
|
||||
cd \${HOME}/${TARGET_DIR}
|
||||
|
||||
echo \"📦 Kompiliere Anwendung nativ auf dem Raspberry Pi...\"
|
||||
# Korrigierter Go-Build für Modul-Projekte (.)
|
||||
go build -o spannung-aktiv .
|
||||
|
||||
REAL_PATH=\$(pwd)
|
||||
REAL_USER=\$(whoami)
|
||||
|
||||
echo \"🔧 Konfiguriere Systemd-Dienst...\"
|
||||
|
||||
# Windows-Zeilenumbrüche entfernen
|
||||
sed -i 's/\r//' ./${SERVICE_NAME}
|
||||
|
||||
# Platzhalter durch reale Systemwerte ersetzen
|
||||
sed -i \"s|TARGET_DIR|\$REAL_PATH|g\" ./${SERVICE_NAME}
|
||||
sed -i \"s|TARGET_USER|\$REAL_USER|g\" ./${SERVICE_NAME}
|
||||
|
||||
# ExecStart auf die neu gebaute Binärdatei umbiegen (Anführungszeichen korrigiert)
|
||||
sed -i \"s|exec .*|exec \$REAL_PATH/spannung-aktiv'|g\" ./${SERVICE_NAME}
|
||||
|
||||
# Service-Datei ins Systemverzeichnis kopieren und Dienst starten
|
||||
sudo cp ./${SERVICE_NAME} /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable ${SERVICE_NAME}
|
||||
sudo systemctl restart ${SERVICE_NAME}
|
||||
"
|
||||
|
||||
echo "✅ Deployment erfolgreich abgeschlossen!"
|
||||
echo "📍 Die Dateien liegen auf dem Pi unter: $TARGET_DIR/app"
|
||||
echo "✅ Deployment erfolgreich abgeschlossen! Zeige Live-Logs (Beenden mit STRG+C)..."
|
||||
ssh -t "$PI_USER@$PI_HOST" "sudo journalctl -f -u ${SERVICE_NAME}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue