Skip to main content

Version_0.5 xfreerdp - Skript

RDP-Launcher mit xfreerdp3 einrichten & nutzen

Diese Anleitung führt dich vom leeren Linux-System bis zur laufenden RDP-Sitzung. Sie ist so geschrieben, dass du keinerlei Erfahrung mit Linux, Python oder virtuellen Umgebungen (venv) benötigst. Arbeite jeden Schritt nacheinander ab – dann hast du am Ende:

  • einen sauberen Projekt-Ordner
  • eine eigene Python-Umgebung (damit du nichts „kaputt“ installierst)
  • das fertige Skript xfreerdp_launcher.py
  • eine Beispiel-config.json mit zwei Standorten
  • einen Eintrag im System-Keyring, damit Passwörter sicher gespeichert werden

1 Grund­voraus­setzungen prüfen

  1. Linux-Shell öffnen: Ctrl+Alt+T drücken oder „Terminal“ aus dem Menü wählen.
  2. Python & Pip vorhanden?
    python3 --version   # sollte ≥ 3.9 anzeigen
    pip3    --version   # sollte eine Versionsnummer zeigen
    Falls eines fehlt (Beispiel für Debian/Ubuntu):
    sudo apt update
    sudo apt install python3 python3-pip python3-venv
  3. xfreerdp3 installieren:
    sudo apt install freerdp3-x11
  4. System-Keyring vorhanden?
    Bei Gnome, KDE, Cinnamon … schon integriert. Keine Aktion nötig.

2 Projekt­ordner anlegen & virtuelle Umgebung erstellen

  1. Verzeichnis anlegen:
    mkdir -p ~/rdp-launcher
    cd ~/rdp-launcher
  2. Virtuelle Umgebung (venv) erstellen:
    python3 -m venv .venv
  3. venv aktivieren (wichtig – bei jedem neuen Terminal!):
    source .venv/bin/activate
    Links in der Eingabe­aufforderung erscheint jetzt (.venv).
  4. Abhängigkeit keyring installieren:
    pip install keyring
Tipp: Immer erst source .venv/bin/activate ausführen, bevor du an dem Projekt weiterarbeitest.

3 Skript speichern

Lege die Datei xfreerdp_launcher.py im Projekt­ordner an und kopiere den kompletten, kommentierten Quelltext hinein:

▶ Python-Code anzeigen / ausklappen
#!/usr/bin/env python3
# ──────────────────────────────────────────────────────────────────────────────
# xfreerdp_launcher.py
# Ein komfortabler Launcher für xfreerdp3-Sessions mit:
#   • farbiger TUI-Menüführung
#   • Passwort-Handling via systemweiten Keyring
#   • automatischem Neuladen der config.json, damit Änderungen sofort wirksam
#     werden, ohne das Programm neu zu starten
# Erfordert:
#   – Python ≥ 3.9
#   – Paket „keyring“
#   – gültige config.json (Beispiel siehe unten)
# ──────────────────────────────────────────────────────────────────────────────

# ----------------------------- Standardbibliotheken --------------------------
import argparse           # CLI-Argument-Parsing
import json               # Einlesen / Parsen der config.json
import subprocess         # Ausführen externer Programme (xfreerdp3)
import sys                # System-Exit & Ausnahmebehandlung
from pathlib import Path  # Plattform-unabhängige Pfadangaben
from getpass import getpass  # Sichere Passwort-Abfrage im Terminal
import keyring            # Zugriff auf den System-Keyring
import os                 # Terminal-Steuerung (clear)

# ----------------------------- Konstanten ------------------------------------
CONFIG_FILE = Path("./config.json").resolve()   # Vollständiger Pfad zur Konfig
SERVICE = "xfreerdp_launcher"                   # Namespace im Keyring

# ----------------------------- Hilfsfunktionen -------------------------------
def farbig(text: str, farbe: str) -> str:
    """
    Liefert einen farbigen String für ANSI-Terminals.
    """
    farben = {
        "black": "\033[30m", "red": "\033[31m", "green": "\033[32m",
        "yellow": "\033[33m", "blue": "\033[34m", "magenta": "\033[35m",
        "cyan": "\033[36m", "white": "\033[37m", "gray": "\033[90m",
        "bright_red": "\033[91m", "bright_green": "\033[92m",
        "bright_yellow": "\033[93m", "bright_blue": "\033[94m",
        "bright_magenta": "\033[95m", "bright_cyan": "\033[96m",
        "bright_white": "\033[97m", "bold": "\033[1m",
        "underline": "\033[4m", "reversed": "\033[7m", "reset": "\033[0m"
    }
    return f"{farben.get(farbe, '')}{text}{farben['reset']}"

# -----------------------------------------------------------------------------
def load_config_safe() -> dict:
    """
    Lädt config.json robust und gibt ein leeres Dict bei Fehlern zurück.
    """
    try:
        if not CONFIG_FILE.exists():
            print(farbig(f"⚠️  config.json nicht gefunden: {CONFIG_FILE}", "yellow"))
            return {}
        return json.loads(CONFIG_FILE.read_text())
    except json.JSONDecodeError as e:
        print(farbig(f"⚠️  Fehler in config.json: {e}", "red"))
        return {}

# -----------------------------------------------------------------------------
def fetch_password(profile_alias: str, domain: str, user: str) -> str:
    """
    Holt (oder fragt) das Passwort für eine Domäne aus dem Keyring.
    """
    cred_id = f"{profile_alias}|{domain}|{user}"
    pw = keyring.get_password(SERVICE, cred_id)
    if pw is None:
        pw = getpass(f"Passwort für {user}@{domain}: ")
        if not pw:
            sys.exit("Abbruch – leeres Passwort.")
        keyring.set_password(SERVICE, cred_id, pw)
        print(farbig("✅  Passwort im System-Keyring gespeichert.", "green"))
    return pw

# -----------------------------------------------------------------------------
def start_rdp(ip: str, domain: str, user: str, password: str, extra: list[str]):
    """
    Startet eine RDP-Sitzung via xfreerdp3.
    """
    cmd = [
        "xfreerdp3",
        f"/u:{user}", f"/d:{domain}", f"/v:{ip}",
        "/dynamic-resolution", "/clipboard", "/cert:ignore",
        f"/p:{password}"
    ] + extra
    try:
        subprocess.Popen(cmd)  # nicht blockierend
    except Exception as e:
        print(farbig(f"❌ Fehler beim Starten: {e}", "red"))

# -----------------------------------------------------------------------------
def handle_host(entry: dict,
                credentials: dict,
                default_cred: str,
                extra_args: list[str]):
    """
    Öffnet einen einzelnen Host aus dem Menü.
    """
    cred = entry.get("cred") or default_cred
    profile = credentials.get(cred)
    if not profile:
        print(farbig(f"❌ Credential-Alias '{cred}' nicht gefunden.", "red"))
        input("⏎ Weiter...")
        return
    password = fetch_password(cred, profile["domain"], profile["user"])
    start_rdp(entry["host"], profile["domain"], profile["user"],
              password, extra_args)
    print(farbig(f"💻 Sitzung zu {entry['alias']} gestartet.", "cyan"))
    input("⏎ Weiter...")

# -----------------------------------------------------------------------------
def navigate_menu(section: dict,
                  credentials: dict,
                  default_cred: str,
                  extra_args: list[str],
                  path: str = "",
                  offene: set = set()):
    """
    Rekursive Menü-Navigation.
    """
    is_top_level = (path == "")

    while True:
        # ----- Live-Reload der Konfiguration -----
        config = load_config_safe()
        new_groups       = config.get("groups", {})
        new_credentials  = config.get("credentials", {})
        new_default_cred = config.get("default_cred", default_cred)

        if is_top_level:
            section = {"groups": new_groups}
        credentials  = new_credentials
        default_cred = new_default_cred

        # ----- Bildschirm leeren & Kopfzeile -----
        os.system("clear")
        print(farbig(f"=== RDP-Kurzwahl {path} ===\n", "bold"))

        groups = section.get("groups", {})
        hosts  = section.get("hosts", [])
        index_map = {}

        # ----- Einträge auflisten -----
        i = 1
        if is_top_level:
            for name, content in groups.items():
                color = content.get("color", "reset")
                print(f" {i:>2}) {farbig(name, color)} →")
                index_map[str(i)] = ("group_flat", content, f"{path}/{name}")
                i += 1
        else:
            for h in hosts:
                alias = h["alias"]
                markiert = farbig("✅", "green") if alias in offene else "  "
                print(f" {i:>2}) {farbig(alias, h.get('color', 'reset'))} "
                      f"– {h['host']}  {markiert}")
                index_map[str(i)] = ("host", h)
                i += 1

        # ----- Standardoptionen -----
        print("\n")
        print(farbig("  b) Zurück", "bold"))
        print(farbig("  q) Beenden", "bold"))
        sel = input(farbig("Auswahl: ", "bold")).strip()

        # ----- Eingabe auswerten -----
        if sel == "q":
            sys.exit("Beende Launcher.")
        elif sel == "b":
            return
        elif sel in index_map:
            typ, eintrag, *rest = index_map[sel]
            if typ == "group_flat":
                navigate_menu({"hosts": eintrag.get("hosts", [])},
                              credentials, default_cred, extra_args,
                              rest[0], offene)
            elif typ == "host":
                offene.add(eintrag["alias"])
                handle_host(eintrag, credentials, default_cred, extra_args)
        else:
            print(farbig("❌ Ungültige Auswahl.", "red"))
            input("⏎ Weiter...")

# ----------------------------- Haupteinstieg ---------------------------------
def main():
    """
    Parses CLI-Optionen und ruft das Hauptmenü auf.
    """
    parser = argparse.ArgumentParser(
        description="xfreerdp Launcher mit Keyring"
    )
    parser.add_argument(
        "freerdp_args",
        nargs=argparse.REMAINDER,
        help="zusätzliche xfreerdp3-Parameter (/f /multimon …)"
    )
    args = parser.parse_args()
    offene_sitzungen = set()

    while True:
        config       = load_config_safe()
        credentials  = config.get("credentials", {})
        default_cred = config.get("default_cred", "")
        groups       = config.get("groups")

        if not groups:
            print(farbig("⚠️  Keine Gruppen in config.json gefunden.", "yellow"))
            input("⏎ Weiter...")
            continue

        navigate_menu({"groups": groups}, credentials, default_cred,
                      args.freerdp_args, "", offene_sitzungen)

# ----------------------------- Skript-Guard ----------------------------------
if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n[Abbruch]")

4 Beispiel-config.json anlegen

Lege im selben Ordner die Datei config.json an:

▶ JSON-Konfiguration anzeigen / ausklappen
{
  "default_cred": "user1",

  "credentials": {
    "user1": { "domain": "FIRMENNETZ", "user": "max.mustermann" },
    "user2": { "domain": "INTRANET",   "user": "maria.musterfrau" }
  },

  "groups": {
    "Standort_A": {
      "color": "bright_blue",
      "hosts": [
        { "alias": "Server-A1", "host": "10.0.10.11", "color": "bright_green",  "cred": "user1" },
        { "alias": "Server-A2", "host": "10.0.10.12", "color": "bright_magenta","cred": "user2" }
      ]
    },
    "Standort_B": {
      "color": "bright_yellow",
      "hosts": [
        { "alias": "Server-B1", "host": "10.0.20.21", "color": "bright_cyan",   "cred": "user1" },
        { "alias": "Server-B2", "host": "10.0.20.22", "color": "bright_red",    "cred": "user2" }
      ]
    }
  }
}

Passe Alias-Namen, IP-Adressen und Farben nach Bedarf an. Die gewählten Server liegen alle im privaten Netz 10.0.0.0/16.


5 Alles ausführen – vom Terminalstart bis zur ersten RDP-Sitzung

  1. Terminal öffnen (meist Ctrl+Alt+T oder „Terminal“ im Menü).
  2. Zum Projektordner wechseln
    cd ~/rdp-launcher
  3. Virtuelle Umgebung aktivieren
    source .venv/bin/activate
    Du musst diesen Befehl jedes Mal ausführen, wenn du ein neues Terminal öffnest. Die Prompt zeigt jetzt etwas wie (.venv) user@host:~/rdp-launcher$
  4. Skript beim ersten Mal ausführbar machen
    chmod +x xfreerdp_launcher.py
    (Nur einmal nötig. Danach bleibt die Datei ausführbar.)
  5. Launcher starten
    ./xfreerdp_launcher.py               # Standard
    ./xfreerdp_launcher.py /f            # z. B. direkt Vollbild
    ./xfreerdp_launcher.py /multimon /p: # weitere xfreerdp-Parameter

    Alternative – falls „Permission denied“ oder du das Executable-Bit nicht setzen möchtest:

    python3 xfreerdp_launcher.py

     

  6. Menü durchklicken
    1. Standort wählen (z. B. Standort_A)
    2. Server wählen (z. B. Server-A1)
  7. Erste Passwortabfrage Beim allerersten Verbindungsversuch fragt das Script nach dem Passwort (kein Echo im Terminal). Eingeben → Enter drücken. Der Keyring speichert es sicher; beim nächsten Mal wird nicht mehr gefragt.
  8. Sitzung schließen RDP-Fenster beenden → im Menü q eingeben, um den Launcher zu schließen.
Kurzzusammenfassung für Azubis:
Ctrl+Alt+T  cd ~/rdp-launcher  source .venv/bin/activate  ./xfreerdp_launcher.py  → Standort → Server → Passwort → fertig!

6 Typische Fehler & Lösungen

Problem Ursache & Fix
command not found: xfreerdp3 freerdp3-x11 nicht installiert oder nicht im $PATH. sudo apt install freerdp3-x11
ModuleNotFoundError: keyring venv nicht aktiviert oder pip install keyring vergessen.
Passwort wird bei jedem Start abgefragt Kein Keyring-Backend vorhanden (z. B. minimale Server-Installation). Installiere gnome-keyring oder kwallet.

7 Nächste Schritte für Fort­ge­schrittene

  • Weitere Hosts, Farben oder Standorte in config.json ergänzen.
  • /f (Fullscreen) oder /multimon als Zusatz­parameter ausprobieren.
  • Projekt in Git versionieren.
  • Symlink in /usr/local/bin erstellen, um den Launcher global aufzurufen.

🎉 Glückwunsch! Du hast deinen ersten Python-Launcher inkl. venv, Abhängigkeiten und Konfiguration eingerichtet.