feat(scan): add guided Linux agent setup in administration
Some checks failed
Build and Release FEDEO / verify (push) Successful in 23s
Build and Release FEDEO / build-and-release (push) Failing after 29s

This commit is contained in:
2026-09-17 18:56:30 +02:00
parent 1329f164d8
commit 68c0a224e5
15 changed files with 869 additions and 89 deletions

View File

@@ -0,0 +1,40 @@
import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const output = path.resolve(root, '../../frontend/assets/device-agent/bundle.json')
async function filesUnder(directory) {
const entries = await readdir(path.join(root, directory), { withFileTypes: true })
const paths = await Promise.all(entries.map(entry => entry.isDirectory()
? filesUnder(`${directory}/${entry.name}`)
: [`${directory}/${entry.name}`]))
return paths.flat().sort()
}
const packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'))
const paths = [
...(await filesUnder('dist')).filter(file => file.endsWith('.js')),
'scripts/scan_document.py', 'scripts/opencv_postprocess.py', 'scripts/setup-opencv.sh',
'requirements-opencv.txt',
]
const files = Object.fromEntries(await Promise.all(paths.map(async file => [file, (await readFile(path.join(root, file))).toString('base64')])))
files['package.json'] = Buffer.from(JSON.stringify({
name: packageJson.name, version: packageJson.version, type: 'module', engines: packageJson.engines,
scripts: { start: 'node dist/main.js', 'setup:opencv': 'sh scripts/setup-opencv.sh' },
}, null, 2) + '\n').toString('base64')
const bundle = JSON.stringify({
version: packageJson.version,
installerTemplate: await readFile(path.join(root, 'scripts/install-linux.sh'), 'utf8'),
files,
}, null, 2) + '\n'
if (process.argv.includes('--check')) {
if (await readFile(output, 'utf8') !== bundle) {
throw new Error('Das Installationspaket ist veraltet. Bitte npm run build:installer ausführen und bundle.json mitcommitten.')
}
console.log('Installationspaket entspricht dem aktuellen Agenten.')
} else {
await mkdir(path.dirname(output), { recursive: true })
await writeFile(output, bundle)
console.log(`Installationspaket erstellt: ${output}`)
}

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env bash
# Generated by the FEDEO setup wizard. This file contains the agent's access token.
set -euo pipefail
umask 077
if [[ "$(uname -s)" != Linux ]]; then
echo 'Diese Installation ist für Linux vorgesehen.' >&2
exit 1
fi
if [[ "$(id -u)" == 0 ]]; then
echo 'Bitte dieses Skript als normaler Benutzer ohne sudo starten.' >&2
exit 1
fi
if [[ "${1:-}" != '' && "${1:-}" != --no-start ]]; then
echo 'Aufruf: bash install.sh [--no-start]' >&2
exit 1
fi
FEDEO_SETUP_ID='@@AGENT_ID@@'
FEDEO_SETUP_AUTOSTART='@@AUTOSTART@@'
FEDEO_SETUP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/fedeo-device-agent/$FEDEO_SETUP_ID"
FEDEO_SETUP_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/fedeo-device-agent/$FEDEO_SETUP_ID.env"
FEDEO_SETUP_WORK="${XDG_STATE_HOME:-$HOME/.local/state}/fedeo-device-agent/$FEDEO_SETUP_ID"
FEDEO_SETUP_UNIT="fedeo-device-agent-$FEDEO_SETUP_ID.service"
FEDEO_SETUP_UNITS="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
echo 'FEDEO Scan-Agent wird für diesen Benutzer eingerichtet.'
if ! command -v node >/dev/null || ! command -v scanimage >/dev/null || ! python3 -c 'import PIL' >/dev/null 2>&1; then
if ! command -v apt-get >/dev/null; then
echo 'Bitte Node.js >= 20, SANE (scanimage), Python 3 und Pillow installieren und das Skript erneut starten.' >&2
exit 1
fi
echo 'Fehlende Systempakete werden über apt installiert (sudo benötigt).'
sudo apt-get update
sudo apt-get install -y nodejs sane-utils python3-pil
fi
node -e 'if (Number(process.versions.node.split(".")[0]) < 20) { console.error("Node.js >= 20 wird benötigt. Bitte Node.js aktualisieren und die Installation erneut starten."); process.exit(1) }'
python3 -c 'import PIL'
FEDEO_SETUP_NODE="$(command -v node)"
FEDEO_SETUP_HAS_SYSTEMD=false
if command -v systemctl >/dev/null && systemctl --user show-environment >/dev/null 2>&1; then
FEDEO_SETUP_HAS_SYSTEMD=true
fi
if [[ "$FEDEO_SETUP_AUTOSTART" == true && "$FEDEO_SETUP_HAS_SYSTEMD" != true ]]; then
echo 'Der Benutzerdienst von systemd ist nicht erreichbar. Bitte in der Desktop-Sitzung starten oder den Autostart im Assistenten ausschalten.' >&2
exit 1
fi
mkdir -p "$FEDEO_SETUP_DIR" "$(dirname "$FEDEO_SETUP_CONFIG")" "$FEDEO_SETUP_WORK" "$FEDEO_SETUP_UNITS"
# Stop only the matching installation before replacing its files.
if [[ "$FEDEO_SETUP_HAS_SYSTEMD" == true && "${1:-}" != --no-start ]]; then
systemctl --user stop "$FEDEO_SETUP_UNIT" 2>/dev/null || true
fi
python3 - "$FEDEO_SETUP_DIR" "$FEDEO_SETUP_CONFIG" "$FEDEO_SETUP_WORK" "$FEDEO_SETUP_NODE" "$FEDEO_SETUP_UNITS/$FEDEO_SETUP_UNIT" <<'FEDEO_PYTHON'
import base64
import json
import os
from pathlib import Path
import sys
agent_dir, config_path, work_dir, node, unit_path = map(Path, sys.argv[1:])
files = json.loads(base64.b64decode('@@PAYLOAD_BASE64@@'))
for name, encoded in files.items():
relative = Path(name)
if relative.is_absolute() or '..' in relative.parts:
raise ValueError('Ungültiger Dateiname im Installationspaket')
target = agent_dir / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(base64.b64decode(encoded, validate=True))
config = base64.b64decode('@@CONFIG_BASE64@@').decode('utf8')
config += 'FEDEO_WORK_DIR=' + str(work_dir) + '\n'
config_path.write_text(config, encoding='utf8')
config_path.chmod(0o600)
def unit_quote(value):
return '"' + str(value).replace('\\', '\\\\').replace('"', '\\"').replace('%', '%%') + '"'
unit_path.write_text('\n'.join([
'[Unit]', 'Description=FEDEO Scan-Agent', 'After=network-online.target', '',
'[Service]', 'Type=simple',
'WorkingDirectory=' + unit_quote(agent_dir),
'Environment=' + unit_quote('FEDEO_AGENT_ENV=' + str(config_path)),
'ExecStart=' + unit_quote(node) + ' ' + unit_quote(agent_dir / 'dist/main.js'),
'Restart=on-failure', 'RestartSec=5', 'UMask=0077', '',
'[Install]', 'WantedBy=default.target', '',
]), encoding='utf8')
FEDEO_PYTHON
echo "Agent installiert: $FEDEO_SETUP_DIR"
echo "Konfiguration: $FEDEO_SETUP_CONFIG"
if [[ "${1:-}" == --no-start ]]; then
echo 'Installation abgeschlossen; Agent wurde nicht gestartet.'
elif [[ "$FEDEO_SETUP_AUTOSTART" == true ]]; then
systemctl --user daemon-reload
systemctl --user enable "$FEDEO_SETUP_UNIT"
systemctl --user restart "$FEDEO_SETUP_UNIT"
echo 'Agent gestartet. Er startet künftig bei der Anmeldung.'
printf 'Protokoll: journalctl --user -u %q -f\n' "$FEDEO_SETUP_UNIT"
echo 'Jetzt im FEDEO-Assistenten die Verbindung prüfen. Die heruntergeladene Installationsdatei kann anschließend gelöscht werden.'
else
if [[ "$FEDEO_SETUP_HAS_SYSTEMD" == true ]]; then
systemctl --user disable "$FEDEO_SETUP_UNIT" 2>/dev/null || true
systemctl --user daemon-reload
fi
echo 'Agent startet im Terminal. Dieses Fenster geöffnet lassen; Beenden mit Strg+C.'
cd "$FEDEO_SETUP_DIR"
FEDEO_AGENT_ENV="$FEDEO_SETUP_CONFIG" "$FEDEO_SETUP_NODE" dist/main.js
fi