Installer-Paket System: Backend speichert/served ZIP, Frontend Upload+Download, install.sh auto-download von Backend statt Git

This commit is contained in:
cynfo3000
2026-03-03 23:37:31 +01:00
parent c387e56816
commit 25a6cc2a1a
6 changed files with 300 additions and 4 deletions
+27
View File
@@ -272,6 +272,33 @@ class ApiClient {
requestUpdateAll() {
return this.post('/api/v1/agents/request-update-all')
}
// Installer
getInstallerInfo() {
return this.get('/api/v1/installer')
}
async uploadInstaller(platform, file) {
const headers = {}
if (this.token) headers['Authorization'] = `Bearer ${this.token}`
const formData = new FormData()
formData.append('file', file)
const res = await fetch(`${API_BASE}/api/v1/installer/upload?platform=${encodeURIComponent(platform)}`, {
method: 'POST',
headers,
body: formData,
})
if (res.status === 401) {
this.setToken(null)
window.location.href = '/login'
throw new Error('Unauthorized')
}
if (!res.ok) {
const err = await res.json().catch(() => ({ error: res.statusText }))
throw new Error(err.error || 'Upload fehlgeschlagen')
}
return res.json()
}
}
export const api = new ApiClient()
+3 -2
View File
@@ -3,7 +3,6 @@ import { Copy, Check, Terminal, Monitor } from 'lucide-react'
const BACKEND_URL = 'https://dsbmueller.spdns.org:8443'
const API_KEY = '01532e5a7c9e70bf2757df77a2f5b9b9'
const INSTALL_SH_URL = 'https://git.cynfo.net/christian/rmm/raw/branch/main/opnsense-plugin/install.sh'
export default function InstallGuide({ agentName, platform: initialPlatform }) {
const [platform, setPlatform] = useState(initialPlatform || 'freebsd')
@@ -49,7 +48,9 @@ export default function InstallGuide({ agentName, platform: initialPlatform }) {
</div>
)
const opnInstallCmd = `fetch -o /tmp/install.sh "${INSTALL_SH_URL}" && sh /tmp/install.sh`
const opnInstallCmd = `fetch --no-verify-peer --no-verify-hostname -o /tmp/installer.zip \\
"${BACKEND_URL}/api/v1/installer/download?platform=freebsd&api_key=${API_KEY}" \\
&& cd /tmp && unzip -o installer.zip && sh /tmp/install.sh`
const linuxInstallCmd = `#!/bin/sh
# RMM Agent + Updater Installation
+97 -2
View File
@@ -1,6 +1,6 @@
import { useEffect, useState, useRef } from 'react'
import api from '../api/client'
import { Upload, RefreshCw, CheckCircle, AlertTriangle, ArrowUpCircle, X, Monitor, Server, Cpu } from 'lucide-react'
import { Upload, RefreshCw, CheckCircle, AlertTriangle, ArrowUpCircle, X, Monitor, Server, Cpu, Package, Copy, Check } from 'lucide-react'
const PLATFORMS = [
{ id: 'freebsd', label: 'FreeBSD / OPNsense', icon: Server },
@@ -8,25 +8,34 @@ const PLATFORMS = [
{ id: 'windows', label: 'Windows', icon: Cpu },
]
const BACKEND_URL = 'https://dsbmueller.spdns.org:8443'
export default function Firmware() {
const [firmwareData, setFirmwareData] = useState(null)
const [installerData, setInstallerData] = useState(null)
const [agents, setAgents] = useState([])
const [loading, setLoading] = useState(true)
const [uploading, setUploading] = useState(false)
const [installerUploading, setInstallerUploading] = useState(false)
const [version, setVersion] = useState('')
const [platform, setPlatform] = useState('freebsd')
const [installerPlatform, setInstallerPlatform] = useState('freebsd')
const [msg, setMsg] = useState('')
const [msgType, setMsgType] = useState('info')
const fileRef = useRef(null)
const installerFileRef = useRef(null)
const [copied, setCopied] = useState(false)
const load = async () => {
try {
const [fw, ag] = await Promise.all([
const [fw, ag, inst] = await Promise.all([
api.getFirmwareInfo().catch(() => null),
api.getAgents().catch(() => []),
api.getInstallerInfo().catch(() => null),
])
setFirmwareData(fw)
setAgents(Array.isArray(ag) ? ag : [])
setInstallerData(inst)
} finally {
setLoading(false)
}
@@ -202,6 +211,92 @@ export default function Firmware() {
</div>
</div>
{/* Installer Packages */}
<div className="bg-gray-800/50 rounded-lg border border-gray-700/50 p-5">
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">Installer-Pakete</h2>
<p className="text-xs text-gray-500 mb-4">
ZIP-Pakete mit install.sh und Plugin-Dateien. Werden auf neuen Geraeten per fetch heruntergeladen und ausgefuehrt.
</p>
{/* Existing installers */}
{installerData?.installers?.length > 0 && (
<div className="space-y-2 mb-4">
{installerData.installers.map(inst => (
<div key={inst.platform} className="flex items-center justify-between bg-gray-900/50 rounded-lg px-4 py-3 border border-gray-700/30">
<div>
<div className="flex items-center gap-2">
<Package className="w-4 h-4 text-orange-400" />
<span className="text-white text-sm font-medium">{inst.platform === 'linux' ? 'Linux' : 'OPNsense / FreeBSD'}</span>
</div>
<div className="text-xs text-gray-500 mt-1">
{inst.filename} — {formatSize(inst.size)} — {new Date(inst.uploaded_at).toLocaleString('de-DE')}
</div>
<div className="text-[10px] text-gray-600 font-mono mt-0.5">{inst.sha256?.substring(0, 32)}...</div>
</div>
</div>
))}
</div>
)}
{/* Install command */}
{installerData?.installers?.some(i => i.platform === 'freebsd') && (
<div className="mb-4 bg-gray-950 rounded p-3 relative">
<div className="text-[10px] text-gray-500 mb-1">Installationsbefehl (OPNsense):</div>
<pre className="text-xs font-mono text-orange-300 whitespace-pre-wrap">{`fetch --no-verify-peer --no-verify-hostname -o /tmp/installer.zip "${BACKEND_URL}/api/v1/installer/download?platform=freebsd&api_key=01532e5a7c9e70bf2757df77a2f5b9b9" && cd /tmp && unzip -o installer.zip && sh /tmp/install.sh`}</pre>
<button
onClick={() => {
navigator.clipboard.writeText(`fetch --no-verify-peer --no-verify-hostname -o /tmp/installer.zip "${BACKEND_URL}/api/v1/installer/download?platform=freebsd&api_key=01532e5a7c9e70bf2757df77a2f5b9b9" && cd /tmp && unzip -o installer.zip && sh /tmp/install.sh`)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}}
className="absolute top-2 right-2 text-gray-600 hover:text-orange-400 transition-colors"
>
{copied ? <Check className="w-4 h-4 text-green-400" /> : <Copy className="w-4 h-4" />}
</button>
</div>
)}
{/* Upload */}
<div className="flex flex-wrap gap-3 items-end">
<select
value={installerPlatform}
onChange={(e) => setInstallerPlatform(e.target.value)}
className="bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-white focus:border-orange-500 focus:outline-none"
>
<option value="freebsd">OPNsense / FreeBSD</option>
<option value="linux">Linux</option>
</select>
<input
type="file"
ref={installerFileRef}
accept=".zip,.sh"
className="bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-gray-400 file:mr-3 file:bg-gray-700 file:text-gray-300 file:border-0 file:rounded file:px-3 file:py-1 file:text-xs file:cursor-pointer flex-1"
/>
<button
onClick={async () => {
const file = installerFileRef.current?.files?.[0]
if (!file) return showMsg('Bitte Datei auswaehlen', 'error')
setInstallerUploading(true)
try {
const result = await api.uploadInstaller(installerPlatform, file)
showMsg(`Installer fuer ${installerPlatform} hochgeladen (${formatSize(result.size)})`, 'success')
installerFileRef.current.value = ''
load()
} catch (err) {
showMsg(`Upload fehlgeschlagen: ${err.message}`, 'error')
} finally {
setInstallerUploading(false)
}
}}
disabled={installerUploading}
className="flex items-center gap-2 bg-orange-600 hover:bg-orange-500 disabled:bg-gray-700 disabled:text-gray-500 text-white px-4 py-2 rounded text-sm transition-colors shrink-0"
>
{installerUploading ? <RefreshCw className="w-4 h-4 animate-spin" /> : <Upload className="w-4 h-4" />}
{installerUploading ? 'Hochladen...' : 'Hochladen'}
</button>
</div>
</div>
{/* Agent List */}
<div className="bg-gray-800/50 rounded-lg border border-gray-700/50 p-5">
<div className="flex items-center justify-between mb-4">