Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c1f56a60e | ||
|
|
796aeead06 | ||
|
|
4758ee4306 | ||
|
|
eff7d0d98b |
@@ -1,15 +1,20 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(gh auth *)",
|
|
||||||
"PowerShell(gh auth *)",
|
|
||||||
"PowerShell(winget install *)",
|
|
||||||
"Bash(gh repo *)",
|
|
||||||
"Bash(git init *)",
|
"Bash(git init *)",
|
||||||
|
"Bash(git config *)",
|
||||||
"Bash(git remote *)",
|
"Bash(git remote *)",
|
||||||
|
"Bash(git checkout *)",
|
||||||
"Bash(git add *)",
|
"Bash(git add *)",
|
||||||
"Bash(git commit -m 'Initial commit: Clownfischserver v0.5.0 *)",
|
"Bash(git commit -m 'v0.5.0 – Clownfischserver HomeIP Auto-Update: myfritz Automatik für SSH \\(5-Min Cron\\) *)",
|
||||||
"Bash(git config *)"
|
"Bash(git push *)",
|
||||||
|
"Bash(bash -n install.sh)",
|
||||||
|
"Bash(bash -n scripts/clownfischserver-homeip-updater.sh)",
|
||||||
|
"Bash(python3 -m py_compile bot/*.py)",
|
||||||
|
"Bash(awk '{print \"Datei: \" $1 \" Größe: \" $5 \" Bytes\"}')",
|
||||||
|
"Bash(grep -oE '\\\\b\\([0-9]{1,3}\\\\.\\){3}[0-9]{1,3}\\\\b')",
|
||||||
|
"Bash(git commit -m 'Bugfix: Clownfischserver HomeIP Updater – Fehlerbehandlung & Fallbacks *)",
|
||||||
|
"Bash(git commit -m 'Feature: Multi-Distro Firewall-Support für HomeIP Updater *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**Clownfischserver** is a self-hosted, AI-driven Linux server manager controlled exclusively via Telegram. The core principle: admins write naturally, the bot understands, generates commands, creates scripts, manages the server.
|
||||||
|
|
||||||
|
- **Version:** v0.5.0 (in development)
|
||||||
|
- **License:** GPL-3.0
|
||||||
|
- **Status:** Active Development (feature/homeip-auto-update branch)
|
||||||
|
- **Core Dev:** Mehlzoerwer-Claude (Thomas Matthes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture & Key Patterns
|
||||||
|
|
||||||
|
### Core Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Telegram User → bot.py → {ollama_client, shell.py, aider_wrapper} → Server Actions
|
||||||
|
↓
|
||||||
|
Chat-Model (gemma3) + Coder-Model (qwen2.5-coder)
|
||||||
|
Fallback: OpenRouter Free (if Ollama unavailable)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Central Patterns
|
||||||
|
|
||||||
|
**Multi-Distro Abstraction:** The codebase abstracts firewall & package managers:
|
||||||
|
- **install.sh Step 2:** Detects `apt`/`dnf`/`pacman` → installs appropriate packages
|
||||||
|
- **install.sh Step 4:** Installs firewall (`ufw` for apt, `firewalld` for dnf, manual for pacman)
|
||||||
|
- **homeip-updater.sh:** Detects active firewall at runtime (`detect_firewall()` at line 18-26) → adapts rule syntax
|
||||||
|
|
||||||
|
This is critical: **Any Firewall change must support all three backends** (ufw, firewalld, iptables).
|
||||||
|
|
||||||
|
**Dual-Model KI Strategy:**
|
||||||
|
- **Coder-Model (qwen2.5-coder:7b):** `/shell`, `/code`, command generation → requires good programming ability
|
||||||
|
- **Chat-Model (gemma3:4b):** Freetext, explanations, translations → requires good German + multilingual
|
||||||
|
- **Fallback:** OpenRouter free tier if Ollama times out (optional, configured in `.env`)
|
||||||
|
|
||||||
|
**Telegram Safety Patterns:**
|
||||||
|
- `safe_reply()` wraps all bot responses (Markdown fallback for special chars)
|
||||||
|
- Long responses auto-split (Telegram 4096 char limit)
|
||||||
|
- Command confirmation: `/shell` generates → `/ja` confirms → executes (no auto-exec)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure & Responsibilities
|
||||||
|
|
||||||
|
### `bot/` – Core Bot Logic
|
||||||
|
- **bot.py** (700+ lines): Main handler, all commands, auth guard, self-update dialog
|
||||||
|
- **ollama_client.py**: Dual-model manager, OpenRouter fallback, context-window config
|
||||||
|
- **openrouter_client.py**: Cloud fallback (openrouter/free auto-router)
|
||||||
|
- **shell.py**: Command execution (shlex.quote for safety), result capture
|
||||||
|
- **aider_wrapper.py**: Code agent wrapper (--edit-format diff, 600s timeout)
|
||||||
|
- **snapshot.py**: tar.gz backups, workspace tracking, package list snapshots
|
||||||
|
- **boot_notify.py**: Sends Telegram message 120s after server boot (systemd oneshot)
|
||||||
|
- **knock_notify.py**: Alerts on port-knock SSH access attempts
|
||||||
|
|
||||||
|
### `scripts/` – Utility Scripts (NEW in v0.5.0)
|
||||||
|
- **clownfischserver-homeip-updater.sh**: Auto-update firewall rule for home IP (5-min cron)
|
||||||
|
- Detects firewall type (ufw/firewalld/iptables)
|
||||||
|
- Resolves myfritz DNS (dig/nslookup/getent fallbacks)
|
||||||
|
- State-file tracking to skip redundant updates
|
||||||
|
- **Critical:** Must work on Ubuntu/Debian/Fedora/Arch/openSUSE
|
||||||
|
- **README-HOMEIP.md**: Complete homeip-updater documentation (setup, troubleshooting, firewall details)
|
||||||
|
|
||||||
|
### `install.sh` – Multi-Step Installation (900+ lines)
|
||||||
|
8-step installer with full automation:
|
||||||
|
1. **System checks** → RAM, disk, distro detection
|
||||||
|
2. **Packages** → distro-specific (apt/dnf/pacman)
|
||||||
|
3. **User setup** → custom username, passwordless sudo
|
||||||
|
4. **SSH + Firewall** → sshd_config tuning, ufw/firewalld setup, **NEW: myfritz prompt**
|
||||||
|
5. **Ollama** → download, models selection (RAM-aware defaults)
|
||||||
|
6. **Python venv** → bot + aider (separate venvs due to dependency conflicts)
|
||||||
|
7. **Telegram bot** → BotFather token, chat-ID, optional OpenRouter key
|
||||||
|
8. **Systemd** → clownfisch service + boot-notification service, **NEW: cron-job for homeip-updater**
|
||||||
|
|
||||||
|
**Key Pattern:** `install.sh` runs **as root** and manages root-level resources. The homeip-updater cron-job runs as root (for firewall access).
|
||||||
|
|
||||||
|
### Configuration & Data
|
||||||
|
- **config/.env** → All secrets, model choices, paths (generated by install.sh, permissions 0600)
|
||||||
|
- **config/.env.example** → Template (must be kept in sync with install.sh additions)
|
||||||
|
- **config/knockd.conf** → Port-knocking rules (7000→8000→9000)
|
||||||
|
- **systemd/clownfisch.service** → Bot service (User patched dynamically from MGMT_USER)
|
||||||
|
- **systemd/clownfisch-boot.service** → Boot notification (oneshot, 120s delay)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Design Decisions
|
||||||
|
|
||||||
|
### 1. Firewall Abstraction (New in v0.5.0)
|
||||||
|
|
||||||
|
The homeip-updater.sh demonstrates the new **multi-firewall abstraction pattern**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
detect_firewall() {
|
||||||
|
systemctl is-active ufw → use: ufw allow from X port 22
|
||||||
|
systemctl is-active firewalld → use: firewall-cmd rich-rule
|
||||||
|
command -v iptables → use: iptables -I INPUT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this matters:** When adding firewall-dependent features, always implement for all three. This is not optional.
|
||||||
|
|
||||||
|
### 2. No Interactive Commands in /shell
|
||||||
|
|
||||||
|
The `/shell` command uses a "non-interactive only" prompt. Why?
|
||||||
|
- **Cron-friendly:** homeip-updater runs in cron, can't wait for user input
|
||||||
|
- **Safety:** No `nano`, `vim`, `crontab -e` (can't pipe credentials)
|
||||||
|
- **Pattern:** UFW delete needs `echo "y" |` confirmation (line 81 in homeip-updater.sh)
|
||||||
|
|
||||||
|
### 3. .env Parsing Robustness
|
||||||
|
|
||||||
|
Variable loading must handle CRLF (Windows git clones):
|
||||||
|
```bash
|
||||||
|
MYFRITZ_DOMAIN=$(grep "^VAR=" .env | cut -d= -f2 | tr -d '\r\n')
|
||||||
|
```
|
||||||
|
|
||||||
|
This trims trailing newlines/carriage returns. Critical for .env variables used in firewall rules.
|
||||||
|
|
||||||
|
### 4. State File Tracking in homeip-updater
|
||||||
|
|
||||||
|
The `/var/lib/clownfischserver-homeip-state` file prevents redundant UFW updates:
|
||||||
|
- If DNS resolves to same IP → skip rule updates (idempotent)
|
||||||
|
- If IP changed → remove old rule, add new rule
|
||||||
|
- Logging includes old IP vs new IP for audit trail
|
||||||
|
|
||||||
|
This is important because UFW rule deletion prompts interactively (hence the `echo "y" |` workaround).
|
||||||
|
|
||||||
|
### 5. Package Dependency Management
|
||||||
|
|
||||||
|
- **dnsutils** must be in apt install (Step 2) for `dig` command
|
||||||
|
- **firewalld** installed for dnf systems (handled in Step 4)
|
||||||
|
- **iptables** assumed on pacman (Arch) systems
|
||||||
|
- The homeip-updater has DNS fallbacks: dig → nslookup → getent (lines 43-52)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Testing on Multiple Distros
|
||||||
|
|
||||||
|
The project must work on:
|
||||||
|
- **Ubuntu/Debian** (primary, uses ufw)
|
||||||
|
- **Fedora/RHEL** (uses firewalld)
|
||||||
|
- **Arch/Manjaro** (uses iptables/nftables)
|
||||||
|
- **openSUSE** (uses firewalld)
|
||||||
|
|
||||||
|
**Recommended setup (from project notes):**
|
||||||
|
- Pop!_OS on laptop → development & editing
|
||||||
|
- Homlab VMs (Arch, openSUSE) → local testing of install.sh + homeip-updater
|
||||||
|
- VPS Server (Ubuntu) → real-world myfritz testing
|
||||||
|
|
||||||
|
### Bash Script Syntax Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash -n install.sh
|
||||||
|
bash -n scripts/clownfischserver-homeip-updater.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Run before commits. CRLF warnings are harmless (Windows git clones), but scripts must be syntactically valid.
|
||||||
|
|
||||||
|
### Python Syntax Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m py_compile bot/*.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Firewall Rules
|
||||||
|
|
||||||
|
**ufw (Ubuntu/Debian):**
|
||||||
|
```bash
|
||||||
|
ufw status numbered
|
||||||
|
ufw allow from 192.168.1.100 to any port 22 comment "test"
|
||||||
|
echo "y" | ufw delete allow from 192.168.1.100 to any port 22
|
||||||
|
```
|
||||||
|
|
||||||
|
**firewalld (Fedora/openSUSE):**
|
||||||
|
```bash
|
||||||
|
firewall-cmd --list-all
|
||||||
|
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='192.168.1.100' port protocol='tcp' port='22' accept"
|
||||||
|
firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
**iptables (Arch):**
|
||||||
|
```bash
|
||||||
|
iptables -L INPUT -n
|
||||||
|
iptables -I INPUT -s 192.168.1.100 -p tcp --dport 22 -j ACCEPT
|
||||||
|
iptables -D INPUT -s 192.168.1.100 -p tcp --dport 22 -j ACCEPT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Git Workflow & Policies
|
||||||
|
|
||||||
|
### Push Policy
|
||||||
|
|
||||||
|
**Maximum 4 pushes per hour** (one every ~15 minutes). This is documented in `memory/feedback_github_policy.md`.
|
||||||
|
|
||||||
|
**Why:** Reduces GitHub notification spam, allows batching commits logically, leaves review time between updates.
|
||||||
|
|
||||||
|
### Branch Strategy
|
||||||
|
|
||||||
|
- **main:** Stable releases only (v0.4.13, v0.5.0, etc.)
|
||||||
|
- **feature/*** → PR reviewed → merged to main after testing
|
||||||
|
- Current: `feature/homeip-auto-update` (v0.5.0 preparation)
|
||||||
|
|
||||||
|
### Commit Message Style
|
||||||
|
|
||||||
|
```
|
||||||
|
<Type>: <Title> – <Subtitle if complex>
|
||||||
|
|
||||||
|
Details paragraph (optional, multi-line OK)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- `v0.5.0 – Clownfischserver HomeIP Auto-Update: myfritz Automatik für SSH (5-Min Cron)`
|
||||||
|
- `Bugfix: Clownfischserver HomeIP Updater – Fehlerbehandlung & Fallbacks`
|
||||||
|
- `Feature: Multi-Distro Firewall-Support für HomeIP Updater`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Gotchas & Caveats
|
||||||
|
|
||||||
|
### 1. iptables Persistence on Arch
|
||||||
|
|
||||||
|
The homeip-updater uses `iptables -I INPUT` to add rules. On some systems, these don't persist after reboot. **Future enhancement:** integrate with `iptables-persistent` or `nftables` for persistence. Current status: evaluating.
|
||||||
|
|
||||||
|
### 2. Firewall Rule Comments
|
||||||
|
|
||||||
|
UFW and firewalld support rule comments (`comment "..."`) for tracking. **iptables does not.** This makes tracking which rules were added by clownfischserver harder on Arch. Document this if implementing rule deletion on iptables.
|
||||||
|
|
||||||
|
### 3. systemd Service User Patching
|
||||||
|
|
||||||
|
The MGMT_USER (arbitrary username) must be patched into systemd service files during installation. This uses regex: `User=\S+` (line 822 in install.sh). If systemd syntax changes, update regex.
|
||||||
|
|
||||||
|
### 4. Ollama Model Download Brittleness
|
||||||
|
|
||||||
|
`ollama pull` can hang or timeout. The installer includes progress bars but no retry logic. Users on slow/intermittent connections may need manual intervention.
|
||||||
|
|
||||||
|
### 5. .env Variable Escaping
|
||||||
|
|
||||||
|
Variables like `CLOWNFISCHSERVER_HOME_IP_DOMAIN=vorname.myfritz.net` are read with `grep` and `cut`. They must not contain `=` characters. Document constraints in `.env.example`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Patterns to Follow
|
||||||
|
|
||||||
|
### Safe Telegram Replies
|
||||||
|
|
||||||
|
Always use `safe_reply()` wrapper (bot.py). Never call `send_message()` directly.
|
||||||
|
|
||||||
|
### Async Telegram Handlers
|
||||||
|
|
||||||
|
All message handlers must be `async def`. Use `@app.message_handler()` decorator pattern from existing code.
|
||||||
|
|
||||||
|
### Shell Command Safety
|
||||||
|
|
||||||
|
Use `shlex.quote()` for untrusted user input in shell.py. Example:
|
||||||
|
```python
|
||||||
|
cmd = f"rm {shlex.quote(user_path)}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Firewall Rule Matching
|
||||||
|
|
||||||
|
When detecting or modifying firewall rules, match exact patterns to avoid false positives:
|
||||||
|
- UFW: `"22.*ALLOW.*FROM\s+$IP"` (line 79, homeip-updater.sh)
|
||||||
|
- firewalld: exact rule with family + port + protocol
|
||||||
|
- iptables: exact `-s $IP -p tcp --dport 22` match
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Memory & Context Files
|
||||||
|
|
||||||
|
For other Claude instances working on this project:
|
||||||
|
- **project_status_v05.md** – Current v0.5.0 status, tested scenarios, known limitations
|
||||||
|
- **feedback_github_policy.md** – Push frequency policy, batching rules
|
||||||
|
|
||||||
|
These are in `.claude/projects/.../memory/`. They provide context for continued development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key References
|
||||||
|
|
||||||
|
- **CLOWNFISCHSERVER.md** – Detailed project context (architecture, .env variables, roadmap)
|
||||||
|
- **README.md** – User-facing documentation
|
||||||
|
- **scripts/README-HOMEIP.md** – Complete homeip-updater guide
|
||||||
|
- **CONTRIBUTING.md** – Contribution guidelines (already aligned with multi-distro goals)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Syntax check
|
||||||
|
bash -n install.sh
|
||||||
|
python3 -m py_compile bot/*.py
|
||||||
|
|
||||||
|
# Test homeip-updater (dry-run, logs to stdout)
|
||||||
|
bash -x scripts/clownfischserver-homeip-updater.sh
|
||||||
|
|
||||||
|
# Check git status
|
||||||
|
git status
|
||||||
|
git log --oneline -5
|
||||||
|
|
||||||
|
# Build release ZIP (from install.sh docs)
|
||||||
|
cd .. && zip -r v0.5.0-clownfischserver.zip clownfischserver/ -x "*__pycache__*" "*.pyc" ".git/*"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2026-05-01 (v0.5.0 HomeIP Feature)
|
||||||
|
**Next Version:** v0.5.0 (pending VPS/VM testing, then merge to main)
|
||||||
+33
-10
@@ -2,13 +2,7 @@
|
|||||||
|
|
||||||
> Diese Datei ist der zentrale Wissensspeicher für das Clownfischserver-Projekt.
|
> Diese Datei ist der zentrale Wissensspeicher für das Clownfischserver-Projekt.
|
||||||
> Lade sie in jede KI/Chat-Session um sofort produktiv zu sein.
|
> Lade sie in jede KI/Chat-Session um sofort produktiv zu sein.
|
||||||
> Letzte Aktualisierung: v0.5.0 (30.05.2026)
|
> Letzte Aktualisierung: v0.4.13 (18.04.2026)
|
||||||
|
|
||||||
> **⚠️ Bewusst entfernt (nicht wieder einbauen!):** Ein „Vault/MCP-Context"-System
|
|
||||||
> (`_get_vault_context`, `MCP_VAULT_URL`, `MCP_API_KEY`) wurde in v0.5.0 komplett
|
|
||||||
> entfernt. Es war toter Code, nutzte `verify=False` und schickte Chat-Nachrichten an
|
|
||||||
> externe Endpoints – ein klarer DSGVO-Verstoß und Widerspruch zum „local-first"-Prinzip.
|
|
||||||
> Token-Optimierung über Obsidian gehört NICHT in den Bot.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -86,7 +80,7 @@ clownfischserver/
|
|||||||
- **Service:** `clownfisch.service` (Deutsch, mit "ch")
|
- **Service:** `clownfisch.service` (Deutsch, mit "ch")
|
||||||
- **Linux-User:** Beliebig (z.B. `clownfisch`, `clownfish`, `admin`) – wird aus `MGMT_USER` in `.env` gelesen
|
- **Linux-User:** Beliebig (z.B. `clownfisch`, `clownfish`, `admin`) – wird aus `MGMT_USER` in `.env` gelesen
|
||||||
- **Branding:** Immer "Clownfisch" (Deutsch)
|
- **Branding:** Immer "Clownfisch" (Deutsch)
|
||||||
- **ZIP-Benennung:** `v0.5.0-clownfischserver.zip`
|
- **ZIP-Benennung:** `v0.4.13-clownfischserver-opus.zip`
|
||||||
|
|
||||||
### Code-Stil
|
### Code-Stil
|
||||||
- Python 3, keine Type Hints in Funktionssignaturen (außer Optional)
|
- Python 3, keine Type Hints in Funktionssignaturen (außer Optional)
|
||||||
@@ -119,6 +113,35 @@ SNAPSHOT_METHOD=tar
|
|||||||
INSTALL_DIR=/opt/clownfischserver
|
INSTALL_DIR=/opt/clownfischserver
|
||||||
MGMT_USER=clownfisch # Linux-User (beliebig wählbar)
|
MGMT_USER=clownfisch # Linux-User (beliebig wählbar)
|
||||||
AIDER_WORKDIR=/opt/clownfisch-workspace
|
AIDER_WORKDIR=/opt/clownfisch-workspace
|
||||||
|
CLOWNFISCHSERVER_HOME_IP_DOMAIN=vorname.myfritz.net # Optional: myfritz für Auto-SSH-Freigabe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clownfischserver HomeIP Auto (neu in v0.5.0)
|
||||||
|
|
||||||
|
**Feature:** Automatische SSH-Freigabe für deine Home-IP alle 5 Minuten
|
||||||
|
|
||||||
|
- **myfritz.net:** DynDNS-Service (AVM Fritz.Box) – kostenlos
|
||||||
|
- **Cron-Job:** Läuft als `root` alle 5 Min
|
||||||
|
- **Workflow:**
|
||||||
|
1. DNS-Auflösung: `vorname.myfritz.net` → IP-Adresse
|
||||||
|
2. Vergleich: Alte IP vs. neue IP
|
||||||
|
3. Bei Änderung: UFW-Regel aktualisieren
|
||||||
|
4. Logging: `/var/log/clownfischserver-homeip.log`
|
||||||
|
|
||||||
|
**Voraussetzungen:**
|
||||||
|
- myfritz-Account bei https://myfritz.net/ (kostenlos)
|
||||||
|
- `dig` oder `nslookup` für DNS-Auflösung (meist bereits installiert)
|
||||||
|
|
||||||
|
**Beispiel:**
|
||||||
|
```bash
|
||||||
|
# Installation: wird vom install.sh gefragt
|
||||||
|
? myfritz Home-IP permanent erlauben? [j/n]
|
||||||
|
→ j
|
||||||
|
? Deine myfritz Adresse:
|
||||||
|
→ mehlzoerwer.myfritz.net
|
||||||
|
|
||||||
|
# Dann läuft:
|
||||||
|
*/5 * * * * /opt/clownfischserver/scripts/clownfischserver-homeip-updater.sh >> /var/log/clownfischserver-homeip.log 2>&1
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -394,7 +417,7 @@ bash -n install.sh
|
|||||||
### Schritt 2: ZIP erstellen
|
### Schritt 2: ZIP erstellen
|
||||||
```bash
|
```bash
|
||||||
cd .. # Eine Ebene über clownfischserver/
|
cd .. # Eine Ebene über clownfischserver/
|
||||||
zip -r v0.5.0-clownfischserver.zip clownfischserver/ -x "*__pycache__*" "*.pyc"
|
zip -r v0.4.13-clownfischserver-opus.zip clownfischserver/ -x "*__pycache__*" "*.pyc"
|
||||||
```
|
```
|
||||||
|
|
||||||
**WICHTIG:**
|
**WICHTIG:**
|
||||||
@@ -406,7 +429,7 @@ zip -r v0.5.0-clownfischserver.zip clownfischserver/ -x "*__pycache__*" "*.pyc"
|
|||||||
|
|
||||||
### Schritt 3: Verifizieren
|
### Schritt 3: Verifizieren
|
||||||
```bash
|
```bash
|
||||||
unzip -l v0.5.0-clownfischserver.zip | head -30
|
unzip -l v0.4.13-clownfischserver-opus.zip | head -30
|
||||||
# Muss zeigen: clownfischserver/bot/bot.py etc. (20 Dateien)
|
# Muss zeigen: clownfischserver/bot/bot.py etc. (20 Dateien)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -23,15 +23,6 @@ Danke dass du mitmachen willst! Hier sind die Regeln:
|
|||||||
- Kommentare auf Deutsch oder Englisch
|
- Kommentare auf Deutsch oder Englisch
|
||||||
- Header in jeder Datei (siehe bestehende Dateien)
|
- Header in jeder Datei (siehe bestehende Dateien)
|
||||||
|
|
||||||
## Code-Hygiene (wichtig!)
|
|
||||||
|
|
||||||
- **Kein toter Code** – keine Funktionen/Module die definiert aber nie aufgerufen werden
|
|
||||||
- **Keine versteckten Cloud-Calls** – jeder externe Request muss klar ersichtlich und opt-in sein
|
|
||||||
- **Niemals `verify=False`** bei HTTPS-Requests
|
|
||||||
- **Keine User-Eingaben an Dritte** ohne explizites Opt-in (DSGVO by design)
|
|
||||||
- **Klare Conditions** – verschachtelte Ternaries (`A if B else C` innerhalb von `and`-Ketten) vermeiden
|
|
||||||
- Selbst-Review vor jedem Release: macht jede Zeile noch das was sie soll?
|
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
- Bug? → Issue mit Log-Output
|
- Bug? → Issue mit Log-Output
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Ein selbst-hostbarer, KI-gestützter Server-Manager der ausschließlich über Te
|
|||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -56,8 +56,8 @@ Das Ziel: **Server-Administration so einfach wie eine Chat-Nachricht.** Heute ei
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Frisches Ubuntu/Debian – als root:
|
# Frisches Ubuntu/Debian – als root:
|
||||||
wget https://github.com/mehlzoerwer-claude/clownfischserver/archive/refs/heads/main.zip
|
wget https://github.com/mehlzoerwer-claude/clownfischserver/releases/latest/download/clownfischserver.zip
|
||||||
unzip main.zip && cd clownfischserver-main
|
unzip clownfischserver.zip && cd clownfischserver
|
||||||
bash install.sh
|
bash install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ Nach jedem Server-Neustart bekommst du eine Telegram-Nachricht mit Status aller
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
### Fertig (v0.1 – v0.5.0)
|
### Fertig (v0.1 – v0.4.13)
|
||||||
|
|
||||||
- [x] Basis-Installer mit geführtem Setup (8 Schritte)
|
- [x] Basis-Installer mit geführtem Setup (8 Schritte)
|
||||||
- [x] Telegram Bot mit Auth Guard (Single-User)
|
- [x] Telegram Bot mit Auth Guard (Single-User)
|
||||||
@@ -265,16 +265,9 @@ Nach jedem Server-Neustart bekommst du eine Telegram-Nachricht mit Status aller
|
|||||||
- [x] DSGVO-Regel: `.env` nur per SSH, nie über Telegram lesbar
|
- [x] DSGVO-Regel: `.env` nur per SSH, nie über Telegram lesbar
|
||||||
- [x] Projekt-Kontext-Datei (CLOWNFISCHSERVER.md) für KI-gestütztes Pair-Programming
|
- [x] Projekt-Kontext-Datei (CLOWNFISCHSERVER.md) für KI-gestütztes Pair-Programming
|
||||||
|
|
||||||
### v0.5.0 – Cleanup & Stabilität (aktuelles Release)
|
### v0.5.0 – Geplant
|
||||||
|
|
||||||
**Bugfixes & Aufräumarbeiten (dieser Release):**
|
**Sicherheit & DSGVO:**
|
||||||
- [x] Totes Vault/MCP-Context-System komplett entfernt (war nie verdrahtet, `verify=False`-Lücke)
|
|
||||||
- [x] Privacy-Fix: Chat-Nachrichten gingen via Vault-Suche an externe Endpoints → entfernt
|
|
||||||
- [x] `/code` Filepath-Logik: fragiler Ternary-Ausdruck sauber umgeschrieben (kein Crash bei leerem Task)
|
|
||||||
- [x] Aider-Model-Default vereinheitlicht (zieht sich `OLLAMA_MODEL` aus der Umgebung)
|
|
||||||
- [x] Irreführende „Token-Optimization"-Logs (Anthropic-Kosten/Obsidian) aus dem Start-Log entfernt
|
|
||||||
|
|
||||||
**Sicherheit & DSGVO (geplant):**
|
|
||||||
- [ ] Sensitive Dateien über Telegram blockieren (`.env`, `shadow`, Keys)
|
- [ ] Sensitive Dateien über Telegram blockieren (`.env`, `shadow`, Keys)
|
||||||
- [ ] Output-Sanitizer: API Keys/Tokens in jeder Bot-Antwort maskieren
|
- [ ] Output-Sanitizer: API Keys/Tokens in jeder Bot-Antwort maskieren
|
||||||
- [ ] Log-Sanitizer: Keine Secrets im Audit-Log
|
- [ ] Log-Sanitizer: Keine Secrets im Audit-Log
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ logger = logging.getLogger(__name__)
|
|||||||
INSTALL_DIR = os.getenv("INSTALL_DIR", "/opt/clownfischserver")
|
INSTALL_DIR = os.getenv("INSTALL_DIR", "/opt/clownfischserver")
|
||||||
AIDER_WORKDIR = os.getenv("AIDER_WORKDIR", "/opt/clownfisch-workspace")
|
AIDER_WORKDIR = os.getenv("AIDER_WORKDIR", "/opt/clownfisch-workspace")
|
||||||
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
||||||
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "qwen2.5-coder:7b")
|
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "qwen2.5:2b")
|
||||||
TIMEOUT = 600 # 10 minutes for aider tasks on CPU
|
TIMEOUT = 600 # 10 minutes for aider tasks on CPU
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-19
@@ -98,7 +98,7 @@ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
fallback_info = "\n✅ OpenRouter Fallback aktiv"
|
fallback_info = "\n✅ OpenRouter Fallback aktiv"
|
||||||
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
f"🐠 *Clownfischserver v0.5.0 online.*{fallback_info}\n\n"
|
f"🐠 *Clownfischserver v0.4.13 online.*{fallback_info}\n\n"
|
||||||
"Schreib mir einfach – ich antworte als Chat.\n\n"
|
"Schreib mir einfach – ich antworte als Chat.\n\n"
|
||||||
"*Befehle:*\n"
|
"*Befehle:*\n"
|
||||||
"• `/shell <beschreibung>` – KI generiert Befehl, du bestätigst\n"
|
"• `/shell <beschreibung>` – KI generiert Befehl, du bestätigst\n"
|
||||||
@@ -367,16 +367,7 @@ async def cmd_code(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
workspace = os.getenv("AIDER_WORKDIR", "/opt/clownfisch-workspace")
|
workspace = os.getenv("AIDER_WORKDIR", "/opt/clownfisch-workspace")
|
||||||
task_lower = task.lower()
|
task_lower = task.lower()
|
||||||
|
|
||||||
# Only auto-generate a filepath if the task doesn't already point at one.
|
if workspace not in task and "/opt/" not in task and "/" not in task.split()[0] if task.split() else True:
|
||||||
words = task.split()
|
|
||||||
first_word_is_path = bool(words) and "/" in words[0]
|
|
||||||
needs_filepath = (
|
|
||||||
workspace not in task
|
|
||||||
and "/opt/" not in task
|
|
||||||
and not first_word_is_path
|
|
||||||
)
|
|
||||||
|
|
||||||
if needs_filepath:
|
|
||||||
file_match = re.search(r'(\S+\.(sh|py|js|ts|html|css|yaml|yml|json|conf|cfg|txt))', task)
|
file_match = re.search(r'(\S+\.(sh|py|js|ts|html|css|yaml|yml|json|conf|cfg|txt))', task)
|
||||||
if file_match:
|
if file_match:
|
||||||
filename = file_match.group(1)
|
filename = file_match.group(1)
|
||||||
@@ -688,7 +679,7 @@ async def _handle_update_dialog(update, context, user_input, step):
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logger.info("🐠 Clownfischserver v0.5.0 Bot startet...")
|
logger.info("🐠 Clownfischserver v0.4.13 Bot startet...")
|
||||||
|
|
||||||
import requests as req
|
import requests as req
|
||||||
import time
|
import time
|
||||||
@@ -758,14 +749,7 @@ def main():
|
|||||||
app.add_handler(MessageHandler(filters.Document.ALL, handle_file))
|
app.add_handler(MessageHandler(filters.Document.ALL, handle_file))
|
||||||
|
|
||||||
logger.info(f"Bot läuft. Authorized Chat-ID: {CHAT_ID}")
|
logger.info(f"Bot läuft. Authorized Chat-ID: {CHAT_ID}")
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(app.initialize())
|
|
||||||
app.run_polling(allowed_updates=Update.ALL_TYPES)
|
app.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||||
finally:
|
|
||||||
loop.run_until_complete(app.shutdown())
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class OllamaClient:
|
|||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
if "Timeout" not in str(e) and "nicht erreichbar" not in str(e):
|
if "Timeout" not in str(e) and "nicht erreichbar" not in str(e):
|
||||||
raise
|
raise
|
||||||
logger.warning("Ollama nicht verfügbar für /shell – trying OpenRouter fallback")
|
logger.warning(f"Ollama nicht verfügbar für /shell – trying OpenRouter fallback")
|
||||||
|
|
||||||
# 2. Fallback to OpenRouter
|
# 2. Fallback to OpenRouter
|
||||||
if self.openrouter and self.openrouter.is_available():
|
if self.openrouter and self.openrouter.is_available():
|
||||||
|
|||||||
@@ -103,8 +103,6 @@ class OpenRouterClient:
|
|||||||
"""Generate shell command via OpenRouter."""
|
"""Generate shell command via OpenRouter."""
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
system = "Server: Ubuntu 24.04, Clownfischserver setup."
|
|
||||||
|
|
||||||
prompt = (
|
prompt = (
|
||||||
"You are a Linux bash command generator for Ubuntu 24.04.\n"
|
"You are a Linux bash command generator for Ubuntu 24.04.\n"
|
||||||
"Convert the request into a single non-interactive bash command.\n"
|
"Convert the request into a single non-interactive bash command.\n"
|
||||||
@@ -118,7 +116,6 @@ class OpenRouterClient:
|
|||||||
None,
|
None,
|
||||||
lambda: self._chat_sync(
|
lambda: self._chat_sync(
|
||||||
messages=[{"role": "user", "content": prompt}],
|
messages=[{"role": "user", "content": prompt}],
|
||||||
system=system,
|
|
||||||
model=FREE_MODELS["coder"],
|
model=FREE_MODELS["coder"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-4
@@ -20,11 +20,12 @@ OLLAMA_NUM_CTX=16384
|
|||||||
SNAPSHOT_DIR=/opt/clownfisch-snapshots
|
SNAPSHOT_DIR=/opt/clownfisch-snapshots
|
||||||
SNAPSHOT_METHOD=tar
|
SNAPSHOT_METHOD=tar
|
||||||
|
|
||||||
# HomeIP Auto-Update (optional – myfritz.net Dynamic DNS)
|
|
||||||
# Domain für automatische Firewall-Regel-Updates bei IP-Wechsel
|
|
||||||
# CLOWNFISCHSERVER_HOME_IP_DOMAIN=vorname.myfritz.net
|
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
INSTALL_DIR=/opt/clownfischserver
|
INSTALL_DIR=/opt/clownfischserver
|
||||||
MGMT_USER=clownfish
|
MGMT_USER=clownfish
|
||||||
AIDER_WORKDIR=/opt/clownfisch-workspace
|
AIDER_WORKDIR=/opt/clownfisch-workspace
|
||||||
|
|
||||||
|
# Clownfischserver HomeIP Automatik (optional)
|
||||||
|
# myfritz Adresse für automatische SSH-Freigabe von Zuhause
|
||||||
|
# Falls aktiviert: Cron-Job prüft alle 5 Min und aktualisiert ufw-Regeln
|
||||||
|
# CLOWNFISCHSERVER_HOME_IP_DOMAIN=vorname.myfritz.net
|
||||||
|
|||||||
+64
-60
@@ -29,13 +29,8 @@ MGMT_USER=""
|
|||||||
PKG_MANAGER=""
|
PKG_MANAGER=""
|
||||||
DISTRO=""
|
DISTRO=""
|
||||||
OLLAMA_MODEL=""
|
OLLAMA_MODEL=""
|
||||||
OLLAMA_MODEL_FAST=""
|
|
||||||
SNAPSHOT_METHOD="tar"
|
SNAPSHOT_METHOD="tar"
|
||||||
OLLAMA_NUM_CTX=4096
|
OLLAMA_NUM_CTX=4096
|
||||||
MYFRITZ_DOMAIN=""
|
|
||||||
BOT_TOKEN=""
|
|
||||||
CHAT_ID=""
|
|
||||||
OPENROUTER_API_KEY=""
|
|
||||||
|
|
||||||
# HELPERS
|
# HELPERS
|
||||||
|
|
||||||
@@ -110,7 +105,7 @@ print_banner() {
|
|||||||
echo -e "${CYAN}${BOLD}"
|
echo -e "${CYAN}${BOLD}"
|
||||||
echo " ╔══════════════════════════════════════════════════════╗"
|
echo " ╔══════════════════════════════════════════════════════╗"
|
||||||
echo " ║ 🐠 CLOWNFISCHSERVER INSTALLER ║"
|
echo " ║ 🐠 CLOWNFISCHSERVER INSTALLER ║"
|
||||||
echo " ║ v0.5.0 | GPL-3.0 ║"
|
echo " ║ v0.4.13 | GPL-3.0 ║"
|
||||||
echo " ╚══════════════════════════════════════════════════════╝"
|
echo " ╚══════════════════════════════════════════════════════╝"
|
||||||
echo -e "${NC}"
|
echo -e "${NC}"
|
||||||
echo -e " Willkommen! Dieses Script richtet deinen"
|
echo -e " Willkommen! Dieses Script richtet deinen"
|
||||||
@@ -173,7 +168,7 @@ step_packages() {
|
|||||||
case "$PKG_MANAGER" in
|
case "$PKG_MANAGER" in
|
||||||
apt)
|
apt)
|
||||||
run_with_spinner "Paketliste aktualisieren" apt-get update -qq || fail "apt update fehlgeschlagen"
|
run_with_spinner "Paketliste aktualisieren" apt-get update -qq || fail "apt update fehlgeschlagen"
|
||||||
run_with_spinner "Pakete installieren" apt-get install -y -qq curl wget git ufw python3 python3-pip python3-venv sudo htop net-tools lsof rsync openssl knockd || warn "Einige Pakete konnten nicht installiert werden – siehe Log"
|
run_with_spinner "Pakete installieren" apt-get install -y -qq curl wget git ufw python3 python3-pip python3-venv sudo htop net-tools lsof rsync openssl knockd dnsutils || warn "Einige Pakete konnten nicht installiert werden – siehe Log"
|
||||||
;;
|
;;
|
||||||
dnf)
|
dnf)
|
||||||
run_with_spinner "Pakete aktualisieren" dnf update -y -q
|
run_with_spinner "Pakete aktualisieren" dnf update -y -q
|
||||||
@@ -274,6 +269,33 @@ step_ssh() {
|
|||||||
ok "SSH läuft – Passwort + Key erlaubt"
|
ok "SSH läuft – Passwort + Key erlaubt"
|
||||||
info "SSH wird erst gesperrt wenn du im Bot /ssh close sendest"
|
info "SSH wird erst gesperrt wenn du im Bot /ssh close sendest"
|
||||||
|
|
||||||
|
# Home IP Auto-Allow via myfritz
|
||||||
|
CLOWNFISCHSERVER_HOME_IP_DOMAIN=""
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e " ${BOLD} myfritz Home-IP Automatik${NC}"
|
||||||
|
echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e " SSH von Zuhause immer offen – ohne Port-Knocking."
|
||||||
|
echo -e " ${CYAN} Clownfischserver aktualisiert deine Home-IP alle 5 Min automatisch.${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if ask_yn "myfritz Home-IP permanent erlauben?"; then
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}?${NC} ${BOLD}Deine myfritz Adresse:${NC}"
|
||||||
|
echo -e " ${CYAN} Format: vorname.myfritz.net${NC}"
|
||||||
|
read -r -p " → " CLOWNFISCHSERVER_HOME_IP_DOMAIN
|
||||||
|
|
||||||
|
if [[ -n "$CLOWNFISCHSERVER_HOME_IP_DOMAIN" ]]; then
|
||||||
|
ok "myfritz Adresse gespeichert: $CLOWNFISCHSERVER_HOME_IP_DOMAIN"
|
||||||
|
info "⏱ Clownfischserver-HomeIP Updater wird alle 5 Min gestartet"
|
||||||
|
else
|
||||||
|
warn "Leere Adresse – myfritz Automatik deaktiviert"
|
||||||
|
CLOWNFISCHSERVER_HOME_IP_DOMAIN=""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "myfritz Automatik deaktiviert"
|
||||||
|
fi
|
||||||
|
|
||||||
# Firewall
|
# Firewall
|
||||||
case "$PKG_MANAGER" in
|
case "$PKG_MANAGER" in
|
||||||
apt)
|
apt)
|
||||||
@@ -340,34 +362,6 @@ KNOCKEOF
|
|||||||
warn "Bitte Firewall manuell einrichten (iptables/nftables)"
|
warn "Bitte Firewall manuell einrichten (iptables/nftables)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo -e " ${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
||||||
echo -e " ${BOLD} myfritz – Home-IP Auto-Update (optional)${NC}"
|
|
||||||
echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
||||||
echo -e " Wenn du ein AVM-Gerät (Fritzbox) mit myfritz.net hast,"
|
|
||||||
echo -e " kann der Bot deine Home-IP automatisch ins Firewall-Regelwerk"
|
|
||||||
echo -e " schreiben. Das bedeutet: SSH bleibt immer erreichbar, auch"
|
|
||||||
echo -e " wenn sich deine IP ändert (z.B. ISP, mobil, VPN)."
|
|
||||||
echo ""
|
|
||||||
echo -e " ${YELLOW}?${NC} ${BOLD}Hast du ein AVM myfritz.net Gerät?${NC} [j/n]"
|
|
||||||
read -r -p " -> " USE_MYFRITZ
|
|
||||||
|
|
||||||
if [[ "$USE_MYFRITZ" =~ ^[jJ]$ ]]; then
|
|
||||||
echo ""
|
|
||||||
echo -e " ${YELLOW}?${NC} ${BOLD}Deine myfritz-Domain:${NC}"
|
|
||||||
echo -e " ${CYAN} Format: vorname.myfritz.net${NC}"
|
|
||||||
read -r -p " -> " MYFRITZ_DOMAIN
|
|
||||||
if [[ -z "$MYFRITZ_DOMAIN" ]]; then
|
|
||||||
MYFRITZ_DOMAIN=""
|
|
||||||
warn "myfritz übersprungen"
|
|
||||||
else
|
|
||||||
ok "myfritz Domain: $MYFRITZ_DOMAIN"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
MYFRITZ_DOMAIN=""
|
|
||||||
info "myfritz übersprungen"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# HELPER: Pull an Ollama model with progress bar
|
# HELPER: Pull an Ollama model with progress bar
|
||||||
@@ -714,7 +708,7 @@ SNAPSHOT_METHOD=$SNAPSHOT_METHOD
|
|||||||
INSTALL_DIR=$INSTALL_DIR
|
INSTALL_DIR=$INSTALL_DIR
|
||||||
MGMT_USER=$MGMT_USER
|
MGMT_USER=$MGMT_USER
|
||||||
AIDER_WORKDIR=$WORKSPACE_DIR
|
AIDER_WORKDIR=$WORKSPACE_DIR
|
||||||
CLOWNFISCHSERVER_HOME_IP_DOMAIN=$MYFRITZ_DOMAIN
|
CLOWNFISCHSERVER_HOME_IP_DOMAIN=$CLOWNFISCHSERVER_HOME_IP_DOMAIN
|
||||||
ENVEOF
|
ENVEOF
|
||||||
|
|
||||||
chmod 600 "$INSTALL_DIR/config/.env"
|
chmod 600 "$INSTALL_DIR/config/.env"
|
||||||
@@ -730,13 +724,6 @@ step_service() {
|
|||||||
cp -r "$SCRIPT_DIR/bot/"* "$INSTALL_DIR/bot/" || fail "Bot-Dateien nicht gefunden – ist der bot/ Ordner dabei?"
|
cp -r "$SCRIPT_DIR/bot/"* "$INSTALL_DIR/bot/" || fail "Bot-Dateien nicht gefunden – ist der bot/ Ordner dabei?"
|
||||||
ok "Bot-Dateien kopiert"
|
ok "Bot-Dateien kopiert"
|
||||||
|
|
||||||
mkdir -p "$INSTALL_DIR/scripts"
|
|
||||||
if [ -d "$SCRIPT_DIR/scripts" ]; then
|
|
||||||
cp -r "$SCRIPT_DIR/scripts/"* "$INSTALL_DIR/scripts/" 2>/dev/null || true
|
|
||||||
chmod +x "$INSTALL_DIR/scripts/"*.sh 2>/dev/null || true
|
|
||||||
ok "Scripts kopiert"
|
|
||||||
fi
|
|
||||||
|
|
||||||
chown -R "$MGMT_USER:$MGMT_USER" "$INSTALL_DIR" "$SNAPSHOT_DIR" "$WORKSPACE_DIR"
|
chown -R "$MGMT_USER:$MGMT_USER" "$INSTALL_DIR" "$SNAPSHOT_DIR" "$WORKSPACE_DIR"
|
||||||
chmod -R u+w "$INSTALL_DIR/bot"
|
chmod -R u+w "$INSTALL_DIR/bot"
|
||||||
ok "Verzeichnis-Rechte gesetzt"
|
ok "Verzeichnis-Rechte gesetzt"
|
||||||
@@ -804,24 +791,27 @@ BOOTEOF
|
|||||||
run_with_spinner "Boot-Notification aktivieren" bash -c "systemctl daemon-reload && systemctl enable clownfisch-boot"
|
run_with_spinner "Boot-Notification aktivieren" bash -c "systemctl daemon-reload && systemctl enable clownfisch-boot"
|
||||||
ok "Boot-Notification: Telegram-Meldung 2 Min nach Serverstart"
|
ok "Boot-Notification: Telegram-Meldung 2 Min nach Serverstart"
|
||||||
|
|
||||||
# HomeIP Updater – falls myfritz konfiguriert
|
# Clownfischserver HomeIP Updater (wenn aktiviert)
|
||||||
if [[ -n "$MYFRITZ_DOMAIN" ]]; then
|
if [[ -n "$CLOWNFISCHSERVER_HOME_IP_DOMAIN" ]]; then
|
||||||
echo ""
|
mkdir -p "$INSTALL_DIR/scripts"
|
||||||
info "Installiere HomeIP Auto-Updater (5-Min Cron)..."
|
cp "$SCRIPT_DIR/scripts/clownfischserver-homeip-updater.sh" "$INSTALL_DIR/scripts/" 2>/dev/null || \
|
||||||
|
warn "homeip-updater.sh nicht gefunden – manuell nachträglich einrichten"
|
||||||
|
|
||||||
# Copy the homeip-updater script if it exists
|
if [ -f "$INSTALL_DIR/scripts/clownfischserver-homeip-updater.sh" ]; then
|
||||||
if [ -f "$SCRIPT_DIR/scripts/clownfischserver-homeip-updater.sh" ]; then
|
chmod +x "$INSTALL_DIR/scripts/clownfischserver-homeip-updater.sh"
|
||||||
mkdir -p /opt/clownfischserver-scripts
|
chown root:root "$INSTALL_DIR/scripts/clownfischserver-homeip-updater.sh"
|
||||||
cp "$SCRIPT_DIR/scripts/clownfischserver-homeip-updater.sh" /opt/clownfischserver/scripts/
|
|
||||||
chmod +x /opt/clownfischserver/scripts/clownfischserver-homeip-updater.sh
|
|
||||||
|
|
||||||
# Add cron job (every 5 minutes)
|
# Cron-Job für root (da ufw sudo braucht)
|
||||||
CRON_JOB="*/5 * * * * /opt/clownfischserver/scripts/clownfischserver-homeip-updater.sh >> /var/log/clownfischserver-homeip-updater.log 2>&1"
|
CRON_JOB="*/5 * * * * $INSTALL_DIR/scripts/clownfischserver-homeip-updater.sh >> /var/log/clownfischserver-homeip.log 2>&1"
|
||||||
(crontab -l 2>/dev/null | grep -v "clownfischserver-homeip-updater" ; echo "$CRON_JOB") | crontab - 2>/dev/null || true
|
|
||||||
|
|
||||||
ok "HomeIP Auto-Updater installiert (myfritz: $MYFRITZ_DOMAIN)"
|
# Bestehende clownfischserver Cron-Jobs entfernen (install.sh läuft als root)
|
||||||
else
|
crontab -l 2>/dev/null | grep -v clownfischserver-homeip-updater | crontab - 2>/dev/null || true
|
||||||
warn "homeip-updater Script nicht gefunden – übersprungen"
|
|
||||||
|
# Neuen Job hinzufügen
|
||||||
|
(crontab -l 2>/dev/null; echo "$CRON_JOB") | crontab - >> "$LOG_FILE" 2>&1
|
||||||
|
|
||||||
|
ok "Cron-Job aktiviert: alle 5 Min HomeIP prüfen"
|
||||||
|
info "Log: /var/log/clownfischserver-homeip.log"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -833,13 +823,20 @@ print_summary() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}${BOLD}"
|
echo -e "${GREEN}${BOLD}"
|
||||||
echo " +======================================================+"
|
echo " +======================================================+"
|
||||||
echo " | CLOWNFISCHSERVER BEREIT! v0.5.0 |"
|
echo " | CLOWNFISCHSERVER BEREIT! v0.4.13 |"
|
||||||
echo " +======================================================+"
|
echo " +======================================================+"
|
||||||
echo -e "${NC}"
|
echo -e "${NC}"
|
||||||
echo -e " ${BOLD}Server:${NC} $SERVER_IP"
|
echo -e " ${BOLD}Server:${NC} $SERVER_IP"
|
||||||
echo -e " ${BOLD}SSH User:${NC} $MGMT_USER (Passwort aktiv!)"
|
echo -e " ${BOLD}SSH User:${NC} $MGMT_USER (Passwort aktiv!)"
|
||||||
echo -e " ${BOLD}KI-Modell:${NC} $OLLAMA_MODEL"
|
echo -e " ${BOLD}KI-Modell:${NC} $OLLAMA_MODEL"
|
||||||
echo -e " ${BOLD}Snapshots:${NC} $SNAPSHOT_DIR ($SNAPSHOT_METHOD)"
|
echo -e " ${BOLD}Snapshots:${NC} $SNAPSHOT_DIR ($SNAPSHOT_METHOD)"
|
||||||
|
|
||||||
|
if [[ -n "$CLOWNFISCHSERVER_HOME_IP_DOMAIN" ]]; then
|
||||||
|
echo -e " ${BOLD}${GREEN}HomeIP Auto:${NC} $CLOWNFISCHSERVER_HOME_IP_DOMAIN (alle 5 Min aktualisiert)"
|
||||||
|
else
|
||||||
|
echo -e " ${BOLD}HomeIP Auto:${NC} nicht aktiviert"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e " ${BOLD}Logs:${NC} journalctl -u clownfisch -f"
|
echo -e " ${BOLD}Logs:${NC} journalctl -u clownfisch -f"
|
||||||
echo -e " ${BOLD}Install-Log:${NC} $LOG_FILE"
|
echo -e " ${BOLD}Install-Log:${NC} $LOG_FILE"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -854,6 +851,13 @@ print_summary() {
|
|||||||
echo -e " ${CYAN} /ssh open 1.2.3.4 -> SSH nur fuer deine IP${NC}"
|
echo -e " ${CYAN} /ssh open 1.2.3.4 -> SSH nur fuer deine IP${NC}"
|
||||||
echo -e " ${CYAN} /ssh close -> SSH sperren${NC}"
|
echo -e " ${CYAN} /ssh close -> SSH sperren${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
if [[ -n "$CLOWNFISCHSERVER_HOME_IP_DOMAIN" ]]; then
|
||||||
|
echo -e " ${GREEN}${BOLD}🐠 Clownfischserver-HomeIP Status:${NC}"
|
||||||
|
echo -e " ${GREEN} Cron-Job aktiv: /var/log/clownfischserver-homeip.log${NC}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e " ${YELLOW}Install-Log (letzte 10 Zeilen):${NC}"
|
echo -e " ${YELLOW}Install-Log (letzte 10 Zeilen):${NC}"
|
||||||
tail -10 "$LOG_FILE"
|
tail -10 "$LOG_FILE"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -924,7 +928,7 @@ main() {
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "=== Clownfischserver v0.5.0 Installation gestartet ==="
|
log "=== Clownfischserver v0.4.13 Installation gestartet ==="
|
||||||
step_checks
|
step_checks
|
||||||
step_packages
|
step_packages
|
||||||
step_user
|
step_user
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# 🐠 Clownfischserver HomeIP Auto-Update
|
||||||
|
|
||||||
|
Automatische SSH-Freigabe für deine Home-IP – ohne Port-Knocking.
|
||||||
|
|
||||||
|
**Multi-Distro Support:** ✅ Ubuntu/Debian (ufw) | ✅ Fedora/RHEL (firewalld) | ✅ Arch/Manjaro (iptables) | ✅ openSUSE (firewalld)
|
||||||
|
|
||||||
|
## Wie es funktioniert
|
||||||
|
|
||||||
|
1. **Installation:** `install.sh` fragt nach deiner myfritz-Adresse
|
||||||
|
2. **Cron-Job:** Läuft alle 5 Minuten (root crontab)
|
||||||
|
3. **DNS-Auflösung:** Löst `vorname.myfritz.net` auf
|
||||||
|
4. **UFW-Update:** Aktualisiert die Firewall-Regel wenn IP sich ändert
|
||||||
|
|
||||||
|
## Multi-Distro Firewall-Support
|
||||||
|
|
||||||
|
| Distribution | Firewall | Support |
|
||||||
|
|--------------|----------|---------|
|
||||||
|
| Ubuntu/Debian | `ufw` | ✅ Vollständig |
|
||||||
|
| Fedora/RHEL/CentOS | `firewalld` | ✅ Vollständig |
|
||||||
|
| Arch/Manjaro | `iptables` | ✅ Vollständig |
|
||||||
|
| openSUSE | `firewalld` | ✅ Vollständig |
|
||||||
|
| Alpine | `ufw`/`iptables` | ✅ Fallback |
|
||||||
|
|
||||||
|
**Automatische Erkennung:** Das Script erkennt die aktive Firewall zur Laufzeit und nutzt die entsprechenden Kommandos.
|
||||||
|
|
||||||
|
## Setup während Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
[4/8] SSH + Firewall
|
||||||
|
|
||||||
|
? myfritz Home-IP permanent erlauben? [j/n]
|
||||||
|
→ j
|
||||||
|
|
||||||
|
? Deine myfritz Adresse:
|
||||||
|
Format: vorname.myfritz.net
|
||||||
|
→ mehlzoerwer.myfritz.net
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Script wird dann automatisch:
|
||||||
|
- In `/opt/clownfischserver/scripts/` installiert
|
||||||
|
- Executable gemacht
|
||||||
|
- In `root`-Crontab als 5-Minuten-Job eingetragen
|
||||||
|
- **Firewall automatisch erkannt** (ufw/firewalld/iptables)
|
||||||
|
|
||||||
|
## Manuelle Verwaltung
|
||||||
|
|
||||||
|
### Script manuell ausführen
|
||||||
|
```bash
|
||||||
|
sudo /opt/clownfischserver/scripts/clownfischserver-homeip-updater.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cron-Job prüfen
|
||||||
|
```bash
|
||||||
|
sudo crontab -l | grep clownfischserver-homeip
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs anschauen
|
||||||
|
```bash
|
||||||
|
tail -f /var/log/clownfischserver-homeip.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manuell aktivieren/deaktivieren
|
||||||
|
|
||||||
|
**Aktivieren** (wenn nicht aktiviert während Installation):
|
||||||
|
```bash
|
||||||
|
# myfritz-Adresse in .env eintragen
|
||||||
|
sudo nano /opt/clownfischserver/config/.env
|
||||||
|
# CLOWNFISCHSERVER_HOME_IP_DOMAIN=vorname.myfritz.net
|
||||||
|
|
||||||
|
# Cron-Job hinzufügen
|
||||||
|
(sudo crontab -l 2>/dev/null; echo "*/5 * * * * /opt/clownfischserver/scripts/clownfischserver-homeip-updater.sh >> /var/log/clownfischserver-homeip.log 2>&1") | sudo crontab -
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deaktivieren:**
|
||||||
|
```bash
|
||||||
|
# Aus .env entfernen
|
||||||
|
sudo nano /opt/clownfischserver/config/.env
|
||||||
|
# CLOWNFISCHSERVER_HOME_IP_DOMAIN= (leer lassen)
|
||||||
|
|
||||||
|
# Cron-Job entfernen
|
||||||
|
sudo crontab -l 2>/dev/null | grep -v clownfischserver-homeip | sudo crontab -
|
||||||
|
|
||||||
|
# UFW-Regeln cleanup (optional)
|
||||||
|
sudo ufw status | grep "clownfischserver-homeip"
|
||||||
|
sudo ufw delete allow from [IP] to any port 22
|
||||||
|
```
|
||||||
|
|
||||||
|
## Was die myfritz-Adresse ist
|
||||||
|
|
||||||
|
**myfritz** ist ein DynDNS-Service der AVM (Fritz.Box Hersteller):
|
||||||
|
- `https://myfritz.net/`
|
||||||
|
- Deine Home-IP bekommst darin immer einen stabilen Hostnamen
|
||||||
|
- Auch wenn deine DSL-IP sich ändert, bleibt `vorname.myfritz.net` gleich
|
||||||
|
- ✅ Das Script prüft alle 5 Min und aktualisiert die UFW-Regel automatisch
|
||||||
|
|
||||||
|
## Sicherheit
|
||||||
|
|
||||||
|
- **UFW-Kommentar:** `clownfischserver-homeip-vorname.myfritz.net`
|
||||||
|
- Zeigt in `ufw status` an woher die Regel kommt
|
||||||
|
|
||||||
|
- **Logging:**
|
||||||
|
- `/var/log/clownfischserver-homeip.log` – vollständiger Audit-Trail
|
||||||
|
- Timestamp + alte IP + neue IP + Aktion
|
||||||
|
|
||||||
|
- **State-File:**
|
||||||
|
- `/var/lib/clownfischserver-homeip-state` – speichert die letzte bekannte IP
|
||||||
|
- Verhindert unnötige UFW-Updates bei gleicher IP
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Cron-Job läuft nicht
|
||||||
|
```bash
|
||||||
|
# Status prüfen
|
||||||
|
sudo crontab -l | grep clownfischserver-homeip
|
||||||
|
|
||||||
|
# Logs prüfen
|
||||||
|
sudo tail -20 /var/log/clownfischserver-homeip.log
|
||||||
|
|
||||||
|
# Manuell testen
|
||||||
|
sudo /opt/clownfischserver/scripts/clownfischserver-homeip-updater.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### DNS-Auflösung schlägt fehl
|
||||||
|
```bash
|
||||||
|
# myfritz-Adresse testen
|
||||||
|
dig vorname.myfritz.net
|
||||||
|
|
||||||
|
# Oder mit nslookup
|
||||||
|
nslookup vorname.myfritz.net
|
||||||
|
|
||||||
|
# In .env prüfen
|
||||||
|
grep CLOWNFISCHSERVER_HOME_IP /opt/clownfischserver/config/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
### UFW-Regel wird nicht hinzugefügt
|
||||||
|
```bash
|
||||||
|
# UFW Status prüfen
|
||||||
|
sudo ufw status
|
||||||
|
|
||||||
|
# Manuelle Regel hinzufügen (Debugging)
|
||||||
|
sudo ufw allow from 1.2.3.4 to any port 22 comment "clownfischserver-homeip-test"
|
||||||
|
|
||||||
|
# Logs prüfen
|
||||||
|
sudo journalctl -u ufw -n 20
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technische Details – Firewall-Kommandos
|
||||||
|
|
||||||
|
### ufw (Ubuntu/Debian)
|
||||||
|
```bash
|
||||||
|
ufw allow from 192.168.1.100 to any port 22 comment "clownfischserver-homeip-vorname.myfritz.net"
|
||||||
|
ufw delete allow from 192.168.1.100 to any port 22
|
||||||
|
```
|
||||||
|
|
||||||
|
### firewalld (Fedora/openSUSE)
|
||||||
|
```bash
|
||||||
|
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='192.168.1.100' port protocol='tcp' port='22' accept"
|
||||||
|
firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### iptables (Arch/Manjaro)
|
||||||
|
```bash
|
||||||
|
iptables -I INPUT -s 192.168.1.100 -p tcp --dport 22 -j ACCEPT
|
||||||
|
iptables -D INPUT -s 192.168.1.100 -p tcp --dport 22 -j ACCEPT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Script-Info:** Clownfischserver v0.5.0+ | GPL-3.0 | Multi-Distro Support
|
||||||
@@ -1,174 +1,168 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 🐠 CLOWNFISCHSERVER – HomeIP Auto-Update
|
# 🐠 Clownfischserver HomeIP Updater
|
||||||
# Purpose: Auto-update firewall rule for dynamic home IP (myfritz.net)
|
# Version: v0.5.0
|
||||||
# Runs: Every 5 minutes (via cron)
|
# Purpose: Automatisch die Home-IP aktualisieren (alle 5 Min via Cron)
|
||||||
|
# Multi-Distro: ufw (Ubuntu/Debian), firewalld (Fedora/openSUSE), iptables (Arch)
|
||||||
# Author: Mehlzoerwer-Claude
|
# Author: Mehlzoerwer-Claude
|
||||||
# License: GPL-3.0
|
# License: GPL-3.0
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
set -e
|
# Konfiguration
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
INSTALL_DIR="/opt/clownfischserver"
|
INSTALL_DIR="/opt/clownfischserver"
|
||||||
ENV_FILE="$INSTALL_DIR/config/.env"
|
ENV_FILE="$INSTALL_DIR/config/.env"
|
||||||
STATE_FILE="/var/lib/clownfischserver-homeip-state"
|
STATE_FILE="/var/lib/clownfischserver-homeip-state"
|
||||||
LOG_FILE="/var/log/clownfischserver-homeip-updater.log"
|
LOG_FILE="/var/log/clownfischserver-homeip.log"
|
||||||
SSH_PORT=22
|
|
||||||
|
|
||||||
# --- Helpers ---
|
# Logging
|
||||||
log() {
|
log() {
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Load .env ---
|
log_error() {
|
||||||
load_env() {
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >> "$LOG_FILE"
|
||||||
if [ ! -f "$ENV_FILE" ]; then
|
|
||||||
log "ERROR: .env not found at $ENV_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Load and trim CRLF/LF
|
|
||||||
MYFRITZ_DOMAIN=$(grep "^CLOWNFISCHSERVER_HOME_IP_DOMAIN=" "$ENV_FILE" | cut -d= -f2 | tr -d '\r\n')
|
|
||||||
if [ -z "$MYFRITZ_DOMAIN" ]; then
|
|
||||||
log "ERROR: CLOWNFISCHSERVER_HOME_IP_DOMAIN not set in .env"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Detect Firewall ---
|
# Erkenne welche Firewall aktiv ist
|
||||||
detect_firewall() {
|
detect_firewall() {
|
||||||
if systemctl is-active --quiet ufw; then
|
if systemctl is-active --quiet ufw 2>/dev/null; then
|
||||||
echo "ufw"
|
echo "ufw"
|
||||||
elif systemctl is-active --quiet firewalld; then
|
elif systemctl is-active --quiet firewalld 2>/dev/null; then
|
||||||
echo "firewalld"
|
echo "firewalld"
|
||||||
elif command -v iptables &>/dev/null; then
|
elif command -v iptables &>/dev/null; then
|
||||||
echo "iptables"
|
echo "iptables"
|
||||||
else
|
else
|
||||||
log "ERROR: No firewall detected (ufw/firewalld/iptables)"
|
echo "unknown"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
FIREWALL=$(detect_firewall)
|
||||||
|
log "Firewall erkannt: $FIREWALL"
|
||||||
|
|
||||||
|
# Prüfe ob .env existiert
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
log_error ".env nicht gefunden: $ENV_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Erstelle Log-File wenn nicht existiert
|
||||||
|
touch "$LOG_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Lade myfritz-Adresse aus .env (Zeilenumbrüche entfernen)
|
||||||
|
MYFRITZ_DOMAIN=$(grep "^CLOWNFISCHSERVER_HOME_IP_DOMAIN=" "$ENV_FILE" | cut -d= -f2 | tr -d '\r\n')
|
||||||
|
|
||||||
|
if [[ -z "$MYFRITZ_DOMAIN" ]]; then
|
||||||
|
log "Clownfischserver HomeIP: Deaktiviert (keine myfritz-Adresse)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Versuche IP aufzulösen (mit Fallback)
|
||||||
|
if command -v dig &>/dev/null; then
|
||||||
|
NEW_IP=$(dig +short "$MYFRITZ_DOMAIN" A 2>/dev/null | tail -1 | grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b')
|
||||||
|
elif command -v nslookup &>/dev/null; then
|
||||||
|
NEW_IP=$(nslookup "$MYFRITZ_DOMAIN" 2>/dev/null | grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' | tail -1)
|
||||||
|
elif command -v getent &>/dev/null; then
|
||||||
|
NEW_IP=$(getent hosts "$MYFRITZ_DOMAIN" 2>/dev/null | awk '{print $1}')
|
||||||
|
else
|
||||||
|
log_error "Kein DNS-Tool verfügbar (dig/nslookup/getent)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$NEW_IP" ]]; then
|
||||||
|
log_error "DNS-Auflösung fehlgeschlagen: $MYFRITZ_DOMAIN"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Lese alte IP aus State-File
|
||||||
|
OLD_IP=""
|
||||||
|
if [[ -f "$STATE_FILE" ]]; then
|
||||||
|
OLD_IP=$(cat "$STATE_FILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Domain: $MYFRITZ_DOMAIN | Neue IP: $NEW_IP | Alte IP: ${OLD_IP:-keine}"
|
||||||
|
|
||||||
|
# Wenn IP sich nicht geändert hat → nichts tun
|
||||||
|
if [[ "$NEW_IP" == "$OLD_IP" ]] && [[ -n "$OLD_IP" ]]; then
|
||||||
|
log "IP unverändert – keine Aktion nötig"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# IP hat sich geändert oder ist neue Installation
|
||||||
|
log "IP-Änderung erkannt! Aktualisiere Firewall-Regeln..."
|
||||||
|
|
||||||
|
# Firewall-spezifische Funktionen
|
||||||
|
remove_ufw_rule() {
|
||||||
|
if ufw status numbered | grep -qE "22.*ALLOW.*FROM\s+$1"; then
|
||||||
|
echo "y" | ufw delete allow from "$1" to any port 22 >> "$LOG_FILE" 2>&1
|
||||||
|
log "UFW: Alte Regel entfernt: allow from $1 to port 22"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
add_ufw_rule() {
|
||||||
|
ufw allow from "$1" to any port 22 comment "clownfischserver-homeip-$MYFRITZ_DOMAIN" >> "$LOG_FILE" 2>&1
|
||||||
|
log "UFW: Neue Regel hinzugefügt: allow from $1 to port 22"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_firewalld_rule() {
|
||||||
|
firewall-cmd --permanent --remove-rich-rule="rule family='ipv4' source address='$1' port protocol='tcp' port='22' accept" >> "$LOG_FILE" 2>&1
|
||||||
|
log "firewalld: Alte Regel entfernt: source address $1 port 22"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_firewalld_rule() {
|
||||||
|
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='$1' port protocol='tcp' port='22' accept" >> "$LOG_FILE" 2>&1
|
||||||
|
firewall-cmd --reload >> "$LOG_FILE" 2>&1
|
||||||
|
log "firewalld: Neue Regel hinzugefügt: source address $1 port 22"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_iptables_rule() {
|
||||||
|
iptables -D INPUT -s "$1" -p tcp --dport 22 -j ACCEPT 2>> "$LOG_FILE" || true
|
||||||
|
log "iptables: Alte Regel entfernt: source $1 port 22"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_iptables_rule() {
|
||||||
|
iptables -I INPUT -s "$1" -p tcp --dport 22 -j ACCEPT >> "$LOG_FILE" 2>&1
|
||||||
|
log "iptables: Neue Regel hinzugefügt: source $1 port 22"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Entferne alte Regel falls vorhanden
|
||||||
|
if [[ -n "$OLD_IP" ]]; then
|
||||||
|
case "$FIREWALL" in
|
||||||
|
ufw) remove_ufw_rule "$OLD_IP" ;;
|
||||||
|
firewalld) remove_firewalld_rule "$OLD_IP" ;;
|
||||||
|
iptables) remove_iptables_rule "$OLD_IP" ;;
|
||||||
|
*) log_error "Unbekannte Firewall: $FIREWALL" && exit 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Füge neue Regel hinzu
|
||||||
|
case "$FIREWALL" in
|
||||||
|
ufw)
|
||||||
|
if ! add_ufw_rule "$NEW_IP"; then
|
||||||
|
log_error "UFW: Regel konnte nicht hinzugefügt werden"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
;;
|
||||||
|
firewalld)
|
||||||
# --- Resolve DNS with fallbacks ---
|
if ! add_firewalld_rule "$NEW_IP"; then
|
||||||
resolve_ip() {
|
log_error "firewalld: Regel konnte nicht hinzugefügt werden"
|
||||||
local domain="$1"
|
exit 1
|
||||||
|
|
||||||
# Try dig first
|
|
||||||
if command -v dig &>/dev/null; then
|
|
||||||
IP=$(dig +short "$domain" A | grep -oE '^[0-9.]+$' | head -1)
|
|
||||||
[ -n "$IP" ] && echo "$IP" && return 0
|
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
# Try nslookup
|
iptables)
|
||||||
if command -v nslookup &>/dev/null; then
|
if ! add_iptables_rule "$NEW_IP"; then
|
||||||
IP=$(nslookup "$domain" 2>/dev/null | grep "Address:" | tail -1 | awk '{print $2}' | grep -oE '^[0-9.]+$')
|
log_error "iptables: Regel konnte nicht hinzugefügt werden"
|
||||||
[ -n "$IP" ] && echo "$IP" && return 0
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unbekannte Firewall: $FIREWALL"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Try getent
|
# Speichere neue IP
|
||||||
if command -v getent &>/dev/null; then
|
echo "$NEW_IP" > "$STATE_FILE"
|
||||||
IP=$(getent hosts "$domain" | awk '{print $1}' | grep -oE '^[0-9.]+$' | head -1)
|
chmod 644 "$STATE_FILE"
|
||||||
[ -n "$IP" ] && echo "$IP" && return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "ERROR: Could not resolve $domain"
|
log "✓ HomeIP Update erfolgreich: $MYFRITZ_DOMAIN → $NEW_IP ($FIREWALL)"
|
||||||
return 1
|
exit 0
|
||||||
}
|
|
||||||
|
|
||||||
# --- Load previous state ---
|
|
||||||
load_state() {
|
|
||||||
if [ -f "$STATE_FILE" ]; then
|
|
||||||
PREVIOUS_IP=$(cat "$STATE_FILE" | grep "^IP=" | cut -d= -f2)
|
|
||||||
echo "$PREVIOUS_IP"
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Save state ---
|
|
||||||
save_state() {
|
|
||||||
local ip="$1"
|
|
||||||
echo "IP=$ip" > "$STATE_FILE"
|
|
||||||
chmod 600 "$STATE_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- UFW Functions ---
|
|
||||||
ufw_add_rule() {
|
|
||||||
local ip="$1"
|
|
||||||
ufw allow from "$ip" to any port "$SSH_PORT" comment "clownfisch-homeip" >/dev/null 2>&1
|
|
||||||
log "UFW: Added rule for $ip:$SSH_PORT"
|
|
||||||
}
|
|
||||||
|
|
||||||
ufw_remove_rule() {
|
|
||||||
local ip="$1"
|
|
||||||
echo "y" | ufw delete allow from "$ip" to any port "$SSH_PORT" >/dev/null 2>&1
|
|
||||||
log "UFW: Deleted rule for $ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Firewalld Functions ---
|
|
||||||
firewalld_add_rule() {
|
|
||||||
local ip="$1"
|
|
||||||
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='$ip' port protocol='tcp' port='$SSH_PORT' accept" >/dev/null 2>&1
|
|
||||||
firewall-cmd --reload >/dev/null 2>&1
|
|
||||||
log "firewalld: Added rule for $ip:$SSH_PORT"
|
|
||||||
}
|
|
||||||
|
|
||||||
firewalld_remove_rule() {
|
|
||||||
local ip="$1"
|
|
||||||
firewall-cmd --permanent --remove-rich-rule="rule family='ipv4' source address='$ip' port protocol='tcp' port='$SSH_PORT' accept" >/dev/null 2>&1
|
|
||||||
firewall-cmd --reload >/dev/null 2>&1
|
|
||||||
log "firewalld: Deleted rule for $ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- iptables Functions ---
|
|
||||||
iptables_add_rule() {
|
|
||||||
local ip="$1"
|
|
||||||
iptables -I INPUT -s "$ip" -p tcp --dport "$SSH_PORT" -j ACCEPT
|
|
||||||
log "iptables: Added rule for $ip:$SSH_PORT"
|
|
||||||
}
|
|
||||||
|
|
||||||
iptables_remove_rule() {
|
|
||||||
local ip="$1"
|
|
||||||
iptables -D INPUT -s "$ip" -p tcp --dport "$SSH_PORT" -j ACCEPT 2>/dev/null || true
|
|
||||||
log "iptables: Deleted rule for $ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Main ---
|
|
||||||
main() {
|
|
||||||
log "=== HomeIP Auto-Update started ==="
|
|
||||||
|
|
||||||
load_env
|
|
||||||
FIREWALL=$(detect_firewall)
|
|
||||||
NEW_IP=$(resolve_ip "$MYFRITZ_DOMAIN") || exit 1
|
|
||||||
PREVIOUS_IP=$(load_state)
|
|
||||||
|
|
||||||
if [ "$NEW_IP" = "$PREVIOUS_IP" ]; then
|
|
||||||
log "No change: $NEW_IP (same as previous)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "IP change detected: $PREVIOUS_IP → $NEW_IP (firewall: $FIREWALL)"
|
|
||||||
|
|
||||||
# Remove old rule
|
|
||||||
if [ -n "$PREVIOUS_IP" ]; then
|
|
||||||
case "$FIREWALL" in
|
|
||||||
ufw) ufw_remove_rule "$PREVIOUS_IP" ;;
|
|
||||||
firewalld) firewalld_remove_rule "$PREVIOUS_IP" ;;
|
|
||||||
iptables) iptables_remove_rule "$PREVIOUS_IP" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add new rule
|
|
||||||
case "$FIREWALL" in
|
|
||||||
ufw) ufw_add_rule "$NEW_IP" ;;
|
|
||||||
firewalld) firewalld_add_rule "$NEW_IP" ;;
|
|
||||||
iptables) iptables_add_rule "$NEW_IP" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Save state
|
|
||||||
save_state "$NEW_IP"
|
|
||||||
|
|
||||||
log "=== HomeIP Auto-Update completed successfully ==="
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user