fix: add manual mode indicator to status messages #76
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Charm Tests | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| jobs: | |
| lint: | |
| name: Lint Charm Code | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y shellcheck | |
| - name: Lint shell scripts | |
| run: | | |
| find hooks -type f -executable -exec shellcheck {} + | |
| - name: Check hook permissions | |
| run: | | |
| for hook in hooks/*; do | |
| if [ -f "$hook" ] && [ ! -x "$hook" ]; then | |
| echo "ERROR: Hook $hook is not executable" | |
| exit 1 | |
| fi | |
| done | |
| - name: Validate metadata | |
| run: | | |
| if [ ! -f metadata.yaml ]; then | |
| echo "ERROR: metadata.yaml not found" | |
| exit 1 | |
| fi | |
| # Check required fields | |
| for field in name summary description; do | |
| if ! grep -q "^${field}:" metadata.yaml; then | |
| echo "ERROR: Required field '${field}' not found in metadata.yaml" | |
| exit 1 | |
| fi | |
| done | |
| - name: Validate config | |
| run: | | |
| if [ ! -f config.yaml ]; then | |
| echo "ERROR: config.yaml not found" | |
| exit 1 | |
| fi | |
| build: | |
| name: Build Charm | |
| runs-on: ubuntu-24.04 | |
| needs: lint | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install charmcraft | |
| run: | | |
| sudo snap install charmcraft --classic | |
| - name: Pack charm | |
| run: | | |
| charmcraft pack --destructive-mode | |
| - name: Upload charm artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: openclaw-charm | |
| path: openclaw_*.charm | |
| retention-days: 1 | |
| test-install: | |
| name: Test Charm Installation | |
| runs-on: ubuntu-24.04 | |
| needs: build | |
| strategy: | |
| matrix: | |
| install-method: [npm, pnpm, bun] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Download charm artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: openclaw-charm | |
| - name: Set up LXD | |
| uses: canonical/setup-lxd@main | |
| with: | |
| channel: latest/stable | |
| - name: Install Juju | |
| run: | | |
| sudo snap install juju --channel=3/stable | |
| sudo snap install juju-wait --classic | |
| - name: Bootstrap Juju | |
| run: | | |
| lxc network set lxdbr0 ipv6.address none | |
| juju bootstrap localhost test-controller | |
| - name: Create test model | |
| run: | | |
| juju add-model test-openclaw | |
| - name: Deploy charm | |
| run: | | |
| CHARM_FILE=$(ls openclaw_*.charm | head -1) | |
| juju deploy ./$CHARM_FILE \ | |
| --config install-method=${{ matrix.install-method }} \ | |
| --config ai-provider="anthropic" \ | |
| --config ai-model="claude-sonnet-3-5" \ | |
| --config ai-api-key="test-key-placeholder" | |
| - name: Wait for deployment | |
| run: | | |
| # Wait up to 15 minutes for installation to complete | |
| juju-wait -v -m test-openclaw -t 900 | |
| - name: Verify installation | |
| run: | | |
| # Get unit IP | |
| UNIT_IP=$(juju status openclaw --format=json | jq -r '.applications.openclaw.units | to_entries[0].value."public-address"') | |
| echo "Unit IP: $UNIT_IP" | |
| # Check if openclaw command exists | |
| juju ssh openclaw/0 'which openclaw' | |
| # Check openclaw version | |
| juju ssh openclaw/0 'openclaw --version' || echo "Version check skipped (may require config)" | |
| # Check systemd service | |
| juju ssh openclaw/0 'systemctl status openclaw.service' || true | |
| # Check if port is open | |
| juju ssh openclaw/0 'ss -tulpn | grep 18789' || echo "Port not bound (expected without valid API key)" | |
| - name: Test configuration changes | |
| run: | | |
| # Change gateway port | |
| juju config openclaw gateway-port=18790 | |
| # Wait for config change to apply | |
| sleep 30 | |
| # Verify port change | |
| juju ssh openclaw/0 'ss -tulpn | grep 18790' || echo "Port change test (may not bind without valid key)" | |
| # Change back | |
| juju config openclaw gateway-port=18789 | |
| - name: Collect logs on failure | |
| if: failure() | |
| run: | | |
| echo "=== Juju Status ===" | |
| juju status --format=yaml | |
| echo "=== Debug Log ===" | |
| juju debug-log --replay --no-tail --limit 500 | |
| echo "=== Unit Logs ===" | |
| juju ssh openclaw/0 'journalctl -u openclaw.service -n 200' || true | |
| echo "=== OpenClaw Config ===" | |
| juju ssh openclaw/0 'cat /home/ubuntu/.openclaw/openclaw.json' || true | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| juju destroy-model test-openclaw -y --force --no-wait || true | |
| juju destroy-controller test-controller -y --destroy-all-models --force --no-wait || true | |
| test-channels: | |
| name: Test Messaging Channel Configuration | |
| runs-on: ubuntu-24.04 | |
| needs: build | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Download charm artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: openclaw-charm | |
| - name: Set up LXD | |
| uses: canonical/setup-lxd@main | |
| - name: Install Juju | |
| run: | | |
| sudo snap install juju --channel=3/stable | |
| sudo snap install juju-wait --classic | |
| - name: Bootstrap Juju | |
| run: | | |
| lxc network set lxdbr0 ipv6.address none | |
| juju bootstrap localhost test-controller | |
| - name: Create test model | |
| run: | | |
| juju add-model test-channels | |
| - name: Deploy with channel config | |
| run: | | |
| CHARM_FILE=$(ls openclaw_*.charm | head -1) | |
| juju deploy ./$CHARM_FILE \ | |
| --config ai-provider="anthropic" \ | |
| --config ai-model="claude-sonnet-3-5" \ | |
| --config ai-api-key="test-key" \ | |
| --config telegram-bot-token="123456:TEST" | |
| - name: Wait for deployment | |
| run: | | |
| juju-wait -v -m test-channels -t 900 | |
| - name: Verify channel configuration | |
| run: | | |
| # Check that config file contains channel configuration | |
| juju ssh openclaw/0 'cat /home/ubuntu/.openclaw/openclaw.json' > config.json | |
| if grep -q "telegram" config.json; then | |
| echo "✓ Channel configuration verified" | |
| else | |
| echo "✗ Channel configuration missing" | |
| exit 1 | |
| fi | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| juju destroy-model test-channels -y --force --no-wait || true | |
| juju destroy-controller test-controller -y --destroy-all-models --force --no-wait || true | |
| test-upgrade: | |
| name: Test Charm Upgrade | |
| runs-on: ubuntu-24.04 | |
| needs: build | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Download charm artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: openclaw-charm | |
| - name: Set up LXD | |
| uses: canonical/setup-lxd@main | |
| - name: Install Juju | |
| run: | | |
| sudo snap install juju --channel=3/stable | |
| sudo snap install juju-wait --classic | |
| - name: Bootstrap Juju | |
| run: | | |
| lxc network set lxdbr0 ipv6.address none | |
| juju bootstrap localhost test-controller | |
| - name: Create test model | |
| run: | | |
| juju add-model test-upgrade | |
| - name: Deploy v1 | |
| run: | | |
| CHARM_FILE=$(ls openclaw_*.charm | head -1) | |
| juju deploy ./$CHARM_FILE \ | |
| --config ai-provider="anthropic" \ | |
| --config ai-model="claude-sonnet-3-5" \ | |
| --config ai-api-key="test-key" | |
| - name: Wait for initial deployment | |
| run: | | |
| juju-wait -v -m test-upgrade -t 900 | |
| - name: Refresh charm (simulate upgrade) | |
| run: | | |
| CHARM_FILE=$(ls openclaw_*.charm | head -1) | |
| juju refresh openclaw --path ./$CHARM_FILE | |
| sleep 60 # Give upgrade-charm hook time to complete | |
| - name: Verify upgrade success | |
| run: | | |
| # Get unit workload status (field is "current", not "status") | |
| status=$(juju status openclaw --format=json | jq -r '.applications.openclaw.units[]["workload-status"].current' | head -1) | |
| echo "Current workload status: $status" | |
| if [ "$status" = "active" ] || [ "$status" = "blocked" ]; then | |
| echo "✓ Upgrade successful (status: $status)" | |
| else | |
| echo "✗ Upgrade failed - unexpected status: $status" | |
| echo "Full juju status:" | |
| juju status openclaw | |
| exit 1 | |
| fi | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| juju destroy-model test-upgrade -y --force --no-wait || true | |
| juju destroy-controller test-controller -y --destroy-all-models --force --no-wait || true | |
| test-manual-mode: | |
| name: Test Manual Configuration Mode | |
| runs-on: ubuntu-24.04 | |
| needs: build | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Download charm artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: openclaw-charm | |
| - name: Set up LXD | |
| uses: canonical/setup-lxd@main | |
| - name: Install Juju | |
| run: | | |
| sudo snap install juju --channel=3/stable | |
| sudo snap install juju-wait --classic | |
| - name: Bootstrap Juju | |
| run: | | |
| lxc network set lxdbr0 ipv6.address none | |
| juju bootstrap localhost test-controller | |
| - name: Create test model | |
| run: | | |
| juju add-model test-manual | |
| - name: Deploy with manual=true | |
| run: | | |
| CHARM_FILE=$(ls openclaw_*.charm | head -1) | |
| juju deploy ./$CHARM_FILE \ | |
| --config manual=true \ | |
| --config install-method=npm | |
| - name: Wait for deployment | |
| run: | | |
| juju-wait -v -m test-manual -t 900 | |
| - name: Verify manual mode behavior | |
| run: | | |
| # Check status message indicates manual mode | |
| status_msg=$(juju status openclaw --format=json | jq -r '.applications.openclaw.units[]["workload-status"].message' | head -1) | |
| echo "Status message: $status_msg" | |
| if echo "$status_msg" | grep -qi "manual"; then | |
| echo "✓ Manual mode status verified" | |
| else | |
| echo "✗ Status does not indicate manual mode: $status_msg" | |
| exit 1 | |
| fi | |
| # Verify OpenClaw is installed | |
| juju ssh openclaw/0 'sudo -u ubuntu bash -l -c ". ~/.nvm/nvm.sh && nvm use 24 && which openclaw"' | |
| echo "✓ OpenClaw binary installed" | |
| # Verify version | |
| version=$(juju ssh openclaw/0 'sudo -u ubuntu bash -l -c ". ~/.nvm/nvm.sh && nvm use 24 && openclaw --version 2>&1 | tail -1"') | |
| echo "OpenClaw version: $version" | |
| # Check that config files were NOT auto-generated by charm | |
| if juju ssh openclaw/0 'test -f /home/ubuntu/.openclaw/openclaw.json'; then | |
| echo "⚠ Config file exists (may be from OpenClaw itself, not charm)" | |
| # Verify it doesn't contain charm-managed settings | |
| if juju ssh openclaw/0 'grep -q "ai-provider\|ai-model" /home/ubuntu/.openclaw/openclaw.json 2>/dev/null'; then | |
| echo "✗ Config contains charm-managed settings in manual mode" | |
| exit 1 | |
| fi | |
| fi | |
| echo "✓ No charm-managed config generated" | |
| - name: Test user config preservation | |
| run: | | |
| # Create a custom config file | |
| juju ssh openclaw/0 'cat > /tmp/custom-config.json << "EOF" | |
| { | |
| "user_custom_field": "test-value-12345", | |
| "gateway": { | |
| "mode": "local", | |
| "port": 18789 | |
| } | |
| } | |
| EOF' | |
| # Copy it to OpenClaw directory | |
| juju ssh openclaw/0 'sudo -u ubuntu cp /tmp/custom-config.json /home/ubuntu/.openclaw/openclaw.json' | |
| # Trigger a config change (this would normally regenerate config in automatic mode) | |
| juju config openclaw gateway-port=18790 | |
| sleep 30 | |
| # Verify custom field still exists | |
| if juju ssh openclaw/0 'grep -q "user_custom_field.*test-value-12345" /home/ubuntu/.openclaw/openclaw.json'; then | |
| echo "✓ User config preserved after charm config change" | |
| else | |
| echo "✗ User config was overwritten" | |
| juju ssh openclaw/0 'cat /home/ubuntu/.openclaw/openclaw.json' | |
| exit 1 | |
| fi | |
| - name: Test multi-unit with manual mode | |
| run: | | |
| # Add a second unit | |
| juju add-unit openclaw | |
| # Wait for it to deploy | |
| timeout 900 bash -c 'until juju status openclaw --format=json | jq -e ".applications.openclaw.units[\"openclaw/1\"]" > /dev/null 2>&1; do sleep 10; done' | |
| sleep 60 # Additional wait for installation | |
| # Check second unit has manual mode | |
| unit1_status=$(juju status openclaw --format=json | jq -r '.applications.openclaw.units["openclaw/1"]["workload-status"].message' 2>/dev/null || echo "not-ready") | |
| echo "Unit 1 status: $unit1_status" | |
| # Verify OpenClaw installed on second unit | |
| juju ssh openclaw/1 'sudo -u ubuntu bash -l -c ". ~/.nvm/nvm.sh && nvm use 24 && which openclaw"' || echo "Unit 1 still installing" | |
| # Verify peer relation node config exists on Node unit | |
| if juju ssh openclaw/1 'test -f /home/ubuntu/.openclaw/node.json'; then | |
| echo "✓ Node peer relation config exists" | |
| juju ssh openclaw/1 'cat /home/ubuntu/.openclaw/node.json' | |
| else | |
| echo "⚠ Node config not yet created (unit may still be starting)" | |
| fi | |
| - name: Collect logs on failure | |
| if: failure() | |
| run: | | |
| echo "=== Juju Status ===" | |
| juju status --format=yaml | |
| echo "=== Debug Log ===" | |
| juju debug-log --replay --no-tail --limit 500 | |
| echo "=== Unit 0 Config ===" | |
| juju ssh openclaw/0 'ls -la /home/ubuntu/.openclaw/' || true | |
| juju ssh openclaw/0 'cat /home/ubuntu/.openclaw/openclaw.json 2>/dev/null' || true | |
| echo "=== Unit 1 Config ===" | |
| juju ssh openclaw/1 'ls -la /home/ubuntu/.openclaw/' || true | |
| juju ssh openclaw/1 'cat /home/ubuntu/.openclaw/node.json 2>/dev/null' || true | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| juju destroy-model test-manual -y --force --no-wait || true | |
| juju destroy-controller test-controller -y --destroy-all-models --force --no-wait || true |