diff --git a/ansible/roles/developer_core/tasks/rust.yml b/ansible/roles/developer_core/tasks/rust.yml index e1516ca..a254e95 100644 --- a/ansible/roles/developer_core/tasks/rust.yml +++ b/ansible/roles/developer_core/tasks/rust.yml @@ -222,3 +222,81 @@ environment: "{{ cargo_env }}" become: "{{ ansible_facts['distribution'] != 'MacOSX' }}" become_user: "{{ actual_user if ansible_facts['distribution'] != 'MacOSX' else omit }}" + +- name: Install sccache (shared compilation cache) + ansible.builtin.command: + cmd: cargo install sccache --locked + creates: "{{ user_home }}/.cargo/bin/sccache" + environment: "{{ cargo_env }}" + become: "{{ ansible_facts['distribution'] != 'MacOSX' }}" + become_user: "{{ actual_user if ansible_facts['distribution'] != 'MacOSX' else omit }}" + +- name: Install cargo-sweep (build artifact cleanup) + ansible.builtin.command: + cmd: cargo install cargo-sweep --locked + creates: "{{ user_home }}/.cargo/bin/cargo-sweep" + environment: "{{ cargo_env }}" + become: "{{ ansible_facts['distribution'] != 'MacOSX' }}" + become_user: "{{ actual_user if ansible_facts['distribution'] != 'MacOSX' else omit }}" + +# ============================================================ +# mold linker — 5-10x faster linking for incremental builds +# Native x86_64 builds only. Cross-compilation uses BFD. +# ============================================================ + +- name: Install mold linker (Ubuntu) + ansible.builtin.apt: + name: mold + state: present + when: ansible_facts['distribution'] == 'Ubuntu' + +- name: Install mold linker (Fedora) + ansible.builtin.dnf: + name: mold + state: present + when: ansible_facts['distribution'] == 'Fedora' + +- name: Install mold linker via Homebrew (macOS) + community.general.homebrew: + name: mold + state: present + become: false + environment: "{{ homebrew_env }}" + when: ansible_facts['distribution'] == 'MacOSX' + +# ============================================================ +# Global Cargo configuration +# Sets sccache as rustc-wrapper, mold as linker, jobs = 8 +# Per-project .cargo/config.toml files override where needed. +# ============================================================ + +- name: Create global Cargo config (Linux) + ansible.builtin.blockinfile: + path: "{{ user_home }}/.cargo/config.toml" + create: true + owner: "{{ actual_user }}" + group: "{{ actual_user }}" + mode: '0644' + marker: "# {mark} MANAGED BY DFE-DEVELOPER — build optimisation" + block: | + [build] + jobs = 8 + rustc-wrapper = "sccache" + + [target.x86_64-unknown-linux-gnu] + linker = "clang" + rustflags = ["-C", "link-arg=-fuse-ld=mold"] + when: ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] + +- name: Create global Cargo config (macOS) + ansible.builtin.blockinfile: + path: "{{ user_home }}/.cargo/config.toml" + create: true + mode: '0644' + marker: "# {mark} MANAGED BY DFE-DEVELOPER — build optimisation" + block: | + [build] + jobs = 8 + rustc-wrapper = "sccache" + become: false + when: ansible_facts['distribution'] == 'MacOSX' diff --git a/ansible/roles/rdp/defaults/main.yml b/ansible/roles/rdp/defaults/main.yml index faad40a..b1af0ae 100644 --- a/ansible/roles/rdp/defaults/main.yml +++ b/ansible/roles/rdp/defaults/main.yml @@ -7,6 +7,11 @@ rdp_username: "hyperi" rdp_password: "hyperi" -# Display refresh rate (Hz) - lower values reduce software encoding CPU load -# Only relevant for VMs using software RFX encoding (no VA-API H.264) -rdp_refresh_rate: 30 +# FreeRDP library version to pin (Ubuntu only) +# ubuntu0.3 is broken (heap corruption). Base 3.16.0+dfsg-2 is known-good. +# See ~/RDP-FREERDP-FIX.md for details. +freerdp_pin_version: "3.16.0+dfsg-2ubuntu0.4" + +# Patched GRD version with configurable max-framerate gsettings key +# Stock GRD 49.0 hardcodes 60fps. This build reads from gsettings. +grd_patched_version: "49.0-0ubuntu1.1hyperi1" diff --git a/ansible/roles/rdp/files/dconf-rdp-performance b/ansible/roles/rdp/files/dconf-rdp-performance index 7534d91..7a16896 100644 --- a/ansible/roles/rdp/files/dconf-rdp-performance +++ b/ansible/roles/rdp/files/dconf-rdp-performance @@ -4,3 +4,8 @@ [org/gnome/desktop/interface] enable-animations=false + +[org/gnome/desktop/remote-desktop/rdp] +# Cap PipeWire screencast framerate to reduce software encoding load. +# Requires patched GRD with max-framerate gsettings key. +max-framerate=uint32 30 diff --git a/ansible/roles/rdp/files/gnome-remote-desktop_49.0-0ubuntu1.1hyperi1_amd64.deb b/ansible/roles/rdp/files/gnome-remote-desktop_49.0-0ubuntu1.1hyperi1_amd64.deb new file mode 100644 index 0000000..4934be4 Binary files /dev/null and b/ansible/roles/rdp/files/gnome-remote-desktop_49.0-0ubuntu1.1hyperi1_amd64.deb differ diff --git a/ansible/roles/rdp/files/pin-freerdp b/ansible/roles/rdp/files/pin-freerdp new file mode 100644 index 0000000..edb4c0f --- /dev/null +++ b/ansible/roles/rdp/files/pin-freerdp @@ -0,0 +1,13 @@ +# Pin FreeRDP libs — prevent apt upgrade from installing broken versions +# ubuntu0.3 confirmed broken (heap corruption in handover daemon) +# ubuntu0.4 confirmed stable (2026-03-25) +# Base 3.16.0+dfsg-2 is the known-good fallback +# See ~/RDP-FREERDP-FIX.md for details +# +# To downgrade if current version breaks: +# sudo apt install libfreerdp3-3=3.16.0+dfsg-2 libwinpr3-3=3.16.0+dfsg-2 libfreerdp-server3-3=3.16.0+dfsg-2 libfreerdp-client3-3=3.16.0+dfsg-2 libfreerdp-shadow3-3=3.16.0+dfsg-2 libfreerdp-shadow-subsystem3-3=3.16.0+dfsg-2 libfreerdp-server-proxy3-3=3.16.0+dfsg-2 +# Then update Pin: version below to 3.16.0+dfsg-2 + +Package: libfreerdp3-3 libwinpr3-3 libfreerdp-server3-3 libfreerdp-client3-3 libfreerdp-shadow3-3 libfreerdp-shadow-subsystem3-3 libfreerdp-server-proxy3-3 freerdp3-dev +Pin: version {{ freerdp_pin_version }} +Pin-Priority: 1001 diff --git a/ansible/roles/rdp/files/rdp-nice-limits.conf b/ansible/roles/rdp/files/rdp-nice-limits.conf new file mode 100644 index 0000000..cd1e1e4 --- /dev/null +++ b/ansible/roles/rdp/files/rdp-nice-limits.conf @@ -0,0 +1,6 @@ +# Allow desktop users to run processes with elevated priority (Nice=-10) +# Required for gnome-remote-desktop-handover systemd user service +# The Nice=-10 drop-in on the handover service is ignored without this +# Managed by Ansible (dfe-developer rdp role) +* soft nice -10 +* hard nice -10 diff --git a/ansible/roles/rdp/files/rdp-refresh-rate.desktop b/ansible/roles/rdp/files/rdp-refresh-rate.desktop deleted file mode 100644 index 89bde7b..0000000 --- a/ansible/roles/rdp/files/rdp-refresh-rate.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Type=Application -Name=RDP Refresh Rate -Comment=Set display refresh rate for RDP performance -Exec=/usr/local/bin/rdp-set-refresh-rate -Hidden=false -NoDisplay=true -X-GNOME-Autostart-enabled=true -X-GNOME-Autostart-Phase=Applications diff --git a/ansible/roles/rdp/files/rdp-set-refresh-rate b/ansible/roles/rdp/files/rdp-set-refresh-rate deleted file mode 100644 index 93f45d9..0000000 --- a/ansible/roles/rdp/files/rdp-set-refresh-rate +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Set display refresh rate for RDP sessions -# Reduces software encoding load on VMs without hardware video encoding -# Deployed by dfe-developer ansible role - -set -euo pipefail - -RATE_FILE="/etc/dfe-developer/rdp-refresh-rate" - -# Read configured rate or default to 30 -RATE=30 -if [[ -f "$RATE_FILE" ]]; then - RATE=$(cat "$RATE_FILE" 2>/dev/null || echo 30) -fi - -# Wait for display to be ready -sleep 2 - -# Apply to all connected outputs -for output in $(xrandr --query 2>/dev/null | grep ' connected' | awk '{print $1}'); do - xrandr --output "$output" --rate "$RATE" 2>/dev/null || true -done diff --git a/ansible/roles/rdp/tasks/display.yml b/ansible/roles/rdp/tasks/display.yml deleted file mode 100644 index 5206e67..0000000 --- a/ansible/roles/rdp/tasks/display.yml +++ /dev/null @@ -1,51 +0,0 @@ ---- -# Display refresh rate configuration for RDP performance -# Reduces software encoding load by capping refresh rate -# Only affects VMs using software RFX encoding (no VA-API H.264) - -# ============================================================================ -# DEPLOY REFRESH RATE SCRIPT AND AUTOSTART -# ============================================================================ - -- name: Deploy refresh rate script - ansible.builtin.copy: - src: rdp-set-refresh-rate - dest: /usr/local/bin/rdp-set-refresh-rate - mode: '0755' - when: - - ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] - - has_gnome - -- name: Ensure dfe-developer config directory exists - ansible.builtin.file: - path: /etc/dfe-developer - state: directory - mode: '0755' - when: - - ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] - - has_gnome - -- name: Write configured refresh rate - ansible.builtin.copy: - content: "{{ rdp_refresh_rate }}" - dest: /etc/dfe-developer/rdp-refresh-rate - mode: '0644' - when: - - ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] - - has_gnome - -- name: Deploy refresh rate autostart entry - ansible.builtin.copy: - src: rdp-refresh-rate.desktop - dest: /etc/xdg/autostart/rdp-refresh-rate.desktop - mode: '0644' - when: - - ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] - - has_gnome - -- name: Display refresh rate status - ansible.builtin.debug: - msg: "Display refresh rate will be capped at {{ rdp_refresh_rate }}Hz on login (reduces software encoding load)" - when: - - ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] - - has_gnome diff --git a/ansible/roles/rdp/tasks/freerdp_pin.yml b/ansible/roles/rdp/tasks/freerdp_pin.yml new file mode 100644 index 0000000..0a2a4ad --- /dev/null +++ b/ansible/roles/rdp/tasks/freerdp_pin.yml @@ -0,0 +1,13 @@ +--- +# Pin FreeRDP libraries to prevent apt upgrades from breaking RDP handover +# ubuntu0.3 caused heap corruption in gnome-remote-desktop-handover +# See ~/RDP-FREERDP-FIX.md for details + +- name: Deploy FreeRDP apt pin (Ubuntu) + ansible.builtin.template: + src: ../files/pin-freerdp + dest: /etc/apt/preferences.d/pin-freerdp + mode: '0644' + when: + - ansible_facts['distribution'] == 'Ubuntu' + - has_gnome diff --git a/ansible/roles/rdp/tasks/grd_patched.yml b/ansible/roles/rdp/tasks/grd_patched.yml new file mode 100644 index 0000000..839236a --- /dev/null +++ b/ansible/roles/rdp/tasks/grd_patched.yml @@ -0,0 +1,67 @@ +--- +# Deploy patched GRD with configurable max-framerate +# The stock GRD 49.0 hardcodes 60fps for the PipeWire screencast stream. +# This patched build reads max-framerate from gsettings, halving software +# encoding load on virtio-gpu VMs. + +- name: Check if patched GRD is already installed + ansible.builtin.command: + cmd: dpkg-query -W -f '${Version}' gnome-remote-desktop + register: grd_version + changed_when: false + failed_when: false + when: + - ansible_facts['distribution'] == 'Ubuntu' + - has_gnome + +- name: Copy patched GRD deb to target + ansible.builtin.copy: + src: "gnome-remote-desktop_{{ grd_patched_version }}_amd64.deb" + dest: "/tmp/gnome-remote-desktop_{{ grd_patched_version }}_amd64.deb" + mode: '0644' + when: + - ansible_facts['distribution'] == 'Ubuntu' + - has_gnome + - grd_version.stdout | default('') != grd_patched_version + register: grd_deb_copied + +- name: Install patched GRD deb + ansible.builtin.apt: + deb: "/tmp/gnome-remote-desktop_{{ grd_patched_version }}_amd64.deb" + when: + - ansible_facts['distribution'] == 'Ubuntu' + - has_gnome + - grd_deb_copied.changed | default(false) + register: grd_installed + +- name: Pin patched GRD to prevent apt overwrite + ansible.builtin.copy: + content: | + Package: gnome-remote-desktop + Pin: version {{ grd_patched_version }} + Pin-Priority: 1001 + dest: /etc/apt/preferences.d/pin-grd + mode: '0644' + when: + - ansible_facts['distribution'] == 'Ubuntu' + - has_gnome + +- name: Clean up deb file + ansible.builtin.file: + path: "/tmp/gnome-remote-desktop_{{ grd_patched_version }}_amd64.deb" + state: absent + when: + - ansible_facts['distribution'] == 'Ubuntu' + - has_gnome + +# NOTE: Do NOT restart gnome-remote-desktop here. +# Changes take effect after reboot. +- name: Notify about patched GRD + ansible.builtin.debug: + msg: >- + Patched GRD {{ grd_patched_version }} installed. + Reboot required for max-framerate setting to take effect. + when: + - ansible_facts['distribution'] == 'Ubuntu' + - has_gnome + - grd_installed.changed | default(false) diff --git a/ansible/roles/rdp/tasks/main.yml b/ansible/roles/rdp/tasks/main.yml index f545095..6d0b3cf 100644 --- a/ansible/roles/rdp/tasks/main.yml +++ b/ansible/roles/rdp/tasks/main.yml @@ -57,10 +57,20 @@ file: vaapi.yml tags: ['vaapi'] -- name: Configure display refresh rate +- name: Pin FreeRDP libraries ansible.builtin.include_tasks: - file: display.yml - tags: ['display'] + file: freerdp_pin.yml + tags: ['freerdp_pin'] + +- name: Configure PAM nice limits for GRD priority + ansible.builtin.include_tasks: + file: pam_nice.yml + tags: ['pam_nice'] + +- name: Deploy patched GRD with max-framerate support + ansible.builtin.include_tasks: + file: grd_patched.yml + tags: ['grd_patched'] - name: Verify RDP optimizations ansible.builtin.include_tasks: diff --git a/ansible/roles/rdp/tasks/pam_nice.yml b/ansible/roles/rdp/tasks/pam_nice.yml new file mode 100644 index 0000000..fd457f3 --- /dev/null +++ b/ansible/roles/rdp/tasks/pam_nice.yml @@ -0,0 +1,12 @@ +--- +# Deploy PAM nice limits so the GRD handover daemon can run at Nice=-10 +# Without this, the systemd user service drop-in for Nice=-10 is silently ignored + +- name: Deploy PAM nice limits for GRD priority + ansible.builtin.copy: + src: rdp-nice-limits.conf + dest: /etc/security/limits.d/50-rdp-nice.conf + mode: '0644' + when: + - ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] + - has_gnome diff --git a/ansible/roles/rdp/tasks/service.yml b/ansible/roles/rdp/tasks/service.yml index 45c1690..5d77aa2 100644 --- a/ansible/roles/rdp/tasks/service.yml +++ b/ansible/roles/rdp/tasks/service.yml @@ -122,17 +122,11 @@ no_log: true # Hide password from logs # ============================================================================ -# RESTART SERVICE TO APPLY CONFIGURATION +# NOTE: DO NOT auto-restart gnome-remote-desktop.service +# Restarting kills active RDP sessions and crashes GNOME. +# Config changes take effect after reboot. # ============================================================================ -- name: Restart RDP service to apply configuration - ansible.builtin.systemd: - name: gnome-remote-desktop.service - state: restarted - when: - - ansible_facts['distribution'] in ['Fedora', 'Ubuntu'] - - has_gnome - # ============================================================================ # STATUS & USER INSTRUCTIONS # ============================================================================ diff --git a/ansible/roles/rdp/tasks/verify.yml b/ansible/roles/rdp/tasks/verify.yml index 19139eb..6f59848 100644 --- a/ansible/roles/rdp/tasks/verify.yml +++ b/ansible/roles/rdp/tasks/verify.yml @@ -51,11 +51,17 @@ failed_when: false changed_when: false -- name: Verify refresh rate script deployed +- name: Verify FreeRDP apt pin exists (Ubuntu) ansible.builtin.stat: - path: /usr/local/bin/rdp-set-refresh-rate - register: verify_refresh_rate - failed_when: not verify_refresh_rate.stat.exists + path: /etc/apt/preferences.d/pin-freerdp + register: verify_freerdp_pin + when: ansible_facts['distribution'] == 'Ubuntu' + +- name: Verify PAM nice limits deployed + ansible.builtin.stat: + path: /etc/security/limits.d/50-rdp-nice.conf + register: verify_pam_nice + failed_when: not verify_pam_nice.stat.exists - name: Display RDP optimizer verification ansible.builtin.debug: @@ -67,6 +73,7 @@ - TCP config: /etc/sysctl.d/99-rdp-optimization.conf - MTU config: /etc/sysctl.d/99-rdp-mtu.conf - GRD priority (Nice=-10): {{ 'CONFIGURED' if verify_grd_priority.stat.exists else 'MISSING' }} + - PAM nice limits: {{ 'CONFIGURED' if verify_pam_nice.stat.exists else 'MISSING' }} - mesa-va-drivers: {{ 'INSTALLED' if verify_mesa_va.rc == 0 else 'NOT INSTALLED' }} - - Refresh rate cap ({{ rdp_refresh_rate }}Hz): {{ 'CONFIGURED' if verify_refresh_rate.stat.exists else 'MISSING' }} + - FreeRDP pin: {{ 'CONFIGURED' if (verify_freerdp_pin.stat.exists | default(false)) else 'N/A (Fedora)' }} - Status: RDP with auto-resize will be active after reboot on port 3389