Commit b873742

Eric Bower  ·  2026-08-15 09:42:28 -0400 EDT
parent a5aaf30
docs: props
1 files changed,  +156, -138
+156, -138
......@@ -2,177 +2,195 @@
22
33 ## 1. Executive Summary
44
5-Migrating existing projects away from GitHub Actions to a dedicated self-hosted CI system is difficult due to ecosystem inertia and configuration friction.
6-
7-This proposal outlines a strategy to **integrate `pici` with GitHub Actions as a drop-in execution engine**. Developers write standard, parallel bash workflows (`pico.sh`) using `zmx`, execute them identically on both local workstations and GitHub Actions runners, and gain **live, interactive terminal debugging on failure** powered by `pipe.pico.sh`.
8-
9-```mermaid
10-flowchart TD
11- subgraph Local Environment
12- Dev[Developer] -->|Runs locally| LocalScript["./pico.sh (or pici run)"]
13- LocalScript -->|PTY Sessions| LocalZMX["zmx (Parallel Tasks)"]
14- end
15-
16- subgraph GitHub Actions Runner
17- GHA[GitHub Actions Workflow] --> InstallZMX["Install zmx"]
18- InstallZMX --> RunScript["Run ./pici-debug.sh"]
19- RunScript --> RunnerZMX["zmx sessions (Host & Docker)"]
20-
21- RunnerZMX -->|Success| GHASuccess["Finish Job (0)"]
22- RunnerZMX -->|Failure| CatchError["Catch non-zero exit code"]
23-
24- CatchError --> StartBridge["Open bidirectional pipe to pipe.pico.sh"]
25- StartBridge --> AllocPTY["Allocate interactive PTY (script / bash -i)"]
26- end
27-
28- subgraph Pico Cloud Relay
29- PipeRelay["ssh pipe.pico.sh pipe {topic}"]
30- end
31-
32- StartBridge <-->|SSH stdin/stdout| PipeRelay
33- Dev -.->|ssh -t pipe.pico.sh pipe {topic}| PipeRelay
34-```
5+Migrating existing projects away from GitHub Actions to a dedicated self-hosted CI system is difficult due to ecosystem inertia and configuration friction.
6+
7+This proposal outlines a strategy to provide an **official custom GitHub Action (`pico-sh/pici-action`)** powered by `pici run`. Developers write standard, parallel bash workflows (`pico.sh`) using `zmx`, execute them with 1:1 parity on both local workstations and GitHub Actions runners, and gain **live, interactive terminal debugging on failure** powered by native `pipe.pico.sh` bridging in Go.
358
369 ---
3710
3811 ## 2. Core Design Goals
3912
40-1. **1:1 Local & Remote Parity**: The exact same `./pico.sh` runs locally and on CI without emulation layers or proprietary YAML DSLs.
41-2. **Native Docker Compatibility**: Seamless support for `docker run ...` steps in `pico.sh` within standard GitHub-hosted runners (`ubuntu-latest`).
42-3. **Interactive Debug on Failure ("Attach")**: When a task fails on GitHub Actions, the runner pauses and establishes an outbound reverse PTY bridge via `pipe.pico.sh`.
43-4. **Zero-Config Pico Authentication**: Key-based authentication directly through the developer's SSH private key—no usernames or external tunnel tokens required.
13+1. **1-Step GitHub Action Experience**: A custom composite action (`pico-sh/pici-action@v1`) that installs `pici` + `zmx`, configures SSH keys, executes the workflow in-place, and uploads artifacts with zero boilerplate.
14+2. **1:1 Local & Remote Parity**: `pici run` provides identical execution semantics across environments: metadata environment variable injection (`PICI_JOB`, `PICI_REPO`, `PICI_BRANCH`, `PICI_COMMIT`), `pico.sh` execution via `zmx`, live progress streaming, artifact rendering, and exit code propagation.
15+3. **Explicit Workspace Semantics**: By default, `pici run` rsyncs to an isolated `/tmp` workspace to keep local working trees clean. In CI environments where the runner is already an ephemeral container/VM, an explicit `--in-place` flag instructs `pici run` to execute directly without rsyncing.
16+4. **Native Docker Compatibility**: Mounts like `$(pwd):/app` work seamlessly in both local and CI environments.
17+5. **Native Go Failure Hook (`pici debug`)**: When a job fails, `pici` directly opens a secure, authenticated PTY bridge to `pipe.pico.sh` in Go, avoiding brittle bash FIFOs or background SSH wrappers.
18+6. **Security & Secret Scrubbing**: `pici` controls the debug shell environment and scrubs `GITHUB_TOKEN` and repository secrets before spawning interactive shells.
19+
20+---
21+
22+## 3. The Custom GitHub Action: `pico-sh/pici-action`
23+
24+We are creating and publishing an official custom action—`pico-sh/pici-action`—that serves as the turnkey entrypoint for GitHub Actions users.
25+
26+### Standard Workflow (`.github/workflows/ci.yml`)
27+
28+With the custom action, adding Pici CI with remote debugging to any repo is a single step:
29+
30+```yaml
31+name: CI
32+on: [push, pull_request]
33+
34+jobs:
35+ ci:
36+ runs-on: ubuntu-latest
37+ steps:
38+ - uses: actions/checkout@v4
39+
40+ - name: Run CI with Pici
41+ uses: picosh/pici-action@v1
42+ with:
43+ pico_ssh_key: ${{ secrets.PICO_SSH_KEY }}
44+```
45+
46+### Action Configuration & Inputs
47+
48+| Input | Description | Default |
49+| :--- | :--- | :--- |
50+| `pico_ssh_key` | SSH private key registered with pico.sh for authenticated debug relay access. | `""` (optional, required for debug relay) |
51+| `debug_on_fail` | Automatically open an interactive `pipe.pico.sh` debug bridge if a job fails. | `"false"` |
52+
53+### What `pico-sh/pici-action` Does Under the Hood
54+1. **Tool Installation**: Installs the static `pici` and pinned `zmx` binaries (with SHA256 checksum verification) to `/usr/local/bin`.
55+2. **Credential Setup**: Writes `pico_ssh_key` to a secure temp keyfile or SSH agent if provided.
56+3. **Environment Propagation**: Passes GitHub Actions run metadata to `pici run`:
57+ - `PICI_JOB="${GITHUB_RUN_ID}"`
58+ - `PICI_EVENT="git.${GITHUB_EVENT_NAME}"`
59+ - `PICI_BRANCH="${GITHUB_REF_NAME}"`
60+ - `PICI_COMMIT="${GITHUB_SHA}"`
61+4. **Execution (`pici run`)**: Invokes `pici run --in-place` (with `--debug-on-fail` if enabled).
62+5. **Job Summaries & Artifact Upload**:
63+ - `pici` formats the run status table and failed logs into `$GITHUB_STEP_SUMMARY`.
64+ - If `upload_artifacts` is enabled, the action uploads `/tmp/pici-artifacts/<repo>/<jobid>/` as a workflow artifact.
65+
66+---
67+
68+## 4. Direct CLI Execution (Without the Action)
69+
70+For users who prefer manual step-by-step control or non-GitHub CI environments, `pici run` can be invoked directly:
71+
72+```yaml
73+ - name: Run CI
74+ run: |
75+ pici run --in-place \
76+ -e PICI_JOB="$GITHUB_RUN_ID" \
77+ -e PICI_EVENT=git.push \
78+ -e PICI_BRANCH="$GITHUB_REF_NAME" \
79+ -e PICI_COMMIT="$GITHUB_SHA"
80+```
4481
4582 ---
4683
47-## 3. Remote Debugging Architecture (`pipe.pico.sh`)
84+## 5. Native Go Debug Bridge (`pipe.pico.sh`)
4885
49-### Why `pipe.pico.sh`?
50-`pipe.pico.sh` provides authenticated, bidirectional byte streaming (`io.ReadWriter`) across topics over SSH. Because connections are outbound from the runner, no inbound ports or complex firewall configurations are necessary.
86+Rather than relying on bash scripts with named FIFOs (`mkfifo`) and background `ssh -N` commands, the relay bridge is implemented directly in Go inside `pici`:
5187
52-1. **Authentication & Topic Privacy**:
53- - The GitHub Actions runner authenticates using the developer's `PICO_SSH_KEY` secret.
54- - Topics created by an authenticated SSH key are automatically private and isolated to that account.
55-2. **PTY Bridging**:
56- - Standard shell redirection (`A | B`) does not allocate a pseudo-terminal (PTY) and is unidirectional.
57- - By creating named FIFOs (`mkfifo`) and wrapping an interactive shell with `script -q -f -c "bash -i"`, the bridge forwards full PTY escape sequences, window sizes, and raw terminal input.
88+### How it works
89+1. **Hook into Failure Path**: When `resolveJobExitCode()` detects a non-zero exit in `waitAndReport()` and debug mode is enabled (e.g. `--debug-on-fail` or `-e PICI_DEBUG=1`), `pici` initializes the debug session.
90+2. **Native SSH Dialing**: `pici` dials `pipe.pico.sh` using the existing `-pk` / `-ck` SSH key infrastructure.
91+3. **PTY Allocation (`creack/pty`)**: Spawns an interactive shell (`bash -i` or `$SHELL`) connected to the bidirectional SSH stream.
92+4. **Attach Instructions & Notifications**: Prints the `ssh -t pipe.pico.sh pipe <topic>` command to stdout and appends it to `$GITHUB_STEP_SUMMARY`.
93+5. **Idle Timeout & Clean Exit**: Waits for a connection with an idle timeout (e.g., 15 minutes). Once closed or timed out, `pici` terminates and exits with the original non-zero job exit code.
94+6. **Standalone `pici debug`**: `pici debug <repo>/<jobid>` attaches to running or stalled jobs by targeting the deterministic session prefix (`ci.<repo>.<jobid>.`).
95+
96+### Security Advantages
97+- **Environment Scrubbing**: Sensitive CI environment variables (`GITHUB_TOKEN`, `ACTIONS_RUNTIME_TOKEN`, injected secrets) are stripped from the debug shell process environment before launching.
98+- **Host Key Pinning**: The `pipe.pico.sh` host key is verified natively in Go without passing `-o StrictHostKeyChecking=no`.
5899
59100 ---
60101
61-## 4. Initial Implementation: `pici-debug.sh`
102+## 6. Implementation & Verification Plan (Test Repository)
103+
104+To validate the complete flow end-to-end before public release, we will use a dedicated test repository (e.g., `picosh/pici-test`) to confirm local parity, GitHub Actions execution, and interactive failure recovery.
105+
106+### Step 1: Create the Test Repository Structure
107+
108+```
109+pici-test/
110+├── .github/
111+│ └── workflows/
112+│ ├── ci-action.yml # Tests the custom picosh/pici-action
113+│ └── ci-direct.yml # Tests direct pici binary execution
114+├── src/
115+│ └── main_test.go
116+├── pico.sh # Core CI script with configurable test modes
117+└── README.md
118+```
62119
63-A minimal, pure-bash wrapper that can be placed in any repository to execute CI and open the debug bridge on failure.
120+### Step 2: Implement the Configurable `pico.sh`
121+
122+The test script supports environment variables to deterministically simulate success, failure, Docker steps, and hangs:
64123
65124 ```bash
66125 #!/usr/bin/env bash
67-set -e
68-
69-TOPIC="pici-${GITHUB_RUN_ID:-$RANDOM}"
70-FIFO_IN=$(mktemp -u /tmp/pici-in.XXXXXX)
71-FIFO_OUT=$(mktemp -u /tmp/pici-out.XXXXXX)
72-
73-cleanup() {
74- kill "$SSH_PID" 2>/dev/null || true
75- rm -f "$FIFO_IN" "$FIFO_OUT"
76-}
77-trap cleanup EXIT
78-
79-# 1. Run the CI script
80-echo "==> Running pico.sh..."
81-if ./pico.sh; then
82- echo "==> CI succeeded!"
83- exit 0
84-fi
126+set -euo pipefail
85127
86-CI_EXIT_CODE=$?
128+JOB_ID="${PICI_JOB:-local}"
129+REPO="${PICI_REPO:-pici-test}"
130+EVENT="${PICI_EVENT:-local}"
131+ZMX_SESSION_PREFIX="${ZMX_SESSION_PREFIX:-local.}"
87132
88-# 2. Check if debug mode is enabled
89-if [ "${DEBUG_ON_FAIL:-1}" != "1" ]; then
90- exit "$CI_EXIT_CODE"
133+echo "==> Running CI for $REPO (job: $JOB_ID, event: $EVENT)"
134+
135+# 1. Parallel test steps
136+zmx run unit-tests -d bash -c 'echo "Running unit tests..."; sleep 2; echo "Tests passed!"'
137+zmx run lint -d bash -c 'echo "Running linter..."; sleep 1; echo "Lint clean!"'
138+
139+# 2. Simulated failure scenario (when TEST_FAILURE=1)
140+if [ "${TEST_FAILURE:-0}" = "1" ]; then
141+ zmx run failing-step -d bash -c 'echo "Simulating fatal compilation error..."; sleep 2; exit 42'
91142 fi
92143
93-echo "================================================================"
94-echo "🔴 CI FAILED! Debug bridge active on pipe.pico.sh"
95-echo "Connect from your terminal using:"
96-echo " ssh -t pipe.pico.sh pipe $TOPIC"
97-echo "================================================================"
98-
99-# 3. Create FIFOs for bidirectional PTY stream
100-mkfifo "$FIFO_IN" "$FIFO_OUT"
101-
102-# 4. Connect background bridge to pipe.pico.sh using SSH key
103-ssh -N -o StrictHostKeyChecking=no \
104- -o ServerAliveInterval=15 \
105- pipe.pico.sh "pipe $TOPIC" < "$FIFO_OUT" > "$FIFO_IN" &
106-SSH_PID=$!
107-
108-# 5. Spawn interactive PTY bash shell connected to the pipe
109-script -q -f -c "
110- echo '=== Connected to GitHub Actions Debug Shell ==='
111- echo ''
112- echo 'Current zmx sessions:'
113- zmx list || true
114- echo ''
115- echo 'Type exit to finish CI run.'
116- bash -i
117-" /dev/null < "$FIFO_IN" > "$FIFO_OUT"
118-
119-exit "$CI_EXIT_CODE"
120-```
144+# 3. Simulated hang scenario (when TEST_HANG=1)
145+if [ "${TEST_HANG:-0}" = "1" ]; then
146+ zmx run hung-test -d bash -c 'echo "Simulating hung test process..."; sleep 3600'
147+fi
121148
122----
149+# 4. Docker container step
150+zmx run docker-step -d docker run --rm alpine:latest sh -c "echo 'Docker step executed inside alpine container'"
151+
152+# 5. Wait for all steps
153+zmx wait "*"
154+
155+printf "\x1b[32mAll test steps succeeded!\x1b[0m\n"
156+```
123157
124-## 5. GitHub Actions Workflow Integration
158+### Step 3: Configure GitHub Actions Workflows
125159
126-### Workflow Example (`.github/workflows/ci.yml`)
160+#### Test Workflow 1: Using `picosh/pici-action` (`.github/workflows/ci-action.yml`)
127161
128162 ```yaml
129-name: CI
130-on: [push, pull_request]
163+name: Pici Action Validation
164+on:
165+ push:
166+ workflow_dispatch:
167+ inputs:
168+ test_failure:
169+ description: "Simulate step failure (exit 42)"
170+ type: boolean
171+ default: false
131172
132173 jobs:
133- pici:
174+ test-ci:
134175 runs-on: ubuntu-latest
135176 steps:
136177 - uses: actions/checkout@v4
137178
138- - name: Install zmx
139- run: |
140- # Fetch and install zmx binary
141- sudo curl -fsSL https://github.com/picosh/zmx/releases/latest/download/zmx_linux_amd64 -o /usr/local/bin/zmx
142- sudo chmod +x /usr/local/bin/zmx
143-
144- - name: Setup Pico SSH Key
145- if: always()
146- env:
147- PICO_SSH_KEY: ${{ secrets.PICO_SSH_KEY }}
148- run: |
149- mkdir -p ~/.ssh
150- echo "$PICO_SSH_KEY" > ~/.ssh/id_ed25519
151- chmod 600 ~/.ssh/id_ed25519
152-
153- - name: Run CI with Pici Debug Bridge
179+ - name: Run Pici CI
180+ uses: picosh/pici-action@main
181+ with:
182+ pico_ssh_key: ${{ secrets.PICO_SSH_KEY }}
183+ debug_on_fail: "true"
154184 env:
155- DEBUG_ON_FAIL: "1"
156- run: ./pici-debug.sh
185+ TEST_FAILURE: ${{ github.event.inputs.test_failure && '1' || '0' }}
157186 ```
158187
159----
160-
161-## 6. Docker Considerations
188+### Step 4: Verification Matrix & Acceptance Criteria
162189
163-- **Out of the box**: GitHub Actions `ubuntu-latest` runners come with Docker pre-installed and running. `docker run` commands inside `pico.sh` execute directly against the host Docker daemon.
164-- **Debugging Docker Containers**: When attached via the debug shell:
165- - Users can inspect running/stopped containers via `docker ps -a`.
166- - Users can enter failed containers using `docker exec -it <container_id> /bin/bash`.
167- - For steps using `--rm`, users can rerun the command without `--rm` inside the debug shell to inspect intermediate layers.
168-
169----
170-
171-## 7. Adoption & Evolution Roadmap
172-
173-| Phase | Deliverable | Description |
174-| :--- | :--- | :--- |
175-| **Phase 1 (Immediate)** | `pici-debug.sh` Bash Script | Pure script wrapper requiring zero changes to `pici` or Go codebases. |
176-| **Phase 2 (Packaging)** | `pico-sh/pici-action` | Dedicated GitHub Action packaging `zmx` installation, key setup, and step summary outputs. |
177-| **Phase 3 (Native Go Bridge)** | `pici debug` Subcommand | Built-in `pici` CLI command implementing native SSH dialing to `pipe.pico.sh` with `creack/pty`. |
178-| **Phase 4 (Reporting)** | Rich Step Summaries | Exporting `zmx` session history (`zmx history --html`) directly into `$GITHUB_STEP_SUMMARY`. |
190+| Test Scenario | Trigger | Expected GHA Behavior | Acceptance Criteria |
191+| :--- | :--- | :--- | :--- |
192+| **1. Happy Path** | Push commit with `TEST_FAILURE=0` | All `zmx` sessions pass. | • Workflow exits with `0` (green).<br>• GHA Step Summary displays full table with durations.<br>• HTML report uploaded as workflow artifact. |
193+| **2. Failure & Remote Attach** | Dispatch workflow with `test_failure=true` | `failing-step` exits `42`. | • `waitAndReport` catches exit `42`.<br>• Relay bridge opens on `pipe.pico.sh`.<br>• `ssh -t pipe.pico.sh pipe <topic>` printed to logs and step summary.<br>• Connecting via terminal drops into interactive debug shell with active `zmx` sessions.<br>• Exiting debug shell ends GHA job with code `42` (red). |
194+| **3. Secret Scrubbing Audit** | During interactive debug session | Inspect environment variables inside attached shell. | • `GITHUB_TOKEN`, `ACTIONS_RUNTIME_TOKEN`, and `PICO_SSH_KEY` are absent from `env`. |
195+| **4. Docker Step Execution** | Standard run | Alpine container runs via `docker run --rm`. | • Docker mounts `$(pwd)` correctly in-place.<br>• Container logs captured in `zmx history docker-step`. |
196+| **5. Idle Timeout Cleanup** | Failure triggered without developer attaching | Wait for configured idle timeout (e.g. 15m). | • Bridge cleanly closes upon timeout.<br>• GHA runner exits with the original step failure code (does not hang indefinitely). |