diff --git a/life-church/playbooks/thinkcenter-xrdp.yml b/life-church/playbooks/thinkcenter-xrdp.yml new file mode 100644 index 0000000..827d2db --- /dev/null +++ b/life-church/playbooks/thinkcenter-xrdp.yml @@ -0,0 +1,185 @@ +--- +# ============================================================================ +# life-church/playbooks/thinkcenter-xrdp.yml +# Install (if missing) + configure + enable xrdp on thinkcenter. Convergent: +# safe to run whether xrdp is absent or already set up. thinkcenter ONLY. +# CHANGES the system. become password from Infisical /lc-hosts at runtime. +# +# What it guarantees: +# - xrdp + xorgxrdp installed (+ pipewire-module-xrdp so RDP has audio). +# - the `xrdp` service user is in the `ssl-cert` group (classic xrdp gotcha: +# without it xrdp can't read /etc/xrdp/key.pem and sessions fail). +# - a per-user ~/.xsession selects Cinnamon for the RDP login. +# - xrdp + xrdp-sesman enabled at boot AND started now. +# - listening on 3389; UFW rule added only if UFW is active. +# +# UNDO: +# sudo systemctl disable --now xrdp xrdp-sesman +# sudo gpasswd -d xrdp ssl-cert # (only if you want to revert the group) +# sudo apt remove --purge xrdp xorgxrdp +# rm -f ~/.xsession +# ============================================================================ + +# --- Play 1 (localhost): fetch thinkcenter sudo pass from Infisical ---------- +- name: "thinkcenter xrdp — resolve become password from Infisical" + hosts: localhost + gather_facts: false + tasks: + - name: "[resolve] Infisical creds from env" + ansible.builtin.set_fact: + infisical_client_id: "{{ lookup('env','INFISICAL_CLIENT_ID') }}" + infisical_client_secret: "{{ lookup('env','INFISICAL_CLIENT_SECRET') }}" + infisical_project_id: "{{ lookup('env','INFISICAL_PROJECT_ID') }}" + infisical_url: "{{ lookup('env','INFISICAL_API_URL') | default('http://192.168.122.41:8080', true) }}" + infisical_env_slug: "{{ lookup('env','INFISICAL_ENV_SLUG') | default('prod', true) }}" + + - name: "[resolve] Assert Infisical creds present" + ansible.builtin.assert: + that: + - infisical_client_id | length > 0 + - infisical_client_secret | length > 0 + - infisical_project_id | length > 0 + fail_msg: "Infisical creds missing from Semaphore environment 'infisical-dev'." + + - name: "[infisical] Log in" + infisical.vault.login: + url: "{{ infisical_url }}" + auth_method: universal_auth + universal_auth_client_id: "{{ infisical_client_id }}" + universal_auth_client_secret: "{{ infisical_client_secret }}" + register: infisical_login + no_log: true + + - name: "[infisical] Read /lc-hosts" + infisical.vault.read_secrets: + login_data: "{{ infisical_login.login_data }}" + project_id: "{{ infisical_project_id }}" + env_slug: "{{ infisical_env_slug }}" + path: "/lc-hosts" + as_dict: true + register: lc_secrets + no_log: true + + - name: "[set] Stash thinkcenter sudo pass + login user" + ansible.builtin.set_fact: + thinkcenter_become_pass: "{{ lc_secrets.secrets['THINKCENTER_SUDO_PASS'] }}" + thinkcenter_login_user: "{{ lc_secrets.secrets['THINKCENTER_USER'] }}" + no_log: true + +# --- Play 2: xrdp on thinkcenter ONLY ---------------------------------------- +- name: "Install + configure + enable xrdp on thinkcenter" + hosts: thinkcenter + gather_facts: true + become: true + vars: + ansible_become_pass: "{{ hostvars['localhost'].thinkcenter_become_pass }}" + rdp_user: "{{ hostvars['localhost'].thinkcenter_login_user }}" + xrdp_packages: + - xrdp # the RDP server + sesman + - xorgxrdp # Xorg backend for xrdp + - pipewire-module-xrdp # audio redirection through PipeWire in RDP sessions + xrdp_session_cmd: "cinnamon-session-cinnamon2d" # lightweight Cinnamon over RDP + tasks: + - name: "[safety] Confirm we are on thinkcenter" + ansible.builtin.assert: + that: inventory_hostname == 'thinkcenter' + fail_msg: "Refusing to run — this play is scoped to thinkcenter only." + + - name: "[apt] Install xrdp packages (no-op if already present)" + ansible.builtin.apt: + name: "{{ xrdp_packages }}" + state: present + update_cache: true + cache_valid_time: 3600 + register: xrdp_pkg + + - name: "[tls] Add xrdp service user to ssl-cert group (so it can read key.pem)" + ansible.builtin.user: + name: xrdp + groups: ssl-cert + append: true + register: sslcert_grp + + - name: "[session] Set Cinnamon as the RDP session for {{ rdp_user }} (~/.xsession)" + ansible.builtin.copy: + dest: "/home/{{ rdp_user }}/.xsession" + owner: "{{ rdp_user }}" + group: "{{ rdp_user }}" + mode: "0644" + content: "{{ xrdp_session_cmd }}\n" + register: xsession + + # Cinnamon uses D-Bus/PolicyKit; the common xrdp fix is a polkit rule so the + # RDP session isn't blocked on colord authentication prompts. + - name: "[polkit] Allow colord actions in xrdp sessions (avoids auth popup loop)" + ansible.builtin.copy: + dest: /etc/polkit-1/localauthority/50-local.d/45-allow-colord.pkla + mode: "0644" + content: | + [Allow Colord all Users] + Identity=unix-user:* + Action=org.freedesktop.color-manager.create-device;org.freedesktop.color-manager.create-profile;org.freedesktop.color-manager.delete-device;org.freedesktop.color-manager.delete-profile;org.freedesktop.color-manager.modify-device;org.freedesktop.color-manager.modify-profile + ResultAny=no + ResultInactive=no + ResultActive=yes + register: polkit_rule + + - name: "[services] Enable + start xrdp and xrdp-sesman" + ansible.builtin.systemd_service: + name: "{{ item }}" + enabled: true + state: started + loop: + - xrdp + - xrdp-sesman + register: xrdp_services + + - name: "[services] Restart xrdp if config/group changed this run" + ansible.builtin.systemd_service: + name: xrdp + state: restarted + when: sslcert_grp.changed or xsession.changed or polkit_rule.changed or xrdp_pkg.changed + + - name: "[firewall] Is UFW active?" + ansible.builtin.command: ufw status + register: ufw_status + changed_when: false + failed_when: false + + - name: "[firewall] Allow RDP 3389/tcp when UFW is active" + community.general.ufw: + rule: allow + port: "3389" + proto: tcp + when: "'Status: active' in (ufw_status.stdout | default(''))" + + - name: "[verify] xrdp listening on 3389?" + ansible.builtin.shell: "ss -tlnp | grep -q ':3389' && echo listening || echo not-listening" + register: rdp_listen + changed_when: false + + - name: "[verify] Service states" + ansible.builtin.shell: "systemctl is-active xrdp xrdp-sesman | tr '\n' ' '" + register: svc_active + changed_when: false + + - name: "[verify] Assert xrdp is up and listening" + ansible.builtin.assert: + that: + - "'listening' in rdp_listen.stdout" + - "'active' in svc_active.stdout" + fail_msg: "xrdp is not listening on 3389 or a service is not active ({{ svc_active.stdout }})." + success_msg: "xrdp is enabled, active, and listening on 3389." + + - name: "[report] xrdp summary" + ansible.builtin.debug: + msg: + - "===== thinkcenter xrdp =====" + - "packages changed this run : {{ xrdp_pkg.changed }}" + - "xrdp added to ssl-cert : {{ sslcert_grp.changed }}" + - ".xsession set for {{ rdp_user }} : {{ xsession.changed }}" + - "colord polkit rule changed : {{ polkit_rule.changed }}" + - "services (xrdp sesman) : {{ svc_active.stdout | trim }}" + - "3389 listen : {{ rdp_listen.stdout }}" + - "Connect via RDP to 100.96.78.26 (mesh) or 192.168.68.51 (LAN) as {{ rdp_user }}." + - "NOTE: log out of the local console session first — xorgxrdp can't attach to an already-active seat0 login."