diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000..a1449dcc4 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,236 @@ +name: Nightly Build + +on: + schedule: + # Run every day at 00:00 UTC + - cron: '0 0 * * *' + workflow_dispatch: + # Allow manual triggering + inputs: + skip_version: + description: 'Skip version generation (use current)' + required: false + default: 'false' + type: boolean + build_only: + description: 'Build only, skip release' + required: false + default: 'false' + type: boolean + +env: + CARGO_TERM_COLOR: always + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + generate-version: + name: Generate Nightly Version + runs-on: ubuntu-latest + if: github.event.inputs.skip_version != 'true' + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate nightly version + id: version + run: | + # Get current date + DATE=$(date +%Y%m%d) + + # Get short commit hash + SHA=$(git rev-parse --short HEAD) + + # Generate version: v0.1.0-nightly-YYYYMMDD-sha + VERSION="v0.1.0-nightly-${DATE}-${SHA}" + TAG="nightly-${DATE}-${SHA}" + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Generated version: ${VERSION}" + echo "Generated tag: ${TAG}" + + build: + name: Build Picoclaw + runs-on: ubuntu-latest + needs: generate-version + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: true + + - name: Build binary + run: | + go build -v -o picoclaw . + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: picoclaw-linux-amd64 + path: picoclaw + + build-docker: + name: Build Docker Image + runs-on: ubuntu-latest + needs: generate-version + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=nightly-{{date 'YYYYMMDD'}},priority=100 + type=raw,value=nightly,priority=90 + type=sha,prefix={{branch}}-,priority=50 + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + create-release: + name: Create Nightly Release + runs-on: ubuntu-latest + needs: [generate-version, build] + if: github.event.inputs.build_only != 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: picoclaw-linux-amd64 + path: ./artifacts + + - name: Prepare release notes + run: | + cat > release_notes.md << 'EOF' + # Picoclaw Nightly Release + + 📅 **Date**: $(date -u +'%Y-%m-%d %H:%M:%S UTC') + 🏷️ **Version**: ${{ needs.generate-version.outputs.version }} + 🌿 **Branch**: ${{ github.ref_name }} + 🔗 **Commit**: ${{ github.sha }} + + --- + + ## 📦 Download + + ### Linux AMD64 Binary + - `picoclaw-linux-amd64` - Pre-built binary for Linux x86_64 + + ### Docker Image + ```bash + docker pull ghcr.io/${{ github.repository }}:nightly-$(date +%Y%m%d) + # or latest nightly + docker pull ghcr.io/${{ github.repository }}:nightly + ``` + + --- + + ## 🔧 Installation + + ### Using binary + ```bash + chmod +x picoclaw + sudo mv picoclaw /usr/local/bin/ + ``` + + ### Using Docker + ```bash + docker run -d --name picoclaw \ + -v /path/to/config:/config \ + ghcr.io/${{ github.repository }}:nightly + ``` + + --- + + ## ⚠️ Disclaimer + + This is a **nightly build** and may contain: + - Unfinished features + - Bugs and instability + - Breaking changes + + For production use, please use a stable release instead. + + --- + + 🦞 **Built with ❤️ by Picoclaw** + EOF + + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ needs.generate-version.outputs.version }} + TAG: ${{ needs.generate-version.outputs.tag }} + run: | + gh release create "$TAG" \ + --title "$VERSION" \ + --notes-file release_notes.md \ + ./artifacts/picoclaw-linux-amd64 + + - name: Update nightly tag reference + if: success() + run: | + # Update 'nightly' tag to point to latest nightly + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -f nightly ${{ github.sha }} + git push origin nightly --force + + - name: Clean up old nightly releases (keep last 7) + if: success() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # List all nightly releases, keep only the 7 most recent + gh release list --limit 100 | grep nightly- | tail -n +8 | awk '{print $3}' | xargs -I {} gh release delete --yes --cleanup-tag {} + + notify: + name: Notify Results + runs-on: ubuntu-latest + needs: [build-docker, create-release] + if: always() + steps: + - name: Check build status + run: | + if [ "${{ needs.create-release.result }}" == "success" ]; then + echo "✅ Nightly build completed successfully!" + echo "Version: ${{ needs.generate-version.outputs.version }}" + else + echo "❌ Nightly build failed" + echo "Check the workflow logs for details" + fi diff --git a/docs/research/nightly-build-research.md b/docs/research/nightly-build-research.md new file mode 100644 index 000000000..252f89f02 --- /dev/null +++ b/docs/research/nightly-build-research.md @@ -0,0 +1,208 @@ +# Picoclaw Nightly Build Research + +## Repository Overview + +**Repository**: https://github.com/sipeed/picoclaw +**Current Branch**: `main` +**Latest Commit**: `66e6fb6` + +### Project Structure + +``` +picoclaw/ +├── .github/workflows/ # GitHub Actions workflows +│ ├── build.yml # CI build on push to main +│ ├── pr.yml # PR validation +│ ├── release.yml # Release workflow (manual trigger) +│ └── upload-tos.yml # Upload to Volcengine TOS +├── .goreleaser.yaml # GoReleaser configuration +├── Makefile # Build targets +├── go.mod # Go 1.25.7 +├── cmd/ # Main entry points +│ ├── picoclaw/ # Main binary +│ ├── picoclaw-launcher/ # Web launcher +│ └── picoclaw-launcher-tui/ # TUI launcher +├── pkg/ # Core packages +│ ├── agent/ # Agent logic +│ ├── auth/ # Authentication +│ ├── channels/ # Communication channels +│ ├── config/ # Configuration +│ ├── memory/ # Memory management +│ ├── providers/ # LLM providers +│ ├── skills/ # Skills system +│ └── tools/ # Tool functions +├── docker/ # Docker configurations +└── docs/ # Documentation +``` + +## Existing Workflows + +### 1. build.yml +- **Trigger**: Push to `main` +- **Action**: Builds picoclaw using `make build-all` +- **Platform**: Ubuntu latest + +### 2. release.yml +- **Trigger**: Manual workflow dispatch +- **Actions**: + - Creates git tag + - Runs GoReleaser to build for all platforms + - Uploads to GitHub Releases + - Optionally uploads to TOS +- **Platforms**: Linux, Windows, Darwin, FreeBSD (amd64, arm64, riscv64, loong64, arm) +- **Docker**: Multi-platform builds to GHCR and DockerHub + +### 3. pr.yml +- **Trigger**: Pull requests +- **Action**: PR validation (details not visible in current session) + +### 4. upload-tos.yml +- **Trigger**: Called from release workflow +- **Action**: Uploads artifacts to Volcengine TOS + +## Build System + +### Makefile Targets +| Target | Description | +|--------|-------------| +| `build` | Build for current platform | +| `build-all` | Build for all platforms (8 variants) | +| `build-whatsapp-native` | Build with WhatsApp native support | +| `build-linux-arm` | Build for Linux ARMv7 | +| `build-linux-arm64` | Build for Linux ARM64 | +| `build-pi-zero` | Build for Raspberry Pi Zero 2 W | +| `install` | Install to `~/.local/bin` | +| `test` | Run tests | +| `lint` | Run golangci-lint | +| `docker-build` | Build minimal Docker image (Alpine) | +| `docker-build-full` | Build full Docker image (Node.js 24) | + +### GoReleaser Configuration + +**Builds:** +- `picoclaw` - Main binary +- `picoclaw-launcher` - Web launcher +- `picoclaw-launcher-tui` - TUI launcher + +**Target Platforms:** +- OS: Linux, Windows, Darwin, FreeBSD +- Arch: amd64, arm64, riscv64, loong64, arm (v6, v7) + +**Docker Images:** +- `ghcr.io//picoclaw` +- `docker.io/` +- Platforms: linux/amd64, linux/arm64, linux/riscv64 + +## Nightly Build Requirements + +### Goals +1. Build daily snapshots of the `main` branch +2. Upload artifacts to GitHub Releases (as pre-release) +3. Optionally build and push Docker images +4. Keep limited number of nightly builds (cleanup old ones) + +### Design Decisions + +| Decision | Rationale | +|----------|-----------| +| **Cron Schedule**: `0 2 * * *` (2 AM UTC) | Off-peak hours, allows testing during business day | +| **Release Type**: Pre-release | Clearly distinguishes from stable releases | +| **Tag Format**: `nightly-YYYYMMDD` | Easy to identify and sort by date | +| **Artifact Retention**: 30 days | Balance between availability and storage | +| **Docker Tags**: `nightly`, `nightly-YYYYMMDD` | Allow both latest and pinned versions | + +## Proposed Nightly.yml Workflow + +```yaml +name: Nightly Build + +on: + schedule: + - cron: '0 2 * * *' # Daily at 2 AM UTC + workflow_dispatch: # Allow manual trigger + +jobs: + nightly: + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Set nightly tag + id: tag + run: | + NIGHTLY_TAG="nightly-$(date +%Y%m%d)" + echo "tag=$NIGHTLY_TAG" >> $GITHUB_OUTPUT + + - name: Create/Update nightly tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -f "${{ steps.tag.outputs.tag }}" + git push -f origin "${{ steps.tag.outputs.tag }}" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - 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: Run GoReleaser (snapshot) + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: ~> v2 + args: release --clean --snapshot + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NIGHTLY_TAG: ${{ steps.tag.outputs.tag }} + DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: nightly-artifacts + path: dist/ + + - name: Clean old nightly releases + run: | + # Keep only last 30 nightly releases + gh release list \ + --query "[startsWith(name, 'nightly-')] | [30:].[].name" \ + --json name \ + -t '{{ range . }}{{ .name }}{{ "\n" }}{{ end }}' \ + | xargs -I {} gh release delete {} --yes || true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +## Notes + +1. **Security**: No secrets required for basic nightly builds +2. **Storage**: GitHub provides 2GB free storage; implement cleanup if needed +3. **Testing**: Nightly builds serve as canary builds for testing +4. **Docker**: Can optionally push `nightly` Docker tags +5. **Notifications**: Could add Slack/discord notifications on failure + +## References + +- GoReleaser docs: https://goreleaser.com/ +- GitHub Actions docs: https://docs.github.com/en/actions +- Picoclaw docs: /docs/tools_configuration.md