--- # ansible/playbook.yml # RDP Thin Client Setup - Main Playbook v14 - name: Setup RDP Thin Client hosts: rdp_clients become: yes tasks: # Non-free Repos aktivieren - name: Enable non-free and contrib repositories lineinfile: path: /etc/apt/sources.list regexp: '^deb http://deb.debian.org/debian/ {{ ansible_distribution_release }} main$' line: 'deb http://deb.debian.org/debian/ {{ ansible_distribution_release }} main contrib non-free non-free-firmware' backrefs: yes when: ansible_distribution == 'Debian' ignore_errors: yes - name: Enable non-free and contrib for security updates lineinfile: path: /etc/apt/sources.list regexp: '^deb http://security.debian.org/debian-security {{ ansible_distribution_release }}-security main$' line: 'deb http://security.debian.org/debian-security {{ ansible_distribution_release }}-security main contrib non-free non-free-firmware' backrefs: yes when: ansible_distribution == 'Debian' ignore_errors: yes # === BASE SYSTEM === - name: Update APT cache apt: update_cache: yes cache_valid_time: 3600 - name: Ensure sudo is installed apt: name: sudo state: present update_cache: yes become: yes - name: Remove PulseAudio if present (conflicts with PipeWire) apt: name: - pulseaudio - pulseaudio-utils state: absent ignore_errors: yes - name: Install base packages apt: name: "{{ item }}" state: present loop: "{{ base_packages }}" ignore_errors: yes - name: Ensure util-linux is installed (provides lsblk) apt: name: util-linux state: present - name: Install udiskie for USB automount apt: name: udiskie state: present - name: Update APT cache again for emoji fonts apt: update_cache: yes - name: Install Emoji fonts for GUI apt: name: fonts-noto-color-emoji state: present - name: Install FreeRDP (try multiple package names for Debian 12/13 compatibility) apt: name: "{{ item }}" state: present loop: - freerdp3-x11 - freerdp2-x11 ignore_errors: yes - name: Find FreeRDP binary location shell: which xfreerdp 2>/dev/null || which xfreerdp3 2>/dev/null || echo "not_found" register: freerdp_location changed_when: false - name: Create xfreerdp symlink if only xfreerdp3 exists file: src: /usr/bin/xfreerdp3 dest: /usr/bin/xfreerdp state: link when: "'xfreerdp3' in freerdp_location.stdout and 'not_found' not in freerdp_location.stdout" ignore_errors: yes - name: Display FreeRDP installation status debug: msg: "FreeRDP found at: {{ freerdp_location.stdout }}" - name: Create thin client user user: name: "{{ thin_client_user }}" password: "{{ thin_client_password }}" shell: /bin/bash groups: audio,video,bluetooth,plugdev,netdev,sudo append: yes - name: Allow rdpuser to use sudo for package installation lineinfile: path: /etc/sudoers.d/rdpuser line: '{{ thin_client_user }} ALL=(ALL) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get, /sbin/reboot, /sbin/shutdown' create: yes mode: '0440' validate: 'visudo -cf %s' - name: Create media directory for USB mounts file: path: /media/rdpuser state: directory owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0755' - name: Create udiskie config directory file: path: /home/{{ thin_client_user }}/.config/udiskie state: directory owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0755' - name: Configure udiskie to mount in /media/rdpuser copy: dest: /home/{{ thin_client_user }}/.config/udiskie/config.yml owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0644' content: | device_config: - id_type: filesystem mount_path: /media/rdpuser/{device_file} - name: Create LightDM config directory file: path: /etc/lightdm/lightdm.conf.d state: directory mode: '0755' - name: Configure auto-login for LightDM copy: dest: /etc/lightdm/lightdm.conf.d/50-autologin.conf content: | [Seat:*] autologin-user={{ thin_client_user }} autologin-user-timeout=0 user-session=openbox # === OPENBOX CONFIGURATION === - name: Create openbox config directory file: path: /home/{{ thin_client_user }}/.config/openbox state: directory owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0755' - name: Configure Openbox autostart copy: dest: /home/{{ thin_client_user }}/.config/openbox/autostart owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0755' content: | #!/bin/bash #Nummernblock aktivieren numlockx on & # Auto-load display configuration # Try to load the last saved profile, fallback to common, then auto-detect if [ -f ~/.config/autorandr/last_profile ]; then LAST_PROFILE=$(cat ~/.config/autorandr/last_profile) autorandr --load "$LAST_PROFILE" 2>/dev/null || autorandr --change else autorandr --load common 2>/dev/null || autorandr --change fi # Deaktiviere Energiesparfunktionen xset s off # Screensaver aus xset -dpms # DPMS (Display Power Management) aus xset s noblank # Kein Blank-Screen # Start PipeWire pipewire & pipewire-pulse & # Start Bluetooth blueman-applet & # Start USB automount (udiskie) udiskie --tray --automount --notify & # Session Watcher läuft als systemd-Service # Start RDP Launcher /usr/local/bin/rdp-launcher.sh & - name: Configure Openbox menu (right-click context menu) copy: dest: /home/{{ thin_client_user }}/.config/openbox/menu.xml owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" content: | /usr/local/bin/rdp-profile-manager.py blueman-manager pcmanfm pavucontrol arandr sh -c 'profile=$(zenity --entry --title="Display Profil speichern" --text="Profilnamen eingeben:"); [ -n "$profile" ] && autorandr --save "$profile" --force && echo "$profile" > ~/.config/autorandr/last_profile && zenitiy --info --text="Profil gespeichert: $profile"' sh -c 'profile=$(zenity --list --title="Display Profil laden" --column="Profile" $(autorandr --list));[ -n "$profile" ] && autorandr --load "$profile" && echo "$profile" > ~/.config/autorandr/last_profile && zenity --info --text="Dieses Profil wird automatisch beim nächsten Start geladen"' sh -c 'profile=$(zenity --list --title="Display Profil löschen" --text="Profile auswählen zum löschen:" --column="Profiles" $(autorandr --list));[ -n "$profile" ] && zenity --question --title="Löschen bestätigen" --text="Delete profile: $profile?" && autorandr --remove "$profile" && zenity --info --text="Profil gelöscht: $profile"' lxterminal -e "bash -c 'echo Installing Intel graphics driver...; sudo apt update && sudo apt install -y xserver-xorg-video-intel firmware-misc-nonfree && echo Done! Please reboot for changes to take effect.; read -p Press Enter to close...'" lxterminal -e "bash -c 'echo Installing AMD/Radeon graphics driver...; sudo apt update && sudo apt install -y firmware-amd-graphics xserver-xorg-video-amdgpu xserver-xorg-video-radeon && echo Done! Please reboot for changes to take effect.; read -p Press Enter to close...'" lxterminal -e "bash -c 'echo Installing NVIDIA proprietary driver...; sudo apt update && sudo apt install -y nvidia-driver firmware-misc-nonfree && echo Done! Please reboot for changes to take effect.; read -p Press Enter to close...'" lxterminal -e "bash -c 'echo === Graphics Card Info ===; lspci | grep -i vga; echo; echo === Loaded Driver ===; lsmod | grep -iE video|radeon|amdgpu|nouveau|nvidia|i915; echo; echo === Xorg Driver ===; grep -i driver /var/log/Xorg.0.log 2>/dev/null | tail -n 20; read -p Press Enter to close...'" lxterminal systemctl reboot systemctl poweroff # === AUDIO CONFIGURATION === # PipeWire wird über Openbox autostart gestartet (siehe autostart config) # Keine systemd user services nötig # === BLUETOOTH CONFIGURATION === - name: Check if Bluetooth hardware is present stat: path: /sys/class/bluetooth register: bluetooth_hw - name: Enable Bluetooth service (if hardware present) systemd: name: bluetooth enabled: yes state: started when: bluetooth_hw.stat.exists ignore_errors: yes # === SMART CARD CONFIGURATION === - name: Check if pcscd is installed command: which pcscd register: pcscd_installed ignore_errors: yes changed_when: false - name: Enable pcscd service (socket-activated on demand) systemd: name: pcscd enabled: yes when: pcscd_installed.rc == 0 ignore_errors: yes # === PROFILE DIRECTORY === - name: Create RDP profile directory file: path: "{{ profile_dir }}" state: directory owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0755' - name: Create empty profiles.ini if not exists copy: dest: "{{ profile_file }}" owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0644' content: | # RDP Profile Configuration # Auto-generated by RDP Thin Client Setup force: no # === INSTALL PYTHON DEPENDENCIES === - name: Install python-xlib via pip shell: pip3 install python-xlib --break-system-packages args: creates: /usr/local/lib/python3.11/dist-packages/Xlib ignore_errors: yes - name: Alternative - Install python-xlib from apt apt: name: python3-xlib state: present ignore_errors: yes # === COPY SCRIPTS === - name: Copy RDP Profile Manager copy: src: ../files/rdp-profile-manager.py dest: /usr/local/bin/rdp-profile-manager.py mode: '0755' - name: Copy RDP Launcher copy: src: ../files/rdp-launcher.sh dest: /usr/local/bin/rdp-launcher.sh mode: '0755' - name: Copy Session Watcher copy: src: ../files/session-watcher.py dest: /usr/local/bin/session-watcher.py mode: '0755' # === SESSION WATCHER SYSTEMD SERVICE === - name: Create systemd user service directory file: path: /home/{{ thin_client_user }}/.config/systemd/user state: directory owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0755' - name: Create Session Watcher systemd service copy: dest: /home/{{ thin_client_user }}/.config/systemd/user/session-watcher.service owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0644' content: | [Unit] Description=RDP Session Watcher - Exit Hotkey Monitor After=graphical.target [Service] Type=simple ExecStart=/usr/local/bin/session-watcher.py Restart=always RestartSec=3 Environment=DISPLAY=:0 [Install] WantedBy=default.target - name: Create default.target.wants directory file: path: /home/{{ thin_client_user }}/.config/systemd/user/default.target.wants state: directory owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" mode: '0755' - name: Create symlink to enable Session Watcher service file: src: /home/{{ thin_client_user }}/.config/systemd/user/session-watcher.service dest: /home/{{ thin_client_user }}/.config/systemd/user/default.target.wants/session-watcher.service owner: "{{ thin_client_user }}" group: "{{ thin_client_user }}" state: link force: yes ignore_errors: yes # === BRANDING === - name: Check if branding files exist stat: path: ../files/branding/boot-logo.png register: branding_check delegate_to: localhost become: no - name: Create branding directories file: path: "{{ item }}" state: directory mode: '0755' loop: - /usr/share/pixmaps/hackersoft - /boot/grub when: branding_check.stat.exists - name: Copy boot logo for Plymouth copy: src: ../files/branding/boot-logo.png dest: /usr/share/pixmaps/hackersoft/boot-logo.png mode: '0644' when: branding_check.stat.exists - name: Copy GRUB background copy: src: ../files/branding/grub-background.png dest: /boot/grub/hackersoft-bg.png mode: '0644' when: branding_check.stat.exists - name: Configure LightDM to use custom background lineinfile: path: /etc/default/grub regexp: '^#?GRUB_BACKGROUND=' line: 'GRUB_BACKGROUND="/boot/grub/hackersoft-bg.png"' when: branding_check.stat.exists notify: Update GRUB # === POWER MANAGEMENT DEAKTIVIEREN === - name: Disable systemd suspend/hibernate systemd: name: "{{ item }}" masked: yes loop: - sleep.target - suspend.target - hibernate.target - hybrid-sleep.target ignore_errors: yes - name: Create logind config directory file: path: /etc/systemd/logind.conf.d state: directory mode: '0755' - name: Create systemd logind config to disable power management copy: dest: /etc/systemd/logind.conf.d/no-suspend.conf content: | [Login] HandlePowerKey=ignore HandleSuspendKey=ignore HandleHibernateKey=ignore HandleLidSwitch=ignore HandleLidSwitchExternalPower=ignore IdleAction=ignore mode: '0644' notify: Restart systemd-logind - name: Enable NumLock in LightDM lineinfile: path: /etc/lightdm/lightdm.conf regexp: '^#?greeter-setup-script=' line: 'greeter-setup-script=/usr/bin/numlockx on' create: yes - name: Install Plymouth for boot splash apt: name: - plymouth - plymouth-themes state: present when: branding_check.stat.exists - name: Create custom Plymouth theme directory file: path: /usr/share/plymouth/themes/hackersoft state: directory mode: '0755' when: branding_check.stat.exists - name: Create Plymouth theme configuration copy: dest: /usr/share/plymouth/themes/hackersoft/hackersoft.plymouth content: | [Plymouth Theme] Name=HackerSoft Description=HackerSoft RDP Thin Client Boot Splash ModuleName=script [script] ImageDir=/usr/share/plymouth/themes/hackersoft ScriptFile=/usr/share/plymouth/themes/hackersoft/hackersoft.script mode: '0644' when: branding_check.stat.exists - name: Create Plymouth script copy: dest: /usr/share/plymouth/themes/hackersoft/hackersoft.script content: | logo.image = Image("boot-logo.png"); logo.sprite = Sprite(logo.image); logo.opacity = 1.0; screen_width = Window.GetWidth(); screen_height = Window.GetHeight(); logo_width = logo.image.GetWidth(); logo_height = logo.image.GetHeight(); logo.x = screen_width / 2 - logo_width / 2; logo.y = screen_height / 2 - logo_height / 2; logo.sprite.SetPosition(logo.x, logo.y, 0); fun refresh_callback() { logo.sprite.SetOpacity(logo.opacity); } Plymouth.SetRefreshFunction(refresh_callback); mode: '0644' when: branding_check.stat.exists - name: Copy logo to Plymouth theme copy: src: ../files/branding/boot-logo.png dest: /usr/share/plymouth/themes/hackersoft/boot-logo.png mode: '0644' when: branding_check.stat.exists - name: Set Plymouth theme command: plymouth-set-default-theme -R hackersoft when: branding_check.stat.exists ignore_errors: yes - name: Create backgrounds directory if needed file: path: /usr/share/backgrounds state: directory mode: '0755' when: branding_check.stat.exists - name: Check if desktop-background.png exists stat: path: ../files/branding/desktop-background.png register: desktop_bg_check delegate_to: localhost become: no - name: Copy desktop background (dedicated file) copy: src: ../files/branding/desktop-background.png dest: /usr/share/backgrounds/hackersoft-wallpaper.png mode: '0644' when: branding_check.stat.exists and desktop_bg_check.stat.exists - name: Copy desktop background (fallback to login-background) copy: src: ../files/branding/login-background.png dest: /usr/share/backgrounds/hackersoft-wallpaper.png mode: '0644' when: branding_check.stat.exists and not desktop_bg_check.stat.exists - name: Install feh for wallpaper management apt: name: feh state: present when: branding_check.stat.exists - name: Set Openbox wallpaper in autostart lineinfile: path: /home/{{ thin_client_user }}/.config/openbox/autostart line: 'feh --bg-scale /usr/share/backgrounds/hackersoft-wallpaper.png &' insertafter: '^fi' when: branding_check.stat.exists # === CLEANUP === - name: Remove unnecessary packages apt: name: - gnome-* - libreoffice-* state: absent autoremove: yes ignore_errors: yes - name: Disable unnecessary services systemd: name: "{{ item }}" enabled: no state: stopped loop: - ModemManager - cups ignore_errors: yes - name: Final message debug: msg: "RDP Thin Client setup complete! Please reboot the system." handlers: - name: Update GRUB command: update-grub - name: Restart systemd-logind systemd: name: systemd-logind state: restarted