diff --git a/.gitignore b/.gitignore index 14ccdfe..1ec3255 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.env .artifacts/acp_codex/xworkmate-go-core .artifacts/acp_opencode/xworkmate-go-core diff --git a/ansible.cfg b/ansible.cfg index 784e1f6..6b3837b 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,6 +1,7 @@ [defaults] # 常用参数 -inventory = ./inventory # 默认清单文件路径,可按需改 +# 默认清单文件路径,可按需改 +inventory = ./inventory.ini vault_password_file = ~/.vault_password timeout = 10 forks = 10 diff --git a/gnome_xrdp_minimal.yaml b/gnome_xrdp_minimal.yaml new file mode 100644 index 0000000..d09f98a --- /dev/null +++ b/gnome_xrdp_minimal.yaml @@ -0,0 +1,7 @@ +--- +- name: Setup minimal GNOME + XRDP desktop + hosts: all + become: true + gather_facts: true + roles: + - roles/vhosts/gnome_xrdp_minimal/ diff --git a/harden_ssh_root_key_only.yml b/harden_ssh_root_key_only.yml new file mode 100644 index 0000000..d841048 --- /dev/null +++ b/harden_ssh_root_key_only.yml @@ -0,0 +1,17 @@ +--- +- name: Harden SSH on all inventory hosts + hosts: all + become: true + gather_facts: true + + vars: + sshd_config_path: /etc/ssh/sshd_config + sshd_dropin_dir: /etc/ssh/sshd_config.d + root_authorized_keys_path: /root/.ssh/authorized_keys + local_public_key_path: "{{ lookup('env', 'HOME') }}/.ssh/id_rsa.pub" + ansible_user: "{{ lookup('env', 'BOOTSTRAP_ROOT_USER') | default('root', true) }}" + ansible_password: "{{ lookup('env', 'BOOTSTRAP_ROOT_PASSWORD') | default(omit, true) }}" + ansible_become_password: "{{ lookup('env', 'BOOTSTRAP_BECOME_PASSWORD') | default(omit, true) }}" + + roles: + - role: harden_ssh_root_key_only diff --git a/inventory.ini b/inventory.ini index d863474..3ac6ad1 100644 --- a/inventory.ini +++ b/inventory.ini @@ -1,39 +1,19 @@ [web] -cn-website.svc.plus ansible_host=47.120.61.35 -global-console.svc.plus ansible_host=35.220.157.80 ansible_user=root -hk-website ansible_host=35.220.224.163 ansible_user=root +cn-front.svc.plus ansible_host=47.120.61.35 ansible_user=root -[deepflow_agents] -192.168.1.101 ansible_user=root ansible_ssh_pass=pass101 -192.168.1.102 ansible_user=admin ansible_ssh_pass=pass102 -192.168.1.103 ansible_user=root ansible_ssh_pass=pass103 ansible_port=2222 -192.168.1.104 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa_ubuntu - -[mail] -smtp.svc.plus ansible_host=45.130.167.90 +[agent_proxy] +jp-xhttp-contabo.svc.plus ansible_host=46.250.251.132 ansible_user=root [k3s] -cn-k3s-vultr.svc.plus ansible_host=1.15.155.245 ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa -jp-xhttp-contabo.svc.plus ansible_host=46.250.251.132 ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa -jp-k3s-vultr.svc.plus ansible_host=jp-k3s-vultr.svc.plus ansible_user=root -k3s_platform_git_private_key_path=~/.ssh/id_rsa - -[bootstrap] -auth.svc.plus ansible_host=34.92.122.119 ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa +jp-k3s-vultr.svc.plus ansible_host=167.179.110.129 ansible_user=root [all:vars] ansible_port=22 ansible_user=root +ansible_ssh_transfer_method=piped ansible_host_key_checking=False # SSH 密钥或密码(二选一) -# ansible_ssh_private_key_file=~/.ssh/id_rsa -# ansible_ssh_pass=your_password +ansible_ssh_private_key_file=~/.ssh/id_rsa +k3s_platform_git_private_key=~/.ssh/id_rsa -# DeepFlow agent 配置变量 -controller_ips=["10.10.10.10", "10.10.10.11"] -vtap_group_id="g-P22vLIMdB6" - -# DeepFlow agent 安装包位置 -agent_base_dir="deepflow-agent-for-linux" -agent_package_name="deepflow-agent-1.0-5407.systemd.x86_64.rpm" diff --git a/plasma_xrdp_minimal.yaml b/plasma_xrdp_minimal.yaml new file mode 100644 index 0000000..470e4d7 --- /dev/null +++ b/plasma_xrdp_minimal.yaml @@ -0,0 +1,7 @@ +--- +- name: Setup minimal Plasma + XRDP desktop + hosts: all + become: true + gather_facts: true + roles: + - roles/vhosts/plasma_xrdp_minimal/ diff --git a/roles/cloudflare_dns/tasks/main.yml b/roles/cloudflare_dns/tasks/main.yml index 07d775b..8a2d22e 100644 --- a/roles/cloudflare_dns/tasks/main.yml +++ b/roles/cloudflare_dns/tasks/main.yml @@ -61,12 +61,16 @@ - name: Set Cloudflare zone id ansible.builtin.set_fact: cloudflare_dns_zone_id: "{{ cloudflare_dns_zone_lookup.json.result[0].id }}" - when: cloudflare_dns_zone_lookup is defined + when: + - cloudflare_dns_zone_lookup is defined + - cloudflare_dns_zone_id | default('', true) | length == 0 - name: Show zone permissions for current token ansible.builtin.debug: msg: "Cloudflare token permissions for {{ cloudflare_dns_zone_name }}: {{ cloudflare_dns_zone_lookup.json.result[0].permissions | default([]) }}" - when: cloudflare_dns_zone_lookup is defined + when: + - cloudflare_dns_zone_lookup is defined + - cloudflare_dns_zone_lookup.json is defined - name: Fail early when token does not have DNS edit permission ansible.builtin.assert: @@ -77,7 +81,9 @@ CLOUDFLARE_API_TOKEN is valid but lacks DNS edit permission for {{ cloudflare_dns_zone_name }}. Current permissions: {{ cloudflare_dns_zone_lookup.json.result[0].permissions | default([]) }}. Required: Zone read + DNS edit on the svc.plus zone. - when: cloudflare_dns_zone_lookup is defined + when: + - cloudflare_dns_zone_lookup is defined + - cloudflare_dns_zone_lookup.json is defined - name: Query existing DNS records ansible.builtin.uri: diff --git a/roles/cloudflare_svc_plus_dns/README.md b/roles/cloudflare_svc_plus_dns/README.md new file mode 100644 index 0000000..5398d6e --- /dev/null +++ b/roles/cloudflare_svc_plus_dns/README.md @@ -0,0 +1,12 @@ +# cloudflare_svc_plus_dns + +Specialized wrapper role for the `svc.plus` DNS set. + +## What it does + +- Loads the managed record manifest from `playbooks/vars/cloudflare_svc_plus_dns.yml` +- Delegates the actual create/update/delete reconciliation to the shared `cloudflare_dns` role + +## Entry point + +Use `update_cloudflare_svc_plus_dns.yml` to apply this role. diff --git a/roles/cloudflare_svc_plus_dns/tasks/main.yml b/roles/cloudflare_svc_plus_dns/tasks/main.yml new file mode 100644 index 0000000..cf7df82 --- /dev/null +++ b/roles/cloudflare_svc_plus_dns/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- name: Load svc.plus DNS manifest + ansible.builtin.include_vars: + file: "{{ role_path }}/../../vars/cloudflare_svc_plus_dns.yml" + +- name: Reconcile svc.plus DNS via shared Cloudflare role + ansible.builtin.include_role: + name: cloudflare_dns diff --git a/roles/harden_ssh_root_key_only/README.md b/roles/harden_ssh_root_key_only/README.md new file mode 100644 index 0000000..4fbce6e --- /dev/null +++ b/roles/harden_ssh_root_key_only/README.md @@ -0,0 +1,43 @@ +# harden_ssh_root_key_only + +Harden SSH access for inventory hosts by installing the local operator public key for `root` and disabling password-based SSH authentication. + +## What it does + +- Reads `~/.ssh/id_rsa.pub` from the local machine by default +- Installs that public key into `/root/.ssh/authorized_keys` +- Writes an SSH daemon drop-in at `/etc/ssh/sshd_config.d/99-codex-root-key-only.conf` +- Disables password and keyboard-interactive SSH auth +- Reloads the SSH service after validating config syntax + +## Variables + +- `local_public_key_path`: path to the operator public key, default `~/.ssh/id_rsa.pub` +- `root_authorized_keys_path`: root authorized_keys path, default `/root/.ssh/authorized_keys` +- `sshd_dropin_dir`: SSH drop-in directory, default `/etc/ssh/sshd_config.d` +- `ssh_service_name_override`: optional service name override, otherwise auto-detects `ssh` or `sshd` + +## Example playbook + +```yaml +- hosts: all + become: true + roles: + - role: harden_ssh_root_key_only +``` + +## Validation + +After running the role, verify: + +```bash +sshd -T | egrep '^(passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|permitrootlogin)' +passwd -S root +``` + +Expected: + +- `PasswordAuthentication no` +- `KbdInteractiveAuthentication no` +- `PubkeyAuthentication yes` +- `root` remains accessible with the installed key diff --git a/roles/harden_ssh_root_key_only/defaults/main.yml b/roles/harden_ssh_root_key_only/defaults/main.yml new file mode 100644 index 0000000..a5ae893 --- /dev/null +++ b/roles/harden_ssh_root_key_only/defaults/main.yml @@ -0,0 +1,6 @@ +--- +sshd_config_path: /etc/ssh/sshd_config +sshd_dropin_dir: /etc/ssh/sshd_config.d +root_authorized_keys_path: /root/.ssh/authorized_keys +local_public_key_path: "{{ lookup('env', 'HOME') }}/.ssh/id_rsa.pub" +ssh_service_name_override: "" diff --git a/roles/harden_ssh_root_key_only/tasks/main.yml b/roles/harden_ssh_root_key_only/tasks/main.yml new file mode 100644 index 0000000..24b2098 --- /dev/null +++ b/roles/harden_ssh_root_key_only/tasks/main.yml @@ -0,0 +1,82 @@ +--- +- name: Read local SSH public key + ansible.builtin.set_fact: + local_ssh_public_key: "{{ lookup('ansible.builtin.file', local_public_key_path) | trim }}" + +- name: Assert local SSH public key exists + ansible.builtin.assert: + that: + - local_ssh_public_key | length > 0 + fail_msg: "local_public_key_path must point to a readable SSH public key." + +- name: Ensure root SSH directory exists + ansible.builtin.file: + path: /root/.ssh + state: directory + mode: "0700" + owner: root + group: root + +- name: Install local public key for root + ansible.posix.authorized_key: + user: root + key: "{{ local_ssh_public_key }}" + state: present + manage_dir: false + +- name: Ensure sshd drop-in directory exists + ansible.builtin.file: + path: "{{ sshd_dropin_dir }}" + state: directory + mode: "0755" + owner: root + group: root + +- name: Write sshd hardening drop-in + ansible.builtin.copy: + dest: "{{ sshd_dropin_dir }}/99-codex-root-key-only.conf" + mode: "0644" + owner: root + group: root + content: | + PermitRootLogin yes + PubkeyAuthentication yes + PasswordAuthentication no + KbdInteractiveAuthentication no + ChallengeResponseAuthentication no + UsePAM yes + +- name: Validate sshd configuration syntax + ansible.builtin.command: sshd -t + changed_when: false + when: not ansible_check_mode + +- name: Collect service facts + ansible.builtin.service_facts: + changed_when: false + +- name: Select SSH service name + ansible.builtin.set_fact: + ssh_service_name: >- + {{ + ssh_service_name_override + if ssh_service_name_override | length > 0 + else ('ssh' if 'ssh.service' in ansible_facts.services else 'sshd') + }} + +- name: Reload SSH service + ansible.builtin.service: + name: "{{ ssh_service_name }}" + state: reloaded + +- name: Read root authorized_keys + ansible.builtin.slurp: + src: "{{ root_authorized_keys_path }}" + register: root_authorized_keys + changed_when: false + +- name: Assert public key was installed + ansible.builtin.assert: + that: + - local_ssh_public_key in (root_authorized_keys.content | b64decode) + fail_msg: "Local public key was not installed into /root/.ssh/authorized_keys" diff --git a/roles/vhosts/acp_opencode/tasks/config.yml b/roles/vhosts/acp_opencode/tasks/config.yml index 63367e5..d681755 100644 --- a/roles/vhosts/acp_opencode/tasks/config.yml +++ b/roles/vhosts/acp_opencode/tasks/config.yml @@ -27,6 +27,18 @@ mode: "0755" notify: Restart opencode acp bridge +- name: Ensure OpenCode runtime directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ acp_opencode_service_user }}" + group: "{{ acp_opencode_service_group }}" + mode: "0755" + loop: + - "{{ acp_opencode_home }}" + - "{{ acp_opencode_home }}/.local" + - "{{ acp_opencode_workdir }}" + - name: Deploy Caddy main file ansible.builtin.template: src: Caddyfile.j2 diff --git a/roles/vhosts/gnome_xrdp_minimal/README.md b/roles/vhosts/gnome_xrdp_minimal/README.md new file mode 100644 index 0000000..66b6076 --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/README.md @@ -0,0 +1,33 @@ +# gnome_xrdp_minimal + +Minimal GNOME + XRDP role for Debian, Ubuntu, Fedora, and OpenSuse VPS hosts. + +## What it does + +- Installs a minimal GNOME desktop stack and XRDP +- Installs the Xorg backend needed by XRDP (`xserver-xorg-core`, `xorgxrdp`) +- Installs open-source Chromium browser +- Installs Chinese font support (`fonts-noto-cjk`) +- Creates `~/.xsession` for the target desktop user +- Enables `xrdp` and `xrdp-sesman` +- Optionally opens TCP `3389` with UFW if UFW is present +- Creates or updates the desktop user and ensures it has a usable local password for XRDP login + +## Variables + +- `gnome_user`: desktop login user, default `ubuntu` +- `gnome_packages`: minimal package list +- `gnome_enable_ufw`: whether to allow the RDP port with UFW +- `gnome_rdp_port`: RDP port, default `3389` +- `gnome_user_groups`: supplemental groups for the desktop user, default `["sudo"]` +- `gnome_user_password_plaintext`: required password for the desktop user so XRDP can authenticate +- Supported platforms: Debian/Ubuntu, Fedora, OpenSuse + +## Example playbook + +```yaml +- hosts: vps + become: true + roles: + - role: roles/vhosts/gnome_xrdp_minimal +``` diff --git a/roles/vhosts/gnome_xrdp_minimal/defaults/main.yml b/roles/vhosts/gnome_xrdp_minimal/defaults/main.yml new file mode 100644 index 0000000..0d4ec41 --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/defaults/main.yml @@ -0,0 +1,48 @@ +--- +gnome_user: ubuntu + +gnome_packages_debian: + - gnome-session + - gnome-shell + - gnome-terminal + - nautilus + - dbus-x11 + - x11-xserver-utils + - xserver-xorg-core + - xorgxrdp + - chromium + - fonts-noto-cjk + - xrdp + +gnome_packages_fedora: + - gnome-session + - gnome-shell + - gnome-terminal + - nautilus + - dbus-x11 + - xorg-x11-server-Xorg + - xorgxrdp + - chromium + - google-noto-sans-cjk-fonts + - xrdp + +gnome_packages_opensuse: + - gnome-session + - gnome-shell + - gnome-terminal + - nautilus + - dbus-1-x11 + - xorg-x11-server + - xorgxrdp + - chromium + - noto-sans-cjk-fonts + - xrdp + +gnome_enable_ufw: true +gnome_rdp_port: 3389 + +gnome_manage_user: true +gnome_user_shell: /bin/bash +gnome_user_groups: + - sudo +gnome_user_password_plaintext: "" diff --git a/roles/vhosts/gnome_xrdp_minimal/handlers/main.yml b/roles/vhosts/gnome_xrdp_minimal/handlers/main.yml new file mode 100644 index 0000000..9381881 --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Restart xrdp + ansible.builtin.service: + name: xrdp + state: restarted + +- name: Restart xrdp sesman + ansible.builtin.service: + name: xrdp-sesman + state: restarted diff --git a/roles/vhosts/gnome_xrdp_minimal/tasks/config.yml b/roles/vhosts/gnome_xrdp_minimal/tasks/config.yml new file mode 100644 index 0000000..db80198 --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/tasks/config.yml @@ -0,0 +1,53 @@ +--- +- name: Ensure the desktop user exists + ansible.builtin.user: + name: "{{ gnome_user }}" + shell: "{{ gnome_user_shell }}" + create_home: true + state: present + password_lock: false + become: true + when: gnome_manage_user | bool + +- name: Fail when the desktop user password is not provided + ansible.builtin.assert: + that: + - gnome_user_password_plaintext | length > 0 + fail_msg: >- + gnome_user_password_plaintext must be set so XRDP can authenticate the + desktop user. + when: gnome_manage_user | bool + +- name: Set desktop user password for XRDP login + ansible.builtin.user: + name: "{{ gnome_user }}" + password: "{{ gnome_user_password_plaintext | password_hash('sha512') }}" + update_password: always + password_lock: false + become: true + no_log: true + when: gnome_manage_user | bool + +- name: Ensure the desktop user can sudo + ansible.builtin.user: + name: "{{ gnome_user }}" + groups: "{{ gnome_user_groups }}" + append: true + state: present + become: true + when: + - gnome_manage_user | bool + - gnome_user_groups | length > 0 + +- name: Ensure GNOME session file is present + ansible.builtin.template: + src: xsession.j2 + dest: "{{ gnome_xsession_file }}" + owner: "{{ gnome_user }}" + group: "{{ gnome_user }}" + mode: "0644" + become: true + when: gnome_manage_user | bool + notify: + - Restart xrdp + - Restart xrdp sesman diff --git a/roles/vhosts/gnome_xrdp_minimal/tasks/install.yml b/roles/vhosts/gnome_xrdp_minimal/tasks/install.yml new file mode 100644 index 0000000..e82d752 --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/tasks/install.yml @@ -0,0 +1,99 @@ +--- +- name: Select GNOME package list for this platform + ansible.builtin.set_fact: + gnome_selected_packages: >- + {{ + gnome_packages_debian if ansible_os_family == 'Debian' + else gnome_packages_fedora if ansible_os_family == 'RedHat' + else gnome_packages_opensuse if ansible_os_family == 'Suse' + else [] + }} + +- name: Fail on unsupported platform + ansible.builtin.assert: + that: + - gnome_selected_packages | length > 0 + fail_msg: "gnome_xrdp_minimal supports Debian, Ubuntu, Fedora, and OpenSuse only." + +- name: Update apt cache + ansible.builtin.apt: + update_cache: true + when: ansible_os_family == 'Debian' + become: true + +- name: Install minimal desktop packages + ansible.builtin.apt: + name: "{{ gnome_selected_packages }}" + state: present + install_recommends: false + environment: + DEBIAN_FRONTEND: noninteractive + APT_LISTCHANGES_FRONTEND: none + when: ansible_os_family == 'Debian' + become: true + +- name: Update DNF cache + ansible.builtin.dnf: + update_cache: true + when: ansible_os_family == 'RedHat' + become: true + +- name: Install GNOME packages on Fedora + ansible.builtin.dnf: + name: "{{ gnome_selected_packages }}" + state: present + when: ansible_os_family == 'RedHat' + become: true + +- name: Refresh Zypper cache + ansible.builtin.command: zypper --non-interactive refresh + changed_when: false + when: ansible_os_family == 'Suse' + become: true + +- name: Install GNOME packages on OpenSuse + ansible.builtin.zypper: + name: "{{ gnome_selected_packages }}" + state: present + type: package + when: ansible_os_family == 'Suse' + become: true + +- name: Check whether the xrdp service account exists + ansible.builtin.command: getent passwd xrdp + register: gnome_xrdp_account + changed_when: false + failed_when: false + become: true + +- name: Ensure xrdp user can read the TLS certificate group + ansible.builtin.user: + name: xrdp + groups: ssl-cert + append: true + become: true + when: gnome_xrdp_account.rc == 0 + +- name: Enable and start XRDP services + ansible.builtin.service: + name: "{{ item }}" + enabled: true + state: started + loop: "{{ gnome_xrdp_services }}" + become: true + +- name: Check whether UFW is installed + ansible.builtin.stat: + path: /usr/sbin/ufw + register: gnome_ufw_binary + become: true + +- name: Allow XRDP through UFW + ansible.builtin.command: "ufw allow {{ gnome_rdp_port }}/tcp" + register: gnome_ufw_allow + changed_when: "'Skipping adding existing rule' not in gnome_ufw_allow.stdout" + failed_when: false + become: true + when: + - gnome_enable_ufw | bool + - gnome_ufw_binary.stat.exists | default(false) diff --git a/roles/vhosts/gnome_xrdp_minimal/tasks/main.yml b/roles/vhosts/gnome_xrdp_minimal/tasks/main.yml new file mode 100644 index 0000000..0d6402e --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Install minimal GNOME + XRDP stack + ansible.builtin.import_tasks: install.yml + tags: [gnome, gnome_install] + +- name: Configure GNOME session and XRDP user setup + ansible.builtin.import_tasks: config.yml + tags: [gnome, gnome_config] + +- name: Apply GNOME resource-saving tweaks + ansible.builtin.import_tasks: optimize.yml + tags: [gnome, gnome_optimize] + +- name: Validate XRDP desktop readiness + ansible.builtin.import_tasks: validate.yml + tags: [gnome, gnome_validate] diff --git a/roles/vhosts/gnome_xrdp_minimal/tasks/optimize.yml b/roles/vhosts/gnome_xrdp_minimal/tasks/optimize.yml new file mode 100644 index 0000000..d44b8af --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/tasks/optimize.yml @@ -0,0 +1,10 @@ +--- +- name: Ensure GNOME session settings are persisted + ansible.builtin.file: + path: "{{ gnome_user_home }}/.config" + state: directory + owner: "{{ gnome_user }}" + group: "{{ gnome_user }}" + mode: "0755" + become: true + when: gnome_manage_user | bool diff --git a/roles/vhosts/gnome_xrdp_minimal/tasks/validate.yml b/roles/vhosts/gnome_xrdp_minimal/tasks/validate.yml new file mode 100644 index 0000000..cbd08b0 --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/tasks/validate.yml @@ -0,0 +1,33 @@ +--- +- name: Check XRDP service status + ansible.builtin.command: systemctl status xrdp --no-pager --full + register: gnome_xrdp_status + changed_when: false + failed_when: false + become: true + +- name: Show XRDP service status + ansible.builtin.debug: + var: gnome_xrdp_status.stdout_lines + +- name: Check memory usage + ansible.builtin.command: free -m + register: gnome_memory_usage + changed_when: false + failed_when: false + +- name: Show memory usage + ansible.builtin.debug: + var: gnome_memory_usage.stdout_lines + +- name: Check current session type if available + ansible.builtin.shell: 'printf "%s\n" "${XDG_SESSION_TYPE:-unknown}"' + args: + executable: /bin/bash + register: gnome_session_type + changed_when: false + failed_when: false + +- name: Show session type hint + ansible.builtin.debug: + msg: "XDG_SESSION_TYPE={{ gnome_session_type.stdout | default('unknown') }} (expected x11 inside the RDP session)" diff --git a/roles/vhosts/gnome_xrdp_minimal/templates/xsession.j2 b/roles/vhosts/gnome_xrdp_minimal/templates/xsession.j2 new file mode 100644 index 0000000..8861ecd --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/templates/xsession.j2 @@ -0,0 +1 @@ +exec gnome-session diff --git a/roles/vhosts/gnome_xrdp_minimal/vars/main.yml b/roles/vhosts/gnome_xrdp_minimal/vars/main.yml new file mode 100644 index 0000000..57138da --- /dev/null +++ b/roles/vhosts/gnome_xrdp_minimal/vars/main.yml @@ -0,0 +1,6 @@ +--- +gnome_user_home: "/home/{{ gnome_user }}" +gnome_xsession_file: "{{ gnome_user_home }}/.xsession" +gnome_xrdp_services: + - xrdp-sesman + - xrdp diff --git a/roles/vhosts/plasma_xrdp_minimal/README.md b/roles/vhosts/plasma_xrdp_minimal/README.md new file mode 100644 index 0000000..0566fa9 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/README.md @@ -0,0 +1,33 @@ +# plasma_xrdp_minimal + +Minimal Plasma + XRDP role for Debian, Ubuntu, Fedora, and OpenSuse VPS hosts. + +## What it does + +- Installs a minimal Plasma desktop stack and XRDP +- Installs the Xorg backend needed by XRDP (`xserver-xorg-core`, `xorgxrdp`) +- Installs open-source Chromium browser +- Installs Chinese font support (`fonts-noto-cjk`) +- Creates `~/.xsession` for the target desktop user +- Enables `xrdp` and `xrdp-sesman` +- Optionally opens TCP `3389` with UFW if UFW is present +- Creates or updates the desktop user and ensures it has a usable local password for XRDP login + +## Variables + +- `plasma_user`: desktop login user, default `ubuntu` +- `plasma_packages`: minimal package list +- `plasma_enable_ufw`: whether to allow the RDP port with UFW +- `plasma_rdp_port`: RDP port, default `3389` +- `plasma_user_groups`: supplemental groups for the desktop user, default `["sudo"]` +- `plasma_user_password_plaintext`: required password for the desktop user so XRDP can authenticate +- Supported platforms: Debian/Ubuntu, Fedora, OpenSuse + +## Example playbook + +```yaml +- hosts: vps + become: true + roles: + - role: roles/vhosts/plasma_xrdp_minimal +``` diff --git a/roles/vhosts/plasma_xrdp_minimal/defaults/main.yml b/roles/vhosts/plasma_xrdp_minimal/defaults/main.yml new file mode 100644 index 0000000..96185a4 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/defaults/main.yml @@ -0,0 +1,48 @@ +--- +plasma_user: ubuntu + +plasma_packages_debian: + - plasma-desktop + - kde-plasma-desktop + - konsole + - dolphin + - dbus-x11 + - x11-xserver-utils + - xserver-xorg-core + - xorgxrdp + - chromium + - fonts-noto-cjk + - xrdp + +plasma_packages_fedora: + - plasma-desktop + - plasma-workspace + - konsole + - dolphin + - dbus-x11 + - xorg-x11-server-Xorg + - xorgxrdp + - chromium + - google-noto-sans-cjk-fonts + - xrdp + +plasma_packages_opensuse: + - plasma-desktop + - plasma5-session + - konsole + - dolphin + - dbus-1-x11 + - xorg-x11-server + - xorgxrdp + - chromium + - noto-sans-cjk-fonts + - xrdp + +plasma_enable_ufw: true +plasma_rdp_port: 3389 + +plasma_manage_user: true +plasma_user_shell: /bin/bash +plasma_user_groups: + - sudo +plasma_user_password_plaintext: "" diff --git a/roles/vhosts/plasma_xrdp_minimal/handlers/main.yml b/roles/vhosts/plasma_xrdp_minimal/handlers/main.yml new file mode 100644 index 0000000..9381881 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Restart xrdp + ansible.builtin.service: + name: xrdp + state: restarted + +- name: Restart xrdp sesman + ansible.builtin.service: + name: xrdp-sesman + state: restarted diff --git a/roles/vhosts/plasma_xrdp_minimal/tasks/config.yml b/roles/vhosts/plasma_xrdp_minimal/tasks/config.yml new file mode 100644 index 0000000..8e5ec75 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/tasks/config.yml @@ -0,0 +1,53 @@ +--- +- name: Ensure the desktop user exists + ansible.builtin.user: + name: "{{ plasma_user }}" + shell: "{{ plasma_user_shell }}" + create_home: true + state: present + password_lock: false + become: true + when: plasma_manage_user | bool + +- name: Fail when the desktop user password is not provided + ansible.builtin.assert: + that: + - plasma_user_password_plaintext | length > 0 + fail_msg: >- + plasma_user_password_plaintext must be set so XRDP can authenticate the + desktop user. + when: plasma_manage_user | bool + +- name: Set desktop user password for XRDP login + ansible.builtin.user: + name: "{{ plasma_user }}" + password: "{{ plasma_user_password_plaintext | password_hash('sha512') }}" + update_password: always + password_lock: false + become: true + no_log: true + when: plasma_manage_user | bool + +- name: Ensure the desktop user can sudo + ansible.builtin.user: + name: "{{ plasma_user }}" + groups: "{{ plasma_user_groups }}" + append: true + state: present + become: true + when: + - plasma_manage_user | bool + - plasma_user_groups | length > 0 + +- name: Ensure Plasma session file is present + ansible.builtin.template: + src: xsession.j2 + dest: "{{ plasma_xsession_file }}" + owner: "{{ plasma_user }}" + group: "{{ plasma_user }}" + mode: "0644" + become: true + when: plasma_manage_user | bool + notify: + - Restart xrdp + - Restart xrdp sesman diff --git a/roles/vhosts/plasma_xrdp_minimal/tasks/install.yml b/roles/vhosts/plasma_xrdp_minimal/tasks/install.yml new file mode 100644 index 0000000..05d9131 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/tasks/install.yml @@ -0,0 +1,99 @@ +--- +- name: Select Plasma package list for this platform + ansible.builtin.set_fact: + plasma_selected_packages: >- + {{ + plasma_packages_debian if ansible_os_family == 'Debian' + else plasma_packages_fedora if ansible_os_family == 'RedHat' + else plasma_packages_opensuse if ansible_os_family == 'Suse' + else [] + }} + +- name: Fail on unsupported platform + ansible.builtin.assert: + that: + - plasma_selected_packages | length > 0 + fail_msg: "plasma_xrdp_minimal supports Debian, Ubuntu, Fedora, and OpenSuse only." + +- name: Update apt cache + ansible.builtin.apt: + update_cache: true + when: ansible_os_family == 'Debian' + become: true + +- name: Install minimal desktop packages + ansible.builtin.apt: + name: "{{ plasma_selected_packages }}" + state: present + install_recommends: false + environment: + DEBIAN_FRONTEND: noninteractive + APT_LISTCHANGES_FRONTEND: none + when: ansible_os_family == 'Debian' + become: true + +- name: Update DNF cache + ansible.builtin.dnf: + update_cache: true + when: ansible_os_family == 'RedHat' + become: true + +- name: Install Plasma packages on Fedora + ansible.builtin.dnf: + name: "{{ plasma_selected_packages }}" + state: present + when: ansible_os_family == 'RedHat' + become: true + +- name: Refresh Zypper cache + ansible.builtin.command: zypper --non-interactive refresh + changed_when: false + when: ansible_os_family == 'Suse' + become: true + +- name: Install Plasma packages on OpenSuse + ansible.builtin.zypper: + name: "{{ plasma_selected_packages }}" + state: present + type: package + when: ansible_os_family == 'Suse' + become: true + +- name: Check whether the xrdp service account exists + ansible.builtin.command: getent passwd xrdp + register: plasma_xrdp_account + changed_when: false + failed_when: false + become: true + +- name: Ensure xrdp user can read the TLS certificate group + ansible.builtin.user: + name: xrdp + groups: ssl-cert + append: true + become: true + when: plasma_xrdp_account.rc == 0 + +- name: Enable and start XRDP services + ansible.builtin.service: + name: "{{ item }}" + enabled: true + state: started + loop: "{{ plasma_xrdp_services }}" + become: true + +- name: Check whether UFW is installed + ansible.builtin.stat: + path: /usr/sbin/ufw + register: plasma_ufw_binary + become: true + +- name: Allow XRDP through UFW + ansible.builtin.command: "ufw allow {{ plasma_rdp_port }}/tcp" + register: plasma_ufw_allow + changed_when: "'Skipping adding existing rule' not in plasma_ufw_allow.stdout" + failed_when: false + become: true + when: + - plasma_enable_ufw | bool + - plasma_ufw_binary.stat.exists | default(false) diff --git a/roles/vhosts/plasma_xrdp_minimal/tasks/main.yml b/roles/vhosts/plasma_xrdp_minimal/tasks/main.yml new file mode 100644 index 0000000..9f09985 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Install minimal Plasma + XRDP stack + ansible.builtin.import_tasks: install.yml + tags: [plasma, plasma_install] + +- name: Configure Plasma session and XRDP user setup + ansible.builtin.import_tasks: config.yml + tags: [plasma, plasma_config] + +- name: Apply Plasma resource-saving tweaks + ansible.builtin.import_tasks: optimize.yml + tags: [plasma, plasma_optimize] + +- name: Validate XRDP desktop readiness + ansible.builtin.import_tasks: validate.yml + tags: [plasma, plasma_validate] diff --git a/roles/vhosts/plasma_xrdp_minimal/tasks/optimize.yml b/roles/vhosts/plasma_xrdp_minimal/tasks/optimize.yml new file mode 100644 index 0000000..0f0c93f --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/tasks/optimize.yml @@ -0,0 +1,10 @@ +--- +- name: Ensure Plasma session settings are persisted + ansible.builtin.file: + path: "{{ plasma_user_home }}/.config" + state: directory + owner: "{{ plasma_user }}" + group: "{{ plasma_user }}" + mode: "0755" + become: true + when: plasma_manage_user | bool diff --git a/roles/vhosts/plasma_xrdp_minimal/tasks/validate.yml b/roles/vhosts/plasma_xrdp_minimal/tasks/validate.yml new file mode 100644 index 0000000..2afebd6 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/tasks/validate.yml @@ -0,0 +1,33 @@ +--- +- name: Check XRDP service status + ansible.builtin.command: systemctl status xrdp --no-pager --full + register: plasma_xrdp_status + changed_when: false + failed_when: false + become: true + +- name: Show XRDP service status + ansible.builtin.debug: + var: plasma_xrdp_status.stdout_lines + +- name: Check memory usage + ansible.builtin.command: free -m + register: plasma_memory_usage + changed_when: false + failed_when: false + +- name: Show memory usage + ansible.builtin.debug: + var: plasma_memory_usage.stdout_lines + +- name: Check current session type if available + ansible.builtin.shell: 'printf "%s\n" "${XDG_SESSION_TYPE:-unknown}"' + args: + executable: /bin/bash + register: plasma_session_type + changed_when: false + failed_when: false + +- name: Show session type hint + ansible.builtin.debug: + msg: "XDG_SESSION_TYPE={{ plasma_session_type.stdout | default('unknown') }} (expected x11 inside the RDP session)" diff --git a/roles/vhosts/plasma_xrdp_minimal/templates/xsession.j2 b/roles/vhosts/plasma_xrdp_minimal/templates/xsession.j2 new file mode 100644 index 0000000..6ea224c --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/templates/xsession.j2 @@ -0,0 +1 @@ +exec startplasma-x11 diff --git a/roles/vhosts/plasma_xrdp_minimal/vars/main.yml b/roles/vhosts/plasma_xrdp_minimal/vars/main.yml new file mode 100644 index 0000000..ad5bbd0 --- /dev/null +++ b/roles/vhosts/plasma_xrdp_minimal/vars/main.yml @@ -0,0 +1,6 @@ +--- +plasma_user_home: "/home/{{ plasma_user }}" +plasma_xsession_file: "{{ plasma_user_home }}/.xsession" +plasma_xrdp_services: + - xrdp-sesman + - xrdp diff --git a/roles/vhosts/xfce_xrdp_minimal/README.md b/roles/vhosts/xfce_xrdp_minimal/README.md index 1a873a6..b98ab32 100644 --- a/roles/vhosts/xfce_xrdp_minimal/README.md +++ b/roles/vhosts/xfce_xrdp_minimal/README.md @@ -12,6 +12,7 @@ Minimal XFCE4 + XRDP role for constrained Ubuntu/Debian VPS hosts. - Enables `xrdp` and `xrdp-sesman` - Disables compositor and animations to reduce resource usage - Optionally opens TCP `3389` with UFW if UFW is present +- Creates or updates the desktop user and ensures it has a usable local password for XRDP login ## Variables @@ -22,6 +23,7 @@ Minimal XFCE4 + XRDP role for constrained Ubuntu/Debian VPS hosts. - `xfce_disable_compositor`: default `true` - `xfce_disable_animations`: default `true` - `xfce_user_groups`: supplemental groups for the desktop user, default `["sudo"]` +- `xfce_user_password_plaintext`: required password for the desktop user so XRDP can authenticate ## Example playbook @@ -40,6 +42,7 @@ Run these on the server after connecting through RDP: systemctl status xrdp --no-pager --full echo "$XDG_SESSION_TYPE" free -m +passwd -S ubuntu ``` Expected: @@ -47,3 +50,4 @@ Expected: - `xrdp` is active - `XDG_SESSION_TYPE=x11` - memory stays under the host budget +- `ubuntu` is not locked and can authenticate with the password you provided diff --git a/roles/vhosts/xfce_xrdp_minimal/defaults/main.yml b/roles/vhosts/xfce_xrdp_minimal/defaults/main.yml index 2d7e2b6..4cdd667 100644 --- a/roles/vhosts/xfce_xrdp_minimal/defaults/main.yml +++ b/roles/vhosts/xfce_xrdp_minimal/defaults/main.yml @@ -27,3 +27,4 @@ xfce_manage_user: true xfce_user_shell: /bin/bash xfce_user_groups: - sudo +xfce_user_password_plaintext: "" diff --git a/roles/vhosts/xfce_xrdp_minimal/tasks/config.yml b/roles/vhosts/xfce_xrdp_minimal/tasks/config.yml index 15bfd4c..b74f1bb 100644 --- a/roles/vhosts/xfce_xrdp_minimal/tasks/config.yml +++ b/roles/vhosts/xfce_xrdp_minimal/tasks/config.yml @@ -5,9 +5,29 @@ shell: "{{ xfce_user_shell }}" create_home: true state: present + password_lock: false become: true when: xfce_manage_user | bool +- name: Fail when the desktop user password is not provided + ansible.builtin.assert: + that: + - xfce_user_password_plaintext | length > 0 + fail_msg: >- + xfce_user_password_plaintext must be set so XRDP can authenticate the + desktop user. + when: xfce_manage_user | bool + +- name: Set desktop user password for XRDP login + ansible.builtin.user: + name: "{{ xfce_user }}" + password: "{{ xfce_user_password_plaintext | password_hash('sha512') }}" + update_password: always + password_lock: false + become: true + no_log: true + when: xfce_manage_user | bool + - name: Ensure the desktop user can sudo ansible.builtin.user: name: "{{ xfce_user }}" diff --git a/update_cloudflare_dns.yml b/update_cloudflare_dns.yml index 15254ef..f836b52 100644 --- a/update_cloudflare_dns.yml +++ b/update_cloudflare_dns.yml @@ -3,7 +3,5 @@ hosts: localhost connection: local gather_facts: false - vars_files: - - vars/cloudflare_svc_plus_dns.yml roles: - - cloudflare_dns + - role: cloudflare_svc_plus_dns diff --git a/update_cloudflare_svc_plus_dns.yml b/update_cloudflare_svc_plus_dns.yml new file mode 100644 index 0000000..bb9e24a --- /dev/null +++ b/update_cloudflare_svc_plus_dns.yml @@ -0,0 +1,7 @@ +--- +- name: Update svc.plus Cloudflare DNS records + hosts: localhost + connection: local + gather_facts: false + roles: + - role: cloudflare_svc_plus_dns diff --git a/vars/cloudflare_svc_plus_dns.yml b/vars/cloudflare_svc_plus_dns.yml index a428d99..3fa95e5 100644 --- a/vars/cloudflare_svc_plus_dns.yml +++ b/vars/cloudflare_svc_plus_dns.yml @@ -42,17 +42,7 @@ cloudflare_dns_records: proxied: false - type: A name: acp-server.svc.plus - content: 167.179.110.129 - ttl: 1 - proxied: false - - type: A - name: acp-server-codex.svc.plus - content: 167.179.110.129 - ttl: 1 - proxied: false - - type: A - name: acp-server-opencode.svc.plus - content: 167.179.110.129 + content: 46.250.251.132 ttl: 1 proxied: false - type: CNAME