fix: install.sh shadows system TMPDIR, can nuke temp directory #843
Workflow file for this run
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: Rust CI/CD | |
| on: | |
| push: | |
| branches: [ "master", "staging" ] | |
| pull_request: | |
| branches: [ "master", "staging" ] | |
| workflow_dispatch: {} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| jobs: | |
| # === Path-based change detection (master push only) === | |
| changes: | |
| name: Detect changes | |
| if: >- | |
| (github.event_name == 'push' && github.ref == 'refs/heads/master') | |
| || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: read | |
| outputs: | |
| std: ${{ steps.filter.outputs.std }} | |
| host: ${{ steps.filter.outputs.host }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| std: | |
| - 'std/**' | |
| - 'crates/guest/**' | |
| - 'examples/**' | |
| host: | |
| - 'src/**' | |
| - 'crates/**' | |
| - 'capnp/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'build.rs' | |
| - 'Containerfile*' | |
| - 'Makefile' | |
| # === Always-run checks (PRs and pushes) === | |
| fmt: | |
| name: Check formatting | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - name: cargo fmt --all -- --check | |
| run: cargo fmt --all -- --check | |
| clippy: | |
| name: Clippy | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - uses: ./.github/actions/setup-capnp | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: cargo clippy (host crates) | |
| run: cargo clippy -p ww -p membrane -p atom -p glia -- -D warnings | |
| solidity: | |
| name: Solidity (Foundry) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Install Foundry | |
| uses: foundry-rs/foundry-toolchain@v1 | |
| - name: Check formatting | |
| working-directory: contracts/stem | |
| run: forge fmt --check | |
| - name: Build contracts | |
| working-directory: contracts/stem | |
| run: forge build --sizes | |
| - name: Run tests | |
| working-directory: contracts/stem | |
| run: forge test -vvv | |
| build: | |
| name: Build ww binary | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: x86_64-unknown-linux-gnu, wasm32-wasip2 | |
| - uses: ./.github/actions/setup-capnp | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: build-test | |
| - name: Build (compile all crates + test binaries) | |
| run: cargo build --tests | |
| test: | |
| name: Test | |
| needs: [build] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: x86_64-unknown-linux-gnu, wasm32-wasip2 | |
| - uses: ./.github/actions/setup-capnp | |
| - name: Install Foundry | |
| uses: foundry-rs/foundry-toolchain@v1 | |
| - name: Install IPFS (Kubo) | |
| run: | | |
| KUBO_VERSION="v0.33.0" | |
| wget -qO- "https://dist.ipfs.tech/kubo/${KUBO_VERSION}/kubo_${KUBO_VERSION}_linux-amd64.tar.gz" | tar xz | |
| sudo mv kubo/ipfs /usr/local/bin/ | |
| rm -rf kubo | |
| - name: Start IPFS daemon | |
| run: | | |
| ipfs init --profile=test | |
| if [ -n "$IPFS_PEER_ADDR" ]; then | |
| ipfs bootstrap rm --all | |
| ipfs bootstrap add "$IPFS_PEER_ADDR" | |
| fi | |
| ipfs daemon --enable-gc & | |
| for i in $(seq 1 30); do ipfs id >/dev/null 2>&1 && break; sleep 0.5; done | |
| env: | |
| IPFS_PEER_ADDR: ${{ secrets.IPFS_PEER_ADDR }} | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: build-test | |
| - name: Build WASI example binaries | |
| run: make -C examples/echo | |
| - name: Run tests (hot cache from build job) | |
| run: cargo test | |
| # === Master-only: fan out after checks pass === | |
| build-wasm: | |
| name: Build WASM components | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| needs: [fmt, clippy, solidity, build] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: wasm32-wasip2 | |
| - uses: ./.github/actions/setup-capnp | |
| with: | |
| cache-key-suffix: wasm | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: wasm-std | |
| - name: Build WASM std + examples | |
| run: make std && make examples | |
| - name: Test WASM components | |
| run: make test-wasm | |
| - uses: actions/upload-artifact@v5 | |
| with: | |
| name: wasm-artifacts | |
| path: | | |
| std/kernel/bin/main.wasm | |
| std/shell/bin/shell.wasm | |
| std/shell/bin/shell.capnpc | |
| std/shell/etc/init.d/50-shell.glia | |
| std/mcp/bin/main.wasm | |
| examples/*/bin/ | |
| retention-days: 1 | |
| build-binaries: | |
| name: Build ${{ matrix.artifact }} | |
| if: >- | |
| github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| && (needs.changes.outputs.std == 'true' || needs.changes.outputs.host == 'true' | |
| || github.event_name == 'workflow_dispatch') | |
| needs: [changes, fmt, clippy, solidity, build, build-wasm] | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| artifact: ww-linux-x86_64 | |
| bin_path: bin/linux/x86_64 | |
| use_cross: false | |
| - target: aarch64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| artifact: ww-linux-aarch64 | |
| bin_path: bin/linux/aarch64 | |
| use_cross: true | |
| - target: aarch64-apple-darwin | |
| os: macos-14 | |
| artifact: ww-macos-aarch64 | |
| bin_path: bin/macos/aarch64 | |
| use_cross: false | |
| - target: x86_64-apple-darwin | |
| os: macos-14 | |
| artifact: ww-macos-x86_64 | |
| bin_path: bin/macos/x86_64 | |
| use_cross: false | |
| - target: x86_64-unknown-linux-musl | |
| os: ubuntu-latest | |
| artifact: ww-linux-musl-x86_64 | |
| bin_path: bin/linux/musl-x86_64 | |
| use_cross: false | |
| install_musl: true | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| name: wasm-artifacts | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: ./.github/actions/setup-capnp | |
| if: ${{ !matrix.use_cross }} | |
| with: | |
| cache-key-suffix: ${{ matrix.target }} | |
| - name: Install musl toolchain | |
| if: ${{ matrix.install_musl }} | |
| run: sudo apt-get update && sudo apt-get install -y musl-tools | |
| - name: Install cross | |
| if: ${{ matrix.use_cross }} | |
| run: cargo install cross --git https://github.com/cross-rs/cross | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: master-${{ matrix.target }} | |
| - name: Build (native) | |
| if: ${{ !matrix.use_cross }} | |
| run: cargo build --release --target ${{ matrix.target }} | |
| - name: Build (cross) | |
| if: ${{ matrix.use_cross }} | |
| run: cross build --release --target ${{ matrix.target }} | |
| - name: Package binary | |
| run: | | |
| mkdir -p ${{ matrix.bin_path }} | |
| cp target/${{ matrix.target }}/release/ww ${{ matrix.bin_path }}/ww | |
| - name: Verify static linking | |
| if: ${{ matrix.install_musl }} | |
| run: file ${{ matrix.bin_path }}/ww | grep -q "statically linked" | |
| - uses: actions/upload-artifact@v5 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: ${{ matrix.bin_path }}/ww | |
| retention-days: 1 | |
| deploy: | |
| name: Deploy to master.wetware.run | |
| if: >- | |
| github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| && (needs.changes.outputs.std == 'true' || needs.changes.outputs.host == 'true' | |
| || github.event_name == 'workflow_dispatch') | |
| needs: [changes, fmt, clippy, solidity, build, build-binaries] | |
| runs-on: ubuntu-latest | |
| environment: master | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| name: ww-linux-musl-x86_64 | |
| path: deploy-context | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| name: wasm-artifacts | |
| path: deploy-context/wasm | |
| - name: Assemble deploy context | |
| run: | | |
| cd deploy-context | |
| # Musl binary is uploaded under bin_path; move to context root | |
| mv bin/linux/musl-x86_64/ww ww | |
| rm -rf bin | |
| chmod +x ww | |
| # FHS layout: bin/ (WASM cells), lib/ (Glia), share/ (schemas), etc/ (config) | |
| mkdir -p wetware/bin wetware/lib/ww wetware/share/schema | |
| mkdir -p wetware/etc/init.d | |
| # WASM cells | |
| cp wasm/std/kernel/bin/main.wasm wetware/bin/kernel.wasm | |
| cp wasm/std/shell/bin/shell.wasm wetware/bin/shell.wasm | |
| cp wasm/std/mcp/bin/main.wasm wetware/bin/mcp.wasm | |
| # Compiled schemas | |
| cp wasm/std/shell/bin/shell.capnpc wetware/share/schema/shell.capnpc 2>/dev/null || true | |
| # Init scripts | |
| cp wasm/std/shell/etc/init.d/50-shell.glia wetware/etc/init.d/ 2>/dev/null || true | |
| cp ../Containerfile.deploy Containerfile | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: deploy-context | |
| push: true | |
| tags: | | |
| ghcr.io/wetware/ww:master | |
| ghcr.io/wetware/ww:master-${{ github.sha }} | |
| - name: Deploy to VPS | |
| env: | |
| SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} | |
| SSH_HOST_KEY: ${{ secrets.DEPLOY_SSH_HOST_KEY }} | |
| VPS_HOST: 77.90.2.37 | |
| VPS_USER: lthibault | |
| run: | | |
| mkdir -p ~/.ssh | |
| echo "$SSH_KEY" > ~/.ssh/deploy_key | |
| chmod 600 ~/.ssh/deploy_key | |
| echo "$SSH_HOST_KEY" > ~/.ssh/known_hosts | |
| for attempt in 1 2; do | |
| echo "Deploy attempt $attempt..." | |
| if ssh -i ~/.ssh/deploy_key "$VPS_USER@$VPS_HOST" \ | |
| "kubectl rollout restart deployment/ww-master && \ | |
| kubectl rollout status deployment/ww-master --timeout=120s"; then | |
| echo "Deploy succeeded on attempt $attempt" | |
| exit 0 | |
| fi | |
| if [ "$attempt" -lt 2 ]; then | |
| echo "Deploy failed, retrying in 30s..." | |
| sleep 30 | |
| fi | |
| done | |
| echo "Deploy failed after 2 attempts" | |
| exit 1 | |
| publish: | |
| name: Publish to IPFS | |
| needs: [changes, test, build-binaries, build-wasm, deploy] | |
| # Run even if build-binaries was skipped (std-only change) | |
| if: >- | |
| !failure() && !cancelled() | |
| && (needs.changes.outputs.std == 'true' || needs.changes.outputs.host == 'true' | |
| || github.event_name == 'workflow_dispatch') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - uses: actions/download-artifact@v5 | |
| with: | |
| name: wasm-artifacts | |
| path: artifacts/wasm-artifacts | |
| # Download binary artifacts (only available when host lane ran) | |
| - uses: actions/download-artifact@v5 | |
| if: needs.changes.outputs.host == 'true' || github.event_name == 'workflow_dispatch' | |
| with: | |
| name: ww-linux-x86_64 | |
| path: artifacts/ww-linux-x86_64 | |
| - uses: actions/download-artifact@v5 | |
| if: needs.changes.outputs.host == 'true' || github.event_name == 'workflow_dispatch' | |
| with: | |
| name: ww-linux-aarch64 | |
| path: artifacts/ww-linux-aarch64 | |
| - uses: actions/download-artifact@v5 | |
| if: needs.changes.outputs.host == 'true' || github.event_name == 'workflow_dispatch' | |
| with: | |
| name: ww-macos-aarch64 | |
| path: artifacts/ww-macos-aarch64 | |
| - uses: actions/download-artifact@v5 | |
| if: needs.changes.outputs.host == 'true' || github.event_name == 'workflow_dispatch' | |
| with: | |
| name: ww-macos-x86_64 | |
| path: artifacts/ww-macos-x86_64 | |
| - name: Publish to IPFS via VPS | |
| env: | |
| SSH_CMD: ssh -i ~/.ssh/deploy_key -o ServerAliveInterval=30 -o ServerAliveCountMax=10 | |
| HOST_CHANGED: ${{ needs.changes.outputs.host }} | |
| run: | | |
| # Set up SSH | |
| mkdir -p ~/.ssh | |
| echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key | |
| chmod 600 ~/.ssh/deploy_key | |
| echo "${{ secrets.DEPLOY_SSH_HOST_KEY }}" > ~/.ssh/known_hosts | |
| # Get IPFS pod name | |
| POD=$($SSH_CMD ${{ secrets.VPS_SSH }} "kubectl get pod -l app=ipfs-daemon -o jsonpath='{.items[0].metadata.name}'") | |
| echo "IPFS pod: $POD" | |
| # Assemble clean release tree (artifacts only, no source code) | |
| STAGE=/tmp/release-tree | |
| mkdir -p "$STAGE/bin/ww/linux/x86_64" "$STAGE/bin/ww/linux/aarch64" | |
| mkdir -p "$STAGE/bin/ww/macos/aarch64" "$STAGE/bin/ww/macos/x86_64" | |
| mkdir -p "$STAGE/include/schema" "$STAGE/lib/ww" "$STAGE/lib/init.d" | |
| mkdir -p "$STAGE/share/schema" | |
| # VERSION | |
| grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/' > "$STAGE/VERSION" | |
| # Changelog | |
| cp CHANGELOG.md "$STAGE/" | |
| # WASM cells (flat under bin/) | |
| cp artifacts/wasm-artifacts/std/kernel/bin/main.wasm "$STAGE/bin/kernel.wasm" | |
| cp artifacts/wasm-artifacts/std/shell/bin/shell.wasm "$STAGE/bin/shell.wasm" | |
| cp artifacts/wasm-artifacts/std/mcp/bin/main.wasm "$STAGE/bin/mcp.wasm" | |
| # Example WASM cells | |
| for example_dir in artifacts/wasm-artifacts/examples/*/bin; do | |
| if [ -d "$example_dir" ]; then | |
| name=$(basename "$(dirname "$example_dir")") | |
| cp "$example_dir"/*.wasm "$STAGE/bin/${name}.wasm" 2>/dev/null || true | |
| fi | |
| done | |
| # Compiled schemas (.capnpc) | |
| cp artifacts/wasm-artifacts/std/shell/bin/shell.capnpc "$STAGE/share/schema/shell.capnpc" 2>/dev/null || true | |
| # Human-readable .capnp schemas | |
| cp capnp/shell.capnp "$STAGE/include/schema/" 2>/dev/null || true | |
| for ex_capnp in examples/*/*.capnp; do | |
| [ -f "$ex_capnp" ] && cp "$ex_capnp" "$STAGE/include/schema/" | |
| done | |
| # Glia stdlib | |
| cp std/lib/ww/*.glia "$STAGE/lib/ww/" 2>/dev/null || true | |
| # Reference init.d scripts | |
| cp std/shell/etc/init.d/*.glia "$STAGE/lib/init.d/" 2>/dev/null || true | |
| for ex_init in examples/*/etc/init.d/*.glia; do | |
| [ -f "$ex_init" ] && cp "$ex_init" "$STAGE/lib/init.d/" | |
| done | |
| # Host binaries: from CI artifacts if host lane ran, else from previous IPNS | |
| if [ "$HOST_CHANGED" = "true" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "Using fresh binaries from build matrix" | |
| cp artifacts/ww-linux-x86_64/ww "$STAGE/bin/ww/linux/x86_64/ww" | |
| cp artifacts/ww-linux-aarch64/ww "$STAGE/bin/ww/linux/aarch64/ww" | |
| cp artifacts/ww-macos-aarch64/ww "$STAGE/bin/ww/macos/aarch64/ww" | |
| cp artifacts/ww-macos-x86_64/ww "$STAGE/bin/ww/macos/x86_64/ww" | |
| else | |
| echo "Host unchanged — fetching previous binaries from IPNS" | |
| PREV=$($SSH_CMD ${{ secrets.VPS_SSH }} "kubectl exec $POD -- ipfs name resolve /ipns/\$(kubectl exec $POD -- ipfs key list -l | grep ww-release | awk '{print \$1}')" 2>/dev/null || true) | |
| if [ -n "$PREV" ]; then | |
| for platform in linux/x86_64 linux/aarch64 macos/aarch64 macos/x86_64; do | |
| $SSH_CMD ${{ secrets.VPS_SSH }} "kubectl exec $POD -- ipfs cat $PREV/bin/ww/$platform/ww" > "$STAGE/bin/ww/$platform/ww" 2>/dev/null || true | |
| chmod +x "$STAGE/bin/ww/$platform/ww" 2>/dev/null || true | |
| done | |
| echo "Fetched previous binaries from $PREV" | |
| else | |
| echo "WARNING: Could not resolve previous IPNS — binaries may be missing" | |
| fi | |
| fi | |
| # Checksums (host binaries only) | |
| cd "$STAGE" | |
| find bin/ww/ -type f | sort | xargs sha256sum > CHECKSUMS.txt | |
| cd - | |
| # Rsync release tree to VPS | |
| rsync -a -e "$SSH_CMD" "$STAGE/" ${{ secrets.VPS_SSH }}:/tmp/ww-release-tree/ | |
| # kubectl cp into pod, ipfs add, pin, publish | |
| $SSH_CMD ${{ secrets.VPS_SSH }} " | |
| kubectl cp /tmp/ww-release-tree $POD:/tmp/release-tree | |
| CID=\$(kubectl exec $POD -- ipfs add -rQ --cid-version=1 /tmp/release-tree/) | |
| echo \"CID=\$CID\" | |
| kubectl exec $POD -- ipfs pin add \$CID | |
| kubectl exec $POD -- ipfs name publish --key=ww-release /ipfs/\$CID | |
| kubectl exec $POD -- rm -rf /tmp/release-tree | |
| rm -rf /tmp/ww-release-tree | |
| " | tee /tmp/ipfs-publish-output.txt | |
| # Extract CID from output | |
| CID=$(grep '^CID=' /tmp/ipfs-publish-output.txt | cut -d= -f2) | |
| echo "Release CID: $CID" | |
| echo "CID=$CID" >> "$GITHUB_ENV" | |
| - name: Record CID | |
| run: | | |
| echo "## IPFS Publish" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- CID: \`${{ env.CID }}\`" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- IPNS: \`/ipns/${{ secrets.IPNS_KEY_ID }}\`" >> "$GITHUB_STEP_SUMMARY" | |
| # Release builds moved to .github/workflows/release.yml |