Files
ps5-linux-image/.github/workflows/build-image.yml
Bug Bounty Zip 7d5efa0583 Add Debian 12 (XFCE) image support
Plain Debian 12 Bookworm with XFCE desktop, LightDM autologin, Firefox
ESR, and standard desktop utilities. Fills the gap between the heavier
Kali (Debian + security tools) and the headless Proxmox (Debian +
hypervisor) — a clean, lightweight Debian desktop for PS5.

Tested on PS5 hardware: XFCE displays over HDMI, boots to desktop.

[skip ci]
2026-06-14 00:39:13 +08:00

272 lines
9.5 KiB
YAML

name: Build Image
run-name: Build Image (${{ inputs.distro || 'all' }})
on:
workflow_dispatch:
inputs:
distro:
description: 'Distribution'
default: 'ubuntu2604'
type: choice
options: [all, ubuntu2604, arch, cachyos, kali, fedora, proxmox, debian]
workflow_call:
inputs:
distro:
type: string
default: 'ubuntu2604'
secrets:
R2_ACCESS_KEY_ID:
required: true
R2_SECRET_ACCESS_KEY:
required: true
R2_ENDPOINT:
required: true
R2_BUCKET:
required: true
jobs:
matrix:
runs-on: self-hosted
outputs:
distros: ${{ steps.set.outputs.distros }}
steps:
- id: set
run: |
INPUT="${{ inputs.distro || 'ubuntu2604' }}"
if [ "$INPUT" = "all" ]; then
echo 'distros=["kali","ubuntu2604","arch","cachyos","fedora","proxmox","debian"]' >> "$GITHUB_OUTPUT"
else
echo "distros=[\"$INPUT\"]" >> "$GITHUB_OUTPUT"
fi
build:
needs: matrix
runs-on: self-hosted
timeout-minutes: 720
permissions:
contents: write
strategy:
fail-fast: true
max-parallel: 1
matrix:
distro: ${{ fromJson(needs.matrix.outputs.distros) }}
env:
CCACHE_DIR: /home/opc/ccache
steps:
- name: Clean image workspace
run: |
set -euo pipefail
if [ -z "${GITHUB_WORKSPACE:-}" ] || [ "$GITHUB_WORKSPACE" = "/" ]; then
echo "Invalid GITHUB_WORKSPACE" >&2
exit 1
fi
sudo rm -rf "$GITHUB_WORKSPACE/image"
- name: Checkout image builder
uses: actions/checkout@v6
with:
path: image
- name: Build ${{ matrix.distro }} image
run: |
bash image/build_image.sh --distro "${{ matrix.distro }}"
- name: Set version info
id: version
run: |
psha=$(git -C image/work/ps5-linux-patches rev-parse --short HEAD 2>/dev/null || echo "unknown")
kver=$(cat image/linux-bin/VERSION 2>/dev/null || echo "unknown")
ts=$(date -u +%Y%m%d-%H%M%S)
echo "psha=$psha" >> "$GITHUB_OUTPUT"
echo "kver=$kver" >> "$GITHUB_OUTPUT"
echo "ts=$ts" >> "$GITHUB_OUTPUT"
- name: Report
run: |
ls -lh image/output/*.img
sha256sum image/output/*.img
- name: Compress image
run: |
xz -T0 -4 -c image/output/ps5-${{ matrix.distro }}.img \
> image/output/ps5-${{ matrix.distro }}.img.xz
- name: Upload to R2
if: github.event_name != 'pull_request' && github.event_name != 'pull_request_target'
env:
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
RCLONE_CONFIG_R2_NO_CHECK_BUCKET: true
run: |
rclone copyto image/output/ps5-${{ matrix.distro }}.img.xz \
"r2:${{ secrets.R2_BUCKET }}/ps5-${{ matrix.distro }}.img.xz"
- name: Save build metadata
run: |
mkdir -p meta
sha256sum image/output/ps5-${{ matrix.distro }}.img.xz \
| sed 's|image/output/||' > meta/${{ matrix.distro }}.sha256
echo "${{ steps.version.outputs.kver }}" > meta/kver
echo "${{ steps.version.outputs.psha }}" > meta/psha
echo "${{ steps.version.outputs.ts }}" > meta/ts
- name: Upload build metadata
uses: actions/upload-artifact@v6
with:
name: meta-${{ matrix.distro }}
path: meta/
retention-days: 1
release:
needs: build
runs-on: self-hosted
if: github.event_name != 'pull_request' && github.event_name != 'pull_request_target'
permissions:
contents: write
steps:
- name: Download build metadata
uses: actions/download-artifact@v6
with:
pattern: meta-*
path: meta
merge-multiple: true
- name: Prepare release body
id: body
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const path = require('path');
const kver = fs.readFileSync('meta/kver', 'utf8').trim();
const psha = fs.readFileSync('meta/psha', 'utf8').trim();
const ts = fs.readFileSync('meta/ts', 'utf8').trim();
// Collect new checksums from this run
const newSums = {};
for (const f of fs.readdirSync('meta').filter(f => f.endsWith('.sha256'))) {
const distro = f.replace('.sha256', '');
newSums[distro] = fs.readFileSync(path.join('meta', f), 'utf8').trim();
}
// All known distros and their display names (order = table order)
const R2 = 'https://pub-561df4012f1a46fbbdf618d5cc5941f6.r2.dev';
const ALL_DISTROS = [
{ key: 'ubuntu2604', label: 'Ubuntu 26.04', file: 'ps5-ubuntu2604.img.xz' },
{ key: 'arch', label: 'Arch', file: 'ps5-arch.img.xz' },
{ key: 'cachyos', label: 'CachyOS', file: 'ps5-cachyos.img.xz' },
{ key: 'kali', label: 'Kali', file: 'ps5-kali.img.xz' },
{ key: 'fedora', label: 'Fedora 44', file: 'ps5-fedora.img.xz' },
{ key: 'proxmox', label: 'Proxmox VE 8', file: 'ps5-proxmox.img.xz' },
{ key: 'debian', label: 'Debian 12', file: 'ps5-debian.img.xz' },
];
// Fetch existing release body to preserve distros not built this run
const existingSums = {};
try {
const { data: rel } = await github.rest.repos.getReleaseByTag({
...context.repo, tag: 'latest',
});
// Extract existing checksum lines (format: "hash filename")
const sumBlock = (rel.body || '').match(/```\n([\s\S]*?)```/);
if (sumBlock) {
for (const line of sumBlock[1].trim().split('\n')) {
const m = line.match(/^(\w+)\s+ps5-(\S+)\.img\.xz$/);
if (m) existingSums[m[2]] = line.trim();
}
}
// Also preserve distros that appear in the table even without checksums
for (const d of ALL_DISTROS) {
if (rel.body && rel.body.includes(d.file) && !existingSums[d.key]) {
existingSums[d.key] = '';
}
}
} catch (e) {
core.info('No existing release found, starting fresh');
}
// Merge: new checksums override existing ones
const mergedSums = { ...existingSums };
for (const [k, v] of Object.entries(newSums)) {
mergedSums[k] = v;
}
// Build the table — only include distros that have been built at least once
const rows = ALL_DISTROS
.filter(d => d.key in mergedSums || d.key in newSums)
.map(d => `| ${d.label} | [\`${d.file}\`](${R2}/${d.file}) |`);
// Combine all checksum lines
const allSumLines = ALL_DISTROS
.filter(d => mergedSums[d.key])
.map(d => mergedSums[d.key])
.join('\n');
const body = [
'PS5 Linux images — built from latest `main`.',
'',
`Kernel: \`${kver}\``,
`Patches: [\`${psha}\`](https://github.com/ps5-linux/ps5-linux-patches/commit/${psha})`,
`Built: \`${ts}\``,
'',
'| Image | Download |',
'|-------|----------|',
...rows,
'',
'**SHA256 checksums:**',
'```',
allSumLines,
'```',
].join('\n');
core.setOutput('text', body);
- name: Move latest tag to current commit
uses: actions/github-script@v8
with:
script: |
const ref = 'tags/latest';
try {
await github.rest.git.updateRef({
...context.repo, ref, sha: context.sha, force: true,
});
} catch (e) {
if (e.status !== 422) throw e;
await github.rest.git.createRef({
...context.repo, ref: 'refs/tags/latest', sha: context.sha,
});
}
- name: Create or update release
uses: softprops/action-gh-release@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: latest
name: PS5 Linux Image (latest)
body: ${{ steps.body.outputs.text }}
make_latest: true
- name: Refresh release publish date
uses: actions/github-script@v8
with:
script: |
const { data: rel } = await github.rest.repos.getReleaseByTag({
...context.repo, tag: 'latest',
});
// Toggling draft off->on->off re-publishes, resetting published_at
// to now while keeping the same release (reactions/comments survive).
await github.rest.repos.updateRelease({
...context.repo, release_id: rel.id, draft: true,
});
await github.rest.repos.updateRelease({
...context.repo, release_id: rel.id, draft: false, make_latest: 'true',
});
core.info(`re-published release ${rel.id}`);