Initial commit: Python dotfiles system

This commit is contained in:
2025-07-03 17:14:52 +02:00
commit 6b4f19c2dc
20 changed files with 2185 additions and 0 deletions

309
dotfiles.py Executable file
View 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()