Skip to content

Deploy GCP Linux Desktop (HyOps Blueprint)

  • Purpose: Provision an Ubuntu 22.04 VM in GCP with a public IP, XFCE4 desktop, and XRDP: accessible via any standard RDP client.
  • Owner: Platform engineering
  • Trigger: Need for a GCP Linux host with GUI access: operator lab, GUI tooling, or a cost-effective alternative to a Windows desktop VM
  • Impact: Creates a GCP Compute instance and two named firewall rules (RDP + SSH); incurs GCP compute cost while running
  • Severity: P2
  • Pre-reqs: hyops init gcp complete for the target env, gcloud SDK installed, XRDP_USER_PASSWORD set in bootstrap vault

  • Rollback strategy: hyops blueprint destroy tears down all steps in reverse order. See §Teardown.


Context

Blueprint ref: gcp/linux-desktop@v1 Location: hybridops-core/blueprints/gcp/linux-desktop@v1/blueprint.yml

Step flow:

  1. platform/gcp/vm-firewall-rules#gcp_linux_desktop_firewall: ingress rules for TCP 3389 (RDP) and TCP 22 (SSH), both scoped to source_ranges
  2. platform/gcp/platform-vm#gcp_linux_desktop_vm: Ubuntu 22.04 VM, public IP, allow-rdp + allow-ssh tags
  3. platform/linux/desktop-xrdp#gcp_linux_desktop_config: Ansible: installs XFCE4, XRDP, and Firefox; suppresses the polkit colour-manager prompt; sets the login password from vault

The Ansible step reads XRDP_USER_PASSWORD from the bootstrap vault (load_vault_env: true). The password must be in the vault before running blueprint deploy.


Cost

An e2-standard-2 Ubuntu VM in europe-west2 costs roughly £0.05–0.07/hr. Stop the instance when not in use to pause compute billing. Firewall rules have no cost.

Stop:

gcloud compute instances stop platform-desktop-linux-desktop-01 \
  --project <PROJECT_ID> --zone <ZONE>

Start:

gcloud compute instances start platform-desktop-linux-desktop-01 \
  --project <PROJECT_ID> --zone <ZONE>

Preconditions and safety checks

  1. GCP init is ready:

    hyops state show --env dev --init gcp
    
  2. XRDP_USER_PASSWORD is in the bootstrap vault:

    hyops secrets set XRDP_USER_PASSWORD=<your-password> --env dev
    

The password is passed to chpasswd on the VM using the format username:password. Do not use a colon (:) in the password.

  1. Confirm your current public IP (used to scope the firewall rules):

    curl -s https://checkip.amazonaws.com
    
  2. Review the two CHANGE_ME values to set before deploy:

  3. CHANGE_ME_GCP_ZONE: e.g., europe-west2-b
  4. CHANGE_ME_YOUR_IP/32: your operator public IP in CIDR notation (appears twice: RDP and SSH rules)

Steps

  1. Initialise the blueprint config
    hyops blueprint init --env dev \
      --ref gcp/linux-desktop@v1 \
      --dest-name linux-desktop.yml
    

This writes the blueprint to:

    ~/.hybridops/envs/dev/config/blueprints/linux-desktop.yml
  1. Set real values

Open ~/.hybridops/envs/dev/config/blueprints/linux-desktop.yml and replace:

  • Both instances of CHANGE_ME_YOUR_IP/32 → your public IP, e.g., 203.0.113.42/32
  • CHANGE_ME_GCP_ZONE → e.g., europe-west2-b

Optional overrides (defaults are usable as-is):

  • machine_type: default e2-standard-2
  • boot_disk_size_gb: default 40
  • ssh_private_key_file: default ~/.ssh/id_ed25519; update if your key is elsewhere

  • Validate

    hyops blueprint validate \
      --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml"
    
  • Preflight

    hyops blueprint preflight --env dev \
      --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml"
    
  • Deploy

    hyops blueprint deploy --env dev \
      --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" \
      --execute
    

All three steps run in sequence. Typical duration: 5–8 minutes (XFCE4 + XRDP install takes 2–3 minutes on the VM).

  1. Get connection details

The deploy output includes the RDP host, port, and user. Retrieve from state if needed:

    cat ~/.hybridops/envs/dev/state/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.json \
      | python3 -m json.tool | grep -A5 '"outputs"'

Or from the VM state directly:

    gcloud compute instances describe platform-desktop-linux-desktop-01 \
      --project <PROJECT_ID> --zone <ZONE> \
      --format="get(networkInterfaces[0].accessConfigs[0].natIP)"
  1. RDP in

Connect with any RDP client to <PUBLIC_IP>:3389.

  • User: opsadmin
  • Password: the value set as XRDP_USER_PASSWORD in §Preconditions

Example with xfreerdp:

    xfreerdp /v:<PUBLIC_IP> /u:opsadmin /p:<PASSWORD> /cert-ignore
  • Windows: built-in Remote Desktop Connection (mstsc)
  • macOS: Microsoft Remote Desktop (App Store)
  • Linux: xfreerdp or rdesktop

Verification

Primary state:

~/.hybridops/envs/dev/state/modules/platform__gcp__vm-firewall-rules/instances/gcp_linux_desktop_firewall.json
~/.hybridops/envs/dev/state/modules/platform__gcp__platform-vm/instances/gcp_linux_desktop_vm.json
~/.hybridops/envs/dev/state/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.json

Cross-check in GCP:

# VM running
gcloud compute instances describe platform-desktop-linux-desktop-01 \
  --project <PROJECT_ID> --zone <ZONE> \
  --format="get(status)"

# Firewall rules present
gcloud compute firewall-rules list \
  --project <PROJECT_ID> \
  --filter="targetTags~allow-rdp OR targetTags~allow-ssh" \
  --format="table(name,sourceRanges,allowed)"

Teardown

Destroy all blueprint resources in reverse order using the blueprint file:

hyops blueprint destroy --env dev \
  --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" \
  --execute

This destroys steps in the order: config → VM → firewall. A confirmation prompt lists all steps and their current state before proceeding. Use --yes to skip the prompt in non-interactive contexts.

Manual teardown (individual steps)

If only specific steps need retargeting, destroy each module individually in reverse apply order. The --inputs flag is required because zone and other required inputs are not stored in the state record directly.

# 1. Remove Ansible config state (no infrastructure to tear down)
hyops destroy --env dev \
  --module platform/linux/desktop-xrdp \
  --state-instance gcp_linux_desktop_config \
  --inputs "$HOME/.hybridops/envs/dev/config/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.inputs.yml"

# 2. Destroy the VM
hyops destroy --env dev \
  --module platform/gcp/platform-vm \
  --state-instance gcp_linux_desktop_vm \
  --inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__platform-vm/instances/gcp_linux_desktop_vm.inputs.yml"

# 3. Destroy the firewall rules
hyops destroy --env dev \
  --module platform/gcp/vm-firewall-rules \
  --state-instance gcp_linux_desktop_firewall \
  --inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__vm-firewall-rules/instances/gcp_linux_desktop_firewall.inputs.yml"

Troubleshooting

RDP connection refused

Check in order:

  1. Public IP is correct: retrieve again via gcloud compute instances describe
  2. Firewall rules exist and source_ranges includes your current IP (your IP may have changed)
  3. VM status is RUNNING:

    gcloud compute instances describe platform-desktop-linux-desktop-01 \
      --project <PROJECT_ID> --zone <ZONE> \
      --format="get(status)"
    
  4. XRDP is running on the VM:

    ssh opsadmin@<PUBLIC_IP> "systemctl status xrdp"
    

Wrong password at RDP login

The XRDP user password is set from XRDP_USER_PASSWORD in the bootstrap vault at deploy time. If the wrong value was in vault, update the vault and re-run the config step:

hyops secrets set XRDP_USER_PASSWORD=<correct-password> --env dev

hyops apply --env dev \
  --module platform/linux/desktop-xrdp \
  --state-instance gcp_linux_desktop_config \
  --inputs "$HOME/.hybridops/envs/dev/config/modules/platform__linux__desktop-xrdp/instances/gcp_linux_desktop_config.inputs.yml"

Ansible step fails with UNREACHABLE

Cause: VM not yet accepting SSH connections at the time Ansible ran (connectivity_wait_s: 90 is usually sufficient but may not be enough on a slow boot).

Fix: re-run the config step; the VM and firewall steps will skip (skip_if_state_ok: true):

hyops blueprint deploy --env dev \
  --file "$HOME/.hybridops/envs/dev/config/blueprints/linux-desktop.yml" \
  --execute

source_ranges locked you out (IP changed)

Fix: update source_ranges in the blueprint config and re-run the firewall step:

hyops apply --env dev \
  --module platform/gcp/vm-firewall-rules \
  --state-instance gcp_linux_desktop_firewall \
  --inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__vm-firewall-rules/instances/gcp_linux_desktop_firewall.inputs.yml"

References