Initial version of the Hermes self learning agent setup of JR IT Services.
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.{sh,bash}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.yaml]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Dockerfile]
|
||||||
|
indent_style = tab
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Copy to .env and adapt.
|
||||||
|
|
||||||
|
# --- project naming ---
|
||||||
|
COMPOSE_PROJECT_NAME=alan
|
||||||
|
|
||||||
|
# --- hermes image ---
|
||||||
|
HERMES_IMAGE=nousresearch/hermes-agent
|
||||||
|
HERMES_TAG=stable
|
||||||
|
|
||||||
|
# --- host paths ---
|
||||||
|
HERMES_DATA_DIR=./state/hermes
|
||||||
|
HERMES_BACKUP_DIR=./backups/hermes
|
||||||
|
|
||||||
|
# --- host ports ---
|
||||||
|
# Bound to 127.0.0.1 on purpose.
|
||||||
|
# Hermes reaches the internet via the external br0-backed Docker network,
|
||||||
|
# while the UI/API remain local to the workstation unless you add a reverse proxy.
|
||||||
|
HERMES_API_PORT=18642
|
||||||
|
HERMES_DASHBOARD_PORT=19119
|
||||||
|
|
||||||
|
# --- resources ---
|
||||||
|
HERMES_MEMORY_LIMIT=4g
|
||||||
|
HERMES_CPUS=2.0
|
||||||
|
HERMES_SHM_SIZE=1g
|
||||||
|
DASHBOARD_MEMORY_LIMIT=512m
|
||||||
|
DASHBOARD_CPUS=0.5
|
||||||
|
|
||||||
|
# --- agent settings ---
|
||||||
|
TZ=Europe/Berlin
|
||||||
|
PUID=1000
|
||||||
|
PGID=1000
|
||||||
|
|
||||||
|
# --- docker networks ---
|
||||||
|
# Shared service-to-service network used only when explicitly needed.
|
||||||
|
AGENTS_SHARED_NETWORK=alan_agents_shared
|
||||||
|
|
||||||
|
# External Docker network that must already exist and be backed by your host bridge br0.
|
||||||
|
# Recommended driver: macvlan with parent=br0.
|
||||||
|
BR0_DOCKER_NETWORK=br0_lan
|
||||||
|
# --- helper values for scripts/docs only ---
|
||||||
|
# Used by scripts/alanctl net-create-br0. Adjust to your LAN.
|
||||||
|
BR0_PARENT=br0
|
||||||
|
BR0_SUBNET=192.168.10.0/24
|
||||||
|
BR0_GATEWAY=192.168.10.1
|
||||||
|
BR0_IP_RANGE=192.168.10.224/28
|
||||||
|
# Optional host-side shim interface for host<->container reachability on macvlan.
|
||||||
|
BR0_HOST_SHIM_CIDR=192.168.10.239/32
|
||||||
|
|
||||||
|
# Optional: forward provider creds from host env instead of storing only inside state/hermes/.env.
|
||||||
|
# OPENAI_API_KEY=
|
||||||
|
# ANTHROPIC_API_KEY=
|
||||||
|
# OPENROUTER_API_KEY=
|
||||||
|
|
||||||
|
# Tipp: fuer reproduzierbare Updates lieber einen festen Release-Tag setzen
|
||||||
|
# statt dauerhaft latest zu verwenden. Beispiel:
|
||||||
|
# HERMES_TAG=v0.9.3
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Fehler im Repo, Skript oder Compose-Verhalten melden
|
||||||
|
title: "bug: "
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Zusammenfassung
|
||||||
|
description: Was ist kaputt?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Umgebung
|
||||||
|
description: Arch-Version, Docker-Version, Repo-Tag, Hermes-Tag
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: Schritte zur Reproduktion
|
||||||
|
description: Bitte die exakten Befehle angeben
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Erwartetes Verhalten
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: Tatsaechliches Verhalten
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevante Logs
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Erweiterung fuer Betrieb, Doku oder Workflow vorschlagen
|
||||||
|
title: "feat: "
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Welches Problem loest die Aenderung?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: proposal
|
||||||
|
attributes:
|
||||||
|
label: Vorschlag
|
||||||
|
description: Beschreibe den gewuenschten Zielzustand
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: impact
|
||||||
|
attributes:
|
||||||
|
label: Betriebswirkung
|
||||||
|
description: Hat die Aenderung Folgen fuer Update, Backup, Ports, Netze oder `.env`?
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## Checkliste
|
||||||
|
|
||||||
|
- [ ] `CHANGELOG.md` aktualisiert
|
||||||
|
- [ ] passende Doku aktualisiert
|
||||||
|
- [ ] `./scripts/release-check` ausgefuehrt
|
||||||
|
- [ ] `./scripts/alanctl validate` ausgefuehrt
|
||||||
|
- [ ] bei Upgrade-Auswirkung `docs/upgrade-policy.md` geprueft
|
||||||
|
|
||||||
|
## Betriebswirkung
|
||||||
|
|
||||||
|
- [ ] keine
|
||||||
|
- [ ] `.env.example` geaendert
|
||||||
|
- [ ] Ports/Netze/Volumes geaendert
|
||||||
|
- [ ] Update-/Rollback-Verhalten geaendert
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: repo-check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'feat/**'
|
||||||
|
- 'fix/**'
|
||||||
|
- 'docs/**'
|
||||||
|
- 'chore/**'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Basic file checks
|
||||||
|
run: |
|
||||||
|
test -f README.md
|
||||||
|
test -f CHANGELOG.md
|
||||||
|
test -f compose.yaml
|
||||||
|
test -x scripts/alanctl
|
||||||
|
test -x scripts/release-check
|
||||||
|
|
||||||
|
- name: Shell syntax
|
||||||
|
run: |
|
||||||
|
bash -n scripts/alanctl
|
||||||
|
bash -n scripts/release-check
|
||||||
|
bash -n scripts/new-release
|
||||||
|
|
||||||
|
- name: Compose validation
|
||||||
|
run: |
|
||||||
|
docker compose -f compose.yaml config >/dev/null
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
backups/
|
||||||
|
state/
|
||||||
|
third_party/
|
||||||
|
*.log
|
||||||
|
*.tar.gz
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
Alle relevanten Aenderungen an diesem Repo werden hier dokumentiert.
|
||||||
|
|
||||||
|
Das Format orientiert sich an "Keep a Changelog" und nutzt semantische Versionen fuer Repo-Releases.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Platzhalter fuer neue Features
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Platzhalter fuer geaenderte Betriebslogik
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Platzhalter fuer Bugfixes
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Platzhalter fuer sicherheitsrelevante Anpassungen
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-04-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Erstes Workstation-Setup fuer Hermes parallel zu OpenClaw
|
||||||
|
- br0-faeiges Docker-Layout mit externem Macvlan-Uplink
|
||||||
|
- Backup-, Update- und Rollback-Workflow ueber `alanctl`
|
||||||
|
- Gitea-Dokumentation und Maintainer-Struktur
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
# Default owners for the whole repository.
|
||||||
|
# Gitea installations with CODEOWNERS support can use this file for review hints.
|
||||||
|
# If your Gitea instance does not enforce CODEOWNERS automatically,
|
||||||
|
# use docs/maintainers.md as the operational fallback.
|
||||||
|
|
||||||
|
* @johannes
|
||||||
|
|
||||||
|
# Core runtime and deployment logic
|
||||||
|
/compose.yaml @johannes
|
||||||
|
/compose/ @johannes
|
||||||
|
/scripts/ @johannes
|
||||||
|
/systemd/ @johannes
|
||||||
|
|
||||||
|
# Documentation and process
|
||||||
|
/docs/ @johannes
|
||||||
|
/README.md @johannes
|
||||||
|
/CHANGELOG.md @johannes
|
||||||
|
/CONTRIBUTING.md @johannes
|
||||||
|
/SECURITY.md @johannes
|
||||||
|
/.gitea/ @johannes
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Dieses Repo ist fuer eine Workstation-/Homelab-nahe Infrastruktur gedacht. Aenderungen sollten daher vor allem diese Ziele schuetzen:
|
||||||
|
|
||||||
|
- reproduzierbares Verhalten
|
||||||
|
- klare Trennung zwischen Hermes und OpenClaw
|
||||||
|
- sichere Defaults
|
||||||
|
- moeglichst wenig manuelle Nacharbeit nach Updates
|
||||||
|
|
||||||
|
## Branching
|
||||||
|
|
||||||
|
- `main` bleibt deploybar
|
||||||
|
- neue Arbeit in `feat/...`, `fix/...`, `docs/...`, `chore/...`
|
||||||
|
- Merge erst nach lokalem Check
|
||||||
|
|
||||||
|
## Vor jedem Merge
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/release-check
|
||||||
|
./scripts/alanctl validate
|
||||||
|
./scripts/alanctl doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formatierung und Ownership
|
||||||
|
|
||||||
|
- nutze einen Editor mit `.editorconfig`-Unterstuetzung
|
||||||
|
- beachte `CODEOWNERS` und `docs/maintainers.md` bei Review und Zustaendigkeit
|
||||||
|
- Aenderungen an Runtime-, Netz- oder Update-Logik nicht ohne bewusste Review freigeben
|
||||||
|
|
||||||
|
## Commit-Stil
|
||||||
|
|
||||||
|
Empfohlen:
|
||||||
|
|
||||||
|
- `feat: ...`
|
||||||
|
- `fix: ...`
|
||||||
|
- `docs: ...`
|
||||||
|
- `chore: ...`
|
||||||
|
- `refactor: ...`
|
||||||
|
|
||||||
|
## Release-Regel
|
||||||
|
|
||||||
|
Jede aenderungsrelevante Anpassung an Compose, Skripten oder Netz-Logik braucht:
|
||||||
|
|
||||||
|
- Eintrag in `CHANGELOG.md`
|
||||||
|
- kurzen Hinweis in passender Doku
|
||||||
|
- bei Upgrade-Auswirkung einen Eintrag in `docs/upgrade-policy.md`
|
||||||
|
|
||||||
|
## Security-Meldungen
|
||||||
|
|
||||||
|
Bitte keine sicherheitsrelevanten Probleme oeffentlich als normales Issue posten. Nutze dafuer die Hinweise in `SECURITY.md`.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Johannes Rest
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
SHELL := /usr/bin/env bash
|
||||||
|
|
||||||
|
.PHONY: bootstrap up down restart status logs doctor init-hermes backup update shell dashboard
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
./scripts/alanctl bootstrap
|
||||||
|
|
||||||
|
up:
|
||||||
|
./scripts/alanctl up
|
||||||
|
|
||||||
|
down:
|
||||||
|
./scripts/alanctl down
|
||||||
|
|
||||||
|
restart:
|
||||||
|
./scripts/alanctl restart
|
||||||
|
|
||||||
|
status:
|
||||||
|
./scripts/alanctl status
|
||||||
|
|
||||||
|
logs:
|
||||||
|
./scripts/alanctl logs
|
||||||
|
|
||||||
|
doctor:
|
||||||
|
./scripts/alanctl doctor
|
||||||
|
|
||||||
|
init-hermes:
|
||||||
|
./scripts/alanctl init-hermes
|
||||||
|
|
||||||
|
backup:
|
||||||
|
./scripts/alanctl backup
|
||||||
|
|
||||||
|
update:
|
||||||
|
./scripts/alanctl update-hermes
|
||||||
|
|
||||||
|
shell:
|
||||||
|
./scripts/alanctl shell
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
./scripts/alanctl up --dashboard
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
# alan-agents-workstation
|
||||||
|
|
||||||
|
Docker-Setup fuer eine Arch-Linux-Workstation, damit **Hermes Agent** parallel zu **OpenClaw** laufen kann, ohne sich in die Quere zu kommen.
|
||||||
|
|
||||||
|
Wichtige Netzannahme in diesem Repo:
|
||||||
|
|
||||||
|
- **Internetzugang fuer Hermes laeuft ueber dein `br0`**.
|
||||||
|
- Dafuer haengt Hermes an einem **externen Docker-Macvlan-Netz**, das auf dem Host-Bridge-Interface `br0` basiert.
|
||||||
|
- OpenClaw bleibt ein **separater Stack** mit eigenen Ports, Volumes und idealerweise demselben Uplink-Prinzip.
|
||||||
|
|
||||||
|
Der Fokus liegt auf:
|
||||||
|
|
||||||
|
- saubere Trennung von Ports, Volumes und Netzwerken
|
||||||
|
- resilientes Compose-Setup
|
||||||
|
- komfortabler Workflow ueber `alanctl`
|
||||||
|
- einfache Ablage in einem Gitea-Repo
|
||||||
|
- explizite Beruecksichtigung von `br0`
|
||||||
|
|
||||||
|
## Was dieses Repo liefert
|
||||||
|
|
||||||
|
- `compose.yaml` fuer Hermes Gateway und optionales Dashboard
|
||||||
|
- Wrapper-Skript `scripts/alanctl`
|
||||||
|
- Backup/Restore/Update/Rollback-Workflow
|
||||||
|
- Doku fuer Architektur, `br0`, Betrieb und Gitea-Import
|
||||||
|
- systemd-User-Unit als Vorlage
|
||||||
|
- Beispiel fuer getrennte OpenClaw-Integration
|
||||||
|
|
||||||
|
## Schnellstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# .env an dein LAN anpassen: BR0_SUBNET, BR0_GATEWAY, BR0_IP_RANGE
|
||||||
|
./scripts/alanctl bootstrap
|
||||||
|
./scripts/alanctl net-create-br0
|
||||||
|
./scripts/alanctl init-hermes
|
||||||
|
./scripts/alanctl up
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional mit Dashboard:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl up --dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach:
|
||||||
|
|
||||||
|
- Hermes Health/API lokal: `http://127.0.0.1:18642/health`
|
||||||
|
- Hermes Dashboard lokal: `http://127.0.0.1:19119`
|
||||||
|
|
||||||
|
|
||||||
|
## Langfristige Repo-Pflege in Gitea
|
||||||
|
|
||||||
|
Neu fuer den laufenden Betrieb:
|
||||||
|
|
||||||
|
- `CHANGELOG.md` fuer nachvollziehbare Aenderungen
|
||||||
|
- `CONTRIBUTING.md` fuer Arbeitsregeln
|
||||||
|
- `docs/release-process.md` fuer Tags und Release Notes
|
||||||
|
- `docs/release-template.md` als kopierbare Vorlage fuer Gitea-Releases
|
||||||
|
- `docs/upgrade-policy.md` fuer Upgrade-Klassen und Rollback-Regeln
|
||||||
|
- `docs/maintenance.md` fuer wiederkehrende Pflege
|
||||||
|
- `.gitea/ISSUE_TEMPLATE/*` und `.gitea/pull_request_template.md`
|
||||||
|
- `.gitea/workflows/repo-check.yml` fuer einfache Repo-Pruefungen
|
||||||
|
- `scripts/release-check` und `scripts/new-release`
|
||||||
|
- `.editorconfig` fuer konsistente Formatierung
|
||||||
|
- `CODEOWNERS` und `docs/maintainers.md` fuer Review-Zustaendigkeiten
|
||||||
|
|
||||||
|
Release-Vorbereitung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/release-check
|
||||||
|
./scripts/new-release 0.1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Warum zwei Netzebenen?
|
||||||
|
|
||||||
|
- **`br0_uplink`**: externer Uplink fuer Internet/LAN via `br0`
|
||||||
|
- **`hermes_private`**: internes Compose-Netz fuer saubere Service-Kommunikation
|
||||||
|
- **`alan_agents_shared`**: optionales Querverbindungsnetz nur bei echtem Bedarf
|
||||||
|
|
||||||
|
So bekommt Hermes den Internetzugang ueber `br0`, waehrend API und Dashboard lokal auf `127.0.0.1` gebunden bleiben.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl logs
|
||||||
|
./scripts/alanctl logs dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health / Diagnose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl restore ./backups/hermes/hermes-YYYYMMDD-HHMMSS.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update
|
||||||
|
|
||||||
|
Empfohlen: Hermes **nicht** auf `latest` lassen, sondern auf einen bewusst gesetzten Tag pinnen.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl pin-hermes stable
|
||||||
|
./scripts/alanctl update-hermes # aktualisiert auf den aktuell gepinnten Tag
|
||||||
|
./scripts/alanctl update-hermes v0.9.3 # gezieltes Upgrade auf einen bestimmten Tag
|
||||||
|
```
|
||||||
|
|
||||||
|
Rollback bei Bedarf:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl rollback-hermes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arch-Linux-Hinweise
|
||||||
|
|
||||||
|
Installiere mindestens:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo pacman -S docker docker-compose curl iproute2
|
||||||
|
sudo systemctl enable --now docker
|
||||||
|
sudo usermod -aG docker "$USER"
|
||||||
|
newgrp docker
|
||||||
|
```
|
||||||
|
|
||||||
|
Fuer den optionalen Host-Shim brauchst du `sudo` fuer `ip link` und `ip route`.
|
||||||
|
|
||||||
|
## Wichtige Macvlan-Einschraenkung
|
||||||
|
|
||||||
|
Bei Docker-Macvlan koennen **Host und Container nicht direkt miteinander sprechen**, solange du keinen Host-Shim anlegst.
|
||||||
|
|
||||||
|
Wenn du das brauchst, z. B. fuer lokale Tests vom Host auf die `br0`-IP des Containers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl net-create-shim
|
||||||
|
```
|
||||||
|
|
||||||
|
Details dazu stehen in `docs/bridge-br0.md`.
|
||||||
|
|
||||||
|
## Verzeichnisstruktur
|
||||||
|
|
||||||
|
```text
|
||||||
|
.
|
||||||
|
├── compose
|
||||||
|
│ └── openclaw.override.example.yaml
|
||||||
|
├── compose.yaml
|
||||||
|
├── docs
|
||||||
|
│ ├── architecture.md
|
||||||
|
│ ├── bridge-br0.md
|
||||||
|
│ ├── gitea-import.md
|
||||||
|
│ ├── hermes.md
|
||||||
|
│ ├── maintenance.md
|
||||||
|
│ ├── openclaw-coexistence.md
|
||||||
|
│ ├── release-process.md
|
||||||
|
│ └── upgrade-policy.md
|
||||||
|
├── scripts
|
||||||
|
│ ├── alanctl
|
||||||
|
│ ├── new-release
|
||||||
|
│ └── release-check
|
||||||
|
├── state
|
||||||
|
│ └── hermes/
|
||||||
|
├── backups
|
||||||
|
│ └── hermes/
|
||||||
|
└── systemd
|
||||||
|
└── user
|
||||||
|
└── alan-hermes.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Warum die Ports absichtlich ungewoehnlich sind
|
||||||
|
|
||||||
|
Die offiziellen Hermes-Beispiele verwenden hostseitig meist `8642` fuer die API und `9119` fuer das Dashboard. Dieses Repo mappt absichtlich auf `18642` und `19119`, bindet sie aber nur an `127.0.0.1`, damit ein bestehendes OpenClaw- oder sonstiges Dev-Setup auf der Workstation seltener kollidiert.
|
||||||
|
|
||||||
|
## Maintainer und Review
|
||||||
|
|
||||||
|
- `.editorconfig` sorgt fuer konsistente Grundformatierung in Editor und CI
|
||||||
|
- `CODEOWNERS` beschreibt Default-Owner und Review-Schwerpunkte
|
||||||
|
- `docs/maintainers.md` ist die betriebliche Fallback-Doku, falls deine Gitea-Instanz `CODEOWNERS` nicht automatisch auswertet
|
||||||
|
|
||||||
|
## Lizenz und Security
|
||||||
|
|
||||||
|
- `LICENSE`: MIT-Lizenz fuer klare Weitergabe und Anpassung
|
||||||
|
- `SECURITY.md`: Richtlinie fuer Security-Meldungen und gepflegte Staende
|
||||||
|
|
||||||
|
## Sicherheit und Betriebsgrenzen
|
||||||
|
|
||||||
|
- Dieses Repo trennt Hermes und OpenClaw bewusst.
|
||||||
|
- Nutze das Shared-Netz nur, wenn wirklich noetig.
|
||||||
|
- Lege keine Secrets ins Git-Repo.
|
||||||
|
- Wenn du OpenClaw separat betreibst, gib ihm andere Host-Ports und eigene Volumes.
|
||||||
|
- Das `br0`-Uplink-Netz ist **extern** und wird absichtlich nicht blind bei `docker compose up` erzeugt.
|
||||||
|
|
||||||
|
## Naechster sinnvoller Ausbau
|
||||||
|
|
||||||
|
- Reverse-Proxy mit lokalem DNS-Namen
|
||||||
|
- systemd-User-Integration aktivieren
|
||||||
|
- Borg/Restic fuer Off-Host-Backups
|
||||||
|
- eigene Profiles fuer mehrere Hermes-Instanzen
|
||||||
|
- optional identisches `br0`-Uplink-Muster fuer OpenClaw
|
||||||
|
|
||||||
|
|
||||||
|
## Update-Strategie
|
||||||
|
|
||||||
|
Dieses Repo ist bewusst so gebaut, dass Updates kontrolliert ablaufen:
|
||||||
|
|
||||||
|
- `HERMES_TAG` wird in `.env` gepinnt
|
||||||
|
- `update-hermes` erstellt zuerst ein Backup
|
||||||
|
- danach wird das Ziel-Image gepullt und Hermes neu erstellt
|
||||||
|
- der Healthcheck wird aktiv abgewartet
|
||||||
|
- bei Fehlschlag wird automatisch auf den vorherigen Tag zurueckgestellt
|
||||||
|
- der zuletzt erfolgreiche Stand wird in `state/hermes/.last-known-good.env` gespeichert
|
||||||
|
|
||||||
|
Fuer reproduzierbare Setups auf Workstation oder Homeserver ist das deutlich robuster als blindes Arbeiten mit `latest`.
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Dieses Repo beschreibt und automatisiert ein lokales Infrastruktur-Setup fuer eine Arch-Linux-Workstation.
|
||||||
|
Sicherheitsrelevante Meldungen sind besonders wichtig bei:
|
||||||
|
|
||||||
|
- Container-Privilegien und Volume-Mounts
|
||||||
|
- Netzwerkdesign rund um `br0`, `macvlan` und lokale Bindings
|
||||||
|
- Backup-/Restore- und Rollback-Verhalten
|
||||||
|
- Secret-Handling in `.env`, systemd-Units oder Hilfsskripten
|
||||||
|
- Healthchecks, Update-Logik und unerwarteten Downgrade-/Rollback-Faellen
|
||||||
|
|
||||||
|
## Was als Sicherheitsproblem gilt
|
||||||
|
|
||||||
|
Bitte melde insbesondere:
|
||||||
|
|
||||||
|
- unbeabsichtigte Exponierung von Hermes oder OpenClaw auf allen Interfaces
|
||||||
|
- unsichere Defaults bei Docker-Rechten oder Netzfreigaben
|
||||||
|
- Fehler, durch die Secrets im Repo, in Logs oder in Backups landen koennen
|
||||||
|
- Rollback-/Restore-Pfade, die zu Datenverlust oder Privilegienproblemen fuehren
|
||||||
|
- Konfigurationsfehler, die Container versehentlich zu stark koppeln
|
||||||
|
|
||||||
|
## Was nicht darunter faellt
|
||||||
|
|
||||||
|
Diese Punkte sind wichtig, aber keine Security-Meldung im engeren Sinn:
|
||||||
|
|
||||||
|
- reine Doku-Fehler ohne Sicherheitsauswirkung
|
||||||
|
- Feature-Wuensche
|
||||||
|
- Port- oder Naming-Praeferenzen ohne Risiko
|
||||||
|
- allgemeine Bedienfragen
|
||||||
|
|
||||||
|
## Meldung von Schwachstellen
|
||||||
|
|
||||||
|
Bitte **keine sicherheitsrelevanten Details direkt in einem oeffentlichen Issue posten**.
|
||||||
|
|
||||||
|
Empfohlener Ablauf:
|
||||||
|
|
||||||
|
1. Privaten Kontakt zum Maintainer herstellen
|
||||||
|
2. Betroffene Datei, Version/Tag und Konfiguration beschreiben
|
||||||
|
3. Moegliche Auswirkung erklaeren
|
||||||
|
4. Reproduktionsschritte knapp dokumentieren
|
||||||
|
5. Falls moeglich einen sicheren Fix-Vorschlag beilegen
|
||||||
|
|
||||||
|
Empfohlene Informationen:
|
||||||
|
|
||||||
|
- Repo-Tag oder Commit
|
||||||
|
- relevante `.env`-Werte **ohne Secrets**
|
||||||
|
- Host-Umgebung (z. B. Arch-Version, Docker-Version)
|
||||||
|
- Logs oder `doctor`-Ausgaben, soweit bereinigt
|
||||||
|
|
||||||
|
## Reaktionsziel
|
||||||
|
|
||||||
|
Es gibt hier kein formelles SLA. Ziel ist dennoch:
|
||||||
|
|
||||||
|
- zeitnahe Bestaetigung der Meldung
|
||||||
|
- Einordnung nach Risiko
|
||||||
|
- Fix oder Mitigation-Hinweis
|
||||||
|
- kurzer Eintrag im `CHANGELOG.md`, wenn eine harte Aenderung noetig ist
|
||||||
|
|
||||||
|
## Support-Fenster
|
||||||
|
|
||||||
|
Aktiv gepflegt wird jeweils der aktuelle Stand auf `main` sowie der letzte getaggte Release-Stand, soweit reproduzierbar.
|
||||||
|
|
||||||
|
Aeltere Tags koennen Hinweise bekommen, aber ohne zugesagte Rueckportierung.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
services:
|
||||||
|
hermes:
|
||||||
|
image: ${HERMES_IMAGE:-nousresearch/hermes-agent}:${HERMES_TAG:-latest}
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME:-alan}-hermes
|
||||||
|
restart: unless-stopped
|
||||||
|
command: gateway run
|
||||||
|
shm_size: ${HERMES_SHM_SIZE:-1g}
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:${HERMES_API_PORT:-18642}:8642"
|
||||||
|
environment:
|
||||||
|
TZ: ${TZ:-Europe/Berlin}
|
||||||
|
volumes:
|
||||||
|
- ${HERMES_DATA_DIR:-./state/hermes}:/opt/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8642/health', timeout=3).read()\""]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 45s
|
||||||
|
mem_limit: ${HERMES_MEMORY_LIMIT:-4g}
|
||||||
|
cpus: ${HERMES_CPUS:-2.0}
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
hermes_private:
|
||||||
|
agents_shared:
|
||||||
|
br0_uplink:
|
||||||
|
aliases:
|
||||||
|
- hermes
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
image: ${HERMES_IMAGE:-nousresearch/hermes-agent}:${HERMES_TAG:-latest}
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME:-alan}-hermes-dashboard
|
||||||
|
restart: unless-stopped
|
||||||
|
command: dashboard --host 0.0.0.0
|
||||||
|
profiles: ["dashboard"]
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:${HERMES_DASHBOARD_PORT:-19119}:9119"
|
||||||
|
environment:
|
||||||
|
TZ: ${TZ:-Europe/Berlin}
|
||||||
|
GATEWAY_HEALTH_URL: http://hermes:8642
|
||||||
|
volumes:
|
||||||
|
- ${HERMES_DATA_DIR:-./state/hermes}:/opt/data:ro
|
||||||
|
depends_on:
|
||||||
|
hermes:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:9119', timeout=3).read()\""]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
mem_limit: ${DASHBOARD_MEMORY_LIMIT:-512m}
|
||||||
|
cpus: ${DASHBOARD_CPUS:-0.5}
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
networks:
|
||||||
|
- hermes_private
|
||||||
|
|
||||||
|
networks:
|
||||||
|
hermes_private:
|
||||||
|
name: ${COMPOSE_PROJECT_NAME:-alan}_hermes_private
|
||||||
|
driver: bridge
|
||||||
|
internal: false
|
||||||
|
agents_shared:
|
||||||
|
name: ${AGENTS_SHARED_NETWORK:-alan_agents_shared}
|
||||||
|
external: true
|
||||||
|
br0_uplink:
|
||||||
|
name: ${BR0_DOCKER_NETWORK:-br0_lan}
|
||||||
|
external: true
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Example only.
|
||||||
|
# Use this as a pattern if you want OpenClaw managed in a separate repo or compose project.
|
||||||
|
# The important part is the same separation model as Hermes:
|
||||||
|
# - localhost-bound host ports
|
||||||
|
# - own state directory
|
||||||
|
# - own private network
|
||||||
|
# - optional shared network
|
||||||
|
# - external br0-backed uplink network for LAN/Internet
|
||||||
|
services:
|
||||||
|
openclaw:
|
||||||
|
image: ghcr.io/example/openclaw:latest
|
||||||
|
container_name: openclaw
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:18080:8080"
|
||||||
|
environment:
|
||||||
|
TZ: Europe/Berlin
|
||||||
|
volumes:
|
||||||
|
- ./state/openclaw:/app/data
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
mem_limit: 4g
|
||||||
|
cpus: 2.0
|
||||||
|
networks:
|
||||||
|
- openclaw_private
|
||||||
|
- agents_shared
|
||||||
|
- br0_uplink
|
||||||
|
|
||||||
|
networks:
|
||||||
|
openclaw_private:
|
||||||
|
name: openclaw_private
|
||||||
|
driver: bridge
|
||||||
|
agents_shared:
|
||||||
|
name: alan_agents_shared
|
||||||
|
external: true
|
||||||
|
br0_uplink:
|
||||||
|
name: br0_lan
|
||||||
|
external: true
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Architektur
|
||||||
|
|
||||||
|
## Ziele
|
||||||
|
|
||||||
|
- Hermes parallel zu OpenClaw betreiben, ohne Port-, Volume- oder Netzwerk-Kollisionen.
|
||||||
|
- Reproduzierbares Setup fuer eine Arch-Linux-Workstation.
|
||||||
|
- Resilienter Betrieb mit Healthchecks, Restart-Policy, Backups und Update-Weg.
|
||||||
|
- Komfortabler Workflow ueber ein einziges Wrapper-Skript.
|
||||||
|
- Internetzugang fuer Hermes ueber die Host-Bridge `br0`.
|
||||||
|
|
||||||
|
## Trennungsprinzipien
|
||||||
|
|
||||||
|
### 1. Eigene Datenverzeichnisse
|
||||||
|
|
||||||
|
- Hermes: `./state/hermes`
|
||||||
|
- OpenClaw: separat, z. B. `./state/openclaw`
|
||||||
|
|
||||||
|
Hermes schreibt seine komplette Identitaet, Konfiguration, Skills, Sessions und Memory in das gemountete Datenverzeichnis. Das darf nicht mit einem zweiten Hermes-Gateway geteilt werden.
|
||||||
|
|
||||||
|
### 2. Eigene Host-Ports
|
||||||
|
|
||||||
|
Default in diesem Repo:
|
||||||
|
|
||||||
|
- Hermes API: `127.0.0.1:18642 -> 8642`
|
||||||
|
- Hermes Dashboard: `127.0.0.1:19119 -> 9119`
|
||||||
|
|
||||||
|
Die absichtlich ungewohnten Host-Ports reduzieren Kollisionen mit anderen lokalen Stacks. Dass die Bindung auf `127.0.0.1` erfolgt, ist Absicht: der LAN-Uplink laeuft separat ueber `br0`, die Bedienoberflaechen bleiben lokal.
|
||||||
|
|
||||||
|
### 3. Eigene Netzrollen
|
||||||
|
|
||||||
|
- `hermes_private`: internes Docker-Bridge-Netz fuer Compose-interne Kommunikation
|
||||||
|
- `br0_uplink`: externes Docker-Macvlan-Netz auf Parent `br0`
|
||||||
|
- `alan_agents_shared`: optionales Querverbindungsnetz fuer Hermes/OpenClaw
|
||||||
|
|
||||||
|
Damit ist die Verkehrsrichtung klar getrennt:
|
||||||
|
|
||||||
|
- Internet/LAN raus ueber `br0_uplink`
|
||||||
|
- interne Service-Aufloesung ueber `hermes_private`
|
||||||
|
- bewusste Interconnects ueber `alan_agents_shared`
|
||||||
|
|
||||||
|
### 4. Eigene Compose-Projektkennung
|
||||||
|
|
||||||
|
Der Standard-Projektname ist `alan`. Dadurch bleiben Container, Netzwerke und sonstige Docker-Ressourcen sauber benannt.
|
||||||
|
|
||||||
|
## Resilienz
|
||||||
|
|
||||||
|
- `restart: unless-stopped`
|
||||||
|
- HTTP-Healthchecks fuer Gateway und Dashboard
|
||||||
|
- `no-new-privileges`
|
||||||
|
- definierte Ressourcenlimits
|
||||||
|
- Backup- und Restore-Skripte
|
||||||
|
- idempotentes Bootstrap fuer `.env`, Verzeichnisse und Shared-Netz
|
||||||
|
- explizite Validierung des externen `br0`-Netzes vor dem Start
|
||||||
|
|
||||||
|
## Nicht absichtlich eingebaut
|
||||||
|
|
||||||
|
- kein Reverse-Proxy per Default
|
||||||
|
- kein gemeinsamer Docker-Socket fuer Hermes und OpenClaw
|
||||||
|
- kein wildes Mounten von `/home`
|
||||||
|
- kein direkter Zwang, OpenClaw in denselben Compose-Stack zu ziehen
|
||||||
|
- keine blind automatische Ermittlung deiner LAN-Parameter
|
||||||
|
|
||||||
|
Das ist Absicht: weniger Seiteneffekte, weniger Sicherheitsrisiko, bessere Fehlersuche.
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Betrieb ueber `br0`
|
||||||
|
|
||||||
|
## Warum hier Macvlan verwendet wird
|
||||||
|
|
||||||
|
Du hast vorgegeben, dass der Container **ueber deine Bridge `br0`** laufen soll, wenn Internetzugang benoetigt wird.
|
||||||
|
|
||||||
|
Fuer Docker auf Linux ist das sauberste Muster dafuer in der Regel ein **externes Macvlan-Netz** mit:
|
||||||
|
|
||||||
|
- Parent: `br0`
|
||||||
|
- eigenem IP-Bereich fuer Container
|
||||||
|
- optionaler statischer IP pro Service
|
||||||
|
|
||||||
|
## Was du in `.env` anpassen musst
|
||||||
|
|
||||||
|
Mindestens:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
BR0_PARENT=br0
|
||||||
|
BR0_SUBNET=192.168.10.0/24
|
||||||
|
BR0_GATEWAY=192.168.10.1
|
||||||
|
BR0_IP_RANGE=192.168.10.224/28
|
||||||
|
BR0_DOCKER_NETWORK=br0_lan
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
BR0_HOST_SHIM_CIDR=192.168.10.239/32
|
||||||
|
```
|
||||||
|
|
||||||
|
Nimm fuer `BR0_IP_RANGE` am besten einen kleinen Bereich, der **nicht** durch DHCP an normale Clients vergeben wird.
|
||||||
|
|
||||||
|
## Externes Docker-Netz anlegen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl net-create-br0
|
||||||
|
```
|
||||||
|
|
||||||
|
Das erzeugt ein Docker-Macvlan-Netz auf `br0`.
|
||||||
|
|
||||||
|
## Bekannte Einschraenkung: Host <-> Container
|
||||||
|
|
||||||
|
Bei Macvlan koennen Host und Container standardmaessig **nicht direkt miteinander sprechen**. Das ist kein Fehler in deinem Compose-Setup, sondern normales Macvlan-Verhalten.
|
||||||
|
|
||||||
|
### Loesung: Host-Shim anlegen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl net-create-shim
|
||||||
|
```
|
||||||
|
|
||||||
|
Das erzeugt auf dem Host ein zusaetzliches Macvlan-Interface und routet den konfigurierten IP-Bereich dorthin.
|
||||||
|
|
||||||
|
## Wann du den Shim wirklich brauchst
|
||||||
|
|
||||||
|
- du willst den Container auf seiner `br0`-IP direkt vom Host anpingen
|
||||||
|
- du willst vom Host Dienste auf der `br0`-IP ansprechen
|
||||||
|
|
||||||
|
Wenn dir dagegen die lokal publizierten Ports `127.0.0.1:18642` und `127.0.0.1:19119` reichen, brauchst du den Shim oft gar nicht.
|
||||||
|
|
||||||
|
## Empfehlung fuer parallelen OpenClaw-Betrieb
|
||||||
|
|
||||||
|
- gleicher Parent `br0`
|
||||||
|
- anderer Host-Port
|
||||||
|
- anderer Volume-Pfad
|
||||||
|
- eigene statische IP oder eigene Zuteilung aus demselben `BR0_IP_RANGE`
|
||||||
|
- kein gemeinsames wildes Mounten von Host-Pfaden
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Gitea-Import
|
||||||
|
|
||||||
|
## Repo-Name
|
||||||
|
|
||||||
|
Empfohlen: `alan-agents-workstation`
|
||||||
|
|
||||||
|
## Schnellweg
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /pfad/zu/alan
|
||||||
|
|
||||||
|
git init
|
||||||
|
git branch -M main
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial workstation stack for Hermes + OpenClaw coexistence"
|
||||||
|
|
||||||
|
git remote add origin ssh://git@<dein-gitea-host>/<dein-user>/alan-agents-workstation.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Branching
|
||||||
|
|
||||||
|
Einfach und robust fuer Single-Workstation-Infra:
|
||||||
|
|
||||||
|
- `main` fuer den stabilen Stand
|
||||||
|
- `feat/...` fuer lokale Erweiterungen
|
||||||
|
- Tags fuer getestete Snapshots, z. B. `v0.1.0`
|
||||||
|
|
||||||
|
## Was ins Repo gehoert
|
||||||
|
|
||||||
|
- Compose-Dateien
|
||||||
|
- Skripte
|
||||||
|
- Doku
|
||||||
|
- Beispiel-Env-Dateien
|
||||||
|
- systemd-User-Units
|
||||||
|
|
||||||
|
## Was nicht ins Repo gehoert
|
||||||
|
|
||||||
|
- echte API-Keys
|
||||||
|
- echte Hermes-Daten aus `state/hermes`
|
||||||
|
- Backup-Archive
|
||||||
|
- lokale Debug-Logs
|
||||||
|
|
||||||
|
|
||||||
|
## Empfohlene Zusatzdateien fuer Gitea
|
||||||
|
|
||||||
|
Dieses Repo bringt bereits mit:
|
||||||
|
|
||||||
|
- `CHANGELOG.md`
|
||||||
|
- `CONTRIBUTING.md`
|
||||||
|
- `.gitea/ISSUE_TEMPLATE/*`
|
||||||
|
- `.gitea/pull_request_template.md`
|
||||||
|
- `.gitea/workflows/repo-check.yml`
|
||||||
|
- `docs/release-process.md`
|
||||||
|
- `docs/upgrade-policy.md`
|
||||||
|
|
||||||
|
Damit bekommst du in Gitea:
|
||||||
|
|
||||||
|
- strukturierte Fehlermeldungen und Feature-Wuensche
|
||||||
|
- einheitliche PR-Checklisten
|
||||||
|
- einfache serverseitige Grundpruefungen fuer das Repo
|
||||||
|
- nachvollziehbare Releases mit Tagging und Upgrade-Hinweisen
|
||||||
|
|
||||||
|
## Release-Empfehlung
|
||||||
|
|
||||||
|
Fuer jedes verteilte Repo-Update:
|
||||||
|
|
||||||
|
1. `CHANGELOG.md` pflegen
|
||||||
|
2. `./scripts/release-check` ausfuehren
|
||||||
|
3. Release-Tag setzen, z. B. `v0.1.1`
|
||||||
|
4. in Gitea Release Notes mit Upgrade-Hinweisen anlegen
|
||||||
|
|
||||||
|
## Beispiel fuer Release Notes
|
||||||
|
|
||||||
|
```text
|
||||||
|
Neu:
|
||||||
|
- robusteres br0-Uplink-Handling
|
||||||
|
- verbesserter Rollback-Workflow
|
||||||
|
|
||||||
|
Wichtig fuer bestehende Installationen:
|
||||||
|
- `.env.example` geaendert: neuer Key XYZ
|
||||||
|
- vor Upgrade bitte `./scripts/alanctl backup` ausfuehren
|
||||||
|
- Rollback bleibt ueber `./scripts/alanctl rollback-hermes` moeglich
|
||||||
|
```
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Hermes in diesem Setup
|
||||||
|
|
||||||
|
## Betriebsmodus
|
||||||
|
|
||||||
|
Dieses Repo faehrt Hermes **im Container** als Gateway.
|
||||||
|
|
||||||
|
Wichtige Daten landen in `./state/hermes`, das auf `/opt/data` im Container gemountet wird. Laut offizieller Doku ist `/opt/data` der Single Source of Truth fuer Config, Sessions, Skills, Memories und Logs.
|
||||||
|
|
||||||
|
## Initialisierung
|
||||||
|
|
||||||
|
1. `./scripts/alanctl bootstrap`
|
||||||
|
2. `./scripts/alanctl init-hermes`
|
||||||
|
3. `./scripts/alanctl up`
|
||||||
|
|
||||||
|
`init-hermes` startet den offiziellen Setup-Wizard interaktiv.
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl up --dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach ist das Dashboard standardmaessig auf `http://127.0.0.1:19119` erreichbar.
|
||||||
|
|
||||||
|
## Wichtige Hinweise
|
||||||
|
|
||||||
|
- Nicht zwei Hermes-Gateway-Container gegen dasselbe Datenverzeichnis starten.
|
||||||
|
- Falls du spaeter Messaging-Plattformen oder Provider-Keys nutzt, verwalte sie im Hermes-Datenordner bzw. in dessen `.env`.
|
||||||
|
- Fuer Browser-Tools ist `shm_size=1g` gesetzt.
|
||||||
|
|
||||||
|
|
||||||
|
## Update-Betrieb
|
||||||
|
|
||||||
|
Empfohlener Alltag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl pin-hermes stable
|
||||||
|
./scripts/alanctl update-hermes
|
||||||
|
```
|
||||||
|
|
||||||
|
Gezieltes Upgrade auf einen bestimmten Tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl update-hermes v0.9.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Update-Workflow macht automatisch:
|
||||||
|
|
||||||
|
1. Backup des Hermes-State
|
||||||
|
2. Pull des Ziel-Images
|
||||||
|
3. Recreate des Containers
|
||||||
|
4. Warten auf erfolgreichen Healthcheck
|
||||||
|
5. automatisches Zurueckstellen auf den vorherigen Tag bei Fehlern
|
||||||
|
|
||||||
|
Der zuletzt erfolgreiche Stand wird lokal gespeichert und kann mit `rollback-hermes` erneut ausgerollt werden.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Maintainers and Review Ownership
|
||||||
|
|
||||||
|
Dieses Repo ist so klein gehalten, dass eine schlanke Maintainer-Struktur sinnvoller ist als komplexe Freigaberegeln.
|
||||||
|
|
||||||
|
## Primaerer Maintainer
|
||||||
|
|
||||||
|
- `@johannes`
|
||||||
|
|
||||||
|
## Verantwortungsbereiche
|
||||||
|
|
||||||
|
- **Runtime / Compose / Netzlogik**
|
||||||
|
- `compose.yaml`
|
||||||
|
- `compose/`
|
||||||
|
- `scripts/`
|
||||||
|
- `systemd/`
|
||||||
|
- **Dokumentation / Betriebswissen**
|
||||||
|
- `docs/`
|
||||||
|
- `README.md`
|
||||||
|
- `CHANGELOG.md`
|
||||||
|
- `CONTRIBUTING.md`
|
||||||
|
- `SECURITY.md`
|
||||||
|
- **Repository-Prozess in Gitea**
|
||||||
|
- `.gitea/`
|
||||||
|
- Release-Notizen
|
||||||
|
- Issue- und PR-Triage
|
||||||
|
|
||||||
|
## Review-Regeln
|
||||||
|
|
||||||
|
Empfohlene Minimalregeln:
|
||||||
|
|
||||||
|
- Aenderungen an `compose.yaml`, `scripts/alanctl` oder Netz-Setup immer bewusst reviewen.
|
||||||
|
- Vor Merge von Infrastruktur-Aenderungen mindestens `./scripts/release-check` ausfuehren.
|
||||||
|
- Bei Aenderungen mit Betriebsfolgen zusaetzlich `./scripts/alanctl validate` und `./scripts/alanctl doctor` laufen lassen.
|
||||||
|
- Bei Update- oder Rollback-Aenderungen immer `CHANGELOG.md` und passende Doku mitziehen.
|
||||||
|
|
||||||
|
## CODEOWNERS-Hinweis
|
||||||
|
|
||||||
|
Dieses Repo enthaelt eine `CODEOWNERS`-Datei fuer Instanzen, die das unterstuetzen.
|
||||||
|
Falls deine Gitea-Version oder Konfiguration CODEOWNERS nicht automatisch auswertet, nutze diese Datei als dokumentierte Zustaendigkeitsmatrix und setze die Review-Regel organisatorisch ueber Pull Requests um.
|
||||||
|
|
||||||
|
## Praktischer Betrieb in kleinen Teams
|
||||||
|
|
||||||
|
Falls spaeter weitere Maintainer dazukommen, ist dieses Muster sinnvoll:
|
||||||
|
|
||||||
|
- `@johannes` als primaerer Owner fuer Runtime und Releases
|
||||||
|
- ein zweiter Owner fuer Dokumentation und Review
|
||||||
|
- sicherheitsrelevante Aenderungen nie ohne sichtbare Review-Notiz mergen
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Maintenance
|
||||||
|
|
||||||
|
## Regelmaessige Aufgaben
|
||||||
|
|
||||||
|
Woechentlich oder vor groesseren Aenderungen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl status
|
||||||
|
./scripts/alanctl doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
Monatlich:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl backup
|
||||||
|
docker image prune -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Vor jedem Hermes-Update:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl backup
|
||||||
|
./scripts/alanctl update-hermes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repo-Pflege
|
||||||
|
|
||||||
|
- `.editorconfig`, `CODEOWNERS` und `docs/maintainers.md` synchron halten
|
||||||
|
- `CHANGELOG.md` aktuell halten
|
||||||
|
- veraltete Screenshots/Beispiele entfernen
|
||||||
|
- `.env.example` synchron zur Doku halten
|
||||||
|
- neue Ports, Netze und Volumes zentral in README und Architektur dokumentieren
|
||||||
|
|
||||||
|
## Regelmaessige Sicherheitspruefung
|
||||||
|
|
||||||
|
- oeffentliche Bindings gegen `127.0.0.1` und `br0` gegenpruefen
|
||||||
|
- `SECURITY.md` bei geaendertem Meldeprozess aktualisieren
|
||||||
|
- nach Netz- oder Mount-Aenderungen gezielt `doctor` und einen Test-Upgrade-Lauf ausfuehren
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Parallelbetrieb mit OpenClaw
|
||||||
|
|
||||||
|
## Empfehlung
|
||||||
|
|
||||||
|
Lass OpenClaw in einem **separaten Compose-Projekt** laufen.
|
||||||
|
|
||||||
|
Das Repo hier geht davon aus, dass OpenClaw bereits existiert oder separat gepflegt wird. Dadurch bleiben Updates, Logik und Fehlersuche voneinander getrennt.
|
||||||
|
|
||||||
|
## Regeln gegen Konflikte
|
||||||
|
|
||||||
|
- andere Host-Ports als Hermes verwenden
|
||||||
|
- eigenes Volume oder eigener Basisordner fuer OpenClaw
|
||||||
|
- eigener Compose-Projektname
|
||||||
|
- wenn moeglich kein gemeinsamer Schreibzugriff auf denselben Host-Pfad
|
||||||
|
- Shared-Netz nur verwenden, wenn es einen echten Grund gibt
|
||||||
|
- fuer Internetzugang idealerweise dasselbe Muster verwenden: **eigenes externes Macvlan-Netz auf `br0` oder bewusst dasselbe externe `br0`-Netz mit eigener IP**
|
||||||
|
|
||||||
|
## Sichere Defaults
|
||||||
|
|
||||||
|
Hermes expose't nur lokal:
|
||||||
|
|
||||||
|
- `127.0.0.1:18642/tcp` fuer API/Health
|
||||||
|
- `127.0.0.1:19119/tcp` fuer Dashboard, falls aktiviert
|
||||||
|
|
||||||
|
OpenClaw sollte auf einem **anderen** Host-Port laufen, z. B. `127.0.0.1:18080` oder `127.0.0.1:18081`, je nach Image.
|
||||||
|
|
||||||
|
## `br0`-Hinweis
|
||||||
|
|
||||||
|
Wenn OpenClaw wie Hermes ueber `br0` ins Netz soll, hast du zwei saubere Optionen:
|
||||||
|
|
||||||
|
1. **gleiches externes Docker-Netz** verwenden, mit eigener statischer IP je Container
|
||||||
|
2. **separates zweites Macvlan-Netz** auf demselben Parent `br0` anlegen
|
||||||
|
|
||||||
|
Option 1 ist einfacher. Option 2 ist strenger getrennt.
|
||||||
|
|
||||||
|
## Wann das Shared-Netz sinnvoll ist
|
||||||
|
|
||||||
|
Nur wenn sich OpenClaw und Hermes wirklich gegenseitig erreichen muessen, z. B. fuer einen lokalen Upstream-Endpoint oder Kontrollpfad. Ansonsten reicht die komplette Trennung.
|
||||||
|
|
||||||
|
## Beispiel fuer OpenClaw
|
||||||
|
|
||||||
|
Siehe `compose/openclaw.override.example.yaml`.
|
||||||
|
|
||||||
|
Das ist bewusst nur eine Vorlage, weil der OpenClaw-Docker-Oekosystem-Stand momentan fragmentiert ist und verschiedene Images unterschiedliche Environment-Variablen erwarten.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Release-Prozess
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Dieses Repo soll sich wie ein kleines Infrastrukturprodukt pflegen lassen:
|
||||||
|
|
||||||
|
- Aenderungen gesammelt entwickeln
|
||||||
|
- lokal validieren
|
||||||
|
- in Gitea taggen
|
||||||
|
- Releases mit klaren Upgrade-Hinweisen veroeffentlichen
|
||||||
|
|
||||||
|
## Versionsschema
|
||||||
|
|
||||||
|
Empfohlen: semantische Versionierung fuer das Repo.
|
||||||
|
|
||||||
|
- `MAJOR`: inkompatible Struktur- oder Betriebswechsel
|
||||||
|
- `MINOR`: neue Features oder neue Betriebsfaelle ohne harte Brueche
|
||||||
|
- `PATCH`: Fixes, Doku, sichere Defaults, kleinere Skriptanpassungen
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
|
||||||
|
- `v0.1.0` erste stabile Struktur
|
||||||
|
- `v0.2.0` neue optionalen Dienste oder neue Netzvarianten
|
||||||
|
- `v0.2.1` Bugfix im Update-/Backup-Verhalten
|
||||||
|
|
||||||
|
## Maintainer-Ablauf
|
||||||
|
|
||||||
|
1. Feature- oder Fix-Branch bauen
|
||||||
|
2. lokal testen
|
||||||
|
3. `CHANGELOG.md` aktualisieren
|
||||||
|
4. bei Upgrade-Auswirkungen `docs/upgrade-policy.md` pflegen
|
||||||
|
5. Merge nach `main`
|
||||||
|
6. Release-Tag setzen
|
||||||
|
7. Release Notes in Gitea anlegen
|
||||||
|
|
||||||
|
## Lokale Checks vor einem Release
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/release-check
|
||||||
|
./scripts/alanctl validate
|
||||||
|
./scripts/alanctl doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional auf einem echten Host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl backup
|
||||||
|
./scripts/alanctl up --dashboard
|
||||||
|
./scripts/alanctl update-hermes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tagging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git pull --ff-only
|
||||||
|
git tag -a v0.1.1 -m "alan-agents-workstation v0.1.1"
|
||||||
|
git push origin main --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inhalt guter Release Notes
|
||||||
|
|
||||||
|
- Was ist neu?
|
||||||
|
- Was ist gefixt?
|
||||||
|
- Muss `.env` angepasst werden?
|
||||||
|
- Gibt es ein neues Netzwerk-/Volume-/Port-Verhalten?
|
||||||
|
- Ist ein manuelles Backup vor Upgrade empfohlen?
|
||||||
|
- Gibt es ein Rollback-Risiko bei State-Migrationen?
|
||||||
|
|
||||||
|
## Release-Text vorbereiten
|
||||||
|
|
||||||
|
Nutze `docs/release-template.md` als portable Vorlage fuer die Release-Beschreibung in Gitea.
|
||||||
|
|
||||||
|
Empfohlener Ablauf:
|
||||||
|
|
||||||
|
1. `./scripts/new-release <version>` ausfuehren
|
||||||
|
2. `CHANGELOG.md` finalisieren
|
||||||
|
3. Release-Text aus `docs/release-template.md` kopieren und auf den konkreten Stand anpassen
|
||||||
|
4. Tag pushen und Release in Gitea anlegen
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Release Template
|
||||||
|
|
||||||
|
Nutze diese Vorlage fuer neue Gitea-Releases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Kurze Zusammenfassung des Releases in 2-4 Saetzen.
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Operational impact
|
||||||
|
|
||||||
|
- Muss `.env` angepasst werden? Ja/Nein
|
||||||
|
- Neue Ports, Volumes oder Netzwerke? Ja/Nein
|
||||||
|
- Neue manuelle Schritte nach dem Update? Ja/Nein
|
||||||
|
|
||||||
|
## Upgrade notes
|
||||||
|
|
||||||
|
1. Optional: `./scripts/alanctl doctor`
|
||||||
|
2. Optional: `./scripts/alanctl backup`
|
||||||
|
3. Update ausfuehren: `./scripts/alanctl update-hermes`
|
||||||
|
4. Health pruefen: `./scripts/alanctl status`
|
||||||
|
|
||||||
|
## Rollback notes
|
||||||
|
|
||||||
|
- Image-Rollback: `./scripts/alanctl rollback-hermes`
|
||||||
|
- State-Restore nur dann, wenn eine Datenmigration oder Konfigurationsaenderung Probleme macht
|
||||||
|
|
||||||
|
## Maintainer checklist
|
||||||
|
|
||||||
|
- [ ] `CHANGELOG.md` aktualisiert
|
||||||
|
- [ ] Doku angepasst
|
||||||
|
- [ ] `./scripts/release-check` erfolgreich
|
||||||
|
- [ ] `./scripts/alanctl validate` erfolgreich
|
||||||
|
- [ ] Release-Tag gesetzt
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Update-Strategie
|
||||||
|
|
||||||
|
Dieses Repo trennt bewusst zwischen **Pinning**, **Upgrade**, **Health-Pruefung** und **Rollback**.
|
||||||
|
|
||||||
|
## Prinzip
|
||||||
|
|
||||||
|
- `.env` enthaelt den gewuenschten `HERMES_TAG`
|
||||||
|
- `pin-hermes <tag>` aendert nur den Ziel-Tag
|
||||||
|
- `update-hermes [tag]` sichert zuerst den State und fuehrt dann das Upgrade aus
|
||||||
|
- `rollback-hermes` rollt auf den zuletzt als gesund gespeicherten Stand zurueck
|
||||||
|
|
||||||
|
## Empfohlener Ablauf
|
||||||
|
|
||||||
|
Vor einem groesseren Update:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl doctor
|
||||||
|
./scripts/alanctl backup
|
||||||
|
./scripts/alanctl update-hermes v0.9.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn der neue Stand nicht sauber hochkommt:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl rollback-hermes
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn das Problem in einer State-Migration liegt:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl restore ./backups/hermes/hermes-YYYYMMDD-HHMMSS.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Warum nicht dauerhaft latest
|
||||||
|
|
||||||
|
`latest` ist bequem, aber fuer einen reproduzierbaren Desktop- oder Homeserver-Betrieb unnoetig riskant. Ein fixer Tag macht Updates nachvollziehbar und Rollbacks deutlich einfacher.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Upgrade-Policy
|
||||||
|
|
||||||
|
## Grundsatz
|
||||||
|
|
||||||
|
Upgrades sollen reversibel, dokumentiert und fuer eine einzelne Workstation realistisch bedienbar sein.
|
||||||
|
|
||||||
|
## Klassen von Aenderungen
|
||||||
|
|
||||||
|
### 1. Sicheres Patch-Upgrade
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
- Doku-Updates
|
||||||
|
- Healthcheck-Feinschliff
|
||||||
|
- nicht-invasive Skriptkorrekturen
|
||||||
|
|
||||||
|
Erwartung:
|
||||||
|
- kein manueller Eingriff noetig
|
||||||
|
- normales `update-hermes` reicht
|
||||||
|
|
||||||
|
### 2. Operatives Minor-Upgrade
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
- neue optionale Dienste
|
||||||
|
- neue `.env`-Variablen mit Default
|
||||||
|
- neue Docs/Tools
|
||||||
|
|
||||||
|
Erwartung:
|
||||||
|
- Release Notes lesen
|
||||||
|
- `git diff .env.example` gegen lokale `.env` pruefen
|
||||||
|
- ggf. neuen Key ergaenzen
|
||||||
|
|
||||||
|
### 3. Breakendes Upgrade
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
- Netzwerknamen aendern
|
||||||
|
- Compose-Service-Namen aendern
|
||||||
|
- persistente Pfade aendern
|
||||||
|
- State-/Datenmigrationen mit Rueckwirkungsrisiko
|
||||||
|
|
||||||
|
Erwartung:
|
||||||
|
- Backup Pflicht
|
||||||
|
- Upgrade-Fenster bewusst planen
|
||||||
|
- Rollback testen
|
||||||
|
- Release Notes muessen eine explizite Migrationsanleitung enthalten
|
||||||
|
|
||||||
|
## Pflicht vor jedem geplanten Upgrade
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl doctor
|
||||||
|
./scripts/alanctl backup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pflicht bei Repo-Upgrade
|
||||||
|
|
||||||
|
Wenn du das Repo selbst aktualisierst:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch --tags
|
||||||
|
git checkout <ziel-tag-oder-branch>
|
||||||
|
./scripts/release-check
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach erst:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/alanctl validate
|
||||||
|
./scripts/alanctl up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Umgang mit `.env`
|
||||||
|
|
||||||
|
Die lokale `.env` ist betriebsrelevant und bleibt lokal.
|
||||||
|
|
||||||
|
Vor jedem Repo-Upgrade:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff HEAD~1 .env.example
|
||||||
|
```
|
||||||
|
|
||||||
|
oder allgemeiner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff <alter-tag>..<neuer-tag> -- .env.example
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn neue Variablen eingefuehrt wurden, muessen diese in die lokale `.env` uebernommen werden.
|
||||||
|
|
||||||
|
## Rollback-Regel
|
||||||
|
|
||||||
|
Image-Rollback und State-Rollback sind getrennt zu betrachten.
|
||||||
|
|
||||||
|
- `rollback-hermes` setzt das letzte bekannte gute Image wieder ein
|
||||||
|
- bei problematischer Datenmigration muss zusaetzlich ein Backup-Archiv via `restore` zurueckgespielt werden
|
||||||
Executable
+455
@@ -0,0 +1,455 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
ENV_FILE="${ROOT_DIR}/.env"
|
||||||
|
ENV_EXAMPLE="${ROOT_DIR}/.env.example"
|
||||||
|
COMPOSE_FILE="${ROOT_DIR}/compose.yaml"
|
||||||
|
VERSIONS_FILE="${ROOT_DIR}/state/hermes/.last-known-good.env"
|
||||||
|
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
PROJECT_NAME="${COMPOSE_PROJECT_NAME:-alan}"
|
||||||
|
HERMES_DATA_DIR="${HERMES_DATA_DIR:-./state/hermes}"
|
||||||
|
HERMES_BACKUP_DIR="${HERMES_BACKUP_DIR:-./backups/hermes}"
|
||||||
|
HERMES_API_PORT="${HERMES_API_PORT:-18642}"
|
||||||
|
HERMES_DASHBOARD_PORT="${HERMES_DASHBOARD_PORT:-19119}"
|
||||||
|
AGENTS_SHARED_NETWORK="${AGENTS_SHARED_NETWORK:-alan_agents_shared}"
|
||||||
|
BR0_DOCKER_NETWORK="${BR0_DOCKER_NETWORK:-br0_lan}"
|
||||||
|
BR0_PARENT="${BR0_PARENT:-br0}"
|
||||||
|
BR0_SUBNET="${BR0_SUBNET:-}"
|
||||||
|
BR0_GATEWAY="${BR0_GATEWAY:-}"
|
||||||
|
BR0_IP_RANGE="${BR0_IP_RANGE:-}"
|
||||||
|
BR0_HOST_SHIM_CIDR="${BR0_HOST_SHIM_CIDR:-}"
|
||||||
|
BR0_HOST_SHIM_IFACE="${BR0_HOST_SHIM_IFACE:-${BR0_DOCKER_NETWORK}-shim}"
|
||||||
|
HERMES_IMAGE="${HERMES_IMAGE:-nousresearch/hermes-agent}"
|
||||||
|
HERMES_TAG="${HERMES_TAG:-latest}"
|
||||||
|
HEALTHCHECK_URL="http://127.0.0.1:${HERMES_API_PORT}/health"
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
docker compose --project-name "$PROJECT_NAME" --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_file() {
|
||||||
|
local path="$1"
|
||||||
|
[[ -f "$path" ]] || { echo "missing file: $path" >&2; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_env() {
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||||
|
echo "[OK] .env created from .env.example"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_dirs() {
|
||||||
|
mkdir -p "$ROOT_DIR/state/hermes" "$ROOT_DIR/backups/hermes"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_networks() {
|
||||||
|
if ! docker network inspect "$AGENTS_SHARED_NETWORK" >/dev/null 2>&1; then
|
||||||
|
docker network create "$AGENTS_SHARED_NETWORK" >/dev/null
|
||||||
|
echo "[OK] created docker network ${AGENTS_SHARED_NETWORK}"
|
||||||
|
fi
|
||||||
|
if ! docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
|
||||||
|
echo "[WARN] missing external br0 network ${BR0_DOCKER_NETWORK}"
|
||||||
|
echo "[HINT] run: ./scripts/alanctl net-create-br0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_docker() {
|
||||||
|
command -v docker >/dev/null 2>&1 || { echo "docker not found" >&2; exit 1; }
|
||||||
|
docker info >/dev/null 2>&1 || { echo "docker daemon not reachable" >&2; exit 1; }
|
||||||
|
docker compose version >/dev/null 2>&1 || { echo "docker compose plugin not available" >&2; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
port_in_use() {
|
||||||
|
local port="$1"
|
||||||
|
ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|:)${port}$"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_ports() {
|
||||||
|
local fail=0
|
||||||
|
for p in "$HERMES_API_PORT" "$HERMES_DASHBOARD_PORT"; do
|
||||||
|
if port_in_use "$p"; then
|
||||||
|
echo "[WARN] host port ${p} is already in use"
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return "$fail"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_openclaw_hint() {
|
||||||
|
if docker ps --format '{{.Names}}' | grep -Eqi 'openclaw'; then
|
||||||
|
echo "[INFO] OpenClaw container(s) detected. Shared network stays isolated by default; only explicit interconnects should use ${AGENTS_SHARED_NETWORK}."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_br0_hint() {
|
||||||
|
if ! ip link show "$BR0_PARENT" >/dev/null 2>&1; then
|
||||||
|
echo "[WARN] host bridge ${BR0_PARENT} not found"
|
||||||
|
fi
|
||||||
|
if docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
|
||||||
|
echo "[OK] external br0 Docker network available: ${BR0_DOCKER_NETWORK}"
|
||||||
|
else
|
||||||
|
echo "[WARN] external br0 Docker network missing: ${BR0_DOCKER_NETWORK}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_last_known_good() {
|
||||||
|
mkdir -p "$(dirname "$VERSIONS_FILE")"
|
||||||
|
cat > "$VERSIONS_FILE" <<EOV
|
||||||
|
HERMES_IMAGE=${HERMES_IMAGE}
|
||||||
|
HERMES_TAG=${HERMES_TAG}
|
||||||
|
EOV
|
||||||
|
echo "[OK] recorded last known good image ${HERMES_IMAGE}:${HERMES_TAG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_last_known_good() {
|
||||||
|
require_file "$VERSIONS_FILE"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$VERSIONS_FILE"
|
||||||
|
echo "${HERMES_IMAGE}:${HERMES_TAG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
health_wait() {
|
||||||
|
local timeout="${1:-120}"
|
||||||
|
local started
|
||||||
|
started="$(date +%s)"
|
||||||
|
while true; do
|
||||||
|
if curl -fsS "$HEALTHCHECK_URL" >/dev/null 2>&1; then
|
||||||
|
echo "[OK] Hermes healthy on ${HEALTHCHECK_URL}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if (( $(date +%s) - started >= timeout )); then
|
||||||
|
echo "[ERROR] Hermes health check timed out after ${timeout}s" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
set_env_value() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
ensure_env
|
||||||
|
if grep -qE "^${key}=" "$ENV_FILE"; then
|
||||||
|
sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
|
||||||
|
else
|
||||||
|
printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
current_image_ref() {
|
||||||
|
echo "${HERMES_IMAGE}:${HERMES_TAG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_bootstrap() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
ensure_dirs
|
||||||
|
ensure_networks
|
||||||
|
check_ports || true
|
||||||
|
show_openclaw_hint
|
||||||
|
show_br0_hint
|
||||||
|
echo "[OK] bootstrap complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_init_hermes() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
ensure_dirs
|
||||||
|
echo "[INFO] starting Hermes setup wizard"
|
||||||
|
docker run -it --rm \
|
||||||
|
--network "$BR0_DOCKER_NETWORK" \
|
||||||
|
-v "${ROOT_DIR}/state/hermes:/opt/data" \
|
||||||
|
"${HERMES_IMAGE}:${HERMES_TAG}" setup
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_validate() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
ensure_dirs
|
||||||
|
ensure_networks
|
||||||
|
echo "[INFO] validating compose"
|
||||||
|
compose config >/dev/null
|
||||||
|
check_ports || true
|
||||||
|
show_br0_hint
|
||||||
|
echo "[OK] compose config valid"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_up() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
ensure_dirs
|
||||||
|
ensure_networks
|
||||||
|
if ! docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
|
||||||
|
echo "missing external br0 Docker network: ${BR0_DOCKER_NETWORK}" >&2
|
||||||
|
echo "create it with: ./scripts/alanctl net-create-br0" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
check_ports || true
|
||||||
|
local args=(up -d hermes)
|
||||||
|
if [[ "${1:-}" == "--dashboard" ]]; then
|
||||||
|
args=(--profile dashboard up -d hermes dashboard)
|
||||||
|
fi
|
||||||
|
compose "${args[@]}"
|
||||||
|
health_wait 120
|
||||||
|
write_last_known_good
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_down() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
compose down
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_restart() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
compose restart hermes
|
||||||
|
health_wait 120
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
compose ps
|
||||||
|
echo
|
||||||
|
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | awk 'NR==1 || /alan|openclaw/'
|
||||||
|
if [[ -f "$VERSIONS_FILE" ]]; then
|
||||||
|
echo
|
||||||
|
echo "[INFO] last known good: $(read_last_known_good)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_logs() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
compose logs -f --tail=200 "${1:-hermes}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_shell() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
docker exec -it "${PROJECT_NAME}-hermes" bash
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_doctor() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
echo "[INFO] docker"
|
||||||
|
docker version --format '{{.Server.Version}}'
|
||||||
|
echo "[INFO] compose"
|
||||||
|
docker compose version
|
||||||
|
echo "[INFO] current image"
|
||||||
|
echo "$(current_image_ref)"
|
||||||
|
echo "[INFO] config check"
|
||||||
|
compose config >/dev/null && echo "[OK] compose config"
|
||||||
|
echo "[INFO] network"
|
||||||
|
docker network inspect "$AGENTS_SHARED_NETWORK" --format '{{.Name}}' 2>/dev/null || echo "missing: ${AGENTS_SHARED_NETWORK}"
|
||||||
|
docker network inspect "$BR0_DOCKER_NETWORK" --format '{{.Name}}' 2>/dev/null || echo "missing: ${BR0_DOCKER_NETWORK}"
|
||||||
|
echo "[INFO] ports"
|
||||||
|
check_ports || true
|
||||||
|
echo "[INFO] host bridge"
|
||||||
|
ip -brief link show "$BR0_PARENT" 2>/dev/null || echo "missing: ${BR0_PARENT}"
|
||||||
|
echo "[INFO] health"
|
||||||
|
if curl -fsS "$HEALTHCHECK_URL" >/dev/null 2>&1; then
|
||||||
|
echo "[OK] Hermes API health endpoint reachable on ${HERMES_API_PORT}"
|
||||||
|
else
|
||||||
|
echo "[WARN] Hermes API health endpoint not reachable on ${HERMES_API_PORT}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_backup() {
|
||||||
|
ensure_env
|
||||||
|
ensure_dirs
|
||||||
|
local src="${ROOT_DIR}/state/hermes"
|
||||||
|
local dst_dir="${ROOT_DIR}/backups/hermes"
|
||||||
|
local ts
|
||||||
|
ts="$(date +%Y%m%d-%H%M%S)"
|
||||||
|
local archive="${dst_dir}/hermes-${ts}.tar.gz"
|
||||||
|
mkdir -p "$dst_dir"
|
||||||
|
tar -C "$ROOT_DIR/state" -czf "$archive" hermes
|
||||||
|
echo "$archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_restore() {
|
||||||
|
ensure_env
|
||||||
|
ensure_dirs
|
||||||
|
local archive="${1:-}"
|
||||||
|
if [[ -z "$archive" || ! -f "$archive" ]]; then
|
||||||
|
echo "usage: $0 restore /path/to/hermes-backup.tar.gz" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local restore_root="${ROOT_DIR}/state"
|
||||||
|
local current_backup
|
||||||
|
current_backup="$(cmd_backup)"
|
||||||
|
echo "[INFO] current state backed up to ${current_backup}"
|
||||||
|
rm -rf "${restore_root}/hermes"
|
||||||
|
tar -C "$restore_root" -xzf "$archive"
|
||||||
|
echo "[OK] restored ${archive}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_update_hermes() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
ensure_dirs
|
||||||
|
local target_tag="${1:-$HERMES_TAG}"
|
||||||
|
local previous_tag="$HERMES_TAG"
|
||||||
|
local backup_file
|
||||||
|
backup_file="$(cmd_backup)"
|
||||||
|
echo "[INFO] backup created: ${backup_file}"
|
||||||
|
echo "[INFO] updating Hermes from ${HERMES_IMAGE}:${previous_tag} to ${HERMES_IMAGE}:${target_tag}"
|
||||||
|
set_env_value HERMES_TAG "$target_tag"
|
||||||
|
# reload updated .env
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
docker pull "${HERMES_IMAGE}:${target_tag}"
|
||||||
|
compose up -d --force-recreate hermes
|
||||||
|
if health_wait 180; then
|
||||||
|
write_last_known_good
|
||||||
|
echo "[OK] update successful"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "[WARN] update failed health check, rolling back to ${previous_tag}"
|
||||||
|
set_env_value HERMES_TAG "$previous_tag"
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
docker pull "${HERMES_IMAGE}:${previous_tag}" || true
|
||||||
|
compose up -d --force-recreate hermes
|
||||||
|
health_wait 180 || true
|
||||||
|
echo "[WARN] restored previous tag. If state migration caused issues, restore backup: ${backup_file}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_rollback_hermes() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
local target_ref
|
||||||
|
target_ref="$(read_last_known_good)"
|
||||||
|
local target_tag="${target_ref##*:}"
|
||||||
|
echo "[INFO] rolling back to ${target_ref}"
|
||||||
|
set_env_value HERMES_TAG "$target_tag"
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
docker pull "$target_ref"
|
||||||
|
compose up -d --force-recreate hermes
|
||||||
|
health_wait 180
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_pin_hermes() {
|
||||||
|
ensure_env
|
||||||
|
local tag="${1:-}"
|
||||||
|
if [[ -z "$tag" ]]; then
|
||||||
|
echo "usage: $0 pin-hermes <tag>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
set_env_value HERMES_TAG "$tag"
|
||||||
|
echo "[OK] pinned HERMES_TAG=${tag} in .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_net_create_br0() {
|
||||||
|
require_docker
|
||||||
|
ensure_env
|
||||||
|
if [[ -z "$BR0_SUBNET" || -z "$BR0_GATEWAY" || -z "$BR0_IP_RANGE" ]]; then
|
||||||
|
echo "BR0_SUBNET, BR0_GATEWAY and BR0_IP_RANGE must be set in .env" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! ip link show "$BR0_PARENT" >/dev/null 2>&1; then
|
||||||
|
echo "host bridge not found: ${BR0_PARENT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
|
||||||
|
echo "[OK] ${BR0_DOCKER_NETWORK} already exists"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
docker network create -d macvlan \
|
||||||
|
--subnet "$BR0_SUBNET" \
|
||||||
|
--gateway "$BR0_GATEWAY" \
|
||||||
|
--ip-range "$BR0_IP_RANGE" \
|
||||||
|
-o parent="$BR0_PARENT" \
|
||||||
|
"$BR0_DOCKER_NETWORK" >/dev/null
|
||||||
|
echo "[OK] created macvlan network ${BR0_DOCKER_NETWORK} on parent ${BR0_PARENT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_net_create_shim() {
|
||||||
|
if [[ -z "$BR0_HOST_SHIM_CIDR" ]]; then
|
||||||
|
echo "BR0_HOST_SHIM_CIDR must be set in .env" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! docker network inspect "$BR0_DOCKER_NETWORK" >/dev/null 2>&1; then
|
||||||
|
echo "missing Docker network: ${BR0_DOCKER_NETWORK}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sudo ip link add "$BR0_HOST_SHIM_IFACE" link "$BR0_PARENT" type macvlan mode bridge 2>/dev/null || true
|
||||||
|
sudo ip addr replace "$BR0_HOST_SHIM_CIDR" dev "$BR0_HOST_SHIM_IFACE"
|
||||||
|
sudo ip link set "$BR0_HOST_SHIM_IFACE" up
|
||||||
|
if [[ -n "$BR0_IP_RANGE" ]]; then
|
||||||
|
sudo ip route replace "$BR0_IP_RANGE" dev "$BR0_HOST_SHIM_IFACE"
|
||||||
|
fi
|
||||||
|
echo "[OK] host shim ready on ${BR0_HOST_SHIM_IFACE} (${BR0_HOST_SHIM_CIDR})"
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Usage: $(basename "$0") <command>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
bootstrap Prepare directories, .env and Docker networks
|
||||||
|
init-hermes Run Hermes setup wizard interactively
|
||||||
|
validate Validate compose config and port availability
|
||||||
|
up [--dashboard] Start Hermes, optionally with dashboard
|
||||||
|
down Stop the stack
|
||||||
|
restart Restart Hermes
|
||||||
|
status Show compose and related container status
|
||||||
|
logs [service] Tail logs (default: hermes)
|
||||||
|
shell Open a shell inside the Hermes container
|
||||||
|
doctor Health and environment checks
|
||||||
|
backup Create a timestamped backup archive
|
||||||
|
restore <file> Restore Hermes state from backup archive
|
||||||
|
update-hermes [tag] Backup, pull target tag, recreate service, verify health, auto-rollback on failure
|
||||||
|
rollback-hermes Reapply the last known good image tag
|
||||||
|
pin-hermes <tag> Persist a specific Hermes image tag in .env
|
||||||
|
net-create-br0 Create the external macvlan Docker network on top of br0
|
||||||
|
net-create-shim Create optional host-side macvlan shim for host<->container access
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local cmd="${1:-}"
|
||||||
|
shift || true
|
||||||
|
case "$cmd" in
|
||||||
|
bootstrap) cmd_bootstrap "$@" ;;
|
||||||
|
init-hermes) cmd_init_hermes "$@" ;;
|
||||||
|
validate) cmd_validate "$@" ;;
|
||||||
|
up) cmd_up "$@" ;;
|
||||||
|
down) cmd_down "$@" ;;
|
||||||
|
restart) cmd_restart "$@" ;;
|
||||||
|
status) cmd_status "$@" ;;
|
||||||
|
logs) cmd_logs "$@" ;;
|
||||||
|
shell) cmd_shell "$@" ;;
|
||||||
|
doctor) cmd_doctor "$@" ;;
|
||||||
|
backup) cmd_backup "$@" ;;
|
||||||
|
restore) cmd_restore "$@" ;;
|
||||||
|
update-hermes) cmd_update_hermes "$@" ;;
|
||||||
|
rollback-hermes) cmd_rollback_hermes "$@" ;;
|
||||||
|
pin-hermes) cmd_pin_hermes "$@" ;;
|
||||||
|
net-create-br0) cmd_net_create_br0 "$@" ;;
|
||||||
|
net-create-shim) cmd_net_create_shim "$@" ;;
|
||||||
|
*) usage; [[ -n "$cmd" ]] && exit 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
version="${1:-}"
|
||||||
|
release_date="${2:-$(date +%F)}"
|
||||||
|
|
||||||
|
if [[ -z "$version" ]]; then
|
||||||
|
echo "usage: $0 <version-without-v> [YYYY-MM-DD]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "version must look like 0.2.1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "## \[$version\]" CHANGELOG.md; then
|
||||||
|
echo "version already exists in CHANGELOG.md" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - <<PY
|
||||||
|
from pathlib import Path
|
||||||
|
p = Path("CHANGELOG.md")
|
||||||
|
text = p.read_text()
|
||||||
|
needle = "## [Unreleased]\n\n### Added\n- Platzhalter fuer neue Features\n\n### Changed\n- Platzhalter fuer geaenderte Betriebslogik\n\n### Fixed\n- Platzhalter fuer Bugfixes\n\n### Security\n- Platzhalter fuer sicherheitsrelevante Anpassungen\n"
|
||||||
|
if needle not in text:
|
||||||
|
raise SystemExit("Unreleased template not found exactly; update CHANGELOG manually")
|
||||||
|
replacement = needle + f"\n## [{version}] - {release_date}\n\n### Added\n-\n\n### Changed\n-\n\n### Fixed\n-\n\n### Security\n-\n"
|
||||||
|
p.write_text(text.replace(needle, replacement, 1))
|
||||||
|
PY
|
||||||
|
|
||||||
|
echo "[OK] prepared CHANGELOG entry for $version on $release_date"
|
||||||
|
echo "[NEXT] fill the new release section, commit, then tag with: git tag -a v${version} -m 'v${version}'"
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "[ERROR] $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -f README.md ]] || fail "README.md missing"
|
||||||
|
[[ -f CHANGELOG.md ]] || fail "CHANGELOG.md missing"
|
||||||
|
[[ -f compose.yaml ]] || fail "compose.yaml missing"
|
||||||
|
[[ -f .env.example ]] || fail ".env.example missing"
|
||||||
|
[[ -x scripts/alanctl ]] || fail "scripts/alanctl missing or not executable"
|
||||||
|
|
||||||
|
bash -n scripts/alanctl
|
||||||
|
|
||||||
|
if [[ -f scripts/new-release ]]; then
|
||||||
|
bash -n scripts/new-release
|
||||||
|
fi
|
||||||
|
|
||||||
|
grep -q '^## \[Unreleased\]' CHANGELOG.md || fail "CHANGELOG.md needs an [Unreleased] section"
|
||||||
|
grep -q 'HERMES_TAG=' .env.example || fail ".env.example should document HERMES_TAG"
|
||||||
|
grep -q 'BR0_DOCKER_NETWORK=' .env.example || fail ".env.example should document BR0_DOCKER_NETWORK"
|
||||||
|
|
||||||
|
if command -v docker >/dev/null 2>&1; then
|
||||||
|
docker compose -f compose.yaml config >/dev/null
|
||||||
|
else
|
||||||
|
echo "[WARN] docker not found; skipped compose validation"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[OK] release-check passed"
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Alan Hermes Docker Stack
|
||||||
|
After=default.target docker.service
|
||||||
|
Requires=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
WorkingDirectory=%h/src/alan
|
||||||
|
ExecStart=%h/src/alan/scripts/alanctl up
|
||||||
|
ExecStop=%h/src/alan/scripts/alanctl down
|
||||||
|
TimeoutStartSec=180
|
||||||
|
TimeoutStopSec=60
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
Reference in New Issue
Block a user