diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..00fa04a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,49 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: "[BUG] " +labels: bug +assignees: '' +--- + +## Describe the Bug +A clear and concise description of what the bug is. + +## To Reproduce +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +## Expected Behavior +A clear and concise description of what you expected to happen. + +## Actual Behavior +A clear and concise description of what actually happened. + +## Environment +- OS: [e.g. macOS 13.0, Ubuntu 22.04] +- Go version: [e.g. 1.21.0] +- Fetch ML version: [e.g. v1.0.0] +- Configuration: [e.g. file-based auth, database auth] + +## Configuration +```yaml +# Paste relevant configuration here +auth: + enabled: true + # ... +``` + +## Logs +``` +# Paste relevant logs here +2024-01-01 12:00:00 ERROR: ... +``` + +## Additional Context +Add any other context about the problem here. + +## Possible Solution +If you have ideas on how to fix this, please describe them here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e17975b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,34 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: "[FEATURE] " +labels: enhancement +assignees: '' +--- + +## Feature Description +A clear and concise description of what the feature is. + +## Problem Statement +What problem does this feature solve? What pain point does it address? + +## Proposed Solution +Describe the solution you'd like to see implemented. + +## Alternative Solutions +Describe any alternative solutions or features you've considered. + +## Use Cases +Describe specific use cases where this feature would be valuable. + +## Implementation Details +If you have technical ideas on how this should be implemented, describe them here. + +## Mockups/UI (if applicable) +If this involves UI changes, include mockups or screenshots. + +## Additional Context +Add any other context, screenshots, or examples about the feature request here. + +## Questions +Do you have any questions about how this feature might work? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6bb2ed9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,35 @@ +## Description +Brief description of what this PR changes. + +## Type of Change +- [ ] Bug fix (non-breaking change that fixes an issue) +- [ ] New feature (non-breaking change that adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +## Testing +- [ ] Unit tests pass +- [ ] Integration tests pass (if applicable) +- [ ] Manual testing completed +- [ ] Security audit passed + +## Checklist +- [ ] Code follows the project's style guidelines +- [ ] Self-review of the code completed +- [ ] Documentation updated if necessary +- [ ] Tests added for new functionality +- [ ] No hardcoded secrets or credentials +- [ ] Error handling implemented appropriately + +## Security Considerations +- [ ] No sensitive data in logs +- [ ] Proper input validation +- [ ] Authentication/authorization properly implemented +- [ ] No SQL injection vulnerabilities +- [ ] No XSS vulnerabilities (if applicable) + +## Screenshots (if applicable) +Add screenshots to help explain your changes. + +## Additional Context +Add any other context about the pull request here. diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..0a3e465 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,82 @@ +# Labeler configuration for automatic PR labeling + +# Bug reports +bug: + - "[BUG]" + - "bug:" + - "fixes #" + - "closes #" + +# Feature requests +enhancement: + - "[FEATURE]" + - "feat:" + - "feature:" + - "add " + +# Documentation +documentation: + - "[DOCS]" + - "docs:" + - "README" + - "documentation" + +# Security +security: + - "[SECURITY]" + - "security:" + - "auth" + - "authentication" + - "RBAC" + - "permissions" + +# Testing +testing: + - "[TEST]" + - "test:" + - "tests" + - "unit test" + - "integration test" + +# CI/CD +ci: + - "[CI]" + - "workflow" + - "github actions" + - "build" + - "deploy" + +# Configuration +configuration: + - "[CONFIG]" + - "config" + - "yaml" + - "settings" + +# Dependencies +dependencies: + - "[DEPS]" + - "go.mod" + - "dependency" + - "update" + +# Performance +performance: + - "[PERF]" + - "performance" + - "optimize" + - "speed" + +# Breaking changes +breaking-change: + - "[BREAKING]" + - "breaking" + - "deprecated" + - "remove" + +# TUI +tui: + - "[TUI]" + - "cmd/tui" + - "terminal ui" + - "interface" diff --git a/.github/rsync_manifest.json b/.github/rsync_manifest.json new file mode 100644 index 0000000..c79fdc8 --- /dev/null +++ b/.github/rsync_manifest.json @@ -0,0 +1,21 @@ +{ + "version": "v3.2.7", + "base_url": "https://github.com/JMarvi3/rsync-static/releases/download", + "platforms": { + "linux-x86_64": { + "target": "x86_64-linux-musl", + "asset": "rsync-linux-x86_64", + "sha256": "0019dfc4b32d63c1392aa264aed2253c1e0c2fb09216f8e2cc269bbfb8bb49b5" + }, + "macos-x86_64": { + "target": "x86_64-macos", + "asset": "rsync-macos-x86_64", + "sha256": "0019dfc4b32d63c1392aa264aed2253c1e0c2fb09216f8e2cc269bbfb8bb49b5" + }, + "macos-arm64": { + "target": "aarch64-macos", + "asset": "rsync-macos-arm64", + "sha256": "0019dfc4b32d63c1392aa264aed2253c1e0c2fb09216f8e2cc269bbfb8bb49b5" + } + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bb8d464 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,272 @@ +name: CI/CD Pipeline + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +# Concurrency control to prevent multiple runs of the same workflow +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Workflow permissions +permissions: + contents: read + security-events: write + actions: read + packages: write + +env: + GO_VERSION: '1.25.0' + ZIG_VERSION: '0.15.2' + +jobs: + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 30 + + services: + redis: + image: redis:7 + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum', '**/go.mod') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Set up Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: ${{ env.ZIG_VERSION }} + + - name: Cache Zig build + uses: actions/cache@v4 + with: + path: | + ~/.cache/zig + cli/zig-cache + cli/zig-out + key: ${{ runner.os }}-zig-${{ hashFiles('cli/**') }} + restore-keys: | + ${{ runner.os }}-zig- + + - name: Install dependencies + run: | + go mod download + sudo apt-get update + sudo apt-get install -y podman redis-tools + + - name: Verify dependencies + run: go mod verify + + - name: Run tests + run: make test + env: + REDIS_URL: redis://localhost:6379 + + - name: Test internal/queue package + run: go test -v -race -coverprofile=queue-coverage.out ./internal/queue/... + env: + REDIS_URL: redis://localhost:6379 + + - name: Run comprehensive tests + run: make test-all + env: + REDIS_URL: redis://localhost:6379 + + - name: Run linters + run: make lint + + - name: Generate coverage report + run: make coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.out + flags: unittests + name: codecov-umbrella + + build: + name: Build + runs-on: ubuntu-latest + needs: test + timeout-minutes: 15 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Set up Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: ${{ env.ZIG_VERSION }} + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum', '**/go.mod') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y podman + + - name: Build binaries + run: | + make build + make cli-build + # Build Zig CLI (dev and prod) + cd cli && zig build dev && zig build prod && cd .. + # Note: prod builds use rsync_placeholder wrapper + # For true embedded rsync, add static binary to cli/src/assets/rsync_release.bin + + - name: Test binaries + run: | + ./bin/user_manager --help + ./bin/worker --help + ./bin/tui --help + ./bin/data_manager --help + # Test Zig CLI + ./cli/zig-out/prod/ml --help + # Verify binary size (should be small with placeholder rsync) + ls -lh ./cli/zig-out/prod/ml + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: fetch_ml_binaries + path: | + bin/ + cli/zig-out/ + dist/ + retention-days: 30 + + test-scripts: + name: Test Scripts + runs-on: ubuntu-latest + needs: test + timeout-minutes: 15 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y podman redis-tools bats + + - name: Test scripts + run: | + # Test script functionality + chmod +x scripts/*.sh + + # Test quick start script (dry run) + ./scripts/quick_start.sh --help || true + + # Test security monitor + ./scripts/security-monitor.sh help + + # Test auto setup + ./scripts/auto_setup.sh help + + # Test deployment scripts + ./scripts/deploy-secure.sh --help || true + ./scripts/deploy-production.sh --help || true + + security-scan: + name: Security Scan + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Gosec Security Scanner + run: | + go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest + gosec ./... + + docker-build: + name: Docker Build + runs-on: ubuntu-latest + needs: [test, build, test-scripts] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: | + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..3a16116 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,50 @@ +name: Documentation + +on: + push: + branches: [ main ] + paths: [ 'docs/**', 'README.md' ] + pull_request: + branches: [ main ] + paths: [ 'docs/**', 'README.md' ] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v3 + + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./docs + destination: ./_site + + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + if: github.ref == 'refs/heads/main' + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 0000000..2f2f131 --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,22 @@ +name: Label Pull Request + +on: + pull_request: + types: [opened, edited, synchronize] + +jobs: + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Label PR + uses: actions/labeler@v4 + with: + configuration-path: .github/labeler.yml + sync-labels: true diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml new file mode 100644 index 0000000..83a39db --- /dev/null +++ b/.github/workflows/license-check.yml @@ -0,0 +1,51 @@ +name: License Check + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +jobs: + license-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check license headers + run: | + # Check if LICENSE file exists + if [ ! -f "LICENSE" ]; then + echo "LICENSE file is missing" + exit 1 + fi + + # Check if it's MIT license + if ! grep -q "MIT License" LICENSE; then + echo "License file should be MIT License" + exit 1 + fi + + echo "License file OK" + + - name: Check Go files for license headers + run: | + # Check for license headers in Go files (optional but good practice) + missing_headers=0 + + for file in $(find . -name "*.go" -not -path "./vendor/*" -not -path "./.git/*"); do + if ! head -10 "$file" | grep -q "Copyright" && ! head -10 "$file" | grep -q "MIT"; then + echo "Missing license header in: $file" + missing_headers=$((missing_headers + 1)) + fi + done + + if [ $missing_headers -gt 0 ]; then + echo "Found $missing_headers Go files without license headers" + echo "Consider adding license headers to Go files" + # Don't fail the build, just warn + else + echo "All Go files have license headers" + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b32d092 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,197 @@ +name: Release + +on: + push: + tags: + - 'v*' # Trigger on version tags like v1.0.0 + +permissions: + contents: write + packages: write + +env: + GO_VERSION: '1.25.0' + ZIG_VERSION: '0.15.2' + +jobs: + prepare_rsync: + name: Prepare rsync metadata + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.manifest.outputs.matrix }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Load rsync manifest + id: manifest + run: | + MANIFEST=.github/rsync_manifest.json + MATRIX=$(jq -c ' + . as $cfg + | $cfg.platforms + | to_entries + | map({ + platform: .key, + target: .value.target, + "rsync-url": ($cfg.base_url + "/" + $cfg.version + "/" + .value.asset), + "rsync-sha256": .value.sha256 + }) + ' "$MANIFEST") + printf 'matrix={"include":%s}\n' "$MATRIX" >> "$GITHUB_OUTPUT" + + build-cli: + name: Build CLI - ${{ matrix.platform }} + needs: prepare_rsync + runs-on: ubuntu-latest + strategy: + matrix: ${{ fromJson(needs.prepare_rsync.outputs.matrix) }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: ${{ env.ZIG_VERSION }} + + - name: Download static rsync + run: | + mkdir -p cli/src/assets + wget -O cli/src/assets/rsync_release.bin ${{ matrix.rsync-url }} || \ + curl -L -o cli/src/assets/rsync_release.bin ${{ matrix.rsync-url }} + echo "${{ matrix.rsync-sha256 }} cli/src/assets/rsync_release.bin" | sha256sum -c + chmod +x cli/src/assets/rsync_release.bin + ls -lh cli/src/assets/rsync_release.bin + + - name: Build CLI + working-directory: cli + run: | + zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseSmall + ls -lh zig-out/bin/ml + + - name: Strip binary (Linux only) + if: matrix.platform == 'linux-x86_64' + working-directory: cli + run: strip zig-out/bin/ml + + - name: Package binary + run: | + mkdir -p dist + cp cli/zig-out/bin/ml dist/ml-${{ matrix.platform }} + cd dist + tar -czf ml-${{ matrix.platform }}.tar.gz ml-${{ matrix.platform }} + sha256sum ml-${{ matrix.platform }}.tar.gz > ml-${{ matrix.platform }}.tar.gz.sha256 + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ml-${{ matrix.platform }} + path: | + dist/ml-${{ matrix.platform }}.tar.gz + dist/ml-${{ matrix.platform }}.tar.gz.sha256 + + build-go-backends: + name: Build Go Backends + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Build binaries + run: | + make cross-platform + ls -lh dist/ + + - name: Package binaries + run: | + cd dist + for binary in api-server worker tui data_manager user_manager; do + if [[ -f "${binary}" ]]; then + tar -czf "fetch_ml_${binary}.tar.gz" "${binary}" + sha256sum "fetch_ml_${binary}.tar.gz" > "fetch_ml_${binary}.tar.gz.sha256" + fi + done + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: go-backends + path: | + dist/*.tar.gz + dist/*.sha256 + + create-release: + name: Create GitHub Release + needs: [build-cli, build-go-backends] + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release assets + run: | + mkdir -p release + # Copy CLI binaries + cp artifacts/ml-*/ml-*.tar.gz* release/ + # Copy Go binaries + cp artifacts/go-backends/*.tar.gz* release/ + + # Generate combined checksums + cd release + sha256sum *.tar.gz > checksums.txt + ls -lh + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + files: release/* + body: | + ## 🚀 Release ${{ github.ref_name }} + + ### CLI Binaries (Zero Dependencies) + All CLI binaries include embedded static rsync for complete independence. + + - **`ml-linux-x86_64.tar.gz`** - Linux x86_64 (fully static, musl) + - **`ml-macos-x86_64.tar.gz`** - macOS Intel + - **`ml-macos-arm64.tar.gz`** - macOS Apple Silicon + + ### Go Backend Binaries + - **`fetch_ml_api-server.tar.gz`** - API Server + - **`fetch_ml_worker.tar.gz`** - Worker + - **`fetch_ml_tui.tar.gz`** - Terminal UI + - **`fetch_ml_data_manager.tar.gz`** - Data Manager + - **`fetch_ml_user_manager.tar.gz`** - User Manager + + ### Installation + ```bash + # Download and extract + tar -xzf ml-.tar.gz + + # Make executable and move to PATH + chmod +x ml- + sudo mv ml- /usr/local/bin/ml + + # Verify installation + ml --help + ``` + + ### Checksums + SHA256 checksums are provided in `checksums.txt` and individual `.sha256` files. + generate_release_notes: true + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..db6bd0f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,33 @@ +name: Mark Stale Issues and PRs + +on: + schedule: + - cron: "0 0 * * 1" # Every Monday at midnight + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - name: Mark stale issues and PRs + uses: actions/stale@v8 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: 14 + stale-issue-label: "stale" + stale-pr-label: "stale" + stale-issue-message: | + This issue has been automatically marked as stale because it has not had recent activity. + It will be closed if no further activity occurs within 14 days. + Thank you for your contributions! + stale-pr-message: | + This pull request has been automatically marked as stale because it has not had recent activity. + It will be closed if no further activity occurs within 14 days. + Thank you for your contributions! + exempt-issue-labels: "pinned,security,help wanted,good first issue" + exempt-pr-labels: "pinned,security,help wanted,good first issue" diff --git a/.windsurf/rules/test-new-features.md b/.windsurf/rules/test-new-features.md new file mode 100644 index 0000000..dbe0e88 --- /dev/null +++ b/.windsurf/rules/test-new-features.md @@ -0,0 +1,6 @@ +--- +trigger: model_decision +description: When a new feature is added, this prompt needs to be run +--- + +When a significant feature is added make sure that the tests are added as well, change the docs to add details and make sure that the scripts, if needed, are changed. Don't forget to cleanup, you tend to leave a lot of unncessary files and code arround. Do not write loose .md to track task and todo, either add to the code or tell me. \ No newline at end of file diff --git a/db/.gitkeep b/db/.gitkeep new file mode 100644 index 0000000..b8cfb49 --- /dev/null +++ b/db/.gitkeep @@ -0,0 +1,3 @@ +# This directory stores SQLite database files +# Database files are automatically created by the application +# Example: fetch_ml.db