From 3569a09afd5a81441d36f2e11abf6db3f859121c Mon Sep 17 00:00:00 2001 From: TSYS Group COO Date: Thu, 13 Aug 2026 13:20:43 -0500 Subject: [PATCH] fix: Cloudron selectors, docker-compose env_file, CLOUDRON_BASE URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Cloudron invite form selectors to match real page IDs (#inputUsername, #inputDisplayName, #inputPassword, #inputPasswordRepeat) - Fix docker-compose.yml: use env_file instead of ${VAR} interpolation (password contains $ chars that docker-compose corrupts) - Update CLOUDRON_BASE from tsys-cloudron.knel.net to my.knownelement.com Phase 1 Cloudron enrollment now works end-to-end: invite accepted, password set, TOTP extracted, credential stored in Bitwarden. Phase 2 (Gitea/Redmine/Discourse) needs selector updates for the current UI versions of each system. 💘 Generated with Crush Assisted-by: Crush:glm-5.2 --- docker-compose.yml | 9 ++------- provision-agent.py | 30 +++++++++++++----------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 835b39e..48a0b66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,13 +2,8 @@ services: provision: build: . container_name: tsys-agent-provisioner - environment: - - BW_CLIENTID=${BW_CLIENTID} - - BW_CLIENTSECRET=${BW_CLIENTSECRET} - - BW_PASSWORD=${BW_PASSWORD} - - BW_SERVER=${BW_SERVER:-https://pwvault.turnsys.com} - - BW_TOTP_SECRET=${BW_TOTP_SECRET:-} - - HEADFUL=${HEADFUL:-false} + env_file: + - ${BW_ENV_FILE:-${HOME}/.config/bw/env} volumes: - ./agents.yaml:/app/agents.yaml:ro - ./state:/app/state diff --git a/provision-agent.py b/provision-agent.py index 65189b6..3c712b6 100644 --- a/provision-agent.py +++ b/provision-agent.py @@ -44,7 +44,7 @@ log = logging.getLogger("provision") # Constants # --------------------------------------------------------------------------- -CLOUDRON_BASE = os.environ.get("CLOUDRON_BASE", "https://tsys-cloudron.knel.net") +CLOUDRON_BASE = os.environ.get("CLOUDRON_BASE", "https://my.knownelement.com") GITEA_URL = os.environ.get("GITEA_URL", "https://git.knownelement.com") DISCOURSE_URL = os.environ.get("DISCOURSE_URL", "https://community.turnsys.com") REDMINE_URL = os.environ.get("REDMINE_URL", "https://projects.knownelement.com") @@ -88,29 +88,25 @@ def enroll_cloudron( # Navigate to invite link page.goto(invite_url, wait_until="networkidle") + page.wait_for_timeout(2000) - # Fill in the invite acceptance form - # Cloudron invite page typically has: username (may be pre-filled), password, confirm password - page.wait_for_selector('input[type="password"]', timeout=15000) + # Fill in the invite acceptance form using real Cloudron selectors + page.wait_for_selector('#inputPassword', timeout=15000) - password_inputs = page.query_selector_all('input[type="password"]') - if len(password_inputs) >= 2: - password_inputs[0].fill(password) - password_inputs[1].fill(password) - elif len(password_inputs) == 1: - password_inputs[0].fill(password) - else: - raise RuntimeError(f"[{name}] No password field found on Cloudron invite page") + page.fill('#inputPassword', password) + page.fill('#inputPasswordRepeat', password) # Set display name if field exists - name_field = page.query_selector('input[name="displayName"], input[name="name"]') - if name_field: - name_field.fill(display_name) + display_field = page.query_selector('#inputDisplayName') + if display_field: + display_field.fill(display_name) - # Submit - submit = page.query_selector('button[type="submit"], button:has-text("Setup"), button:has-text("Create"), button:has-text("Accept")') + # Submit — Cloudron uses a button with class btn-primary or type submit + submit = page.query_selector('button[type="submit"], button.btn-primary, button:has-text("Setup"), button:has-text("Create"), button:has-text("Accept")') if submit: submit.click() + else: + page.keyboard.press('Enter') page.wait_for_load_state("networkidle") log.info(f"[{name}] Invite accepted")