Initial commit: Python dotfiles system
This commit is contained in:
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Temporäre Dateien
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# System-Dateien
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Backup-Dateien
|
||||||
|
*.backup.*
|
||||||
|
|
||||||
|
# Lokale Konfiguration (optional)
|
||||||
|
local.yaml
|
||||||
|
|
||||||
|
# Persönliche Transcrypt-Schlüssel (sollten nicht ins Repo)
|
||||||
|
.transcrypt_key
|
||||||
271
README.md
Normal file
271
README.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# Dotfiles - Multi-Machine Configuration Management
|
||||||
|
|
||||||
|
Ein **einfaches** und sicheres System zur Verwaltung von Konfigurationsdateien (Dotfiles) über mehrere Rechner hinweg. Nach dem **KISS-Prinzip**: Keep It Simple, Stupid.
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- 🖥️ **Multi-Machine Support**: Separate Konfigurationen für Desktop, Laptop, Server
|
||||||
|
- 🔗 **Symlink-basiert**: Keine Kopien, direkte Verknüpfungen zu deinen Configs
|
||||||
|
- 🔐 **Sichere Verschlüsselung**: Sensible Daten mit transcrypt verschlüsselt
|
||||||
|
- ⚙️ **Konfigurationsgesteuert**: Alles wird über `config.yaml` definiert
|
||||||
|
- 🚀 **Ein Skript für alles**: Nur noch ein einziges `dotfiles` Kommando
|
||||||
|
- 🎯 **Automatische Erkennung**: Maschinenerkennung ohne manuelle Konfiguration
|
||||||
|
|
||||||
|
## 📂 Struktur
|
||||||
|
|
||||||
|
```
|
||||||
|
configs/
|
||||||
|
├── machines/ # Maschinenspezifische Konfigurationen
|
||||||
|
│ ├── desktop/ # Desktop-Computer Configs
|
||||||
|
│ ├── laptop/ # Laptop/Notebook Configs
|
||||||
|
│ └── server/ # Server-System Configs
|
||||||
|
├── shared/ # Geteilte Konfigurationen
|
||||||
|
│ ├── shell/ # Shell-Configs (bash, zsh, etc.)
|
||||||
|
│ ├── vim/ # Vim-Konfiguration
|
||||||
|
│ ├── git/ # Git-Konfiguration
|
||||||
|
│ └── ssh/ # SSH-Konfiguration (öffentlich)
|
||||||
|
├── sensitive/ # Verschlüsselte sensible Daten
|
||||||
|
├── dotfiles # ⭐ DAS zentrale Management-Skript
|
||||||
|
├── config.yaml # ⚙️ Zentrale Konfiguration (definiert alles!)
|
||||||
|
└── README.md # Diese Dokumentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Schnellstart
|
||||||
|
|
||||||
|
### 1. Repository klonen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <your-dotfiles-repo-url> ~/.dotfiles
|
||||||
|
cd ~/.dotfiles
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Dotfiles installieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dotfiles install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Aktuelle Configs sichern
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dotfiles push
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Verschlüsselung einrichten (optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dotfiles setup-transcrypt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Alle Befehle
|
||||||
|
|
||||||
|
### Installation und Management
|
||||||
|
```bash
|
||||||
|
./dotfiles install # Installiere alle Dotfiles (Symlinks)
|
||||||
|
./dotfiles push # Sammle und committe aktuelle Configs
|
||||||
|
./dotfiles push --push # Sammle, committe und push zu Remote
|
||||||
|
./dotfiles list # Zeige installierte Konfigurationen
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verschlüsselung
|
||||||
|
```bash
|
||||||
|
./dotfiles setup-transcrypt # Interaktive Transcrypt-Einrichtung
|
||||||
|
./dotfiles setup-transcrypt --auto-password # Mit automatischem Passwort
|
||||||
|
./dotfiles setup-transcrypt --force # Erneut konfigurieren
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hilfe
|
||||||
|
```bash
|
||||||
|
./dotfiles help # Zeige alle verfügbaren Befehle
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Konfiguration über config.yaml
|
||||||
|
|
||||||
|
**Das Herzstück**: Alle Symlinks, Push-Quellen und Einstellungen werden in `config.yaml` definiert!
|
||||||
|
|
||||||
|
### Neue Konfiguration hinzufügen
|
||||||
|
|
||||||
|
Bearbeite `config.yaml` und füge hinzu:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# In der symlinks -> shared Sektion:
|
||||||
|
- src: "shared/tmux/tmux.conf"
|
||||||
|
dest: "~/.tmux.conf"
|
||||||
|
description: "Tmux-Konfiguration"
|
||||||
|
|
||||||
|
# In der push_sources -> shared Sektion:
|
||||||
|
- src: "~/.tmux.conf"
|
||||||
|
dest: "shared/tmux/tmux.conf"
|
||||||
|
description: "Tmux-Konfiguration"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Maschinenspezifische Configs
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Für Desktop-spezifische Konfigurationen:
|
||||||
|
- src: "machines/{machine}/i3_config"
|
||||||
|
dest: "~/.config/i3/config"
|
||||||
|
description: "i3-Konfiguration"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vordefinierte Konfigurationen
|
||||||
|
|
||||||
|
`config.yaml` enthält bereits Definitionen für:
|
||||||
|
- tmux
|
||||||
|
- neovim
|
||||||
|
- vscode
|
||||||
|
- i3
|
||||||
|
|
||||||
|
## 🔐 Sichere Daten
|
||||||
|
|
||||||
|
### Automatische Verschlüsselung
|
||||||
|
|
||||||
|
Alle Dateien in folgenden Orten werden automatisch verschlüsselt:
|
||||||
|
- `sensitive/` - Allgemeine sensible Daten
|
||||||
|
- `machines/*/secrets/` - Maschinenspezifische Geheimnisse
|
||||||
|
- Dateien mit Namen wie `*secret*`, `*.key`, `*.pem`
|
||||||
|
- SSH private keys (`id_rsa`, `id_ed25519`, etc.)
|
||||||
|
|
||||||
|
### Sensible Dateien hinzufügen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Einfach in die entsprechenden Verzeichnisse kopieren
|
||||||
|
cp ~/.ssh/id_rsa sensitive/ssh/id_rsa # Wird automatisch verschlüsselt
|
||||||
|
cp ~/.env sensitive/env/myproject.env # Wird automatisch verschlüsselt
|
||||||
|
echo "API_KEY=secret123" > sensitive/api_keys.txt # Wird automatisch verschlüsselt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Konfigurationen hinzufügen/entfernen
|
||||||
|
|
||||||
|
### Neue Konfiguration hinzufügen
|
||||||
|
|
||||||
|
1. **Datei manuell kopieren**:
|
||||||
|
```bash
|
||||||
|
mkdir -p shared/tmux
|
||||||
|
cp ~/.tmux.conf shared/tmux/tmux.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **config.yaml erweitern**:
|
||||||
|
- Symlink-Definition zu `symlinks -> shared` hinzufügen
|
||||||
|
- Push-Definition zu `push_sources -> shared` hinzufügen
|
||||||
|
|
||||||
|
3. **Testen und sichern**:
|
||||||
|
```bash
|
||||||
|
./dotfiles install # Test der neuen Symlinks
|
||||||
|
./dotfiles push # Änderungen sichern
|
||||||
|
```
|
||||||
|
|
||||||
|
### Konfiguration entfernen
|
||||||
|
|
||||||
|
1. **Aus config.yaml entfernen** (entsprechende Zeilen löschen)
|
||||||
|
2. **Symlink entfernen**: `rm ~/.tmux.conf`
|
||||||
|
3. **Aus Repository entfernen**: `rm -rf shared/tmux/`
|
||||||
|
4. **Backup wiederherstellen** (falls vorhanden): `mv ~/.tmux.conf.backup.* ~/.tmux.conf`
|
||||||
|
|
||||||
|
## 📚 Beispiel-Workflows
|
||||||
|
|
||||||
|
### Neuer Computer einrichten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Repository klonen
|
||||||
|
git clone git@github.com:username/dotfiles.git ~/.dotfiles
|
||||||
|
cd ~/.dotfiles
|
||||||
|
|
||||||
|
# 2. Transcrypt entschlüsseln (falls vorhanden)
|
||||||
|
transcrypt -y
|
||||||
|
|
||||||
|
# 3. Dotfiles installieren
|
||||||
|
./dotfiles install
|
||||||
|
|
||||||
|
# 4. Shell neu starten
|
||||||
|
exec $SHELL
|
||||||
|
```
|
||||||
|
|
||||||
|
### Konfiguration ändern und teilen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Lokale Änderungen machen
|
||||||
|
vim ~/.vimrc
|
||||||
|
|
||||||
|
# 2. Ins Repository sichern und pushen
|
||||||
|
./dotfiles push --push
|
||||||
|
|
||||||
|
# 3. Auf anderen Maschinen updaten
|
||||||
|
git pull
|
||||||
|
./dotfiles install # Falls neue Dateien hinzugekommen sind
|
||||||
|
```
|
||||||
|
|
||||||
|
### Neue Anwendung hinzufügen (Beispiel: Tmux)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Konfiguration ins Repository kopieren
|
||||||
|
mkdir -p shared/tmux
|
||||||
|
cp ~/.tmux.conf shared/tmux/tmux.conf
|
||||||
|
|
||||||
|
# 2. config.yaml bearbeiten (Definitionen hinzufügen)
|
||||||
|
vim config.yaml
|
||||||
|
|
||||||
|
# 3. Testen
|
||||||
|
./dotfiles install
|
||||||
|
|
||||||
|
# 4. Sichern
|
||||||
|
./dotfiles push
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Troubleshooting
|
||||||
|
|
||||||
|
### Problem: "YAML-Parser-Fehler"
|
||||||
|
```bash
|
||||||
|
# Prüfe YAML-Syntax
|
||||||
|
python3 -c "import yaml; yaml.safe_load(open('config.yaml'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: "Symlink funktioniert nicht"
|
||||||
|
```bash
|
||||||
|
# Prüfe Symlink-Ziel
|
||||||
|
readlink ~/.vimrc
|
||||||
|
|
||||||
|
# Liste installierte Symlinks
|
||||||
|
./dotfiles list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: "Transcrypt-Fehler"
|
||||||
|
```bash
|
||||||
|
# Status prüfen
|
||||||
|
transcrypt --display
|
||||||
|
|
||||||
|
# Neu konfigurieren
|
||||||
|
./dotfiles setup-transcrypt --force
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ Wichtige Hinweise
|
||||||
|
|
||||||
|
1. **config.yaml ist der Schlüssel**: Alle Änderungen werden dort definiert
|
||||||
|
2. **Backup**: Existierende Dateien werden automatisch gesichert
|
||||||
|
3. **KISS-Prinzip**: Ein Skript, eine Konfigurationsdatei - einfach!
|
||||||
|
4. **Transcrypt-Passwort**: Sicher aufbewahren!
|
||||||
|
|
||||||
|
## 🎯 Migration von alten Dotfiles-Systemen
|
||||||
|
|
||||||
|
Falls du von einem anderen Dotfiles-System migrierst:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Aktuelle Symlinks entfernen
|
||||||
|
find ~ -maxdepth 2 -type l -delete
|
||||||
|
|
||||||
|
# 2. Konfigurationen ins neue System kopieren
|
||||||
|
cp ~/.vimrc shared/vim/vimrc
|
||||||
|
cp ~/.gitconfig shared/git/gitconfig
|
||||||
|
# ... weitere Dateien
|
||||||
|
|
||||||
|
# 3. config.yaml entsprechend anpassen
|
||||||
|
|
||||||
|
# 4. Neu installieren
|
||||||
|
./dotfiles install
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Das war's!** Ein Skript, eine Konfigurationsdatei - dotfiles management kann so einfach sein! 🚀
|
||||||
|
|
||||||
|
Nach dem **KISS-Prinzip**: Keep It Simple, Stupid. ✨
|
||||||
134
config.yaml
Normal file
134
config.yaml
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
backup:
|
||||||
|
enabled: true
|
||||||
|
format: .backup.{timestamp}
|
||||||
|
keep_backups: 5
|
||||||
|
current_machine: laptop
|
||||||
|
encryption:
|
||||||
|
enabled: false
|
||||||
|
patterns:
|
||||||
|
- sensitive/**
|
||||||
|
- machines/*/secrets/**
|
||||||
|
- '**/*_secret*'
|
||||||
|
- '**/*secret*'
|
||||||
|
- '**/*.key'
|
||||||
|
- '**/*.pem'
|
||||||
|
- '**/*.p12'
|
||||||
|
- '**/*.pfx'
|
||||||
|
- '**/passwords.*'
|
||||||
|
- '**/credentials.*'
|
||||||
|
- '**/.env'
|
||||||
|
- '**/.env.*'
|
||||||
|
- '**/authinfo'
|
||||||
|
- '**/netrc'
|
||||||
|
- '**/*_rsa'
|
||||||
|
- '**/*_ed25519'
|
||||||
|
- '**/*_ecdsa'
|
||||||
|
- '**/id_rsa'
|
||||||
|
- '**/id_ed25519'
|
||||||
|
- '**/id_ecdsa'
|
||||||
|
git:
|
||||||
|
auto_commit: true
|
||||||
|
commit_message_template: Update dotfiles from {machine} - {timestamp}
|
||||||
|
excluded_files:
|
||||||
|
- '*.log'
|
||||||
|
- '*.tmp'
|
||||||
|
- .DS_Store
|
||||||
|
- Thumbs.db
|
||||||
|
- '*.backup.*'
|
||||||
|
- __pycache__/
|
||||||
|
- '*.pyc'
|
||||||
|
machine_variables:
|
||||||
|
desktop:
|
||||||
|
font_size: 12
|
||||||
|
terminal: gnome-terminal
|
||||||
|
window_manager: i3
|
||||||
|
laptop:
|
||||||
|
font_size: 10
|
||||||
|
power_management: true
|
||||||
|
terminal: alacritty
|
||||||
|
window_manager: i3
|
||||||
|
server:
|
||||||
|
font_size: 8
|
||||||
|
headless: true
|
||||||
|
terminal: tmux
|
||||||
|
machines:
|
||||||
|
desktop:
|
||||||
|
description: Desktop-Computer
|
||||||
|
hostname_patterns:
|
||||||
|
- desktop
|
||||||
|
- pc
|
||||||
|
- workstation
|
||||||
|
os_patterns:
|
||||||
|
- Linux
|
||||||
|
- Windows
|
||||||
|
laptop:
|
||||||
|
description: Laptop/Notebook
|
||||||
|
hostname_patterns:
|
||||||
|
- laptop
|
||||||
|
- notebook
|
||||||
|
- mobile
|
||||||
|
- p14s
|
||||||
|
os_patterns:
|
||||||
|
- Linux
|
||||||
|
- Darwin
|
||||||
|
- Windows
|
||||||
|
server:
|
||||||
|
description: Server-System
|
||||||
|
hostname_patterns:
|
||||||
|
- server
|
||||||
|
- srv
|
||||||
|
- vps
|
||||||
|
os_patterns:
|
||||||
|
- Linux
|
||||||
|
sync:
|
||||||
|
create_missing_dirs: true
|
||||||
|
dry_run: false
|
||||||
|
force_overwrite: false
|
||||||
|
interactive: true
|
||||||
|
templates:
|
||||||
|
git:
|
||||||
|
- description: Git-Konfiguration
|
||||||
|
destination: ~/.gitconfig
|
||||||
|
template: git/gitconfig.j2
|
||||||
|
- description: Git-Global-Ignore
|
||||||
|
destination: ~/.gitignore_global
|
||||||
|
template: git/gitignore_global
|
||||||
|
i3:
|
||||||
|
- description: i3-Konfiguration
|
||||||
|
destination: ~/.config/i3/config
|
||||||
|
machine_specific: true
|
||||||
|
template: i3/config.{machine}.j2
|
||||||
|
nvim:
|
||||||
|
- description: Neovim-Konfiguration
|
||||||
|
destination: ~/.config/nvim/init.lua
|
||||||
|
template: nvim/init.lua.j2
|
||||||
|
shell:
|
||||||
|
- description: Bash-Konfiguration
|
||||||
|
destination: ~/.bashrc
|
||||||
|
template: shell/bashrc.j2
|
||||||
|
- description: Zsh-Konfiguration
|
||||||
|
destination: ~/.zshrc
|
||||||
|
template: shell/zshrc.j2
|
||||||
|
- description: Shell-Profile
|
||||||
|
destination: ~/.profile
|
||||||
|
template: shell/profile.j2
|
||||||
|
ssh:
|
||||||
|
- description: SSH-Konfiguration
|
||||||
|
destination: ~/.ssh/config
|
||||||
|
encrypted: true
|
||||||
|
mode: '0600'
|
||||||
|
template: ssh/config.j2
|
||||||
|
tmux:
|
||||||
|
- description: Tmux-Konfiguration
|
||||||
|
destination: ~/.tmux.conf
|
||||||
|
template: tmux/tmux.conf.j2
|
||||||
|
vim:
|
||||||
|
- description: Vim-Konfiguration
|
||||||
|
destination: ~/.vimrc
|
||||||
|
template: vim/vimrc.j2
|
||||||
|
variables:
|
||||||
|
browser: firefox
|
||||||
|
editor: vim
|
||||||
|
git_email: your.email@example.com
|
||||||
|
git_name: Your Name
|
||||||
|
terminal: alacritty
|
||||||
309
dotfiles.py
Executable file
309
dotfiles.py
Executable file
@@ -0,0 +1,309 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Dotfiles Management System
|
||||||
|
|
||||||
|
Ein modernes Python-System zur Verwaltung von Konfigurationsdateien
|
||||||
|
ohne Symlinks - mit echter Datei-Synchronisation und Template-System.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import click
|
||||||
|
from pathlib import Path
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich import print as rprint
|
||||||
|
|
||||||
|
# Füge src/ zum Python-Pfad hinzu
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
||||||
|
|
||||||
|
from src.config import ConfigManager
|
||||||
|
from src.sync import FileSynchronizer
|
||||||
|
from src.templates import TemplateManager, create_example_templates
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.version_option(version="2.0.0")
|
||||||
|
@click.pass_context
|
||||||
|
def cli(ctx):
|
||||||
|
"""
|
||||||
|
🐍 Dotfiles Management System v2.0
|
||||||
|
|
||||||
|
Modernes Python-System zur Verwaltung von Konfigurationsdateien
|
||||||
|
ohne Symlinks - mit echter Datei-Synchronisation und Template-System.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ctx.ensure_object(dict)
|
||||||
|
ctx.obj['config'] = ConfigManager()
|
||||||
|
ctx.obj['sync'] = FileSynchronizer(ctx.obj['config'])
|
||||||
|
ctx.obj['templates'] = TemplateManager(ctx.obj['config'])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
rprint(f"[red]❌ Fehler beim Initialisieren: {e}[/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('--dry-run', '-n', is_flag=True, help='Zeige nur was gemacht würde')
|
||||||
|
@click.option('--force', '-f', is_flag=True, help='Überschreibe ohne Bestätigung')
|
||||||
|
@click.option('--no-interactive', is_flag=True, help='Keine interaktiven Abfragen')
|
||||||
|
@click.pass_context
|
||||||
|
def install(ctx, dry_run, force, no_interactive):
|
||||||
|
"""
|
||||||
|
📦 Installiert Templates ins Home-Verzeichnis
|
||||||
|
|
||||||
|
Kopiert alle konfigurierten Template-Dateien ins Home-Verzeichnis
|
||||||
|
und rendert dabei Jinja2-Templates mit maschinenspezifischen Variablen.
|
||||||
|
"""
|
||||||
|
config = ctx.obj['config']
|
||||||
|
sync = ctx.obj['sync']
|
||||||
|
|
||||||
|
if not config.current_machine:
|
||||||
|
rprint("[red]❌ Keine Maschine erkannt![/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
rprint(f"[green]🖥️ Aktuelle Maschine: {config.current_machine}[/green]")
|
||||||
|
|
||||||
|
# Installation ausführen
|
||||||
|
result = sync.install(
|
||||||
|
dry_run=dry_run,
|
||||||
|
force=force,
|
||||||
|
interactive=not no_interactive
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.errors:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('--dry-run', '-n', is_flag=True, help='Zeige nur was gemacht würde')
|
||||||
|
@click.option('--push', is_flag=True, help='Pushe Änderungen automatisch ins Git-Repository')
|
||||||
|
@click.pass_context
|
||||||
|
def backup(ctx, dry_run, push):
|
||||||
|
"""
|
||||||
|
💾 Sichert Dateien vom Home-Verzeichnis ins Repository
|
||||||
|
|
||||||
|
Kopiert aktuelle Konfigurationsdateien zurück ins Repository
|
||||||
|
um Änderungen zu sichern. Mit --push werden die Änderungen
|
||||||
|
automatisch committet und gepusht.
|
||||||
|
"""
|
||||||
|
sync = ctx.obj['sync']
|
||||||
|
|
||||||
|
result = sync.backup(dry_run=dry_run, git_push=push)
|
||||||
|
|
||||||
|
if result.errors:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def status(ctx):
|
||||||
|
"""
|
||||||
|
📊 Zeigt Status aller Konfigurationsdateien
|
||||||
|
|
||||||
|
Vergleicht Template-Dateien mit installierten Dateien
|
||||||
|
und zeigt Unterschiede an.
|
||||||
|
"""
|
||||||
|
sync = ctx.obj['sync']
|
||||||
|
sync.status()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def templates(ctx):
|
||||||
|
"""
|
||||||
|
📝 Listet alle verfügbaren Templates auf
|
||||||
|
"""
|
||||||
|
template_mgr = ctx.obj['templates']
|
||||||
|
config = ctx.obj['config']
|
||||||
|
|
||||||
|
templates = template_mgr.list_templates()
|
||||||
|
|
||||||
|
if not templates:
|
||||||
|
rprint("[yellow]⚠️ Keine Templates gefunden[/yellow]")
|
||||||
|
rprint("Verwende 'dotfiles init-templates' um Beispiel-Templates zu erstellen")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Erstelle Tabelle
|
||||||
|
table = Table(title=f"Templates für {config.current_machine}")
|
||||||
|
table.add_column("Kategorie", style="cyan")
|
||||||
|
table.add_column("Template", style="green")
|
||||||
|
table.add_column("Typ", style="yellow")
|
||||||
|
table.add_column("Größe", justify="right")
|
||||||
|
|
||||||
|
for template in templates:
|
||||||
|
template_type = "🎨 Jinja2" if template['is_template'] else "📄 Statisch"
|
||||||
|
size = f"{template['size']} B"
|
||||||
|
|
||||||
|
table.add_row(
|
||||||
|
template['category'],
|
||||||
|
template['name'],
|
||||||
|
template_type,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def init_templates(ctx):
|
||||||
|
"""
|
||||||
|
🎨 Erstellt Beispiel-Templates
|
||||||
|
|
||||||
|
Generiert Standard-Templates für bash, git, vim etc.
|
||||||
|
"""
|
||||||
|
rprint("[blue]🎨 Erstelle Beispiel-Templates...[/blue]")
|
||||||
|
create_example_templates()
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument('template_path')
|
||||||
|
@click.pass_context
|
||||||
|
def render(ctx, template_path):
|
||||||
|
"""
|
||||||
|
🖨️ Rendert ein Template und zeigt das Ergebnis
|
||||||
|
|
||||||
|
TEMPLATE_PATH: Pfad zum Template (z.B. shell/bashrc.j2)
|
||||||
|
"""
|
||||||
|
template_mgr = ctx.obj['templates']
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = template_mgr.render_template(template_path)
|
||||||
|
rprint(f"[green]📄 Gerendert: {template_path}[/green]\n")
|
||||||
|
print(content)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
rprint(f"[red]❌ Fehler beim Rendern: {e}[/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument('source_file', type=click.Path(exists=True))
|
||||||
|
@click.argument('template_path')
|
||||||
|
@click.option('--extract-vars', is_flag=True, default=True,
|
||||||
|
help='Extrahiere automatisch Variablen')
|
||||||
|
@click.pass_context
|
||||||
|
def create_template(ctx, source_file, template_path, extract_vars):
|
||||||
|
"""
|
||||||
|
➕ Erstellt ein Template aus einer existierenden Datei
|
||||||
|
|
||||||
|
SOURCE_FILE: Pfad zur Quelldatei
|
||||||
|
TEMPLATE_PATH: Ziel-Template-Pfad (z.B. shell/bashrc.j2)
|
||||||
|
"""
|
||||||
|
template_mgr = ctx.obj['templates']
|
||||||
|
|
||||||
|
source_path = Path(source_file)
|
||||||
|
|
||||||
|
if not template_path.endswith('.j2'):
|
||||||
|
template_path += '.j2'
|
||||||
|
|
||||||
|
try:
|
||||||
|
template_mgr.create_template_from_file(
|
||||||
|
source_path,
|
||||||
|
template_path,
|
||||||
|
extract_variables=extract_vars
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
rprint(f"[red]❌ Fehler beim Erstellen: {e}[/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def variables(ctx):
|
||||||
|
"""
|
||||||
|
🔧 Zeigt alle verfügbaren Template-Variablen
|
||||||
|
"""
|
||||||
|
config = ctx.obj['config']
|
||||||
|
variables = config.get_template_variables()
|
||||||
|
|
||||||
|
table = Table(title="Template-Variablen")
|
||||||
|
table.add_column("Variable", style="cyan")
|
||||||
|
table.add_column("Wert", style="green")
|
||||||
|
table.add_column("Typ", style="yellow")
|
||||||
|
|
||||||
|
for key, value in variables.items():
|
||||||
|
table.add_row(
|
||||||
|
f"{{{{ {key} }}}}",
|
||||||
|
str(value),
|
||||||
|
type(value).__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def info(ctx):
|
||||||
|
"""
|
||||||
|
ℹ️ Zeigt System-Informationen
|
||||||
|
"""
|
||||||
|
config = ctx.obj['config']
|
||||||
|
|
||||||
|
rprint("[bold blue]🐍 Dotfiles Management System v2.0[/bold blue]\n")
|
||||||
|
|
||||||
|
# Aktuelle Maschine
|
||||||
|
if config.current_machine:
|
||||||
|
machine_config = config.get_machine_config()
|
||||||
|
rprint(f"[green]🖥️ Maschine:[/green] {config.current_machine}")
|
||||||
|
rprint(f"[green]📝 Beschreibung:[/green] {machine_config.description}")
|
||||||
|
else:
|
||||||
|
rprint("[red]❌ Keine Maschine erkannt[/red]")
|
||||||
|
|
||||||
|
# Template-Statistiken
|
||||||
|
template_mgr = ctx.obj['templates']
|
||||||
|
templates = template_mgr.list_templates()
|
||||||
|
j2_templates = [t for t in templates if t['is_template']]
|
||||||
|
static_files = [t for t in templates if not t['is_template']]
|
||||||
|
|
||||||
|
rprint(f"\n[cyan]📊 Repository-Statistiken:[/cyan]")
|
||||||
|
rprint(f" 🎨 Jinja2-Templates: {len(j2_templates)}")
|
||||||
|
rprint(f" 📄 Statische Dateien: {len(static_files)}")
|
||||||
|
rprint(f" 📁 Gesamt: {len(templates)}")
|
||||||
|
|
||||||
|
# Verfügbare Maschinen
|
||||||
|
rprint(f"\n[cyan]🖥️ Verfügbare Maschinen:[/cyan]")
|
||||||
|
for name, machine in config.machines.items():
|
||||||
|
status = "✅ Aktiv" if name == config.current_machine else "⚪ Verfügbar"
|
||||||
|
rprint(f" {status} {name}: {machine.description}")
|
||||||
|
|
||||||
|
# Konfiguration
|
||||||
|
rprint(f"\n[cyan]⚙️ Konfiguration:[/cyan]")
|
||||||
|
rprint(f" 📄 Config-Datei: {config.config_path}")
|
||||||
|
rprint(f" 🔐 Verschlüsselung: {'✅ Aktiviert' if config.is_encryption_enabled() else '❌ Deaktiviert'}")
|
||||||
|
|
||||||
|
backup_settings = config.get_backup_settings()
|
||||||
|
rprint(f" 💾 Backups: {'✅ Aktiviert' if backup_settings.get('enabled') else '❌ Deaktiviert'}")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument('machine_name')
|
||||||
|
@click.pass_context
|
||||||
|
def set_machine(ctx, machine_name):
|
||||||
|
"""
|
||||||
|
🖥️ Setzt die aktuelle Maschine
|
||||||
|
|
||||||
|
MACHINE_NAME: Name der Maschine (desktop, laptop, server)
|
||||||
|
"""
|
||||||
|
config = ctx.obj['config']
|
||||||
|
|
||||||
|
if machine_name not in config.machines:
|
||||||
|
rprint(f"[red]❌ Unbekannte Maschine: {machine_name}[/red]")
|
||||||
|
rprint("Verfügbare Maschinen:")
|
||||||
|
for name, machine in config.machines.items():
|
||||||
|
rprint(f" - {name}: {machine.description}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
config.save_current_machine(machine_name)
|
||||||
|
config.current_machine = machine_name
|
||||||
|
|
||||||
|
machine_config = config.get_machine_config(machine_name)
|
||||||
|
rprint(f"[green]✅ Maschine gesetzt: {machine_name}[/green]")
|
||||||
|
rprint(f"[green]📝 Beschreibung: {machine_config.description}[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cli()
|
||||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
PyYAML>=6.0
|
||||||
|
Jinja2>=3.0.0
|
||||||
|
click>=8.0.0
|
||||||
|
rich>=13.0.0
|
||||||
|
cryptography>=3.4.0
|
||||||
|
pathspec>=0.9.0
|
||||||
9
src/__init__.py
Normal file
9
src/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"""
|
||||||
|
Dotfiles Management System
|
||||||
|
|
||||||
|
Ein modernes, Python-basiertes System zur Verwaltung von Konfigurationsdateien
|
||||||
|
ohne Symlinks - mit echter Datei-Synchronisation und Template-System.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "2.0.0"
|
||||||
|
__author__ = "Your Name"
|
||||||
BIN
src/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
src/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/config.cpython-313.pyc
Normal file
BIN
src/__pycache__/config.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/sync.cpython-313.pyc
Normal file
BIN
src/__pycache__/sync.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/templates.cpython-313.pyc
Normal file
BIN
src/__pycache__/templates.cpython-313.pyc
Normal file
Binary file not shown.
231
src/config.py
Normal file
231
src/config.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
"""
|
||||||
|
Konfiguration-Management für Dotfiles System
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Any, Optional
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MachineConfig:
|
||||||
|
"""Konfiguration für einen Maschinen-Typ"""
|
||||||
|
name: str
|
||||||
|
hostname_patterns: List[str]
|
||||||
|
os_patterns: List[str]
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FileMapping:
|
||||||
|
"""Mapping zwischen Template und Ziel-Datei"""
|
||||||
|
template: str
|
||||||
|
destination: str
|
||||||
|
description: str
|
||||||
|
mode: Optional[str] = None
|
||||||
|
machine_specific: bool = False
|
||||||
|
encrypted: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigManager:
|
||||||
|
"""Verwaltet die Dotfiles-Konfiguration"""
|
||||||
|
|
||||||
|
def __init__(self, config_path: Optional[Path] = None):
|
||||||
|
if config_path is None:
|
||||||
|
config_path = Path(__file__).parent.parent / "config.yaml"
|
||||||
|
|
||||||
|
self.config_path = Path(config_path)
|
||||||
|
self.config_data: Dict[str, Any] = {}
|
||||||
|
self.machines: Dict[str, MachineConfig] = {}
|
||||||
|
self.file_mappings: List[FileMapping] = []
|
||||||
|
self.current_machine: Optional[str] = None
|
||||||
|
|
||||||
|
self.load_config()
|
||||||
|
|
||||||
|
def load_config(self) -> None:
|
||||||
|
"""Lädt die Konfiguration aus der YAML-Datei"""
|
||||||
|
try:
|
||||||
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
||||||
|
self.config_data = yaml.safe_load(f)
|
||||||
|
|
||||||
|
self._parse_machines()
|
||||||
|
self._parse_file_mappings()
|
||||||
|
self._detect_current_machine()
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise FileNotFoundError(f"Konfigurationsdatei nicht gefunden: {self.config_path}")
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
raise ValueError(f"Fehler beim Parsen der YAML-Datei: {e}")
|
||||||
|
|
||||||
|
def _parse_machines(self) -> None:
|
||||||
|
"""Parst die Maschinen-Definitionen"""
|
||||||
|
machines_data = self.config_data.get('machines', {})
|
||||||
|
|
||||||
|
for name, data in machines_data.items():
|
||||||
|
self.machines[name] = MachineConfig(
|
||||||
|
name=name,
|
||||||
|
hostname_patterns=data.get('hostname_patterns', []),
|
||||||
|
os_patterns=data.get('os_patterns', []),
|
||||||
|
description=data.get('description', '')
|
||||||
|
)
|
||||||
|
|
||||||
|
def _parse_file_mappings(self) -> None:
|
||||||
|
"""Parst die Datei-Mappings"""
|
||||||
|
self.file_mappings = []
|
||||||
|
|
||||||
|
# Templates (geteilte Konfigurationen)
|
||||||
|
templates = self.config_data.get('templates', {})
|
||||||
|
for category, files in templates.items():
|
||||||
|
if isinstance(files, list):
|
||||||
|
for file_config in files:
|
||||||
|
mapping = FileMapping(
|
||||||
|
template=file_config['template'],
|
||||||
|
destination=file_config['destination'],
|
||||||
|
description=file_config.get('description', ''),
|
||||||
|
mode=file_config.get('mode'),
|
||||||
|
machine_specific=file_config.get('machine_specific', False),
|
||||||
|
encrypted=file_config.get('encrypted', False)
|
||||||
|
)
|
||||||
|
self.file_mappings.append(mapping)
|
||||||
|
|
||||||
|
def _detect_current_machine(self) -> None:
|
||||||
|
"""Erkennt die aktuelle Maschine automatisch"""
|
||||||
|
import socket
|
||||||
|
import platform
|
||||||
|
|
||||||
|
# Erst aus config.yaml prüfen
|
||||||
|
saved_machine = self.config_data.get('current_machine')
|
||||||
|
if saved_machine and saved_machine in self.machines:
|
||||||
|
self.current_machine = saved_machine
|
||||||
|
return
|
||||||
|
|
||||||
|
hostname = socket.gethostname().lower()
|
||||||
|
os_name = platform.system()
|
||||||
|
|
||||||
|
# Automatische Erkennung
|
||||||
|
for machine_name, machine in self.machines.items():
|
||||||
|
# Prüfe Hostname-Patterns
|
||||||
|
for pattern in machine.hostname_patterns:
|
||||||
|
if pattern.lower() in hostname:
|
||||||
|
self.current_machine = machine_name
|
||||||
|
self.save_current_machine(machine_name)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Fallback: Interaktive Auswahl
|
||||||
|
self.current_machine = self._interactive_machine_selection()
|
||||||
|
if self.current_machine:
|
||||||
|
self.save_current_machine(self.current_machine)
|
||||||
|
|
||||||
|
def _interactive_machine_selection(self) -> Optional[str]:
|
||||||
|
"""Interaktive Maschinen-Auswahl"""
|
||||||
|
print("\n🤖 Automatische Maschinen-Erkennung fehlgeschlagen")
|
||||||
|
print("Bitte wähle deinen Maschinen-Typ:\n")
|
||||||
|
|
||||||
|
machine_list = list(self.machines.keys())
|
||||||
|
for i, (name, machine) in enumerate(self.machines.items(), 1):
|
||||||
|
print(f"{i}) {machine.description}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = input(f"\nEingabe (1-{len(machine_list)}): ").strip()
|
||||||
|
index = int(choice) - 1
|
||||||
|
if 0 <= index < len(machine_list):
|
||||||
|
return machine_list[index]
|
||||||
|
else:
|
||||||
|
print("❌ Ungültige Auswahl!")
|
||||||
|
except (ValueError, KeyboardInterrupt):
|
||||||
|
print("❌ Ungültige Eingabe!")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_current_machine(self, machine_name: str) -> None:
|
||||||
|
"""Speichert die aktuelle Maschine in der config.yaml"""
|
||||||
|
self.config_data['current_machine'] = machine_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(self.config_path, 'w', encoding='utf-8') as f:
|
||||||
|
yaml.dump(self.config_data, f, default_flow_style=False,
|
||||||
|
allow_unicode=True, indent=2)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Warnung: Konnte Maschinen-Konfiguration nicht speichern: {e}")
|
||||||
|
|
||||||
|
def get_machine_config(self, machine_name: Optional[str] = None) -> Optional[MachineConfig]:
|
||||||
|
"""Gibt die Konfiguration für eine Maschine zurück"""
|
||||||
|
if machine_name is None:
|
||||||
|
machine_name = self.current_machine
|
||||||
|
|
||||||
|
return self.machines.get(machine_name) if machine_name else None
|
||||||
|
|
||||||
|
def get_templates_for_machine(self, machine_name: Optional[str] = None) -> List[FileMapping]:
|
||||||
|
"""Gibt alle Templates für eine Maschine zurück"""
|
||||||
|
if machine_name is None:
|
||||||
|
machine_name = self.current_machine
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for mapping in self.file_mappings:
|
||||||
|
# Alle nicht-maschinenspezifischen Templates
|
||||||
|
if not mapping.machine_specific:
|
||||||
|
result.append(mapping)
|
||||||
|
# Maschinenspezifische Templates nur für die aktuelle Maschine
|
||||||
|
elif mapping.machine_specific and machine_name:
|
||||||
|
# Template-Pfad für aktuelle Maschine anpassen
|
||||||
|
machine_template = mapping.template.replace('{machine}', machine_name)
|
||||||
|
machine_mapping = FileMapping(
|
||||||
|
template=machine_template,
|
||||||
|
destination=mapping.destination,
|
||||||
|
description=mapping.description,
|
||||||
|
mode=mapping.mode,
|
||||||
|
machine_specific=True,
|
||||||
|
encrypted=mapping.encrypted
|
||||||
|
)
|
||||||
|
result.append(machine_mapping)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_template_variables(self) -> Dict[str, Any]:
|
||||||
|
"""Gibt Template-Variablen zurück"""
|
||||||
|
import getpass
|
||||||
|
import socket
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
'machine': self.current_machine,
|
||||||
|
'hostname': socket.gethostname(),
|
||||||
|
'username': getpass.getuser(),
|
||||||
|
'home': str(Path.home()),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Benutzerdefinierte Variablen aus config.yaml
|
||||||
|
user_vars = self.config_data.get('variables', {})
|
||||||
|
variables.update(user_vars)
|
||||||
|
|
||||||
|
# Maschinenspezifische Variablen
|
||||||
|
if self.current_machine:
|
||||||
|
machine_vars = self.config_data.get('machine_variables', {}).get(self.current_machine, {})
|
||||||
|
variables.update(machine_vars)
|
||||||
|
|
||||||
|
return variables
|
||||||
|
|
||||||
|
def get_encryption_patterns(self) -> List[str]:
|
||||||
|
"""Gibt Muster für zu verschlüsselnde Dateien zurück"""
|
||||||
|
return self.config_data.get('encryption', {}).get('patterns', [])
|
||||||
|
|
||||||
|
def is_encryption_enabled(self) -> bool:
|
||||||
|
"""Prüft ob Verschlüsselung aktiviert ist"""
|
||||||
|
return self.config_data.get('encryption', {}).get('enabled', False)
|
||||||
|
|
||||||
|
def get_backup_settings(self) -> Dict[str, Any]:
|
||||||
|
"""Gibt Backup-Einstellungen zurück"""
|
||||||
|
return self.config_data.get('backup', {
|
||||||
|
'enabled': True,
|
||||||
|
'format': '.backup.{timestamp}',
|
||||||
|
'keep_backups': 5
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_sync_settings(self) -> Dict[str, Any]:
|
||||||
|
"""Gibt Synchronisation-Einstellungen zurück"""
|
||||||
|
return self.config_data.get('sync', {
|
||||||
|
'dry_run': False,
|
||||||
|
'interactive': True,
|
||||||
|
'force_overwrite': False
|
||||||
|
})
|
||||||
397
src/sync.py
Normal file
397
src/sync.py
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
"""
|
||||||
|
Datei-Synchronisation ohne Symlinks
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import hashlib
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
from datetime import datetime
|
||||||
|
import difflib
|
||||||
|
|
||||||
|
from .config import ConfigManager, FileMapping
|
||||||
|
|
||||||
|
|
||||||
|
class SyncResult:
|
||||||
|
"""Ergebnis einer Synchronisation"""
|
||||||
|
def __init__(self):
|
||||||
|
self.copied: List[str] = []
|
||||||
|
self.skipped: List[str] = []
|
||||||
|
self.backed_up: List[str] = []
|
||||||
|
self.conflicts: List[str] = []
|
||||||
|
self.errors: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class FileSynchronizer:
|
||||||
|
"""Synchronisiert Dateien zwischen Templates und Home-Verzeichnis"""
|
||||||
|
|
||||||
|
def __init__(self, config_manager: ConfigManager):
|
||||||
|
self.config = config_manager
|
||||||
|
self.home_dir = Path.home()
|
||||||
|
self.dotfiles_dir = Path(__file__).parent.parent
|
||||||
|
self.templates_dir = self.dotfiles_dir / "templates"
|
||||||
|
|
||||||
|
def install(self, dry_run: bool = False, force: bool = False, interactive: bool = True) -> SyncResult:
|
||||||
|
"""Installiert Templates ins Home-Verzeichnis"""
|
||||||
|
result = SyncResult()
|
||||||
|
|
||||||
|
templates = self.config.get_templates_for_machine()
|
||||||
|
|
||||||
|
print(f"🔄 Installiere {len(templates)} Konfigurationsdateien...")
|
||||||
|
if dry_run:
|
||||||
|
print("🧪 DRY RUN - Keine Dateien werden verändert")
|
||||||
|
|
||||||
|
for mapping in templates:
|
||||||
|
try:
|
||||||
|
self._sync_template_to_home(mapping, result, dry_run, force, interactive)
|
||||||
|
except Exception as e:
|
||||||
|
result.errors.append(f"{mapping.description}: {e}")
|
||||||
|
print(f"❌ Fehler bei {mapping.description}: {e}")
|
||||||
|
|
||||||
|
self._print_sync_summary(result, "Installation")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def backup(self, dry_run: bool = False, git_push: bool = False) -> SyncResult:
|
||||||
|
"""Sichert Dateien vom Home-Verzeichnis ins Repository"""
|
||||||
|
result = SyncResult()
|
||||||
|
|
||||||
|
templates = self.config.get_templates_for_machine()
|
||||||
|
|
||||||
|
print(f"💾 Sichere {len(templates)} Konfigurationsdateien...")
|
||||||
|
if dry_run:
|
||||||
|
print("🧪 DRY RUN - Keine Dateien werden verändert")
|
||||||
|
|
||||||
|
for mapping in templates:
|
||||||
|
try:
|
||||||
|
self._sync_home_to_template(mapping, result, dry_run)
|
||||||
|
except Exception as e:
|
||||||
|
result.errors.append(f"{mapping.description}: {e}")
|
||||||
|
print(f"❌ Fehler bei {mapping.description}: {e}")
|
||||||
|
|
||||||
|
self._print_sync_summary(result, "Backup")
|
||||||
|
|
||||||
|
# Git-Push wenn gewünscht und Dateien kopiert wurden
|
||||||
|
if git_push and not dry_run and (result.copied or result.backed_up):
|
||||||
|
try:
|
||||||
|
self._git_push_changes(result)
|
||||||
|
except Exception as e:
|
||||||
|
result.errors.append(f"Git-Push: {e}")
|
||||||
|
print(f"❌ Git-Push Fehler: {e}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _sync_template_to_home(self, mapping: FileMapping, result: SyncResult,
|
||||||
|
dry_run: bool, force: bool, interactive: bool) -> None:
|
||||||
|
"""Synchronisiert eine Template-Datei ins Home-Verzeichnis"""
|
||||||
|
|
||||||
|
# Template-Pfad
|
||||||
|
template_path = self.templates_dir / mapping.template
|
||||||
|
if not template_path.exists():
|
||||||
|
result.skipped.append(f"{mapping.description}: Template nicht gefunden")
|
||||||
|
print(f"⚠️ Template nicht gefunden: {template_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ziel-Pfad expandieren
|
||||||
|
dest_path = self._expand_path(mapping.destination)
|
||||||
|
|
||||||
|
# Template rendern
|
||||||
|
if template_path.suffix == '.j2':
|
||||||
|
content = self._render_template(template_path)
|
||||||
|
needs_copy = self._content_differs_from_file(content, dest_path)
|
||||||
|
else:
|
||||||
|
needs_copy = self._files_differ(template_path, dest_path)
|
||||||
|
content = None
|
||||||
|
|
||||||
|
# Prüfe ob Kopieren nötig ist
|
||||||
|
if not needs_copy:
|
||||||
|
result.skipped.append(f"{mapping.description}: Bereits aktuell")
|
||||||
|
print(f"✅ {mapping.description}: Bereits aktuell")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle Konflikte
|
||||||
|
if dest_path.exists() and not force:
|
||||||
|
if interactive:
|
||||||
|
action = self._handle_conflict(template_path, dest_path, mapping.description, content)
|
||||||
|
if action == 'skip':
|
||||||
|
result.skipped.append(f"{mapping.description}: Vom Benutzer übersprungen")
|
||||||
|
return
|
||||||
|
elif action == 'backup':
|
||||||
|
self._create_backup(dest_path, result)
|
||||||
|
else:
|
||||||
|
# Im nicht-interaktiven Modus: Backup erstellen
|
||||||
|
self._create_backup(dest_path, result)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
result.copied.append(f"{mapping.description}: Würde kopiert werden")
|
||||||
|
print(f"🔄 {mapping.description}: Würde kopiert werden")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Erstelle Zielverzeichnis
|
||||||
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Kopiere Datei
|
||||||
|
if content is not None:
|
||||||
|
# Template wurde gerendert
|
||||||
|
dest_path.write_text(content, encoding='utf-8')
|
||||||
|
else:
|
||||||
|
# Normale Datei kopieren
|
||||||
|
shutil.copy2(template_path, dest_path)
|
||||||
|
|
||||||
|
# Setze Berechtigungen
|
||||||
|
if mapping.mode:
|
||||||
|
os.chmod(dest_path, int(mapping.mode, 8))
|
||||||
|
|
||||||
|
result.copied.append(mapping.description)
|
||||||
|
print(f"✅ {mapping.description}: Installiert")
|
||||||
|
|
||||||
|
def _sync_home_to_template(self, mapping: FileMapping, result: SyncResult, dry_run: bool) -> None:
|
||||||
|
"""Synchronisiert eine Datei vom Home-Verzeichnis ins Repository"""
|
||||||
|
|
||||||
|
# Quell-Pfad expandieren
|
||||||
|
source_path = self._expand_path(mapping.destination)
|
||||||
|
if not source_path.exists():
|
||||||
|
result.skipped.append(f"{mapping.description}: Datei existiert nicht")
|
||||||
|
print(f"⚠️ {mapping.description}: Datei existiert nicht im Home-Verzeichnis")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Template-Pfad (ohne .j2 Endung für Backup)
|
||||||
|
template_path = self.templates_dir / mapping.template
|
||||||
|
if template_path.suffix == '.j2':
|
||||||
|
template_path = template_path.with_suffix('')
|
||||||
|
|
||||||
|
# Prüfe ob Kopieren nötig ist
|
||||||
|
if self._files_differ(source_path, template_path):
|
||||||
|
if dry_run:
|
||||||
|
result.copied.append(f"{mapping.description}: Würde gesichert werden")
|
||||||
|
print(f"💾 {mapping.description}: Würde gesichert werden")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Erstelle Template-Verzeichnis
|
||||||
|
template_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Kopiere Datei
|
||||||
|
shutil.copy2(source_path, template_path)
|
||||||
|
|
||||||
|
result.copied.append(mapping.description)
|
||||||
|
print(f"✅ {mapping.description}: Gesichert")
|
||||||
|
else:
|
||||||
|
result.skipped.append(f"{mapping.description}: Bereits aktuell")
|
||||||
|
print(f"✅ {mapping.description}: Bereits aktuell")
|
||||||
|
|
||||||
|
def _render_template(self, template_path: Path) -> str:
|
||||||
|
"""Rendert ein Jinja2-Template"""
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
template_content = template_path.read_text(encoding='utf-8')
|
||||||
|
template = Template(template_content)
|
||||||
|
|
||||||
|
variables = self.config.get_template_variables()
|
||||||
|
return template.render(**variables)
|
||||||
|
|
||||||
|
def _expand_path(self, path_str: str) -> Path:
|
||||||
|
"""Expandiert einen Pfad-String mit Variablen"""
|
||||||
|
# ~ expandieren
|
||||||
|
if path_str.startswith('~'):
|
||||||
|
path_str = str(self.home_dir) + path_str[1:]
|
||||||
|
|
||||||
|
# Variablen ersetzen
|
||||||
|
variables = self.config.get_template_variables()
|
||||||
|
for key, value in variables.items():
|
||||||
|
path_str = path_str.replace(f'{{{key}}}', str(value))
|
||||||
|
|
||||||
|
return Path(path_str)
|
||||||
|
|
||||||
|
def _files_differ(self, file1: Path, file2: Path) -> bool:
|
||||||
|
"""Prüft ob zwei Dateien unterschiedlich sind"""
|
||||||
|
if not file1.exists() or not file2.exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return self._get_file_hash(file1) != self._get_file_hash(file2)
|
||||||
|
|
||||||
|
def _content_differs_from_file(self, content: str, file_path: Path) -> bool:
|
||||||
|
"""Prüft ob Inhalt von einer Datei abweicht"""
|
||||||
|
if not file_path.exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
existing_content = file_path.read_text(encoding='utf-8')
|
||||||
|
return content != existing_content
|
||||||
|
|
||||||
|
def _get_file_hash(self, file_path: Path) -> str:
|
||||||
|
"""Berechnet SHA256-Hash einer Datei"""
|
||||||
|
hash_sha256 = hashlib.sha256()
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(4096), b""):
|
||||||
|
hash_sha256.update(chunk)
|
||||||
|
return hash_sha256.hexdigest()
|
||||||
|
|
||||||
|
def _create_backup(self, file_path: Path, result: SyncResult) -> None:
|
||||||
|
"""Erstellt ein Backup einer Datei"""
|
||||||
|
backup_settings = self.config.get_backup_settings()
|
||||||
|
|
||||||
|
if not backup_settings.get('enabled', True):
|
||||||
|
return
|
||||||
|
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
backup_format = backup_settings.get('format', '.backup.{timestamp}')
|
||||||
|
backup_suffix = backup_format.format(timestamp=timestamp)
|
||||||
|
|
||||||
|
backup_path = file_path.with_name(file_path.name + backup_suffix)
|
||||||
|
|
||||||
|
shutil.copy2(file_path, backup_path)
|
||||||
|
result.backed_up.append(str(backup_path))
|
||||||
|
print(f"💾 Backup erstellt: {backup_path.name}")
|
||||||
|
|
||||||
|
def _handle_conflict(self, template_path: Path, dest_path: Path,
|
||||||
|
description: str, template_content: Optional[str] = None) -> str:
|
||||||
|
"""Behandelt Konflikte zwischen Template und existierender Datei"""
|
||||||
|
|
||||||
|
print(f"\n⚠️ Konflikt bei {description}")
|
||||||
|
print(f" Template: {template_path}")
|
||||||
|
print(f" Ziel: {dest_path}")
|
||||||
|
|
||||||
|
# Zeige Unterschiede
|
||||||
|
if template_content:
|
||||||
|
existing_content = dest_path.read_text(encoding='utf-8')
|
||||||
|
template_lines = template_content.splitlines(keepends=True)
|
||||||
|
existing_lines = existing_content.splitlines(keepends=True)
|
||||||
|
else:
|
||||||
|
template_lines = template_path.read_text(encoding='utf-8').splitlines(keepends=True)
|
||||||
|
existing_lines = dest_path.read_text(encoding='utf-8').splitlines(keepends=True)
|
||||||
|
|
||||||
|
diff = list(difflib.unified_diff(
|
||||||
|
existing_lines, template_lines,
|
||||||
|
fromfile=f'aktuell ({dest_path.name})',
|
||||||
|
tofile=f'template ({template_path.name})',
|
||||||
|
lineterm=''
|
||||||
|
))
|
||||||
|
|
||||||
|
if diff:
|
||||||
|
print("\n📝 Unterschiede:")
|
||||||
|
for line in diff[:20]: # Begrenzt auf 20 Zeilen
|
||||||
|
print(f" {line}")
|
||||||
|
if len(diff) > 20:
|
||||||
|
print(f" ... und {len(diff) - 20} weitere Zeilen")
|
||||||
|
|
||||||
|
print("\nWas möchtest du tun?")
|
||||||
|
print("1) Überschreiben (mit Backup)")
|
||||||
|
print("2) Überspringen")
|
||||||
|
print("3) Diff zeigen")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = input("Eingabe (1-3): ").strip()
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
return 'backup'
|
||||||
|
elif choice == '2':
|
||||||
|
return 'skip'
|
||||||
|
elif choice == '3':
|
||||||
|
# Zeige vollständiges Diff
|
||||||
|
for line in diff:
|
||||||
|
print(line)
|
||||||
|
print("\nWas möchtest du tun?")
|
||||||
|
print("1) Überschreiben (mit Backup)")
|
||||||
|
print("2) Überspringen")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print("❌ Ungültige Eingabe!")
|
||||||
|
|
||||||
|
def _print_sync_summary(self, result: SyncResult, operation: str) -> None:
|
||||||
|
"""Gibt eine Zusammenfassung der Synchronisation aus"""
|
||||||
|
print(f"\n📊 {operation} abgeschlossen:")
|
||||||
|
|
||||||
|
if result.copied:
|
||||||
|
print(f"✅ {len(result.copied)} Dateien verarbeitet")
|
||||||
|
|
||||||
|
if result.skipped:
|
||||||
|
print(f"⏭️ {len(result.skipped)} Dateien übersprungen")
|
||||||
|
|
||||||
|
if result.backed_up:
|
||||||
|
print(f"💾 {len(result.backed_up)} Backups erstellt")
|
||||||
|
|
||||||
|
if result.conflicts:
|
||||||
|
print(f"⚠️ {len(result.conflicts)} Konflikte")
|
||||||
|
|
||||||
|
if result.errors:
|
||||||
|
print(f"❌ {len(result.errors)} Fehler")
|
||||||
|
for error in result.errors:
|
||||||
|
print(f" {error}")
|
||||||
|
|
||||||
|
def status(self) -> None:
|
||||||
|
"""Zeigt den Status aller Konfigurationsdateien"""
|
||||||
|
templates = self.config.get_templates_for_machine()
|
||||||
|
|
||||||
|
print(f"📋 Status von {len(templates)} Konfigurationsdateien:\n")
|
||||||
|
|
||||||
|
for mapping in templates:
|
||||||
|
template_path = self.templates_dir / mapping.template
|
||||||
|
dest_path = self._expand_path(mapping.destination)
|
||||||
|
|
||||||
|
# Status ermitteln
|
||||||
|
if not template_path.exists():
|
||||||
|
status = "❓ Template fehlt"
|
||||||
|
elif not dest_path.exists():
|
||||||
|
status = "➕ Nicht installiert"
|
||||||
|
elif template_path.suffix == '.j2':
|
||||||
|
# Template-Datei
|
||||||
|
content = self._render_template(template_path)
|
||||||
|
if self._content_differs_from_file(content, dest_path):
|
||||||
|
status = "🔄 Unterschiede"
|
||||||
|
else:
|
||||||
|
status = "✅ Aktuell"
|
||||||
|
else:
|
||||||
|
# Normale Datei
|
||||||
|
if self._files_differ(template_path, dest_path):
|
||||||
|
status = "🔄 Unterschiede"
|
||||||
|
else:
|
||||||
|
status = "✅ Aktuell"
|
||||||
|
|
||||||
|
print(f"{status} {mapping.description}")
|
||||||
|
print(f" Template: {template_path}")
|
||||||
|
print(f" Ziel: {dest_path}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def _git_push_changes(self, result: SyncResult) -> None:
|
||||||
|
"""Führt Git-Operationen nach erfolgreichem Backup durch"""
|
||||||
|
|
||||||
|
print("\n🔄 Führe Git-Operationen durch...")
|
||||||
|
|
||||||
|
# Prüfe ob wir in einem Git-Repository sind
|
||||||
|
if not (self.dotfiles_dir / '.git').exists():
|
||||||
|
raise Exception("Nicht in einem Git-Repository")
|
||||||
|
|
||||||
|
# Wechsle ins dotfiles-Verzeichnis
|
||||||
|
os.chdir(self.dotfiles_dir)
|
||||||
|
|
||||||
|
# Git status prüfen
|
||||||
|
result_status = subprocess.run(['git', 'status', '--porcelain'],
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
if not result_status.stdout.strip():
|
||||||
|
print("✅ Keine Git-Änderungen zu committen")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Git add .
|
||||||
|
print("📝 Füge Änderungen zu Git hinzu...")
|
||||||
|
subprocess.run(['git', 'add', '.'], check=True)
|
||||||
|
|
||||||
|
# Git commit mit automatischer Message
|
||||||
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
machine = self.config.current_machine or 'unknown'
|
||||||
|
|
||||||
|
changed_configs = len(result.copied) + len(result.backed_up)
|
||||||
|
commit_msg = f"Backup: {changed_configs} configs von {machine} - {timestamp}"
|
||||||
|
|
||||||
|
print(f"💬 Erstelle Commit: {commit_msg}")
|
||||||
|
subprocess.run(['git', 'commit', '-m', commit_msg], check=True)
|
||||||
|
|
||||||
|
# Git push
|
||||||
|
print("🚀 Pushe ins Remote-Repository...")
|
||||||
|
result_push = subprocess.run(['git', 'push'],
|
||||||
|
capture_output=True, text=True, check=True)
|
||||||
|
|
||||||
|
if result_push.returncode == 0:
|
||||||
|
print("✅ Erfolgreich ins Git-Repository gepusht!")
|
||||||
|
else:
|
||||||
|
raise Exception(f"Git push fehlgeschlagen: {result_push.stderr}")
|
||||||
318
src/templates.py
Normal file
318
src/templates.py
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
"""
|
||||||
|
Template-System für dynamische Konfigurationsdateien
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
from jinja2 import Environment, FileSystemLoader, Template
|
||||||
|
|
||||||
|
from .config import ConfigManager
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateManager:
|
||||||
|
"""Verwaltet Templates für Konfigurationsdateien"""
|
||||||
|
|
||||||
|
def __init__(self, config_manager: ConfigManager):
|
||||||
|
self.config = config_manager
|
||||||
|
self.dotfiles_dir = Path(__file__).parent.parent
|
||||||
|
self.templates_dir = self.dotfiles_dir / "templates"
|
||||||
|
|
||||||
|
# Jinja2 Environment einrichten
|
||||||
|
self.env = Environment(
|
||||||
|
loader=FileSystemLoader(str(self.templates_dir)),
|
||||||
|
trim_blocks=True,
|
||||||
|
lstrip_blocks=True,
|
||||||
|
keep_trailing_newline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Custom Filters hinzufügen
|
||||||
|
self.env.filters['home'] = self._filter_home
|
||||||
|
self.env.filters['machine'] = self._filter_machine
|
||||||
|
|
||||||
|
def render_template(self, template_path: str, variables: Dict[str, Any] = None) -> str:
|
||||||
|
"""Rendert ein Template mit den gegebenen Variablen"""
|
||||||
|
if variables is None:
|
||||||
|
variables = self.config.get_template_variables()
|
||||||
|
|
||||||
|
template = self.env.get_template(template_path)
|
||||||
|
return template.render(**variables)
|
||||||
|
|
||||||
|
def create_template_from_file(self, source_file: Path, template_path: str,
|
||||||
|
extract_variables: bool = True) -> None:
|
||||||
|
"""Erstellt ein Template aus einer existierenden Datei"""
|
||||||
|
|
||||||
|
# Lese existierende Datei
|
||||||
|
content = source_file.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
if extract_variables:
|
||||||
|
# Automatische Variablen-Extraktion
|
||||||
|
content = self._extract_common_variables(content)
|
||||||
|
|
||||||
|
# Erstelle Template-Verzeichnis
|
||||||
|
template_file = self.templates_dir / template_path
|
||||||
|
template_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Schreibe Template
|
||||||
|
template_file.write_text(content, encoding='utf-8')
|
||||||
|
|
||||||
|
print(f"✅ Template erstellt: {template_file}")
|
||||||
|
|
||||||
|
def _extract_common_variables(self, content: str) -> str:
|
||||||
|
"""Extrahiert häufige Variablen aus dem Inhalt"""
|
||||||
|
variables = self.config.get_template_variables()
|
||||||
|
|
||||||
|
# Ersetze bekannte Werte durch Template-Variablen
|
||||||
|
for var_name, var_value in variables.items():
|
||||||
|
if isinstance(var_value, str) and var_value in content:
|
||||||
|
content = content.replace(var_value, f'{{{{ {var_name} }}}}')
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def _filter_home(self, path: str) -> str:
|
||||||
|
"""Jinja2 Filter: Expandiert ~ zu Home-Verzeichnis"""
|
||||||
|
if path.startswith('~'):
|
||||||
|
return str(Path.home()) + path[1:]
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _filter_machine(self, template_dict: Dict[str, str]) -> str:
|
||||||
|
"""Jinja2 Filter: Wählt Wert basierend auf aktueller Maschine"""
|
||||||
|
machine = self.config.current_machine
|
||||||
|
return template_dict.get(machine, template_dict.get('default', ''))
|
||||||
|
|
||||||
|
def list_templates(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Listet alle verfügbaren Templates auf"""
|
||||||
|
templates = []
|
||||||
|
|
||||||
|
for template_file in self.templates_dir.rglob('*'):
|
||||||
|
if template_file.is_file():
|
||||||
|
rel_path = template_file.relative_to(self.templates_dir)
|
||||||
|
|
||||||
|
templates.append({
|
||||||
|
'path': str(rel_path),
|
||||||
|
'name': template_file.name,
|
||||||
|
'category': rel_path.parts[0] if len(rel_path.parts) > 1 else 'root',
|
||||||
|
'is_template': template_file.suffix == '.j2',
|
||||||
|
'size': template_file.stat().st_size,
|
||||||
|
'modified': template_file.stat().st_mtime
|
||||||
|
})
|
||||||
|
|
||||||
|
return sorted(templates, key=lambda t: t['path'])
|
||||||
|
|
||||||
|
def validate_template(self, template_path: str) -> List[str]:
|
||||||
|
"""Validiert ein Template und gibt eventuelle Fehler zurück"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
template = self.env.get_template(template_path)
|
||||||
|
|
||||||
|
# Versuche zu rendern mit Standard-Variablen
|
||||||
|
variables = self.config.get_template_variables()
|
||||||
|
template.render(**variables)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"Template-Fehler: {e}")
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def get_template_variables_usage(self, template_path: str) -> List[str]:
|
||||||
|
"""Gibt alle in einem Template verwendeten Variablen zurück"""
|
||||||
|
try:
|
||||||
|
template = self.env.get_template(template_path)
|
||||||
|
|
||||||
|
# Extrahiere verwendete Variablen (vereinfacht)
|
||||||
|
from jinja2 import meta
|
||||||
|
ast = self.env.parse(template.source)
|
||||||
|
variables = meta.find_undeclared_variables(ast)
|
||||||
|
|
||||||
|
return sorted(list(variables))
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def create_example_templates():
|
||||||
|
"""Erstellt Beispiel-Templates"""
|
||||||
|
|
||||||
|
templates_dir = Path(__file__).parent.parent / "templates"
|
||||||
|
|
||||||
|
# Shell-Templates
|
||||||
|
shell_dir = templates_dir / "shell"
|
||||||
|
shell_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Bashrc Template
|
||||||
|
bashrc_template = shell_dir / "bashrc.j2"
|
||||||
|
bashrc_content = '''# Bash configuration for {{ machine }} ({{ hostname }})
|
||||||
|
# Generated by dotfiles system
|
||||||
|
|
||||||
|
# History settings
|
||||||
|
HISTSIZE=10000
|
||||||
|
HISTFILESIZE=20000
|
||||||
|
HISTCONTROL=ignoreboth
|
||||||
|
|
||||||
|
# Aliases
|
||||||
|
alias ll='ls -alF'
|
||||||
|
alias la='ls -A'
|
||||||
|
alias l='ls -CF'
|
||||||
|
alias grep='grep --color=auto'
|
||||||
|
|
||||||
|
{% if machine == 'server' %}
|
||||||
|
# Server-specific settings
|
||||||
|
alias logs='sudo journalctl -f'
|
||||||
|
alias status='systemctl status'
|
||||||
|
{% elif machine == 'desktop' %}
|
||||||
|
# Desktop-specific settings
|
||||||
|
alias open='xdg-open'
|
||||||
|
alias screenshot='gnome-screenshot'
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# User-specific settings
|
||||||
|
export EDITOR=vim
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
|
# Machine-specific additions
|
||||||
|
{% if machine == 'laptop' %}
|
||||||
|
# Laptop power settings
|
||||||
|
alias hibernate='systemctl hibernate'
|
||||||
|
alias suspend='systemctl suspend'
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Load local bashrc if it exists
|
||||||
|
if [ -f ~/.bashrc.local ]; then
|
||||||
|
source ~/.bashrc.local
|
||||||
|
fi
|
||||||
|
'''
|
||||||
|
bashrc_template.write_text(bashrc_content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Git Template
|
||||||
|
git_dir = templates_dir / "git"
|
||||||
|
git_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
gitconfig_template = git_dir / "gitconfig.j2"
|
||||||
|
gitconfig_content = '''[user]
|
||||||
|
name = {{ git_name | default("Your Name") }}
|
||||||
|
email = {{ git_email | default("your.email@example.com") }}
|
||||||
|
|
||||||
|
[core]
|
||||||
|
editor = {{ editor | default("vim") }}
|
||||||
|
autocrlf = input
|
||||||
|
excludesfile = ~/.gitignore_global
|
||||||
|
|
||||||
|
[push]
|
||||||
|
default = simple
|
||||||
|
|
||||||
|
[pull]
|
||||||
|
rebase = false
|
||||||
|
|
||||||
|
[alias]
|
||||||
|
st = status
|
||||||
|
co = checkout
|
||||||
|
br = branch
|
||||||
|
ci = commit
|
||||||
|
lg = log --oneline --graph --decorate
|
||||||
|
unstage = reset HEAD --
|
||||||
|
|
||||||
|
{% if machine == 'desktop' %}
|
||||||
|
[diff]
|
||||||
|
tool = meld
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
[color]
|
||||||
|
ui = auto
|
||||||
|
branch = auto
|
||||||
|
diff = auto
|
||||||
|
status = auto
|
||||||
|
|
||||||
|
[color "branch"]
|
||||||
|
current = yellow reverse
|
||||||
|
local = yellow
|
||||||
|
remote = green
|
||||||
|
|
||||||
|
[color "diff"]
|
||||||
|
meta = yellow bold
|
||||||
|
frag = magenta bold
|
||||||
|
old = red bold
|
||||||
|
new = green bold
|
||||||
|
|
||||||
|
[color "status"]
|
||||||
|
added = yellow
|
||||||
|
changed = green
|
||||||
|
untracked = cyan
|
||||||
|
'''
|
||||||
|
gitconfig_template.write_text(gitconfig_content, encoding='utf-8')
|
||||||
|
|
||||||
|
# Vim Template
|
||||||
|
vim_dir = templates_dir / "vim"
|
||||||
|
vim_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
vimrc_template = vim_dir / "vimrc.j2"
|
||||||
|
vimrc_content = '''" Vim configuration for {{ machine }}
|
||||||
|
" Generated by dotfiles system
|
||||||
|
|
||||||
|
" Basic settings
|
||||||
|
set nocompatible
|
||||||
|
set number
|
||||||
|
set ruler
|
||||||
|
set showcmd
|
||||||
|
set incsearch
|
||||||
|
set hlsearch
|
||||||
|
|
||||||
|
" Indentation
|
||||||
|
set autoindent
|
||||||
|
set smartindent
|
||||||
|
set tabstop=4
|
||||||
|
set shiftwidth=4
|
||||||
|
set expandtab
|
||||||
|
|
||||||
|
" Colors and theme
|
||||||
|
syntax enable
|
||||||
|
set background=dark
|
||||||
|
|
||||||
|
{% if machine == 'desktop' %}
|
||||||
|
" Desktop-specific settings
|
||||||
|
set mouse=a
|
||||||
|
set clipboard=unnamedplus
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
" File handling
|
||||||
|
set encoding=utf-8
|
||||||
|
set fileencoding=utf-8
|
||||||
|
set backspace=indent,eol,start
|
||||||
|
|
||||||
|
" Backup settings
|
||||||
|
set nobackup
|
||||||
|
set nowritebackup
|
||||||
|
set noswapfile
|
||||||
|
|
||||||
|
" Search settings
|
||||||
|
set ignorecase
|
||||||
|
set smartcase
|
||||||
|
|
||||||
|
" Key mappings
|
||||||
|
let mapleader = ","
|
||||||
|
nnoremap <leader>w :w<CR>
|
||||||
|
nnoremap <leader>q :q<CR>
|
||||||
|
|
||||||
|
{% if machine == 'server' %}
|
||||||
|
" Server-specific: lighter config
|
||||||
|
set laststatus=1
|
||||||
|
{% else %}
|
||||||
|
" Desktop/Laptop: enhanced features
|
||||||
|
set laststatus=2
|
||||||
|
set wildmenu
|
||||||
|
set wildmode=list:longest
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
" Load local vimrc if exists
|
||||||
|
if filereadable(expand("~/.vimrc.local"))
|
||||||
|
source ~/.vimrc.local
|
||||||
|
endif
|
||||||
|
'''
|
||||||
|
vimrc_template.write_text(vimrc_content, encoding='utf-8')
|
||||||
|
|
||||||
|
print(f"✅ Beispiel-Templates erstellt in: {templates_dir}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Erstelle Beispiel-Templates wenn direkt ausgeführt
|
||||||
|
create_example_templates()
|
||||||
4
templates/git/gitconfig
Normal file
4
templates/git/gitconfig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[user]
|
||||||
|
name = cseyfferth
|
||||||
|
mail = mail@seyfferth.dev
|
||||||
|
email = mail@seyfferth.dev
|
||||||
49
templates/git/gitconfig.j2
Normal file
49
templates/git/gitconfig.j2
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
[user]
|
||||||
|
name = {{ git_name | default("Your Name") }}
|
||||||
|
email = {{ git_email | default("your.email@example.com") }}
|
||||||
|
|
||||||
|
[core]
|
||||||
|
editor = {{ editor | default("vim") }}
|
||||||
|
autocrlf = input
|
||||||
|
excludesfile = ~/.gitignore_global
|
||||||
|
|
||||||
|
[push]
|
||||||
|
default = simple
|
||||||
|
|
||||||
|
[pull]
|
||||||
|
rebase = false
|
||||||
|
|
||||||
|
[alias]
|
||||||
|
st = status
|
||||||
|
co = checkout
|
||||||
|
br = branch
|
||||||
|
ci = commit
|
||||||
|
lg = log --oneline --graph --decorate
|
||||||
|
unstage = reset HEAD --
|
||||||
|
|
||||||
|
{% if machine == 'desktop' %}
|
||||||
|
[diff]
|
||||||
|
tool = meld
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
[color]
|
||||||
|
ui = auto
|
||||||
|
branch = auto
|
||||||
|
diff = auto
|
||||||
|
status = auto
|
||||||
|
|
||||||
|
[color "branch"]
|
||||||
|
current = yellow reverse
|
||||||
|
local = yellow
|
||||||
|
remote = green
|
||||||
|
|
||||||
|
[color "diff"]
|
||||||
|
meta = yellow bold
|
||||||
|
frag = magenta bold
|
||||||
|
old = red bold
|
||||||
|
new = green bold
|
||||||
|
|
||||||
|
[color "status"]
|
||||||
|
added = yellow
|
||||||
|
changed = green
|
||||||
|
untracked = cyan
|
||||||
23
templates/shell/bashrc
Normal file
23
templates/shell/bashrc
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
OS=$(cat /etc/os-release | grep '^ID=' | cut -d "=" -f2)
|
||||||
|
|
||||||
|
|
||||||
|
. /etc/skel/.bashrc
|
||||||
|
|
||||||
|
alias ls='ls --color=auto'
|
||||||
|
alias grep='grep --color=auto'
|
||||||
|
alias codium="codium --ozone-platform-hint=wayland"
|
||||||
|
|
||||||
|
export PATH=~/.cargo/bin:~/.bin:/home/cseyfferth/.local/bin:$PATH
|
||||||
|
# PS1='[\u@\h \W]\$ '
|
||||||
|
|
||||||
|
export BW_SESSION="+GCKSMq51RO3R/+zNblGqegfqZnA+FnywqaNEuiakQafhtah/4jlenScb387utdOQoyhx+5AorSUOFV149zqnA=="
|
||||||
|
export SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/gcr/ssh
|
||||||
|
|
||||||
|
export PATH=~/node_modules/.bin:$PATH
|
||||||
|
export PATH=~/.bin/:$PATH
|
||||||
|
|
||||||
|
[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path bash)"
|
||||||
|
|
||||||
|
#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
|
||||||
|
export SDKMAN_DIR="$HOME/.sdkman"
|
||||||
|
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||||
39
templates/shell/bashrc.j2
Normal file
39
templates/shell/bashrc.j2
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Bash configuration for {{ machine }} ({{ hostname }})
|
||||||
|
# Generated by dotfiles system
|
||||||
|
|
||||||
|
# History settings
|
||||||
|
HISTSIZE=10000
|
||||||
|
HISTFILESIZE=20000
|
||||||
|
HISTCONTROL=ignoreboth
|
||||||
|
|
||||||
|
# Aliases
|
||||||
|
alias ll='ls -alF'
|
||||||
|
alias la='ls -A'
|
||||||
|
alias l='ls -CF'
|
||||||
|
alias grep='grep --color=auto'
|
||||||
|
|
||||||
|
{% if machine == 'server' %}
|
||||||
|
# Server-specific settings
|
||||||
|
alias logs='sudo journalctl -f'
|
||||||
|
alias status='systemctl status'
|
||||||
|
{% elif machine == 'desktop' %}
|
||||||
|
# Desktop-specific settings
|
||||||
|
alias open='xdg-open'
|
||||||
|
alias screenshot='gnome-screenshot'
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# User-specific settings
|
||||||
|
export EDITOR=vim
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
|
# Machine-specific additions
|
||||||
|
{% if machine == 'laptop' %}
|
||||||
|
# Laptop power settings
|
||||||
|
alias hibernate='systemctl hibernate'
|
||||||
|
alias suspend='systemctl suspend'
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Load local bashrc if it exists
|
||||||
|
if [ -f ~/.bashrc.local ]; then
|
||||||
|
source ~/.bashrc.local
|
||||||
|
fi
|
||||||
85
templates/shell/zshrc
Normal file
85
templates/shell/zshrc
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
bindkey '[3~' delete-char
|
||||||
|
|
||||||
|
function load_venv_on_cd() {
|
||||||
|
if [[ -d "./venv" ]]; then
|
||||||
|
source "./venv/bin/activate"
|
||||||
|
echo "Python venv aktiviert: ./venv"
|
||||||
|
elif [[ -d "./.venv" ]]; then
|
||||||
|
source "./.venv/bin/activate"
|
||||||
|
echo "Python venv aktiviert: ./.venv"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! "$TERM_PROGRAM" == "vscode" ]]; then
|
||||||
|
|
||||||
|
typeset -g POWERLEVEL9K_TERM_SHELL_INTEGRATION=true
|
||||||
|
# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
|
||||||
|
# Initialization code that may require console input (password prompts, [y/n]
|
||||||
|
# confirmations, etc.) must go above this block; everything else may go below.
|
||||||
|
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
|
||||||
|
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# The following lines were added by compinstall
|
||||||
|
|
||||||
|
zstyle ':completion:*' completer _expand _complete _ignored _correct _approximate
|
||||||
|
zstyle ':completion:*' group-name ''
|
||||||
|
zstyle :compinstall filename '/home/cseyfferth/.zshrc'
|
||||||
|
|
||||||
|
autoload -Uz compinit
|
||||||
|
compinit
|
||||||
|
# End of lines added by compinstall
|
||||||
|
# Lines configured by zsh-newuser-install
|
||||||
|
HISTFILE=~/.histfile
|
||||||
|
HISTSIZE=1000
|
||||||
|
SAVEHIST=1000
|
||||||
|
# End of lines configured by zsh-newuser-install
|
||||||
|
|
||||||
|
PROMPT="[%n@%m %~]$ "
|
||||||
|
RPROMPT='%(?.%F{green}√.%F{red}?%?)%f~'
|
||||||
|
|
||||||
|
source /usr/share/zsh/plugins/fast-syntax-highlighting/fast-syntax-highlighting.plugin.zsh
|
||||||
|
#source /usr/share/zsh/scripts/zplug/init.zsh
|
||||||
|
source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme
|
||||||
|
|
||||||
|
# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
|
||||||
|
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
|
||||||
|
|
||||||
|
zstyle ':completion:*:(ssh|scp|sftp):*:hosts' ignored-patterns '*'
|
||||||
|
# zstyle ':completion:*:ssh:*' config-file ~/.ssh/config
|
||||||
|
|
||||||
|
zstyle ':completion:*' insert-tab false
|
||||||
|
autoload -Uz compinit
|
||||||
|
compinit
|
||||||
|
zstyle ':completion:*' menu select
|
||||||
|
|
||||||
|
export BW_SESSION="+GCKSMq51RO3R/+zNblGqegfqZnA+FnywqaNEuiakQafhtah/4jlenScb387utdOQoyhx+5AorSUOFV149zqnA=="
|
||||||
|
export SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/gcr/ssh
|
||||||
|
export PATH=/home/cseyfferth/.bin/:$PATH
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "🚀 Powerlevel10k instant prompt is disabled in Visual Studio Code terminal. To enable, set TERM_PROGRAM to a value other than 'vscode'."
|
||||||
|
[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh 2>/dev/null)" 2>/dev/null || . "$(code-insiders --locate-shell-integration-path zsh 2>/dev/null)"
|
||||||
|
# wenn $? = 0 dann ist der letzte Befehl erfolgreich ausgeführt worden
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
echo "🚀 Visual Studio Code shell integration enabled."
|
||||||
|
else
|
||||||
|
echo "🚀 Visual Studio Code shell integration not found. Please make sure that 'code' is in PATH."
|
||||||
|
fi
|
||||||
|
# set prompt
|
||||||
|
PROMPT="[%n@%m %~]$ "
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PATH=/home/cseyfferth/.cargo/bin:$PATH
|
||||||
|
|
||||||
|
autoload -U add-zsh-hook
|
||||||
|
add-zsh-hook chpwd load_venv_on_cd
|
||||||
|
|
||||||
|
#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
|
||||||
|
export SDKMAN_DIR="$HOME/.sdkman"
|
||||||
|
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||||
|
|
||||||
|
# Created by `pipx` on 2025-06-19 10:50:20
|
||||||
|
export PATH="$PATH:/home/cseyfferth/.local/bin"
|
||||||
230
templates/ssh/config
Normal file
230
templates/ssh/config
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
include ~/.ssh/docker_config
|
||||||
|
#host *
|
||||||
|
# controlmaster auto
|
||||||
|
# controlpath /tmp/ssh-%r@%h:%p
|
||||||
|
|
||||||
|
Host jupyter
|
||||||
|
User cseyfferth
|
||||||
|
ProxyCommand websocat --binary -H='Authorization: token 364c79a34c3d48509cefe2689c991541' asyncstdio: wss://jupyter.informatik.hs-bremerhaven.de/user/cseyfferth/sshd/
|
||||||
|
|
||||||
|
Host bs
|
||||||
|
Hostname localhost
|
||||||
|
Port 2222
|
||||||
|
User ubuntu
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile /dev/null
|
||||||
|
LogLevel ERROR
|
||||||
|
|
||||||
|
Host lima
|
||||||
|
Hostname localhost
|
||||||
|
Port 60022
|
||||||
|
IdentityFile ~/.lima/_config/user
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile /dev/null
|
||||||
|
LogLevel ERROR
|
||||||
|
|
||||||
|
Host limawork
|
||||||
|
Hostname localhost
|
||||||
|
Port 40375
|
||||||
|
IdentityFile ~/.lima/_config/user
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile /dev/null
|
||||||
|
LogLevel ERROR
|
||||||
|
|
||||||
|
Host midgard
|
||||||
|
Hostname 157.173.99.95
|
||||||
|
User cseyfferth
|
||||||
|
Port 22
|
||||||
|
|
||||||
|
Host l-firewall
|
||||||
|
Hostname 194.94.217.73
|
||||||
|
User l-cseyfferth
|
||||||
|
|
||||||
|
Host diso
|
||||||
|
Hostname diso.informatik.hs-bremerhaven.de
|
||||||
|
User cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host l-diso
|
||||||
|
Hostname diso.informatik.hs-bremerhaven.de
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host l-brewster
|
||||||
|
Hostname 194.94.217.32
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-docker-services
|
||||||
|
Hostname 194.94.217.110
|
||||||
|
ProxyJump snowden
|
||||||
|
User l-cseyfferth
|
||||||
|
|
||||||
|
Host l-tanenbaum
|
||||||
|
Hostname 194.94.217.120
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-dmp-website
|
||||||
|
HostName 194.94.217.115
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
Port 8080
|
||||||
|
|
||||||
|
Host l-dmp-nextcloud
|
||||||
|
HostName 194.94.217.117
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-hamilton
|
||||||
|
Hostname 194.94.217.85
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-hilbert
|
||||||
|
HostName 194.94.217.96
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-ecomat
|
||||||
|
Hostname ecomat.hs-bremerhaven.de
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host jitsi-test
|
||||||
|
Hostname jitsi-test.hs-bremerhaven.de
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host bbb-ext
|
||||||
|
Hostname 136.243.192.156
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
|
||||||
|
Host l-noether
|
||||||
|
Hostname 194.94.217.99
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host labormbt
|
||||||
|
Hostname 194.94.217.75
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host l-bladerunner
|
||||||
|
Hostname 194.94.217.80
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host honeypot
|
||||||
|
Hostname 10.10.10.10
|
||||||
|
Port 65535
|
||||||
|
User cseyfferth
|
||||||
|
ProxyJump asgard
|
||||||
|
|
||||||
|
Host chengisao
|
||||||
|
Hostname 194.94.217.111
|
||||||
|
User cseyfferth
|
||||||
|
IdentityFile ~/.ssh/chengisao_cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host weizenbaum
|
||||||
|
Hostname 194.94.217.82
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host demo
|
||||||
|
Hostname 194.94.217.101
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host mitnick
|
||||||
|
Hostname 5.161.51.130
|
||||||
|
|
||||||
|
Host seyfferthHopper
|
||||||
|
Hostname seyfferth.dev
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host hopper
|
||||||
|
User cseyfferth
|
||||||
|
Hostname hopper.informatik.hs-bremerhaven.de
|
||||||
|
Port 8080
|
||||||
|
|
||||||
|
Host l-hopper
|
||||||
|
Hostname hopper.informatik.hs-bremerhaven.de
|
||||||
|
Port 8080
|
||||||
|
User l-cseyfferth
|
||||||
|
IdentityFile ~/.ssh/id_ed25519_sk_solo2_blue
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host gitea
|
||||||
|
Hostname seyfferth.dev
|
||||||
|
Port 2222
|
||||||
|
User git
|
||||||
|
|
||||||
|
Host rhodes
|
||||||
|
Hostname 194.94.217.72
|
||||||
|
ProxyJump snowden
|
||||||
|
User l-cseyfferth
|
||||||
|
|
||||||
|
Host asgard
|
||||||
|
HostName 144.91.94.63
|
||||||
|
Port 22
|
||||||
|
User cseyfferth
|
||||||
|
|
||||||
|
Host l-clarke
|
||||||
|
HostName 194.94.217.105
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host snowden
|
||||||
|
Hostname 194.94.217.95
|
||||||
|
User l-cseyfferth
|
||||||
|
Port 443
|
||||||
|
|
||||||
|
Host tangens
|
||||||
|
Hostname 194.94.217.84
|
||||||
|
User l-cseyfferth
|
||||||
|
Port 443
|
||||||
|
|
||||||
|
Host hopperneu
|
||||||
|
Hostname 194.94.217.93
|
||||||
|
Port 443
|
||||||
|
|
||||||
|
Host l-gitlab
|
||||||
|
Hostname 194.94.217.77
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-mockapetris
|
||||||
|
HostName 194.94.217.11
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-ritchie
|
||||||
|
Hostname 194.94.217.78
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host l-turing
|
||||||
|
HostName 194.94.217.97
|
||||||
|
User l-cseyfferth
|
||||||
|
ProxyJump snowden
|
||||||
|
|
||||||
|
Host mydocker
|
||||||
|
Hostname docker-server
|
||||||
|
User docker-cseyfferth
|
||||||
|
Port 20500
|
||||||
|
ProxyJump hopper
|
||||||
|
# IdentityFile ~/.ssh/id_rsa_hopper
|
||||||
|
|
||||||
|
Host vm40
|
||||||
|
Hostname 194.94.217.83
|
||||||
|
Port 14022
|
||||||
|
ProxyJump hopper
|
||||||
|
|
||||||
|
Host vm41
|
||||||
|
Hostname 194.94.217.83
|
||||||
|
Port 14122
|
||||||
|
ProxyJump hopper
|
||||||
61
templates/vim/vimrc.j2
Normal file
61
templates/vim/vimrc.j2
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
" Vim configuration for {{ machine }}
|
||||||
|
" Generated by dotfiles system
|
||||||
|
|
||||||
|
" Basic settings
|
||||||
|
set nocompatible
|
||||||
|
set number
|
||||||
|
set ruler
|
||||||
|
set showcmd
|
||||||
|
set incsearch
|
||||||
|
set hlsearch
|
||||||
|
|
||||||
|
" Indentation
|
||||||
|
set autoindent
|
||||||
|
set smartindent
|
||||||
|
set tabstop=4
|
||||||
|
set shiftwidth=4
|
||||||
|
set expandtab
|
||||||
|
|
||||||
|
" Colors and theme
|
||||||
|
syntax enable
|
||||||
|
set background=dark
|
||||||
|
|
||||||
|
{% if machine == 'desktop' %}
|
||||||
|
" Desktop-specific settings
|
||||||
|
set mouse=a
|
||||||
|
set clipboard=unnamedplus
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
" File handling
|
||||||
|
set encoding=utf-8
|
||||||
|
set fileencoding=utf-8
|
||||||
|
set backspace=indent,eol,start
|
||||||
|
|
||||||
|
" Backup settings
|
||||||
|
set nobackup
|
||||||
|
set nowritebackup
|
||||||
|
set noswapfile
|
||||||
|
|
||||||
|
" Search settings
|
||||||
|
set ignorecase
|
||||||
|
set smartcase
|
||||||
|
|
||||||
|
" Key mappings
|
||||||
|
let mapleader = ","
|
||||||
|
nnoremap <leader>w :w<CR>
|
||||||
|
nnoremap <leader>q :q<CR>
|
||||||
|
|
||||||
|
{% if machine == 'server' %}
|
||||||
|
" Server-specific: lighter config
|
||||||
|
set laststatus=1
|
||||||
|
{% else %}
|
||||||
|
" Desktop/Laptop: enhanced features
|
||||||
|
set laststatus=2
|
||||||
|
set wildmenu
|
||||||
|
set wildmode=list:longest
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
" Load local vimrc if exists
|
||||||
|
if filereadable(expand("~/.vimrc.local"))
|
||||||
|
source ~/.vimrc.local
|
||||||
|
endif
|
||||||
Reference in New Issue
Block a user