Skip to content

fix: install.sh shadows system TMPDIR, can nuke temp directory #843

fix: install.sh shadows system TMPDIR, can nuke temp directory

fix: install.sh shadows system TMPDIR, can nuke temp directory #843

Workflow file for this run

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