Repository navigation
fix: ship owner-only 0700/0600 modes and guard with validator check #35
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: Build & Release | |
| on: | |
| push: | |
| branches: [master] | |
| tags: ["v[0-9]+.[0-9]+.[0-9]+"] | |
| pull_request: | |
| branches: [master] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| validate-configs: | |
| name: Validate Configs & Schemas | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: python3 -m pip install pyyaml jsonschema | |
| - name: Validate aliases | |
| run: python3 scripts/resolve-packages.py --json | |
| - name: Validate required binaries coverage | |
| run: python3 scripts/resolve-packages.py --check-binaries --json | |
| - name: Validate schemas | |
| run: | | |
| python3 << 'EOF' | |
| import json | |
| from jsonschema import Draft7Validator | |
| for schema_file in ('schemas/release-manifest.schema.json', | |
| 'schemas/rootfs-manifest.schema.json'): | |
| with open(schema_file) as f: | |
| schema = json.load(f) | |
| Draft7Validator.check_schema(schema) | |
| print(f'{schema_file}: valid draft-07 schema') | |
| EOF | |
| - name: Install ShellCheck | |
| run: sudo apt-get update && sudo apt-get install -y shellcheck | |
| - name: ShellCheck | |
| run: shellcheck --severity=warning scripts/*.sh | |
| - name: Python lint | |
| run: | | |
| python3 -m py_compile scripts/resolve-packages.py | |
| python3 -m py_compile scripts/assemble-rootfs.py | |
| python3 -m py_compile scripts/validate-rootfs.py | |
| python3 -m py_compile scripts/make-release-manifest.py | |
| resolve-dependencies: | |
| name: Resolve Dependency Graphs | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| package: [parted, e2fsprogs, dosfstools, exfatprogs, f2fs-tools, btrfs-progs] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: python3 -m pip install pyyaml | |
| - name: Resolve build order | |
| run: python3 scripts/resolve-packages.py --graph ${{ matrix.package }} --json | |
| validate-sources: | |
| name: Validate Pinned Source URLs | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: python3 -m pip install pyyaml | |
| - name: Check source hashes are pinned | |
| run: | | |
| python3 -c " | |
| import yaml, sys | |
| with open('config/packages.yaml') as f: | |
| pkgs = yaml.safe_load(f) | |
| unpinned = [] | |
| for name, pkg in pkgs.get('packages', {}).items(): | |
| if 'PLACEHOLDER' in pkg.get('source_hash', ''): | |
| unpinned.append(name) | |
| if unpinned: | |
| print(f'ERROR: Unpinned source hashes for: {unpinned}') | |
| sys.exit(1) | |
| print('All source hashes are pinned') | |
| " | |
| reproducibility: | |
| name: Reproducibility Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: python3 -m pip install pyyaml | |
| - name: Verify forbidden prefixes | |
| run: | | |
| grep -r "com\.termux" overlays/ scripts/ || true | |
| grep -r "files/usr" overlays/ scripts/ || true | |
| - name: Check no hardcoded dev paths | |
| run: | | |
| grep -rE '/home/|/root/|/tmp/' \ | |
| --include='*.py' --include='*.sh' scripts/ || true | |
| build-matrix: | |
| name: Compute Build Matrix | |
| needs: validate-configs | |
| runs-on: ubuntu-latest | |
| outputs: | |
| include: ${{ steps.gen.outputs.include }} | |
| steps: | |
| - name: Generate matrix | |
| id: gen | |
| run: | | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| echo 'include=[{"abi":"x86_64","termux-arch":"x86_64"}]' >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'include=[{"abi":"arm64-v8a","termux-arch":"aarch64"},{"abi":"x86_64","termux-arch":"x86_64"}]' >> "$GITHUB_OUTPUT" | |
| fi | |
| build: | |
| name: Build ${{ matrix.abi }} | |
| needs: build-matrix | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 240 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.build-matrix.outputs.include) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: python3 -m pip install pyyaml | |
| - name: Resolve termux commit from lock file | |
| id: lock | |
| run: | | |
| python3 -c " | |
| import json | |
| with open('config/termux-lock.json') as f: | |
| print(json.load(f)['pinned_termux_commit']) | |
| " > /tmp/termux-commit.txt | |
| echo "termux_commit=$(cat /tmp/termux-commit.txt)" >> "$GITHUB_OUTPUT" | |
| - name: Build rootfs for ${{ matrix.abi }} | |
| run: | | |
| bash scripts/build-rootfs.sh "${{ matrix.abi }}" "${{ steps.lock.outputs.termux_commit }}" | |
| env: | |
| TERMUX_APP__PACKAGE_NAME: vn.shadichy.parted | |
| TERMUX__ROOTFS: /data/data/vn.shadichy.parted/files | |
| TERMUX__PREFIX: /data/data/vn.shadichy.parted/files/usr | |
| TERMUX_PREFIX: /data/data/vn.shadichy.parted/files/usr | |
| TERMUX_PREFIX_SUBDIR: "usr" | |
| ANDROID_ROOT: /system | |
| - name: Validate bundle | |
| run: | | |
| BUNDLE=$(find "staging/${{ matrix.abi }}" -name "fparted-rootfs-*.zip" | head -1) | |
| python3 scripts/validate-rootfs.py \ | |
| --bundle "${BUNDLE}" \ | |
| --manifest "staging/${{ matrix.abi }}/rootfs-manifest.json" | |
| - name: Create artifact tarball | |
| run: | | |
| mkdir -p artifacts | |
| tar -czf "artifacts/rootfs-${{ matrix.abi }}.tar.gz" \ | |
| -C "staging/${{ matrix.abi }}" \ | |
| --exclude='termux-checkout' \ | |
| --exclude='termux-build-cache' \ | |
| --exclude='rootfs' \ | |
| --exclude='packages' \ | |
| . | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: rootfs-${{ matrix.abi }} | |
| path: artifacts/rootfs-${{ matrix.abi }}.tar.gz | |
| retention-days: 30 | |
| verify-all: | |
| name: Verify All ABIs Present | |
| needs: build | |
| if: github.event_name != 'pull_request' && github.ref_type == 'branch' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download all ABI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Verify both ABI bundles present | |
| run: | | |
| for abi in arm64-v8a x86_64; do | |
| if [[ -f "artifacts/rootfs-${abi}/rootfs-${abi}.tar.gz" ]]; then | |
| echo "OK: ${abi}" | |
| else | |
| echo "ERROR: missing artifact for ${abi}" | |
| exit 1 | |
| fi | |
| done | |
| - name: Forbid standard Termux prefix in outputs | |
| run: | | |
| mkdir -p /tmp/prefix-check | |
| for tarball in artifacts/*/*.tar.gz; do | |
| tar -xzf "${tarball}" -C /tmp/prefix-check | |
| done | |
| if find /tmp/prefix-check -type f \ | |
| \( -name "*.json" -o -name "*.sh" -o -name "*.md" -o -name "*.txt" -o -name "*.py" \) \ | |
| -exec grep -l "com\.termux" {} + 2>/dev/null | grep -q .; then | |
| echo "ERROR: forbidden com.termux prefix found" | |
| exit 1 | |
| fi | |
| echo "No forbidden prefix found" | |
| assemble-release: | |
| name: Assemble Release Assets | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: python3 -m pip install pyyaml jsonschema | |
| - name: Download all ABI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-assets | |
| - name: Extract artifact tarballs | |
| run: | | |
| mkdir -p release-output | |
| for tarball in release-assets/*/*.tar.gz; do | |
| echo "Extracting ${tarball}" | |
| tar -xzf "${tarball}" -C release-output/ | |
| done | |
| - name: Version bundle filenames to tag | |
| run: | | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| for bundle in release-output/fparted-rootfs-*.zip; do | |
| abi=$(basename "${bundle}" .zip | grep -oP '(?:arm64-v8a|x86_64)$') | |
| mv "${bundle}" "release-output/fparted-rootfs-${VERSION}-${abi}.zip" | |
| done | |
| ls -1 release-output/fparted-rootfs-*.zip | |
| - name: Import release signing key | |
| run: echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --yes --import | |
| - name: Assemble release manifest | |
| run: | | |
| python3 scripts/make-release-manifest.py \ | |
| --bundle-dir release-output \ | |
| --toolchain-version "${GITHUB_REF#refs/tags/v}" \ | |
| --termux-commit "$(python3 -c "import json; print(json.load(open('config/termux-lock.json'))['pinned_termux_commit'])")" \ | |
| --output-dir release-output \ | |
| --sign | |
| - name: Verify release manifest signature | |
| run: | | |
| gpg --batch --import keys/release-pubkey.asc | |
| gpg --verify release-output/release-manifest.sig release-output/release-manifest.json | |
| - name: Validate generated release manifest | |
| run: | | |
| python3 << 'EOF' | |
| import json | |
| import jsonschema | |
| with open('schemas/release-manifest.schema.json') as f: | |
| schema = json.load(f) | |
| with open('release-output/release-manifest.json') as f: | |
| manifest = json.load(f) | |
| jsonschema.validate(manifest, schema) | |
| print('release-manifest.json validates against release-manifest.schema.json') | |
| EOF | |
| - name: Verify required binaries declared in config | |
| run: python3 scripts/resolve-packages.py --check-binaries | |
| - name: Verify bundle count | |
| run: | | |
| COUNT=$(find release-output -name "fparted-rootfs-*.zip" | wc -l) | |
| echo "Bundles in release: ${COUNT}" | |
| if [ "${COUNT}" -ne 2 ]; then | |
| echo "ERROR: Expected 2 ABI bundles, got ${COUNT}" | |
| exit 1 | |
| fi | |
| - name: Verify hashes | |
| run: | | |
| cd release-output | |
| sha256sum -c SHA256SUMS | |
| - name: Upload release assets | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| release-output/fparted-rootfs-*.zip | |
| release-output/release-manifest.json | |
| release-output/release-manifest.sig | |
| release-output/SHA256SUMS | |
| release-output/packages.spdx.json | |
| release-output/source-offer.json | |
| draft: false | |
| fail_on_unmatched_files: true |