diff --git a/README.md b/README.md index 424440b1..ee2de0d0 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ The JS and Python versions are kept in lock-step by `release-please` and guarded ## Extensions Catalog -This repository contains **2 marketplace(s)** with **64 extensions** (54 skills, 10 plugins). +This repository contains **2 marketplace(s)** with **65 extensions** (55 skills, 10 plugins). ### large-codebase @@ -108,7 +108,7 @@ OpenHands skills for interacting, improving, and refactoring large codebases Official skills and plugins for OpenHands — the open-source AI software engineer. -**60 extensions** (52 skills, 8 plugins) +**61 extensions** (53 skills, 8 plugins) | Name | Type | Description | Commands | |------|------|-------------|----------| @@ -138,6 +138,7 @@ Official skills and plugins for OpenHands — the open-source AI software engine | github-repo-monitor | skill | Create a cron automation that polls a GitHub repository for issue and PR comments containing a configurable trigger p... | `/github-monitor:poll` | | gitlab | skill | Interact with GitLab repositories, merge requests, and APIs using the GITLAB_TOKEN environment variable. Use when wor... | — | | incident-retrospective | skill | Create an automation that drafts incident retrospectives by gathering incident-channel messages from Slack, collectin... | `/incident-retro:setup` | +| install-openhands-replicated | skill | Guide supported OpenHands Enterprise VM installations through Replicated Embedded Cluster with read-only preflights, ... | — | | iterate | skill | Iterate on a GitHub pull request — drive it through CI, code review, and QA until merge-ready. Monitors state, fixes ... | `/iterate`, `/verify`, `/babysit` | | jira-issue-to-pr | skill | Deploy a cron-based OpenHands automation that watches a Jira Cloud project for issues labeled with a configurable lab... | — | | jupyter | skill | Read, modify, execute, and convert Jupyter notebooks programmatically. Use when working with .ipynb files for data sc... | — | diff --git a/marketplaces/openhands-extensions.json b/marketplaces/openhands-extensions.json index dfed7887..9ef76e6c 100644 --- a/marketplaces/openhands-extensions.json +++ b/marketplaces/openhands-extensions.json @@ -770,6 +770,19 @@ "jira", "atlassian" ] + }, + { + "name": "install-openhands-replicated", + "source": "./skills/install-openhands-replicated", + "description": "Guide supported OpenHands Enterprise VM installations through Replicated Embedded Cluster with read-only preflights, explicit approval gates, provider setup, and end-to-end validation.", + "category": "environment", + "keywords": [ + "openhands-enterprise", + "replicated", + "embedded-cluster", + "vm-install", + "preflight" + ] } ] } diff --git a/skills/index.js b/skills/index.js index 5fe5d59c..70fba5d7 100644 --- a/skills/index.js +++ b/skills/index.js @@ -262,6 +262,13 @@ export const SKILLS_CATALOG = [ "content": "# Incident Retrospective Drafter Automation\n\nSet up an automation that drafts incident retrospectives by pulling data from\nSlack, Linear, and Notion.\n\n---\n\n## Prerequisites\n\n### Required integrations\n\nAll three MCP integrations must be installed in Settings → MCP:\n\n- **Slack MCP** — to gather incident-channel messages\n- **Linear MCP** — to collect linked tickets and follow-ups\n- **Notion MCP** — to publish the retrospective draft\n\n### Information to collect\n\nAsk the user for:\n\n1. **Incident identification** — how are incidents identified? (e.g. Slack channel naming convention like `#inc-*`, a Linear label, or manual trigger)\n2. **Slack channels** — which channels contain incident chatter (e.g. `#incidents`, `#inc-*` pattern)\n3. **Linear teams** — which Linear teams/projects to inspect for follow-up tickets\n4. **Retrospective template** — what sections should the retro include? Default: Timeline, Impact, Root Cause, Action Items, Lessons Learned\n5. **Notion destination** — which Notion database or page should receive the draft\n6. **Trigger type** — manual dispatch, cron schedule, or triggered by an incident label being added\n\n---\n\n## Setup Workflow\n\n### Step 1 — Verify MCP access\n\nTest each integration:\n```\nUse the Slack MCP to list recent messages in an incident channel.\nUse the Linear MCP to list recent issues for the target team.\nUse the Notion MCP to search for the destination database.\n```\n\nIf any fail, tell the user which integration needs to be installed first.\n\n### Step 2 — Determine trigger type\n\nAsk the user how retros should be triggered:\n- **Manual** — dispatch from the automations page when an incident wraps up\n- **Cron** — run daily/weekly to check for recent incidents\n- **Event** — triggered by a Linear label change or Slack message\n\n### Step 3 — Build the retro prompt\n\nConstruct a prompt that includes:\n- How to identify the incident (channel pattern, label, etc.)\n- Which Slack channels and Linear teams to query\n- The retrospective template/sections\n- Where to publish in Notion\n\n### Step 4 — Create the automation\n\nRead the Automation backend URL and auth from ``:\n- Use the **Automation backend** `url_from_agent` as `OPENHANDS_HOST`\n- Auth: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\nUse the **prompt preset** endpoint:\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1/preset/prompt\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Incident Retrospective Drafter\",\n \"prompt\": \"\",\n \"trigger\": \n }'\n```\n\nPowerShell note: use `curl.exe` for this exact flag syntax, and replace `${OPENHANDS_HOST}` / `$OPENHANDS_AUTOMATION_API_KEY` with `$env:OPENHANDS_HOST` / `$env:OPENHANDS_AUTOMATION_API_KEY` if running it natively.\n\n### Step 5 — Confirm\n\nTell the user:\n> ✅ **Incident Retrospective Drafter** is running!\n>\n> - Automation ID: `{id}`\n> - Incident source: `{identification method}`\n> - Slack channels: `{channels}`\n> - Linear teams: `{teams}`\n> - Notion destination: `{destination}`\n> - Trigger: `{trigger description}`", "category": "automations" }, + { + "name": "install-openhands-replicated", + "description": "This skill should be used when the user asks to \"install OpenHands Enterprise\", \"set up OHE on a VM\", \"run an OHE install preflight\", \"configure the Replicated Admin Console\", \"prepare DNS and TLS for OpenHands Enterprise\", or \"validate a Replicated Embedded Cluster installation\". It guides supported AWS Terraform or manual VM installations from scoping through end-to-end validation.", + "triggers": [], + "content": "# Install OpenHands Enterprise on Replicated\n\nGuide a customer or field engineer from installation scoping to a usable OpenHands Enterprise deployment. Treat a green Replicated deployment as an intermediate milestone; prove the user workflows that are in scope.\n\n## Safety Contract\n\n- Start with planning and read-only preflight checks. Do not create infrastructure, modify DNS or firewall rules, run the installer, deploy ConfigValues, restart workloads, or rotate credentials without explicit approval for that exact operation.\n- Obtain the current installer command, license bundle, release channel, and target OHE version from the customer's installer dashboard. Treat download URLs, license files, tokens, private keys, and provider credentials as secrets. Never paste or log their values.\n- State the command category, expected impact, prerequisites, rollback boundary, and verification plan before each mutating phase.\n- Prefer supported Replicated and KOTS surfaces. Do not use direct Kubernetes patches as installation steps. Do not bypass host preflights except under a version-matched procedure from OpenHands or Replicated Support.\n- Keep temporary secret-bearing files permission-restricted and outside repositories. Remove them after the supported configuration surface has consumed them.\n- Use supported defaults first. Add integrations and operational overrides only when they are explicit requirements.\n- Stop when the installed version differs from the documentation or command help, storage safety is unclear, or the requested recovery path can destroy state.\n\n## Installation Workflow\n\n### 1. Establish the Contract\n\nRecord:\n\n- OHE target release and installer/Embedded Cluster version shown by the dashboard;\n- AWS Terraform or manual VM path;\n- base domain, DNS owner, TLS owner, and hostname mode;\n- LLM provider and authentication owner;\n- Git provider and optional integrations;\n- embedded or external PostgreSQL and backup expectations;\n- change approver, maintenance window, and support contact.\n\nCopy `assets/install-plan.yaml` outside the skill repository and populate only non-secret scope and validation state. Do not represent it as a deployable headless configuration file. Draft missing DNS, firewall, certificate, or access requests from `references/operator-requests.md` before changing infrastructure.\n\n### 2. Provision or Inspect Infrastructure\n\nUse the current OpenHands AWS Terraform module when AWS Terraform is selected. For a manual VM, require the documented CPU, memory, disk, latency, OS, systemd, root access, inbound ports, local ports, and outbound destinations.\n\nRun read-only preflights on the target VM:\n\n```bash\nscripts/check_host_preflight.sh\nscripts/check_dns.sh simple\nscripts/check_tls_files.sh wildcard\nscripts/check_outbound.sh \n```\n\nResolve failures before obtaining approval to run the installer. Read `references/install-flow.md` for the current requirements and phased checklist.\n\n### 3. Review the Installer Operation\n\nUse only commands copied from the customer's installer dashboard for the chosen release. Before execution:\n\n1. Confirm the VM and base domain.\n2. Confirm the installer-side instance name is not being confused with the cloud resource name.\n3. Confirm the license and TLS file paths exist without printing their contents.\n4. Confirm host, DNS, port, and outbound preflights passed.\n5. Explain that installation creates system services, Kubernetes state, storage, and an Admin Console.\n6. Obtain explicit approval to run the exact dashboard-provided install command.\n\nRun the interactive installer in a real PTY. Stop rather than scripting around an unexpected password or terminal prompt. Do not claim a headless installation from the non-secret planning asset; require a documented release-specific schema and secret-input method before automating ClickOps.\n\n### 4. Configure the Admin Console in Layers\n\nUse the current `Simple` hostname mode unless the customer requires manual hostnames. Configure and validate one layer at a time:\n\n1. domain and publicly trusted TLS;\n2. one LLM provider;\n3. database choice and storage durability;\n4. core application deployment;\n5. first login and organization;\n6. Git provider authentication using `references/git-provider-auth.md`;\n7. optional integrations, analytics, automations, and advanced settings.\n\nRead `references/admin-config.md` before applying settings. Treat ConfigValues files as potentially secret-bearing. Preview helper operations before execution.\n\n### 5. Prove the Core Product\n\nDo not declare completion from pod readiness alone. Verify:\n\n- Admin Console and application TLS validate for the configured hostnames;\n- deployment status is Ready and workloads have no new warning events;\n- login works in a clean browser session;\n- the first organization and bounded API key work;\n- the configured model completes one tiny request;\n- one no-repository conversation finishes with an expected marker;\n- repository search and one repository-backed conversation work when a Git provider is in scope.\n\nRun `scripts/preflight_storage_guard.sh ` before declaring the deployment durable. Record what the backup does and does not cover.\n\n### 6. Add Optional Integrations\n\nValidate integrations one at a time after core login, LLM, and conversation paths pass. Use `references/integrations.md` and the focused checklist scripts. Prove a real event or linked account; a reachable callback URL is not sufficient.\n\n### 7. Produce the Handoff\n\nRecord:\n\n```text\nOHE release:\nEmbedded Cluster/installer version:\nInfrastructure path and region/site:\nHostname mode and base domain:\nDatabase and storage class:\nEnabled integrations:\nPreflight evidence:\nCore smoke-test evidence:\nBackup and restore boundary:\nKnown limitations:\nSupport-bundle command and approved support channel:\n```\n\nExclude credentials, license contents, private keys, unredacted ConfigValues, and complete environment dumps.\n\n## Mutating Helper Gate\n\nUse `scripts/apply_kots_config.sh` in preview mode first:\n\n```bash\nscripts/apply_kots_config.sh \\\n --appslug openhands \\\n --config-file ./config-values.patch.yaml \\\n --current\n```\n\nAfter reviewing impact and obtaining explicit approval, add `--execute`; add `--deploy` only when an immediate deployment is approved. Re-run workload, readiness, storage, and user-path verification after deployment.\n\n## Resources\n\n- `references/install-flow.md`: current VM requirements, hostname layouts, installer sequence, and completion criteria.\n- `references/admin-config.md`: TLS, LLM, database, sandbox, proxy, and guarded ConfigValues guidance.\n- `references/integrations.md`: GitHub, GitLab, Bitbucket, Jira, Slack, analytics, and automation validation.\n- `references/backup-and-durability.md`: persistence checks and recovery boundaries.\n- `references/blue-green-reinstall.md`: separately approved rebuild and cutover workflow.\n- `references/operator-requests.md`: customer-ready DNS, firewall, TLS, and access request templates.\n- `references/git-provider-auth.md`: provider-specific application setup, approval, and validation routing.\n- `assets/install-plan.yaml`: non-secret scoping and validation record; never use it as deployable ConfigValues.\n- `scripts/check_host_preflight.sh`: read-only Linux host and port checks.\n- `scripts/check_dns.sh`: Simple or Legacy hostname resolution checks.\n- `scripts/check_tls_files.sh`: certificate dates, key matching, SAN coverage, and trust-chain checks.\n- `scripts/check_outbound.sh`: required outbound reachability checks.\n- `scripts/summarize_terraform_outputs.sh`: allowlisted, non-sensitive Terraform output summary.\n- `scripts/apply_kots_config.sh`: preview-first KOTS ConfigValues helper.\n- `scripts/preflight_storage_guard.sh`: Postgres PVC, DiskPressure, host-space, and ClickHouse checks.", + "category": "environment" + }, { "name": "iterate", "description": "Iterate on a GitHub pull request — drive it through CI, code review, and QA until it is merge-ready. Poll verification layers with `gh` CLI, diagnose and fix CI failures, address review feedback, retry flaky checks, push fixes, and repeat. The agent is the orchestration loop.", diff --git a/skills/install-openhands-replicated/.claude-plugin b/skills/install-openhands-replicated/.claude-plugin new file mode 120000 index 00000000..665797f0 --- /dev/null +++ b/skills/install-openhands-replicated/.claude-plugin @@ -0,0 +1 @@ +.plugin \ No newline at end of file diff --git a/skills/install-openhands-replicated/.codex-plugin b/skills/install-openhands-replicated/.codex-plugin new file mode 120000 index 00000000..665797f0 --- /dev/null +++ b/skills/install-openhands-replicated/.codex-plugin @@ -0,0 +1 @@ +.plugin \ No newline at end of file diff --git a/skills/install-openhands-replicated/.plugin/plugin.json b/skills/install-openhands-replicated/.plugin/plugin.json new file mode 100644 index 00000000..f9dc7e45 --- /dev/null +++ b/skills/install-openhands-replicated/.plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "install-openhands-replicated", + "version": "1.0.0", + "description": "Guide supported OpenHands Enterprise VM installations through Replicated Embedded Cluster with read-only preflights, explicit approval gates, provider setup, and end-to-end validation.", + "author": { + "name": "OpenHands", + "email": "contact@all-hands.dev" + }, + "homepage": "https://github.com/OpenHands/extensions", + "repository": "https://github.com/OpenHands/extensions", + "license": "MIT", + "keywords": [ + "openhands-enterprise", + "replicated", + "embedded-cluster", + "vm-install", + "preflight" + ] +} diff --git a/skills/install-openhands-replicated/README.md b/skills/install-openhands-replicated/README.md new file mode 100644 index 00000000..f735131f --- /dev/null +++ b/skills/install-openhands-replicated/README.md @@ -0,0 +1,40 @@ +# Install OpenHands Enterprise on Replicated + +Guide a customer or field engineer through a supported OpenHands Enterprise VM installation delivered with Replicated Embedded Cluster. + +## Use this skill for + +- scoping AWS Terraform or manual VM installations; +- checking host resources, ports, DNS, TLS, outbound access, and LLM endpoints; +- preparing GitHub, GitLab, Bitbucket Data Center, or Azure DevOps authentication; +- guiding version-specific installer and Admin Console steps; +- validating login, LLM routing, conversations, repository access, integrations, and storage; +- drafting DNS, firewall, certificate, infrastructure, and access requests for IT teams. + +## Safety model + +The skill starts read-only. Infrastructure changes, installer execution, ConfigValues merges, deployments, provider application creation, DNS changes, restores, and cutovers require explicit approval for the exact operation. + +Installer download URLs, license files, private keys, provider credentials, ConfigValues, and support bundles are treated as sensitive. The bundled YAML asset records only non-secret scope and validation status; it is not a headless deployment configuration. + +## Current scope + +This draft implements the PRD-137 first milestone: comprehensive preflight, provider setup guidance, guided ClickOps, post-install verification, and customer-ready IT requests. + +A fully headless install remains conditional on a documented, release-specific installer schema and supported secret-input mechanism. The skill does not infer or invent those interfaces. + +## Primary triggers + +- `install OpenHands Enterprise` +- `set up OHE on a VM` +- `run an OHE install preflight` +- `configure the Replicated Admin Console` +- `prepare DNS and TLS for OpenHands Enterprise` +- `validate a Replicated Embedded Cluster installation` + +## Official references + +- [OpenHands Enterprise quick start](https://docs.openhands.dev/enterprise/quick-start) +- [Admin Console configuration](https://docs.openhands.dev/enterprise/vm-install/admin-console-configuration) +- [Replicated Embedded Cluster installation](https://docs.replicated.com/enterprise/installing-embedded) +- [Replicated Embedded Cluster requirements](https://docs.replicated.com/enterprise/installing-embedded-requirements) diff --git a/skills/install-openhands-replicated/SKILL.md b/skills/install-openhands-replicated/SKILL.md new file mode 100644 index 00000000..8c43bc29 --- /dev/null +++ b/skills/install-openhands-replicated/SKILL.md @@ -0,0 +1,145 @@ +--- +name: install-openhands-replicated +description: This skill should be used when the user asks to "install OpenHands Enterprise", "set up OHE on a VM", "run an OHE install preflight", "configure the Replicated Admin Console", "prepare DNS and TLS for OpenHands Enterprise", or "validate a Replicated Embedded Cluster installation". It guides supported AWS Terraform or manual VM installations from scoping through end-to-end validation. +--- + +# Install OpenHands Enterprise on Replicated + +Guide a customer or field engineer from installation scoping to a usable OpenHands Enterprise deployment. Treat a green Replicated deployment as an intermediate milestone; prove the user workflows that are in scope. + +## Safety Contract + +- Start with planning and read-only preflight checks. Do not create infrastructure, modify DNS or firewall rules, run the installer, deploy ConfigValues, restart workloads, or rotate credentials without explicit approval for that exact operation. +- Obtain the current installer command, license bundle, release channel, and target OHE version from the customer's installer dashboard. Treat download URLs, license files, tokens, private keys, and provider credentials as secrets. Never paste or log their values. +- State the command category, expected impact, prerequisites, rollback boundary, and verification plan before each mutating phase. +- Prefer supported Replicated and KOTS surfaces. Do not use direct Kubernetes patches as installation steps. Do not bypass host preflights except under a version-matched procedure from OpenHands or Replicated Support. +- Keep temporary secret-bearing files permission-restricted and outside repositories. Remove them after the supported configuration surface has consumed them. +- Use supported defaults first. Add integrations and operational overrides only when they are explicit requirements. +- Stop when the installed version differs from the documentation or command help, storage safety is unclear, or the requested recovery path can destroy state. + +## Installation Workflow + +### 1. Establish the Contract + +Record: + +- OHE target release and installer/Embedded Cluster version shown by the dashboard; +- AWS Terraform or manual VM path; +- base domain, DNS owner, TLS owner, and hostname mode; +- LLM provider and authentication owner; +- Git provider and optional integrations; +- embedded or external PostgreSQL and backup expectations; +- change approver, maintenance window, and support contact. + +Copy `assets/install-plan.yaml` outside the skill repository and populate only non-secret scope and validation state. Do not represent it as a deployable headless configuration file. Draft missing DNS, firewall, certificate, or access requests from `references/operator-requests.md` before changing infrastructure. + +### 2. Provision or Inspect Infrastructure + +Use the current OpenHands AWS Terraform module when AWS Terraform is selected. For a manual VM, require the documented CPU, memory, disk, latency, OS, systemd, root access, inbound ports, local ports, and outbound destinations. + +Run read-only preflights on the target VM: + +```bash +scripts/check_host_preflight.sh +scripts/check_dns.sh simple +scripts/check_tls_files.sh wildcard +scripts/check_outbound.sh +``` + +Resolve failures before obtaining approval to run the installer. Read `references/install-flow.md` for the current requirements and phased checklist. + +### 3. Review the Installer Operation + +Use only commands copied from the customer's installer dashboard for the chosen release. Before execution: + +1. Confirm the VM and base domain. +2. Confirm the installer-side instance name is not being confused with the cloud resource name. +3. Confirm the license and TLS file paths exist without printing their contents. +4. Confirm host, DNS, port, and outbound preflights passed. +5. Explain that installation creates system services, Kubernetes state, storage, and an Admin Console. +6. Obtain explicit approval to run the exact dashboard-provided install command. + +Run the interactive installer in a real PTY. Stop rather than scripting around an unexpected password or terminal prompt. Do not claim a headless installation from the non-secret planning asset; require a documented release-specific schema and secret-input method before automating ClickOps. + +### 4. Configure the Admin Console in Layers + +Use the current `Simple` hostname mode unless the customer requires manual hostnames. Configure and validate one layer at a time: + +1. domain and publicly trusted TLS; +2. one LLM provider; +3. database choice and storage durability; +4. core application deployment; +5. first login and organization; +6. Git provider authentication using `references/git-provider-auth.md`; +7. optional integrations, analytics, automations, and advanced settings. + +Read `references/admin-config.md` before applying settings. Treat ConfigValues files as potentially secret-bearing. Preview helper operations before execution. + +### 5. Prove the Core Product + +Do not declare completion from pod readiness alone. Verify: + +- Admin Console and application TLS validate for the configured hostnames; +- deployment status is Ready and workloads have no new warning events; +- login works in a clean browser session; +- the first organization and bounded API key work; +- the configured model completes one tiny request; +- one no-repository conversation finishes with an expected marker; +- repository search and one repository-backed conversation work when a Git provider is in scope. + +Run `scripts/preflight_storage_guard.sh ` before declaring the deployment durable. Record what the backup does and does not cover. + +### 6. Add Optional Integrations + +Validate integrations one at a time after core login, LLM, and conversation paths pass. Use `references/integrations.md` and the focused checklist scripts. Prove a real event or linked account; a reachable callback URL is not sufficient. + +### 7. Produce the Handoff + +Record: + +```text +OHE release: +Embedded Cluster/installer version: +Infrastructure path and region/site: +Hostname mode and base domain: +Database and storage class: +Enabled integrations: +Preflight evidence: +Core smoke-test evidence: +Backup and restore boundary: +Known limitations: +Support-bundle command and approved support channel: +``` + +Exclude credentials, license contents, private keys, unredacted ConfigValues, and complete environment dumps. + +## Mutating Helper Gate + +Use `scripts/apply_kots_config.sh` in preview mode first: + +```bash +scripts/apply_kots_config.sh \ + --appslug openhands \ + --config-file ./config-values.patch.yaml \ + --current +``` + +After reviewing impact and obtaining explicit approval, add `--execute`; add `--deploy` only when an immediate deployment is approved. Re-run workload, readiness, storage, and user-path verification after deployment. + +## Resources + +- `references/install-flow.md`: current VM requirements, hostname layouts, installer sequence, and completion criteria. +- `references/admin-config.md`: TLS, LLM, database, sandbox, proxy, and guarded ConfigValues guidance. +- `references/integrations.md`: GitHub, GitLab, Bitbucket, Jira, Slack, analytics, and automation validation. +- `references/backup-and-durability.md`: persistence checks and recovery boundaries. +- `references/blue-green-reinstall.md`: separately approved rebuild and cutover workflow. +- `references/operator-requests.md`: customer-ready DNS, firewall, TLS, and access request templates. +- `references/git-provider-auth.md`: provider-specific application setup, approval, and validation routing. +- `assets/install-plan.yaml`: non-secret scoping and validation record; never use it as deployable ConfigValues. +- `scripts/check_host_preflight.sh`: read-only Linux host and port checks. +- `scripts/check_dns.sh`: Simple or Legacy hostname resolution checks. +- `scripts/check_tls_files.sh`: certificate dates, key matching, SAN coverage, and trust-chain checks. +- `scripts/check_outbound.sh`: required outbound reachability checks. +- `scripts/summarize_terraform_outputs.sh`: allowlisted, non-sensitive Terraform output summary. +- `scripts/apply_kots_config.sh`: preview-first KOTS ConfigValues helper. +- `scripts/preflight_storage_guard.sh`: Postgres PVC, DiskPressure, host-space, and ClickHouse checks. diff --git a/skills/install-openhands-replicated/agents/openai.yaml b/skills/install-openhands-replicated/agents/openai.yaml new file mode 100644 index 00000000..1b23433c --- /dev/null +++ b/skills/install-openhands-replicated/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Install OpenHands Replicated" + short_description: "Install OpenHands Enterprise on Replicated" + default_prompt: "Use $install-openhands-replicated to plan, execute, and validate a generic OpenHands Enterprise Replicated install." diff --git a/skills/install-openhands-replicated/assets/install-plan.yaml b/skills/install-openhands-replicated/assets/install-plan.yaml new file mode 100644 index 00000000..df4949c1 --- /dev/null +++ b/skills/install-openhands-replicated/assets/install-plan.yaml @@ -0,0 +1,60 @@ +# Non-secret planning record only. Do not place credentials, license contents, +# private keys, download URLs, tokens, or decrypted ConfigValues in this file. +installation: + target_ohe_release: "" + installer_version: "" + infrastructure_path: "aws-terraform-or-manual-vm" + environment_name: "" + change_approver: "" + maintenance_window: "" + +infrastructure: + cloud_or_datacenter: "" + region_or_site: "" + vm_identifier: "" + cpu_count: 0 + memory_gib: 0 + disk_gib: 0 + storage_type: "" + disk_p99_write_latency_ms: null + +network: + base_domain: "" + hostname_mode: "simple" + dns_owner: "" + firewall_owner: "" + proxy_required: false + public_tls: true + certificate_owner: "" + +application: + llm_provider: "" + git_provider: "" + database_mode: "embedded-or-external" + optional_integrations: [] + analytics_enabled: false + automations_enabled: false + +recovery: + backup_owner: "" + rpo: "" + rto: "" + restore_procedure: "" + +validation: + host_preflight: "pending" + dns_preflight: "pending" + tls_preflight: "pending" + outbound_preflight: "pending" + installer_approved: false + deployment_ready: false + login: "pending" + llm_smoke_test: "pending" + conversation_smoke_test: "pending" + repository_smoke_test: "not-in-scope" + storage_guard: "pending" + +handoff: + known_limitations: [] + support_channel: "" + cleanup_owner: "" diff --git a/skills/install-openhands-replicated/references/admin-config.md b/skills/install-openhands-replicated/references/admin-config.md new file mode 100644 index 00000000..c87ac1e4 --- /dev/null +++ b/skills/install-openhands-replicated/references/admin-config.md @@ -0,0 +1,118 @@ +# Admin Console Configuration + +Use the Admin Console for supported settings. Treat a saved configuration as a mutating operation that can restart components. Review the intended values, impact, and verification plan before selecting Deploy. + +## Domain and TLS + +Use `Simple` hostname mode for new installations unless DNS policy requires manual hostnames. In Simple mode, all service names sit directly under the base domain: + +```text +admin. +app. +auth. +analytics. +llm-proxy. +runtime-api. +-runtime. +``` + +Keep existing Legacy installations on their current layout unless hostname migration is the approved change. Legacy layouts can include `auth.app.` and `.runtime.`. + +Use a publicly trusted wildcard certificate for customer-facing installations whenever possible. Include intermediate certificates and verify that the private key matches the server certificate without printing either value. + +Self-signed certificates are not supported for the OpenHands application. A private CA requires every browser, OAuth provider, and webhook sender to trust the CA; otherwise callbacks can fail TLS validation. + +If certificates are not passed during installation, use the Admin Console certificate upload flow. Store local certificate and key files outside repositories with restrictive permissions and remove temporary copies after use. + +## Runtime Routing + +Subdomain routing in Simple mode requires wildcard coverage for `*.`. If wildcard certificates are unavailable, use the target release's supported path-based routing mode and provision the complete SAN set from its documentation. + +## LLM Provider + +Configure one working provider first. For Bedrock, verify: + +- AWS auth mode is correct: access key/secret or EC2 instance profile. +- Region has access to the chosen model. +- Model ID is the exact Bedrock or inference-profile ID exposed by AWS. +- LiteLLM model alias is visible through `/v1/models`. + +For current Bedrock model IDs, query AWS rather than relying on stale notes: + +```bash +aws bedrock list-foundation-models --region +``` + +If OpenHands profiles use the internal proxy, model names should normally look like: + +```text +litellm_proxy/ +``` + +and the base URL should be: + +```text +http://openhands-litellm:4000 +``` + +## Sandbox Settings + +Common settings: + +- idle time: how long before idle conversations pause; +- deletion time: how long paused runtimes/PVCs are retained before deletion; +- storage size: PVC size per sandbox; +- memory request/limit and CPU request/limit; +- warm runtime count. + +Interpretation: + +- A longer deletion time helps users resume old conversations, but it keeps runtime PVCs around longer. +- A warm runtime can improve start latency, but it must match the environment needed by the conversation. If a warm runtime lacks required secrets/env vars, the request may cold-start anyway. +- More running sandboxes consume memory and CPU. On small single-node installs, too many active runtimes can indirectly make login and API paths feel unstable. + +## Declarative KOTS Config + +Prefer the Admin Console for interactive customer configuration. Use a small KOTS `ConfigValues` merge patch only when the operator requires repeatable declarative configuration and the target release supports the referenced keys: + +```yaml +apiVersion: kots.io/v1beta1 +kind: ConfigValues +spec: + values: + config_key: + value: "new-value" +``` + +Treat ConfigValues files as potentially secret-bearing. Keep them outside repositories, restrict permissions, avoid shell tracing, and do not paste their contents into chat or tickets. + +Preview the command first: + +```bash +scripts/apply_kots_config.sh \ + --appslug openhands \ + --config-file ./config-values.patch.yaml \ + --current +``` + +After reviewing the preview and obtaining approval, execute without deployment: + +```bash +scripts/apply_kots_config.sh \ + --appslug openhands \ + --config-file ./config-values.patch.yaml \ + --current \ + --execute +``` + +Add `--deploy` only when an immediate rollout is approved. Verify the new sequence, rollout status, application readiness, storage guard, and affected user path. + +## Secret Field Shape + +Avoid exporting decrypted configuration unless a support or migration procedure requires it. When a version-matched procedure requires a decrypted export, preserve the original field shape for secret/file items and do not encode an already encoded KOTS value again. + +A double-encoded GitHub App private key can cause key parsing failures in components that consume it. Correct the value through the supported configuration surface; do not extract or patch Kubernetes Secret values as an installation shortcut. + +## Installer-Managed Secrets + +Do not rotate installer-managed PostgreSQL, Redis, JWT, Keycloak, LiteLLM, sandbox, plugin-directory, or Automations secrets manually. Use a component-specific procedure from OpenHands Support. Changing encryption or salt keys can make previously stored provider credentials unreadable. diff --git a/skills/install-openhands-replicated/references/backup-and-durability.md b/skills/install-openhands-replicated/references/backup-and-durability.md new file mode 100644 index 00000000..a998ed15 --- /dev/null +++ b/skills/install-openhands-replicated/references/backup-and-durability.md @@ -0,0 +1,66 @@ +# Backup And Durability + +At minimum, confirm the main OpenHands Postgres data directory is PVC-backed before production/demo use or any redeploy. + +Run on the target VM or from an operator environment with cluster access: + +```bash +scripts/preflight_storage_guard.sh openhands +``` + +The script automatically uses the Embedded Cluster kubectl path when present on the target VM. Otherwise, set `KUBECTL` to one executable kubectl path; do not include shell words such as `sudo` in the variable. + +The guard checks: + +- main Postgres data path is backed by a Bound PVC; +- node is not under `DiskPressure`; +- host disk has enough free space when checkable; +- ClickHouse diagnostic system logs are not consuming dangerous space. + +## What Postgres Backup Covers + +A daily Postgres dump is cheap and useful for fast recovery of: + +- users/org metadata; +- API keys and app DB records; +- LLM profile metadata; +- LiteLLM DB state when stored in the shared Postgres cluster; +- automation definitions and runs; +- integration state stored in app DBs. + +It does not cover: + +- runtime sandbox PVC contents; +- full VM root disk; +- MinIO/blob storage; +- external provider state; +- Laminar state unless Laminar is also backed up separately. + +## Example Daily Backup Pattern + +Treat this as an architecture pattern, not a ready-to-run customer procedure. A PostgreSQL dump contains credentials, tokens, user data, and other sensitive application state. + +A lightweight design can use: + +- a scheduled job running a version-compatible PostgreSQL dump; +- compression and checksum generation; +- encryption in transit and at rest; +- object storage with tightly scoped write/read permissions and retention controls; +- monitoring for missed or failed jobs; +- a documented, tested restore procedure. + +Define RPO and RTO with the customer rather than assuming a universal value. Confirm whether the design also covers MinIO/blob data, runtime PVCs, analytics, external databases, and infrastructure state. + +## Restore Gate + +Do not execute a restore from this skill alone. A restore can overwrite application state and invalidate newer credentials or integration records. Require: + +1. a version-matched backup and restore procedure; +2. explicit approval and a maintenance window; +3. a verified backup and checksum; +4. source and destination version compatibility; +5. a rollback or snapshot boundary; +6. a plan to quiesce application writers; +7. post-restore login, API, LLM, integration, and conversation tests. + +Escalate to OpenHands Support when database layout, encryption keys, external PostgreSQL, or partial-component recovery is involved. diff --git a/skills/install-openhands-replicated/references/blue-green-reinstall.md b/skills/install-openhands-replicated/references/blue-green-reinstall.md new file mode 100644 index 00000000..1467d68f --- /dev/null +++ b/skills/install-openhands-replicated/references/blue-green-reinstall.md @@ -0,0 +1,126 @@ +# Blue/Green Reinstall + +Use this reference only after the operator explicitly approves a rebuild and cutover. Rebuilding, restoring, switching DNS, or changing provider callbacks can cause downtime, duplicate events, or data loss. + +Prefer a version-matched OpenHands Support procedure when preserving application state across OHE versions. Use this reference when rebuilding an OpenHands Enterprise Replicated instance with minimal downtime or when stale state makes an in-place reinstall risky. + +## Default Recommendation + +Prefer blue/green over in-place wipe: + +1. Keep the current instance running. +2. Provision a new VM or cluster with temporary hostnames. +3. Recreate configuration and app state intentionally. +4. Validate the new instance. +5. Cut DNS and provider webhooks over during a quiet window. +6. Keep the old instance as rollback until the new instance survives real use. + +Use in-place reinstall only when downtime and rollback loss are acceptable. + +## What To Preserve + +Preserve as source material: + +- redacted KOTS `ConfigValues`; +- Terraform inputs and outputs; +- DNS record inventory; +- TLS certificate coverage; +- org secret names, not values; +- LLM profile definitions; +- automation definitions; +- GitHub/Jira/Slack/Laminar integration settings; +- current backup status and restore expectations. + +Preserve backups and snapshots, but do not restore old Postgres by default when +the goal is to remove stale state. + +## Temporary Hostnames + +Temporary hostnames let the new instance be validated without disrupting the old one. For a Simple-mode base domain like `openhands-next.example.com`, expect: + +```text +admin.openhands-next.example.com +app.openhands-next.example.com +auth.openhands-next.example.com +analytics.openhands-next.example.com +llm-proxy.openhands-next.example.com +runtime-api.openhands-next.example.com +-runtime.openhands-next.example.com +``` + +Keep a Legacy-mode source installation on its existing hostname layout unless hostname migration is part of the approved change. + +Before final cutover, update the new instance's Replicated/KOTS hostname config +to the original domains, upload/provision certs for those domains, redeploy, and +then switch DNS. + +## External Provider Constraints + +Do not assume GitHub, Jira, or Slack can point to both old and new instances at +the same time. + +Recommended approach: + +- Use temporary provider apps/webhooks for pre-cutover testing when available. +- Otherwise keep production provider URLs on the old instance until cutover. +- Validate production provider routing immediately after DNS/webhook cutover. + +GitHub has separate concerns: + +- sign-in/OAuth callback; +- user provider token for repo search and repo-backed conversations; +- GitHub App webhook delivery into automations. + +Jira and Slack also have separate delivery and user/workspace-linking states. + +## Clean-State Rehydrate Order + +1. Configure domain, TLS, LLM, sandbox lifecycle, and required app features. +2. Validate `/ready`, pods, statefulsets, and storage guard. +3. Sign in and create/claim the org. +4. Create a fresh org API key. +5. Import canonical org secrets. +6. Recreate LLM profiles with fresh LiteLLM proxy tokens. +7. Recreate automations. +8. Validate one manual conversation. +9. Validate integrations one at a time. +10. Configure and test backups. + +## Cutover Checklist + +Before cutover: + +- final old-instance backup exists; +- old instance remains untouched for rollback; +- new instance passes core smoke tests; +- provider URL changes are known; +- DNS TTL is understood; +- original-domain TLS certs are ready. + +During cutover: + +1. Disable old automations if duplicate events are risky. +2. Set original hostnames in the new Replicated/KOTS config. +3. Deploy and verify new app readiness. +4. Switch DNS records. +5. Update GitHub/Jira/Slack callbacks/webhooks if needed. +6. Re-enable automations on the new instance. +7. Run login, API key, LLM, conversation, and integration smoke tests. + +Rollback: + +1. Switch DNS/webhooks back to the old instance. +2. Re-enable old automations. +3. Keep the failed new instance for analysis. + +## Completion Criteria + +A blue/green reinstall is complete only when: + +- original app URL works on the new instance; +- protected API endpoint works with a new org key; +- at least one LLM-backed conversation completes; +- GitHub/Jira/Slack paths needed for demos are validated; +- Laminar traces are visible if analytics is enabled; +- backups are scheduled and at least one backup job succeeds; +- old instance rollback is no longer needed. diff --git a/skills/install-openhands-replicated/references/git-provider-auth.md b/skills/install-openhands-replicated/references/git-provider-auth.md new file mode 100644 index 00000000..4fe25b4d --- /dev/null +++ b/skills/install-openhands-replicated/references/git-provider-auth.md @@ -0,0 +1,69 @@ +# Git Provider Authentication + +Select one provider for the first installation pass. Configure additional providers only after core login, LLM, and conversation checks pass. + +## Approval and Secret Handling + +Treat provider application creation and callback changes as external mutations. Before running a helper or creating an application: + +1. identify the provider organization and approved test repository; +2. review requested scopes, callback URLs, webhook URLs, and events; +3. identify the provider administrator who approves the change; +4. explain created resources and cleanup steps; +5. obtain explicit approval; +6. transfer generated secrets through an approved secret channel. + +Do not print client secrets, webhook secrets, user tokens, or private keys. Use least privilege and a disposable test repository where possible. + +## GitHub + +The current OpenHands Enterprise quick start links to the official `scripts/create_github_app` helper in the OpenHands Cloud repository. + +Review the current helper before execution. Confirm the base URL and requested permissions match the target release, then obtain approval to run it. Configure the Admin Console with the generated: + +- Client ID +- Client Secret +- App ID +- App Slug +- Webhook Secret +- Private Key + +Use a GitHub App, not a GitHub OAuth App. Install the app only on approved repositories for the initial test. + +Validate sign-in, repository discovery, one bounded repository-backed conversation, and one webhook event when Automations is in scope. + +Official quick start: https://docs.openhands.dev/enterprise/quick-start + +## GitLab + +Configure the GitLab host and OAuth client values supported by the target OHE release. Keep `gitlab.com` for GitLab SaaS or use the customer-managed hostname. + +Follow the current Admin Console and provider documentation rather than adapting the GitHub helper. Validate callback TLS, sign-in, repository discovery, and one bounded repository-backed conversation. + +## Bitbucket Data Center + +Use the current OpenHands Enterprise Bitbucket Data Center guide for application, bot identity, callback, and webhook requirements. Do not assume Bitbucket Cloud instructions apply. + +Official guide: https://docs.openhands.dev/enterprise/integrations/bitbucket-data-center + +## Azure DevOps + +Use the current OpenHands Enterprise Azure DevOps guide for Microsoft Entra tenant, organization, client application, permissions, and callbacks. Validate the exact organization and a disposable repository before broadening access. + +Official guide: https://docs.openhands.dev/enterprise/integrations/azure-devops + +## Completion Record + +Record only non-secret evidence: + +```text +Provider: +Provider organization/host: +Application name and non-secret ID: +Approved repository scope: +Callback/webhook validation: +Sign-in result: +Repository discovery result: +Repository-backed conversation result: +Cleanup owner: +``` diff --git a/skills/install-openhands-replicated/references/install-flow.md b/skills/install-openhands-replicated/references/install-flow.md new file mode 100644 index 00000000..8fe2bfe4 --- /dev/null +++ b/skills/install-openhands-replicated/references/install-flow.md @@ -0,0 +1,184 @@ +# Install Flow + +Use this checklist for a new OpenHands Enterprise VM installation delivered through Replicated Embedded Cluster. Confirm current values against the target OHE release and the customer's installer dashboard. + +## Phase 1: Scope and Approval + +Capture before changing infrastructure: + +- target OHE release and installer/Embedded Cluster version; +- AWS Terraform or manual VM path; +- base domain, DNS owner, hostname mode, and TLS owner; +- LLM provider and authentication owner; +- Git provider and optional integrations; +- embedded or external PostgreSQL; +- backup, recovery, and maintenance-window expectations; +- named approver for infrastructure, installer, DNS, and application changes. + +Keep installer URLs, license files, private keys, and credentials out of tickets, chat, shell history, and repositories. + +## Phase 2: Infrastructure Requirements + +The current OpenHands Enterprise quick start requires the following for a manual VM: + +| Resource | Requirement | +| --- | --- | +| CPU | 16 vCPUs | +| Memory | 64 GB | +| Disk | 200 GB | +| Disk P99 write latency | 10 ms maximum | +| Architecture | Linux x86-64 | +| Init system | systemd | +| Access | root or sudo | + +Inbound TCP ports: + +```text +80 443 30000 +``` + +Local ports that must be available before installation: + +```text +2379 7443 9099 10248 10257 10259 +``` + +Run on the target VM: + +```bash +scripts/check_host_preflight.sh +scripts/check_outbound.sh +``` + +Pass each customer-approved LLM, cloud-model, or corporate gateway HTTPS endpoint required by the selected authentication mode. + +The host script cannot prove P99 storage latency without a write benchmark. Rely on the installer host preflight for the final latency check. Do not bypass a failed latency preflight; increase disk IOPS/throughput or use faster storage. + +For AWS Terraform, use the current module linked by the OpenHands Enterprise quick start. Review `terraform plan` before requesting approval for `apply`. Use the allowlisted output helper after apply: + +```bash +scripts/summarize_terraform_outputs.sh +``` + +Do not print the full Terraform output set because it can include sensitive values or local key paths. + +## Phase 3: DNS and TLS + +The current default is `Simple` hostname mode. A wildcard DNS record and certificate for `*.` cover: + +```text +admin. +app. +auth. +analytics. +llm-proxy. +runtime-api. +-runtime. +``` + +Validate the wildcard route with a synthetic runtime name: + +```bash +scripts/check_dns.sh simple +``` + +Older installations can use `Legacy` hostnames such as `auth.app.` and `.runtime.`. Do not migrate an existing installation's hostname mode during unrelated work. For a confirmed Legacy installation, run: + +```bash +scripts/check_dns.sh legacy +``` + +Use a publicly trusted wildcard certificate whenever possible. Self-signed certificates are not supported for the OpenHands application. A private CA requires every browser, OAuth provider, and webhook sender to trust the chain. + +If wildcard certificates are unavailable, select path-based sandbox routing and obtain the complete SAN set documented for the target release. + +## Phase 4: Outbound Preflight + +Run outbound checks from the target VM. Required destinations currently include Replicated control-plane endpoints, OpenHands image/chart/update endpoints, GitHub, Traefik charts, Docker Hub, GHCR, and each endpoint required by the selected LLM provider or corporate gateway. + +Treat HTTP responses such as 301, 401, 403, or 405 as reachable. Treat HTTP `000` as a DNS, timeout, proxy, or firewall failure. + +Resolve all preflight failures before running the installer. + +## Phase 5: Installer + +Obtain the version-specific commands from the customer's installer dashboard. The normal sequence is: + +1. select the OHE version; +2. download the installation assets with the dashboard-provided command; +3. extract the assets, including the license file; +4. review the exact install command and TLS paths; +5. obtain explicit approval; +6. run the install command in a real interactive PTY. + +A representative command shape is: + +```bash +sudo ./openhands install --license \ + --tls-cert \ + --tls-key +``` + +Do not substitute a representative command for the dashboard-provided command. Do not expose the dashboard download URL or license contents. + +If installation fails after preflights pass, collect a support bundle with the installed application binary: + +```bash +sudo ./openhands support-bundle +``` + +Treat the bundle as sensitive and share it only through the approved support channel. + +## Headless and Declarative Boundary + +Do not claim a fully headless installation unless the target OHE release exposes a documented installer flag, configuration schema, and supported secret-input mechanism. The current customer-safe default is: + +- use the installer dashboard for version-specific download and license commands; +- run the interactive installer in a real PTY; +- complete required Admin Console steps with guided ClickOps; +- use KOTS ConfigValues only for documented keys and preview each merge; +- keep `assets/install-plan.yaml` as a non-secret planning record, not deployment input. + +When headless installation is required, collect the target binary's `install --help`, the release-specific schema, secret-injection method, and rollback procedure from official documentation or OpenHands Support before implementation. + +## Phase 6: Admin Console Configuration + +For a single-node deployment, continue past the add-node screen. Configure in layers: + +1. Simple hostname mode and base domain; +2. certificate and private key; +3. one LLM provider; +4. database choice; +5. core application deployment; +6. first login and organization; +7. Git provider authentication; +8. optional integrations, analytics, and automations. + +Wait for deployment status to reach Ready and inspect resource details before moving to user-path validation. + +## Phase 7: Core Validation + +Minimum done state: + +- `https://admin.:30000` and `https://app.` present valid TLS; +- app readiness succeeds; +- login works in a clean browser session; +- first organization and bounded API key work; +- one tiny model request succeeds; +- one no-repository conversation completes with an expected marker; +- repository search and a repository-backed conversation work when a Git provider is in scope; +- storage guard passes; +- no new warning events appear during the smoke tests. + +Add optional integrations only after these checks pass. + +## Phase 8: Handoff + +Provide versions, topology, hostnames, enabled features, smoke-test evidence, backup boundaries, known limitations, and the approved support path. Exclude secrets and customer data. Use `operator-requests.md` for unresolved DNS, firewall, TLS, and access requests. + +## Official References + +- OpenHands Enterprise quick start: https://docs.openhands.dev/enterprise/quick-start +- Admin Console configuration: https://docs.openhands.dev/enterprise/vm-install/admin-console-configuration +- Replicated Embedded Cluster installation: https://docs.replicated.com/enterprise/installing-embedded +- Replicated requirements: https://docs.replicated.com/enterprise/installing-embedded-requirements diff --git a/skills/install-openhands-replicated/references/integrations.md b/skills/install-openhands-replicated/references/integrations.md new file mode 100644 index 00000000..8fc354ac --- /dev/null +++ b/skills/install-openhands-replicated/references/integrations.md @@ -0,0 +1,67 @@ +# Integration Validation + +Add integrations only after core login, LLM, and conversation checks pass. Validate each integration independently. A working callback URL does not prove account linking, repository access, event routing, or provider-side permissions. + +Never print provider tokens, app secrets, signing secrets, private keys, or complete webhook bodies containing customer data. + +## GitHub + +Use a GitHub App rather than a GitHub OAuth App. Validate separate paths: + +1. user sign-in; +2. GitHub App installation on an approved test repository; +3. repository search for the signed-in user; +4. one bounded repository-backed conversation; +5. webhook or automation delivery when it is in scope. + +Useful bounded checks: + +```bash +curl -sS -H "Authorization: Bearer $OPENHANDS_API_KEY" \ + "$APP_URL/api/v1/users/me" + +curl -sS -H "Authorization: Bearer $OPENHANDS_API_KEY" \ + "$APP_URL/api/v1/git/repositories/search?provider=github&query=/&limit=5" +``` + +Do not enable shell tracing while using API keys. If repository search reports an invalid GitHub token, refresh the user's GitHub authorization rather than extracting credentials from the cluster. + +## GitLab, Bitbucket, and Azure DevOps + +Follow the version-matched OpenHands Enterprise guide for the chosen provider. Validate login or account linking, repository discovery, one bounded repository operation, and any required callback or webhook. Use a disposable test repository when possible. + +## Jira + +The current VM documentation provides a supported Jira Data Center integration. Validate: + +- Admin Console configuration exists for the supported Jira deployment type; +- account linking or service-account access works as configured; +- the Jira webhook reaches the documented OpenHands endpoint; +- one disposable test issue event produces the expected bounded result. + +Treat custom Jira-to-Automations webhooks as a separate automation design, not a generic installation requirement. Do not copy customer-specific shim URLs, project keys, cloud IDs, tokens, or one-off routing patches into this skill. + +## Slack + +Validate both setup layers: + +1. Admin Console credentials and Slack request URL verification; +2. OpenHands-side `Install Slack` workspace and user linking. + +Then invite the bot to a test channel and send one bounded mention. If Slack delivers the event but no conversation starts, verify workspace/user linking before changing webhook configuration. + +## Analytics (Laminar) + +For the bundled analytics option, validate: + +- `https://analytics.` loads; +- Keycloak login works; +- a project exists; +- an ingest-only project API key is configured through the Admin Console; +- a fresh conversation creates a trace. + +Use Laminar for trace-level observability. Use OpenHands application storage for durable conversation metadata and product state. + +## Automations + +When Automations is enabled, create or dispatch one bounded test using the supported UI or API. Verify the event, run, and conversation identifiers without logging secrets or full customer payloads. Delete disposable triggers and credentials after testing when required by policy. diff --git a/skills/install-openhands-replicated/references/operator-requests.md b/skills/install-openhands-replicated/references/operator-requests.md new file mode 100644 index 00000000..00e72798 --- /dev/null +++ b/skills/install-openhands-replicated/references/operator-requests.md @@ -0,0 +1,95 @@ +# Operator Request Templates + +Use these templates to request prerequisites without asking recipients to send credentials in email or chat. Replace placeholders and remove sections that do not apply. + +## DNS and TLS Request + +```text +Subject: DNS and TLS prerequisites for OpenHands Enterprise + +Please create a wildcard DNS record for *. that resolves to . + +The default Simple hostname layout uses: +- admin. +- app. +- auth. +- analytics. +- llm-proxy. +- runtime-api. +- -runtime. + +Please provide a publicly trusted wildcard certificate for *., including the complete intermediate chain. Store the certificate and matching private key in the approved secret-transfer system; do not send the private key by email or chat. + +Owner for DNS validation: +Owner for certificate transfer: +Required by: +``` + +## Firewall and Proxy Request + +```text +Subject: Network prerequisites for OpenHands Enterprise VM + +Target VM or security group: + +Allow inbound TCP: +- 80 +- 443 +- 30000, restricted to approved administrator CIDRs where possible + +Keep these local TCP ports available on the VM before installation: +- 2379 +- 7443 +- 9099 +- 10248 +- 10257 +- 10259 + +Allow outbound HTTPS to the destinations listed in the current OpenHands Enterprise quick start, including Replicated control-plane endpoints, OpenHands image/chart/update endpoints, GitHub, Traefik charts, Docker Hub, and GHCR. + +If TLS inspection or an HTTP proxy is required, provide the proxy URL and CA certificate through the approved configuration channel. Do not disable TLS verification. + +Owner for validation: +Required by: +``` + +## Infrastructure Request + +```text +Subject: VM prerequisites for OpenHands Enterprise + +Please provide a dedicated Linux x86-64 VM with: +- 16 vCPUs +- 64 GB memory +- 200 GB disk +- disk P99 write latency no greater than 10 ms +- systemd +- root or sudo access for the installation operator + +Please identify: +- VM hostname and environment +- cloud region or datacenter +- storage class/type and provisioned IOPS/throughput +- administrator access method +- backup/snapshot owner + +Do not include passwords, SSH private keys, or cloud credentials in the response. +``` + +## Access and Ownership Request + +```text +Subject: Administrative access needed for OpenHands Enterprise setup + +Please identify an authorized owner for each required surface: +- OpenHands installer dashboard and license +- VM sudo access +- DNS +- TLS certificate transfer +- LLM provider +- Git provider application +- optional Slack/Jira/Bitbucket/Azure DevOps administration +- database and backup operations + +Use approved secret-management and transfer systems for all credentials. The installation record will contain only owner names, resource identifiers, and validation results. +``` diff --git a/skills/install-openhands-replicated/scripts/apply_kots_config.sh b/skills/install-openhands-replicated/scripts/apply_kots_config.sh new file mode 100755 index 00000000..f7aed8a4 --- /dev/null +++ b/skills/install-openhands-replicated/scripts/apply_kots_config.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat >&2 <<'EOF' +usage: apply_kots_config.sh --appslug --config-file (--current | --sequence ) [options] + +Previews a KOTS ConfigValues merge by default. Add --execute only after the +specific operation has been reviewed and approved. Add --deploy only when an +immediate rollout is also approved. + +Options: + --namespace KOTS Admin Console namespace. Default: kotsadm + --app-namespace OpenHands app namespace for guard checks. Default: openhands + --current Use the currently deployed version as the base + --sequence Use a specific app sequence as the base + --execute Execute the config merge; otherwise print a preview + --deploy Deploy the resulting sequence after setting config + --skip-guard Skip storage guard before/after an approved deployment + -h, --help Show this help + +Environment: + KUBECTL Path to a kubectl binary that supports `kubectl kots` +EOF +} + +KOTS_NAMESPACE="kotsadm" +APP_NAMESPACE="openhands" +APPSLUG="" +CONFIG_FILE="" +EXECUTE=0 +DEPLOY=0 +RUN_GUARD=1 +CURRENT=0 +SEQUENCE="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --appslug) + APPSLUG="${2:-}" + shift 2 + ;; + --config-file) + CONFIG_FILE="${2:-}" + shift 2 + ;; + --namespace) + KOTS_NAMESPACE="${2:-}" + shift 2 + ;; + --app-namespace) + APP_NAMESPACE="${2:-}" + shift 2 + ;; + --current) + CURRENT=1 + shift + ;; + --sequence) + SEQUENCE="${2:-}" + shift 2 + ;; + --execute) + EXECUTE=1 + shift + ;; + --deploy) + DEPLOY=1 + shift + ;; + --skip-guard) + RUN_GUARD=0 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + usage + exit 1 + ;; + esac +done + +if [[ -z "${APPSLUG}" || -z "${CONFIG_FILE}" ]]; then + usage + exit 1 +fi + +if [[ "${CURRENT}" == "1" && -n "${SEQUENCE}" ]]; then + echo "choose either --current or --sequence, not both" >&2 + exit 1 +fi + +if [[ "${CURRENT}" != "1" && -z "${SEQUENCE}" ]]; then + echo "choose --current or --sequence" >&2 + exit 1 +fi + +if [[ ! -f "${CONFIG_FILE}" ]]; then + echo "config file not found: ${CONFIG_FILE}" >&2 + exit 1 +fi + +if ! grep -Eq '^apiVersion:[[:space:]]*kots.io/v1beta1[[:space:]]*$' "${CONFIG_FILE}"; then + echo "config file must include apiVersion: kots.io/v1beta1" >&2 + exit 1 +fi + +if ! grep -Eq '^kind:[[:space:]]*ConfigValues[[:space:]]*$' "${CONFIG_FILE}"; then + echo "config file must include kind: ConfigValues" >&2 + exit 1 +fi + +KUBECTL_CMD=() +if [[ -n "${KUBECTL:-}" ]]; then + if [[ ! -x "${KUBECTL}" ]]; then + echo "KUBECTL must be an executable path: ${KUBECTL}" >&2 + exit 1 + fi + KUBECTL_CMD=("${KUBECTL}") +elif command -v kubectl >/dev/null 2>&1; then + KUBECTL_CMD=(kubectl) +elif [[ -x /var/lib/embedded-cluster/bin/kubectl ]]; then + KUBECTL_CMD=(/var/lib/embedded-cluster/bin/kubectl) +else + echo "kubectl not found. Install kubectl and the KOTS CLI plugin, or set KUBECTL=/path/to/kubectl." >&2 + exit 1 +fi + +k() { + "${KUBECTL_CMD[@]}" "$@" +} + +if ! k kots version >/dev/null 2>&1; then + cat >&2 < [simple|legacy]" >&2 + exit 1 +fi + +BASE_DOMAIN="${1%.}" +MODE="${2:-simple}" + +if [[ ! "${BASE_DOMAIN}" =~ ^[A-Za-z0-9.-]+$ || "${BASE_DOMAIN}" != *.* ]]; then + echo "invalid base domain: ${BASE_DOMAIN}" >&2 + exit 1 +fi + +case "${MODE}" in + simple) + hosts=( + "admin.${BASE_DOMAIN}" + "app.${BASE_DOMAIN}" + "auth.${BASE_DOMAIN}" + "analytics.${BASE_DOMAIN}" + "llm-proxy.${BASE_DOMAIN}" + "runtime-api.${BASE_DOMAIN}" + "test-runtime.${BASE_DOMAIN}" + ) + ;; + legacy) + hosts=( + "${BASE_DOMAIN}" + "app.${BASE_DOMAIN}" + "auth.app.${BASE_DOMAIN}" + "analytics.app.${BASE_DOMAIN}" + "llm-proxy.${BASE_DOMAIN}" + "runtime-api.${BASE_DOMAIN}" + "test.runtime.${BASE_DOMAIN}" + ) + ;; + *) + echo "mode must be simple or legacy" >&2 + exit 1 + ;; +esac + +failed=0 +printf 'Checking %s-mode DNS for base domain: %s\n\n' "${MODE}" "${BASE_DOMAIN}" + +for host in "${hosts[@]}"; do + echo "[DNS] ${host}" + if command -v getent >/dev/null 2>&1 && output="$(getent hosts "${host}" 2>/dev/null)" && [[ -n "${output}" ]]; then + printf '%s\n' "${output}" + elif command -v dig >/dev/null 2>&1 && output="$(dig +short "${host}" 2>/dev/null)" && [[ -n "${output}" ]]; then + printf '%s\n' "${output}" + elif command -v nslookup >/dev/null 2>&1 && nslookup "${host}"; then + : + else + echo "FAIL ${host} did not resolve" >&2 + failed=1 + fi + echo +done + +exit "${failed}" diff --git a/skills/install-openhands-replicated/scripts/check_host_preflight.sh b/skills/install-openhands-replicated/scripts/check_host_preflight.sh new file mode 100755 index 00000000..dfca7dae --- /dev/null +++ b/skills/install-openhands-replicated/scripts/check_host_preflight.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +set -u + +MIN_CPUS="${MIN_CPUS:-16}" +MIN_MEMORY_GIB="${MIN_MEMORY_GIB:-64}" +MIN_DISK_GIB="${MIN_DISK_GIB:-200}" +MAX_DISK_USE_PERCENT="${MAX_DISK_USE_PERCENT:-80}" +INSTALL_PATH="${INSTALL_PATH:-/}" + +local_ports=(2379 7443 9099 10248 10257 10259) +edge_ports=(80 443 30000) +installer_paths=( + /etc/k0s + /opt/containerd + /run/k0s + /usr/local/bin/k0s + /var/lib/embedded-cluster + /var/lib/kubelet +) +failed=0 + +ok() { printf 'OK %s\n' "$*"; } +warn() { printf 'WARN %s\n' "$*" >&2; } +fail() { printf 'FAIL %s\n' "$*" >&2; failed=1; } + +if [[ "$(uname -s)" == "Linux" ]]; then + ok "operating system is Linux" +else + fail "target must run Linux; found $(uname -s)" +fi + +arch="$(uname -m)" +if [[ "${arch}" == "x86_64" || "${arch}" == "amd64" ]]; then + ok "architecture is ${arch}" +else + fail "target must use x86-64; found ${arch}" +fi + +cpu_count="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0)" +if [[ "${cpu_count}" =~ ^[0-9]+$ ]] && (( cpu_count >= MIN_CPUS )); then + ok "${cpu_count} logical CPUs available" +else + fail "${cpu_count:-unknown} logical CPUs available; minimum is ${MIN_CPUS}" +fi + +if [[ -r /proc/meminfo ]]; then + memory_kib="$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)" + memory_gib=$((memory_kib / 1024 / 1024)) + if (( memory_gib >= MIN_MEMORY_GIB )); then + ok "${memory_gib} GiB memory available" + else + fail "${memory_gib} GiB memory available; minimum is ${MIN_MEMORY_GIB} GiB" + fi +else + fail "cannot read /proc/meminfo on the target" +fi + +if [[ -e "${INSTALL_PATH}" ]]; then + read -r disk_kib disk_used_percent < <(df -Pk "${INSTALL_PATH}" | awk 'NR == 2 {gsub(/%/, "", $5); print $2, $5}') + disk_gib=$((disk_kib / 1024 / 1024)) + if (( disk_gib >= MIN_DISK_GIB )); then + ok "filesystem containing ${INSTALL_PATH} has ${disk_gib} GiB total" + else + fail "filesystem containing ${INSTALL_PATH} has ${disk_gib} GiB total; minimum is ${MIN_DISK_GIB} GiB" + fi + if (( disk_used_percent < MAX_DISK_USE_PERCENT )); then + ok "filesystem containing ${INSTALL_PATH} is ${disk_used_percent}% full" + else + fail "filesystem containing ${INSTALL_PATH} is ${disk_used_percent}% full; required maximum is below ${MAX_DISK_USE_PERCENT}%" + fi +else + fail "INSTALL_PATH does not exist: ${INSTALL_PATH}" +fi + +if command -v systemctl >/dev/null 2>&1 && [[ -d /run/systemd/system ]]; then + ok "systemd is available" + for service in k0scontroller kubelet containerd docker; do + if systemctl is-active --quiet "${service}" 2>/dev/null; then + warn "${service} is already active; confirm this is an approved reinstall or resolve the runtime conflict" + fi + done +else + fail "systemd is not active" +fi + +existing_paths=() +for path in "${installer_paths[@]}"; do + [[ -e "${path}" ]] && existing_paths+=("${path}") +done +if (( ${#existing_paths[@]} > 0 )); then + warn "existing Embedded Cluster or Kubernetes paths found: ${existing_paths[*]}" + warn "do not delete them automatically; determine whether this is an approved reinstall and follow a version-matched procedure" +else + ok "no common Embedded Cluster or Kubernetes installation paths found" +fi + +if [[ -d /sys/fs/cgroup ]]; then + cgroup_type="$(stat -fc '%T' /sys/fs/cgroup 2>/dev/null || true)" + if [[ "${cgroup_type}" == "cgroup2fs" ]]; then + ok "cgroups v2 is active" + else + warn "cgroups v2 is not detected; confirm compatibility with the Kubernetes version in the target OHE release" + fi +fi + +if [[ "${EUID}" -eq 0 ]]; then + ok "running with root privileges" +elif command -v sudo >/dev/null 2>&1; then + ok "sudo is installed; validate operator authorization before installation" +else + fail "root or sudo access is required" +fi + +if command -v ss >/dev/null 2>&1; then + listeners="$(ss -ltnH 2>/dev/null || true)" + for port in "${local_ports[@]}" "${edge_ports[@]}"; do + if awk -v port="${port}" '$4 ~ (":" port "$|\\]" port "$") {found=1} END {exit !found}' <<<"${listeners}"; then + fail "TCP port ${port} is already listening; identify the process before installation" + else + ok "TCP port ${port} is available locally" + fi + done +else + warn "ss is unavailable; local port availability was not verified" +fi + +warn "disk P99 write latency is not measured by this script; require the installer host preflight to report 10 ms or less" +warn "firewall policy is not verified locally; confirm inbound TCP 80, 443, and 30000 with the network owner" + +if (( failed != 0 )); then + exit 1 +fi + +printf '\nHost preflight passed. Continue with DNS and outbound checks before requesting installer approval.\n' diff --git a/skills/install-openhands-replicated/scripts/check_outbound.sh b/skills/install-openhands-replicated/scripts/check_outbound.sh new file mode 100755 index 00000000..ff75f171 --- /dev/null +++ b/skills/install-openhands-replicated/scripts/check_outbound.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + echo "usage: $0 [additional-https-url ...]" + exit 0 +fi + +urls=( + "https://replicated.app" + "https://proxy.replicated.com/v2/" + "https://images.r9.all-hands.dev/v2/" + "https://install.r9.all-hands.dev" + "https://charts.r9.all-hands.dev" + "https://updates.r9.all-hands.dev" + "https://github.com" + "https://traefik.github.io/charts/index.yaml" + "https://registry-1.docker.io/v2/" + "https://ghcr.io/v2/" +) + +for url in "$@"; do + if [[ ! "${url}" =~ ^https://[^[:space:]]+$ ]]; then + echo "additional endpoint must be an https URL: ${url}" >&2 + exit 1 + fi + urls+=("${url}") +done + +echo "Checking outbound reachability" +echo + +failed=0 +for url in "${urls[@]}"; do + code="$(curl -sSIL --max-time 15 -o /dev/null -w "%{http_code}" "${url}" || true)" + if [[ "${code}" == "000" ]]; then + echo "FAIL ${url}" >&2 + failed=1 + else + echo "OK ${url} (HTTP ${code})" + fi +done + +exit "${failed}" diff --git a/skills/install-openhands-replicated/scripts/check_tls_files.sh b/skills/install-openhands-replicated/scripts/check_tls_files.sh new file mode 100755 index 00000000..7117f679 --- /dev/null +++ b/skills/install-openhands-replicated/scripts/check_tls_files.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +set -u + +usage() { + cat >&2 <<'EOF' +usage: check_tls_files.sh [wildcard|path] + +Validates certificate dates, key matching, hostname coverage, self-signing, and +the certificate chain against the operating system CA bundle. It never prints +the private key or derived key hashes. + +Environment: + CA_BUNDLE Optional trusted CA bundle for enterprise/private CA validation +EOF +} + +if [[ $# -lt 3 || $# -gt 4 ]]; then + usage + exit 1 +fi + +BASE_DOMAIN="${1%.}" +CERT_FILE="$2" +KEY_FILE="$3" +ROUTING_MODE="${4:-wildcard}" +failed=0 + +ok() { printf 'OK %s\n' "$*"; } +warn() { printf 'WARN %s\n' "$*" >&2; } +fail() { printf 'FAIL %s\n' "$*" >&2; failed=1; } + +if [[ ! "${BASE_DOMAIN}" =~ ^[A-Za-z0-9.-]+$ || "${BASE_DOMAIN}" != *.* ]]; then + echo "invalid base domain: ${BASE_DOMAIN}" >&2 + exit 1 +fi + +if [[ "${ROUTING_MODE}" != "wildcard" && "${ROUTING_MODE}" != "path" ]]; then + echo "routing mode must be wildcard or path" >&2 + exit 1 +fi + +if ! command -v openssl >/dev/null 2>&1; then + echo "openssl is required" >&2 + exit 1 +fi + +for file in "${CERT_FILE}" "${KEY_FILE}"; do + if [[ ! -r "${file}" ]]; then + echo "file is not readable: ${file}" >&2 + exit 1 + fi +done + +if openssl x509 -in "${CERT_FILE}" -noout >/dev/null 2>&1; then + ok "certificate bundle is parseable" +else + echo "certificate bundle is not parseable" >&2 + exit 1 +fi + +if openssl x509 -in "${CERT_FILE}" -checkend 604800 -noout >/dev/null 2>&1; then + ok "certificate remains valid for at least seven days" +else + fail "certificate is expired or expires within seven days" +fi + +subject="$(openssl x509 -in "${CERT_FILE}" -noout -subject -nameopt RFC2253 2>/dev/null | sed 's/^subject=//')" +issuer="$(openssl x509 -in "${CERT_FILE}" -noout -issuer -nameopt RFC2253 2>/dev/null | sed 's/^issuer=//')" +if [[ -n "${subject}" && "${subject}" == "${issuer}" ]]; then + fail "leaf certificate is self-signed; OpenHands application certificates must be trusted" +else + ok "leaf certificate is not self-signed" +fi + +cert_key_hash="$(openssl x509 -in "${CERT_FILE}" -pubkey -noout 2>/dev/null | openssl pkey -pubin -outform DER 2>/dev/null | openssl dgst -sha256 2>/dev/null || true)" +key_hash="$(openssl pkey -in "${KEY_FILE}" -pubout -outform DER 2>/dev/null | openssl dgst -sha256 2>/dev/null || true)" +if [[ -z "${key_hash}" ]]; then + fail "private key could not be parsed non-interactively; validate encrypted keys in an approved interactive session" +elif [[ -n "${cert_key_hash}" && "${cert_key_hash}" == "${key_hash}" ]]; then + ok "private key matches the leaf certificate" +else + fail "private key does not match the leaf certificate" +fi +unset cert_key_hash key_hash + +sans="$(openssl x509 -in "${CERT_FILE}" -noout -ext subjectAltName 2>/dev/null | tr -d '[:space:]' || true)" +if [[ "${ROUTING_MODE}" == "wildcard" ]]; then + if grep -Fq "DNS:*.${BASE_DOMAIN}" <<<"${sans}"; then + ok "certificate covers wildcard *.${BASE_DOMAIN}" + else + fail "certificate does not contain DNS:*.${BASE_DOMAIN}" + fi +else + required_hosts=( + "admin.${BASE_DOMAIN}" + "app.${BASE_DOMAIN}" + "auth.${BASE_DOMAIN}" + "analytics.${BASE_DOMAIN}" + "llm-proxy.${BASE_DOMAIN}" + "runtime-api.${BASE_DOMAIN}" + "runtime.${BASE_DOMAIN}" + ) + for host in "${required_hosts[@]}"; do + if grep -Fq "DNS:${host}" <<<"${sans}"; then + ok "certificate covers ${host}" + else + fail "certificate does not contain DNS:${host} for path-based routing" + fi + done +fi + +ca_bundle="${CA_BUNDLE:-}" +if [[ -n "${ca_bundle}" && ! -r "${ca_bundle}" ]]; then + echo "CA_BUNDLE is not readable: ${ca_bundle}" >&2 + exit 1 +fi + +if [[ -z "${ca_bundle}" ]]; then + for candidate in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/cert.pem; do + if [[ -r "${candidate}" ]]; then + ca_bundle="${candidate}" + break + fi + done +fi + +if [[ -n "${ca_bundle}" ]]; then + temp_dir="$(mktemp -d)" + cleanup() { + rm -f "${temp_dir}"/cert-*.pem "${temp_dir}/chain.pem" + rmdir "${temp_dir}" 2>/dev/null || true + } + trap cleanup EXIT + awk -v dir="${temp_dir}" ' + /-----BEGIN CERTIFICATE-----/ { n++; file=sprintf("%s/cert-%03d.pem", dir, n) } + n > 0 { print > file } + /-----END CERTIFICATE-----/ { close(file) } + ' "${CERT_FILE}" + + leaf="${temp_dir}/cert-001.pem" + chain="${temp_dir}/chain.pem" + : >"${chain}" + for cert in "${temp_dir}"/cert-*.pem; do + [[ "${cert}" == "${leaf}" ]] && continue + cat "${cert}" >>"${chain}" + done + + if [[ -s "${chain}" ]]; then + if openssl verify -purpose sslserver -CAfile "${ca_bundle}" -untrusted "${chain}" "${leaf}" >/dev/null 2>&1; then + ok "certificate chain validates against ${ca_bundle}" + else + fail "certificate chain does not validate against ${ca_bundle}" + fi + elif openssl verify -purpose sslserver -CAfile "${ca_bundle}" "${leaf}" >/dev/null 2>&1; then + ok "certificate validates directly against ${ca_bundle}" + else + fail "certificate does not validate against ${ca_bundle}; include required intermediates" + fi +else + warn "system CA bundle not found; certificate chain was not verified" +fi + +if command -v stat >/dev/null 2>&1; then + mode="$(stat -c '%a' "${KEY_FILE}" 2>/dev/null || true)" + if [[ -n "${mode}" && $((8#${mode} & 8#077)) -ne 0 ]]; then + warn "private key permissions are ${mode}; remove group and other access" + fi +fi + +if (( failed != 0 )); then + exit 1 +fi + +printf '\nTLS file preflight passed. Keep the private key outside repositories and approved logs.\n' diff --git a/skills/install-openhands-replicated/scripts/preflight_storage_guard.sh b/skills/install-openhands-replicated/scripts/preflight_storage_guard.sh new file mode 100755 index 00000000..275e8ca5 --- /dev/null +++ b/skills/install-openhands-replicated/scripts/preflight_storage_guard.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat >&2 <<'EOF' +usage: preflight_storage_guard.sh [namespace] + +Fails the install/configure flow if the main OpenHands Postgres data path is +not backed by a PVC, if any node has DiskPressure=True, or if optional disk +growth checks exceed thresholds. + +Environment: + KUBECTL one executable kubectl path; defaults to kubectl, + then /var/lib/embedded-cluster/bin/kubectl + POSTGRES_POD_NAME default: openhands-postgresql-0 + POSTGRES_SELECTOR default: app.kubernetes.io/name=postgresql,app.kubernetes.io/instance=openhands + DATA_MOUNT_PATH default: /bitnami/postgresql + MIN_ROOT_FREE_GIB default: 20 + CHECK_HOST_DISK 1, 0, or auto; default: auto + CHECK_CLICKHOUSE 1 or 0; default: 1 + CLICKHOUSE_SYSTEM_LOG_MAX_GIB default: 10 +EOF +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +NAMESPACE="${1:-${NAMESPACE:-openhands}}" +POSTGRES_POD_NAME="${POSTGRES_POD_NAME:-openhands-postgresql-0}" +POSTGRES_SELECTOR="${POSTGRES_SELECTOR:-app.kubernetes.io/name=postgresql,app.kubernetes.io/instance=openhands}" +DATA_MOUNT_PATH="${DATA_MOUNT_PATH:-/bitnami/postgresql}" +MIN_ROOT_FREE_GIB="${MIN_ROOT_FREE_GIB:-20}" +CHECK_HOST_DISK="${CHECK_HOST_DISK:-auto}" +CHECK_CLICKHOUSE="${CHECK_CLICKHOUSE:-1}" +CLICKHOUSE_SYSTEM_LOG_MAX_GIB="${CLICKHOUSE_SYSTEM_LOG_MAX_GIB:-10}" + +failed=0 + +info() { + echo "INFO $*" +} + +ok() { + echo "OK $*" +} + +warn() { + echo "WARN $*" >&2 +} + +record_fail() { + echo "FAIL $*" >&2 + failed=1 +} + +KUBECTL_CMD=() +if [[ -n "${KUBECTL:-}" ]]; then + KUBECTL_CMD=("${KUBECTL}") +elif command -v kubectl >/dev/null 2>&1; then + KUBECTL_CMD=(kubectl) +elif [[ -x /var/lib/embedded-cluster/bin/kubectl ]]; then + if [[ "${EUID}" -eq 0 ]]; then + KUBECTL_CMD=(/var/lib/embedded-cluster/bin/kubectl) + else + KUBECTL_CMD=(sudo /var/lib/embedded-cluster/bin/kubectl) + fi +else + echo "FAIL kubectl not found. Set KUBECTL=/path/to/kubectl." >&2 + exit 1 +fi + +k() { + "${KUBECTL_CMD[@]}" "$@" +} + +if ! k get namespace "${NAMESPACE}" >/dev/null 2>&1; then + echo "FAIL namespace ${NAMESPACE} is not reachable with ${KUBECTL_CMD[*]}" >&2 + exit 1 +fi + +info "checking OpenHands storage in namespace ${NAMESPACE}" + +postgres_pod="" +if k get pod -n "${NAMESPACE}" "${POSTGRES_POD_NAME}" >/dev/null 2>&1; then + postgres_pod="${POSTGRES_POD_NAME}" +else + pod_list="$(k get pods -n "${NAMESPACE}" -l "${POSTGRES_SELECTOR}" -o "jsonpath={range .items[*]}{.metadata.name}{'\n'}{end}" || true)" + pod_count="$(printf '%s\n' "${pod_list}" | sed '/^$/d' | wc -l | tr -d ' ')" + if [[ "${pod_count}" == "1" ]]; then + postgres_pod="$(printf '%s\n' "${pod_list}" | sed '/^$/d' | sed -n '1p')" + elif [[ "${pod_count}" == "0" ]]; then + echo "FAIL no Postgres pod found by name ${POSTGRES_POD_NAME} or selector ${POSTGRES_SELECTOR}" >&2 + exit 1 + else + echo "FAIL multiple Postgres pods matched selector ${POSTGRES_SELECTOR}; set POSTGRES_POD_NAME" >&2 + printf '%s\n' "${pod_list}" >&2 + exit 1 + fi +fi + +ok "found Postgres pod ${postgres_pod}" + +data_volume="$(k get pod -n "${NAMESPACE}" "${postgres_pod}" -o "jsonpath={range .spec.containers[*].volumeMounts[?(@.mountPath=='${DATA_MOUNT_PATH}')]}{.name}{'\n'}{end}" | sed '/^$/d' | sed -n '1p')" + +if [[ -z "${data_volume}" ]]; then + record_fail "no container volumeMount found for ${DATA_MOUNT_PATH} on ${postgres_pod}" +else + ok "${DATA_MOUNT_PATH} is mounted from volume ${data_volume}" +fi + +if [[ -n "${data_volume}" ]]; then + volume_result="$(k get pod -n "${NAMESPACE}" "${postgres_pod}" -o "go-template={{range .spec.volumes}}{{if eq .name \"${data_volume}\"}}{{if .persistentVolumeClaim}}pvc:{{.persistentVolumeClaim.claimName}}{{else if .emptyDir}}emptyDir{{else if .hostPath}}hostPath:{{.hostPath.path}}{{else}}other{{end}}{{end}}{{end}}")" + + case "${volume_result}" in + pvc:*) + claim="${volume_result#pvc:}" + phase="$(k get pvc -n "${NAMESPACE}" "${claim}" -o "jsonpath={.status.phase}" 2>/dev/null || true)" + storage_class="$(k get pvc -n "${NAMESPACE}" "${claim}" -o "jsonpath={.spec.storageClassName}" 2>/dev/null || true)" + if [[ "${phase}" == "Bound" ]]; then + ok "Postgres data volume uses Bound PVC ${claim} (storageClass=${storage_class:-unset})" + else + record_fail "Postgres data volume uses PVC ${claim}, but PVC phase is ${phase:-unknown}" + fi + ;; + emptyDir) + record_fail "Postgres data volume ${data_volume} is emptyDir. This can reset OpenHands, Keycloak, LiteLLM, automation, plugin-directory, and runtime API data when the pod is recreated." + ;; + hostPath:*) + record_fail "Postgres data volume ${data_volume} is hostPath (${volume_result#hostPath:}), not PVC-backed storage" + ;; + "") + record_fail "volume ${data_volume} was not found in pod spec" + ;; + *) + record_fail "Postgres data volume ${data_volume} is ${volume_result}, not PVC-backed storage" + ;; + esac +fi + +info "checking node DiskPressure" +node_pressure_lines="$(k get nodes -o 'go-template={{range .items}}{{.metadata.name}} {{range .status.conditions}}{{if eq .type "DiskPressure"}}{{.status}}{{end}}{{end}}{{"\n"}}{{end}}')" +while read -r node_name pressure_status; do + [[ -z "${node_name:-}" ]] && continue + if [[ "${pressure_status}" == "True" ]]; then + record_fail "node ${node_name} reports DiskPressure=True" + else + ok "node ${node_name} DiskPressure=${pressure_status:-unknown}" + fi +done <<< "${node_pressure_lines}" + +check_host_disk=false +if [[ "${CHECK_HOST_DISK}" == "1" ]]; then + check_host_disk=true +elif [[ "${CHECK_HOST_DISK}" == "auto" && -x /var/lib/embedded-cluster/bin/kubectl ]]; then + check_host_disk=true +fi + +if [[ "${check_host_disk}" == "true" ]]; then + info "checking root filesystem free space on this host" + avail_kb="$(df -Pk / | awk 'NR == 2 {print $4}')" + avail_gib=$((avail_kb / 1024 / 1024)) + if (( avail_gib < MIN_ROOT_FREE_GIB )); then + record_fail "root filesystem has ${avail_gib} GiB free; minimum is ${MIN_ROOT_FREE_GIB} GiB" + else + ok "root filesystem has ${avail_gib} GiB free" + fi +else + warn "skipping host disk free-space check; set CHECK_HOST_DISK=1 when running on the target VM" +fi + +if [[ "${CHECK_CLICKHOUSE}" == "1" ]]; then + info "checking ClickHouse diagnostic system log table size if ClickHouse is present" + clickhouse_pod="$(k get pods -n "${NAMESPACE}" -o name | sed 's#^pod/##' | grep -E 'clickhouse' | sed -n '1p' || true)" + if [[ -z "${clickhouse_pod}" ]]; then + warn "no ClickHouse pod found in namespace ${NAMESPACE}; skipping ClickHouse system log check" + else + max_bytes=$((CLICKHOUSE_SYSTEM_LOG_MAX_GIB * 1024 * 1024 * 1024)) + query="SELECT table, sum(bytes_on_disk) FROM system.parts WHERE active AND database = 'system' AND table IN ('trace_log', 'text_log', 'metric_log', 'asynchronous_metric_log') GROUP BY table FORMAT TabSeparated" + raw_sizes="$(k exec -n "${NAMESPACE}" "${clickhouse_pod}" -- clickhouse-client --query "${query}" 2>/dev/null || true)" + if [[ -z "${raw_sizes}" ]]; then + warn "could not read ClickHouse system.parts from ${clickhouse_pod}; skipping size threshold" + else + while IFS=$'\t' read -r table_name bytes_on_disk; do + [[ -z "${table_name:-}" ]] && continue + if [[ "${bytes_on_disk}" =~ ^[0-9]+$ ]]; then + gib=$((bytes_on_disk / 1024 / 1024 / 1024)) + if (( bytes_on_disk > max_bytes )); then + record_fail "ClickHouse system.${table_name} is ${gib} GiB, above ${CLICKHOUSE_SYSTEM_LOG_MAX_GIB} GiB" + else + ok "ClickHouse system.${table_name} is ${gib} GiB" + fi + else + warn "unexpected ClickHouse size output for ${table_name}: ${bytes_on_disk}" + fi + done <<< "${raw_sizes}" + fi + fi +fi + +if (( failed != 0 )); then + echo + echo "Storage guard failed. Do not proceed with production/demo use until the failed checks are fixed." >&2 + exit 1 +fi + +echo +ok "storage guard passed" diff --git a/skills/install-openhands-replicated/scripts/summarize_terraform_outputs.sh b/skills/install-openhands-replicated/scripts/summarize_terraform_outputs.sh new file mode 100755 index 00000000..dc6e1e59 --- /dev/null +++ b/skills/install-openhands-replicated/scripts/summarize_terraform_outputs.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 " >&2 + exit 1 +fi + +TF_DIR="$1" + +if [[ ! -d "${TF_DIR}" ]]; then + echo "terraform directory not found: ${TF_DIR}" >&2 + exit 1 +fi + +if ! command -v terraform >/dev/null 2>&1; then + echo "terraform is required" >&2 + exit 1 +fi + +cd "${TF_DIR}" + +keys=( + instance_public_ip + instance_id + admin_console_url + app_url + base_url + base_domain +) + +found=0 +for key in "${keys[@]}"; do + if value="$(terraform output -raw "${key}" 2>/dev/null)" && [[ -n "${value}" ]]; then + printf '%s=%s\n' "${key}" "${value}" + found=1 + fi +done + +if (( found == 0 )); then + echo "no allowlisted non-sensitive outputs were found" >&2 + exit 1 +fi + +cat <<'EOF' + +Sensitive outputs and local certificate, private-key, and SSH-key paths are intentionally omitted. +EOF diff --git a/skills/install-openhands-replicated/scripts/verify_github_setup.sh b/skills/install-openhands-replicated/scripts/verify_github_setup.sh new file mode 100755 index 00000000..ae407e7f --- /dev/null +++ b/skills/install-openhands-replicated/scripts/verify_github_setup.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +cat <<'EOF' +GitHub validation checklist + +1. Create a GitHub App with the current OpenHands Enterprise helper; do not use a GitHub OAuth App. +2. Transfer the App ID, slug, client ID, client secret, webhook secret, and private key through approved secret channels. +3. Deploy the Replicated sequence with GitHub authentication enabled. +4. Install the GitHub App on an approved disposable test repository with least privilege. +5. Sign in at https://app. in a clean browser session. +6. Verify the signed-in user can discover the approved test repository. +7. Run one bounded repository-backed conversation. +8. If webhook or Automations routing is in scope, trigger one disposable issue or pull-request event and verify the expected result. +9. Remove disposable credentials or test artifacts when required by policy. + +Do not print the private key, client secret, webhook secret, user token, or complete webhook payload. +EOF diff --git a/skills/install-openhands-replicated/scripts/verify_jira_setup.sh b/skills/install-openhands-replicated/scripts/verify_jira_setup.sh new file mode 100755 index 00000000..3bd1c867 --- /dev/null +++ b/skills/install-openhands-replicated/scripts/verify_jira_setup.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +cat <<'EOF' +Jira Data Center validation checklist + +1. Confirm the target OHE release supports the required Jira deployment type. +2. Configure Jira through the Replicated Admin Console using the version-matched OpenHands Enterprise guide. +3. Store OAuth, service-account, and webhook credentials only in approved secret surfaces. +4. Confirm the Jira base URL presents a trusted certificate to OpenHands. +5. Complete user account linking when the configured flow requires user context. +6. Create a disposable Jira project or issue for validation. +7. Configure the documented OpenHands webhook endpoint and required Jira events. +8. Trigger one bounded test issue or comment event. +9. Confirm OpenHands receives the event and creates the expected bounded result. +10. Record issue, run, and conversation identifiers without recording credentials or full customer payloads. +11. Remove disposable webhooks, test issues, or credentials when required by policy. + +Custom Jira-to-Automations webhooks are a separate automation design. Do not copy +customer-specific cloud IDs, project keys, shim URLs, tokens, or routing patches +into a generic installation. +EOF diff --git a/skills/install-openhands-replicated/scripts/verify_laminar_setup.sh b/skills/install-openhands-replicated/scripts/verify_laminar_setup.sh new file mode 100755 index 00000000..3681683d --- /dev/null +++ b/skills/install-openhands-replicated/scripts/verify_laminar_setup.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +cat <<'EOF' +Analytics validation checklist + +1. Enable Analytics in the Replicated Admin Console only when it is in scope. +2. Review resource impact and deploy the approved Replicated sequence. +3. Confirm https://analytics. presents valid TLS and reaches the sign-in flow. +4. Log in through the supported OpenHands/Keycloak identity path. +5. Create or select the approved Laminar project. +6. Create an ingest-only project API key and enter it through the Admin Console without printing it. +7. Deploy the approved sequence after changing the project key. +8. Start one fresh bounded OpenHands conversation. +9. Confirm the conversation creates a trace in the expected project. +10. Record trace identifiers and timestamps, not prompt contents or credentials. + +A reachable analytics UI does not prove trace ingestion. Treat missing or malformed +traces as a troubleshooting or version-compatibility issue rather than applying +unverified database or Kubernetes patches. +EOF diff --git a/skills/install-openhands-replicated/scripts/verify_slack_setup.sh b/skills/install-openhands-replicated/scripts/verify_slack_setup.sh new file mode 100755 index 00000000..dc26b728 --- /dev/null +++ b/skills/install-openhands-replicated/scripts/verify_slack_setup.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +cat <<'EOF' +Slack validation checklist + +1. Slack app created and credentials entered in Replicated +2. Replicated sequence deployed with Slack enabled +3. Event Subscriptions request URL is verified +4. Bot event includes app_mention +5. Slack app is installed or reinstalled to the workspace +6. User completed OpenHands UI -> Integrations -> Install Slack +7. User completed the Keycloak login flow using the same OpenHands identity +8. Bot is invited to the test channel +9. A fresh @OpenHands mention creates a conversation in the self-hosted instance + +If steps 1-5 are true but mentions still fail, inspect logs for: +- Did not find slack team +EOF diff --git a/tests/test_skills_catalog.py b/tests/test_skills_catalog.py index ea4666b6..d90d2380 100644 --- a/tests/test_skills_catalog.py +++ b/tests/test_skills_catalog.py @@ -374,7 +374,7 @@ def test_index_is_up_to_date(self): SKILLS_WITHOUT_MARKETPLACE_ENTRY = {"qa-changes", "release-notes"} EXPECTED_CATEGORY_COUNTS = { - "environment": 10, + "environment": 11, "automations": 9, "code-hosting": 8, "agent-authoring": 8,