mirror of
https://github.com/ps5-linux/ps5-linux-image.git
synced 2026-08-13 23:19:04 -07:00
Compare commits
96
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc0f22eb2 | ||
|
|
787ec039ba | ||
|
|
d7355b7b3c | ||
|
|
ed43393b8b | ||
|
|
8de5e02050 | ||
|
|
38c61af4a0 | ||
|
|
94284d3c14 | ||
|
|
3c3be20587 | ||
|
|
b67f9a5a47 | ||
|
|
34fa9da3f1 | ||
|
|
74f978b1bb | ||
|
|
d763b8496f | ||
|
|
babdcf0762 | ||
|
|
3db2fb65b3 | ||
|
|
415d6e9ee8 | ||
|
|
d7d3b22c1f | ||
|
|
7b8a097651 | ||
|
|
7f8d0302b7 | ||
|
|
44bd2b4cf5 | ||
|
|
d891b0c01e | ||
|
|
f05b110a46 | ||
|
|
6a10749945 | ||
|
|
4c27d97ce0 | ||
|
|
b17ce46319 | ||
|
|
af8c7d472f | ||
|
|
bfdb43f1a1 | ||
|
|
33c6bc2660 | ||
|
|
7f4b0009d0 | ||
|
|
9f9fd3fd7f | ||
|
|
eafb7fbd56 | ||
|
|
21f86f562e | ||
|
|
de5d60da56 | ||
|
|
9fa102e098 | ||
|
|
096675b60a | ||
|
|
00d89868c8 | ||
|
|
b1aa5e5b50 | ||
|
|
b2c77d5cb2 | ||
|
|
f8d0b65b36 | ||
|
|
875bb35fef | ||
|
|
7e42201d88 | ||
|
|
0367dcb89c | ||
|
|
98892eec6b | ||
|
|
9350227d7b | ||
|
|
b10c0b2855 | ||
|
|
bf96770651 | ||
|
|
7f5dc8df0c | ||
|
|
7fbb7df59d | ||
|
|
247e4ea01b | ||
|
|
71bce4c5cb | ||
|
|
a32505e94d | ||
|
|
fe4e686d62 | ||
|
|
e1f17f7e38 | ||
|
|
f090f170a0 | ||
|
|
9682cada8d | ||
|
|
ccc30f19b4 | ||
|
|
34df7b0b64 | ||
|
|
76824ba725 | ||
|
|
7d5efa0583 | ||
|
|
76d3ba281e | ||
|
|
177cc5cfbf | ||
|
|
ddfda4de88 | ||
|
|
ed54e99482 | ||
|
|
50d8ec0b03 | ||
|
|
5f7cfddd3f | ||
|
|
47b35f48dc | ||
|
|
2ea14c3e0c | ||
|
|
f624aef764 | ||
|
|
1c629a881e | ||
|
|
90f06ee77b | ||
|
|
b68aa667d1 | ||
|
|
a1e2bc92e5 | ||
|
|
0adaf1c6d6 | ||
|
|
d0efec98fe | ||
|
|
7f905df12c | ||
|
|
daf634a6ed | ||
|
|
703dc736e9 | ||
|
|
dc365dda15 | ||
|
|
8160bc649b | ||
|
|
1ea662d598 | ||
|
|
04378b78f4 | ||
|
|
0e1219f422 | ||
|
|
33189d7e7b | ||
|
|
d92e51959b | ||
|
|
01205507fd | ||
|
|
9d65d7a391 | ||
|
|
d55d87e6bd | ||
|
|
4a2aef2e03 | ||
|
|
2d07c87a78 | ||
|
|
4cf616a934 | ||
|
|
142d3913c9 | ||
|
|
a76971ba96 | ||
|
|
3db57b8de7 | ||
|
|
73bfdd288b | ||
|
|
fda932a3a3 | ||
|
|
43d2a65997 | ||
|
|
492e124991 |
@@ -1,7 +1,6 @@
|
|||||||
linux_deb/
|
linux_deb/
|
||||||
linux-bin/
|
linux-bin/
|
||||||
work/
|
work/
|
||||||
work-alpine/
|
|
||||||
output/
|
output/
|
||||||
.git/
|
.git/
|
||||||
ps5-linux-patches/
|
ps5-linux-patches/
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.bash text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.service text eol=lf
|
||||||
|
|
||||||
|
build_image.sh text eol=lf
|
||||||
|
boot/kexec*.sh text eol=lf
|
||||||
|
distros/**/grow-rootfs text eol=lf
|
||||||
|
distros/shared/zz-update-boot* text eol=lf
|
||||||
|
docker/** text eol=lf
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
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, fedora, proxmox, debian, bazzite, bazzite-deck, steamos]
|
||||||
|
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=["ubuntu2604","arch","cachyos","fedora","proxmox","debian","bazzite","bazzite-deck","steamos"]' >> "$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: false
|
||||||
|
max-parallel: 5
|
||||||
|
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
|
||||||
|
# A previous run can leave loop mounts (kpartx / losetup -P) and
|
||||||
|
# bind mounts (distrobuilder chroots) live inside image/work/.
|
||||||
|
# rm -rf then trips on "Directory not empty" because it can't
|
||||||
|
# cross the mountpoint. Unmount everything under the workspace
|
||||||
|
# first, then detach any loop devices still backed by files in
|
||||||
|
# the workspace, then rm.
|
||||||
|
WS="$GITHUB_WORKSPACE"
|
||||||
|
# Lazy-unmount any mount whose target is under the workspace.
|
||||||
|
# Reverse order so deepest mounts go first.
|
||||||
|
for m in $(findmnt -rn -o TARGET | grep "^$WS/" | sort -r || true); do
|
||||||
|
echo "unmounting stale $m"
|
||||||
|
sudo umount -l "$m" || true
|
||||||
|
done
|
||||||
|
# Detach any loop device whose backing file lives under the
|
||||||
|
# workspace (or shows '(deleted)' against one — that means the
|
||||||
|
# backing file was removed but the loop wasn't released).
|
||||||
|
for l in $(sudo losetup -l --noheadings -O NAME,BACK-FILE 2>/dev/null \
|
||||||
|
| awk -v ws="$WS" '$2 ~ "^"ws || $2 ~ ws"/" || $2 ~ "\\(deleted\\)$" {print $1}'); do
|
||||||
|
echo "detaching stale loop $l"
|
||||||
|
sudo losetup -d "$l" || true
|
||||||
|
done
|
||||||
|
sudo rm -rf "$WS/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: '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' },
|
||||||
|
{ key: 'bazzite', label: 'Bazzite (KDE gaming)', file: 'ps5-bazzite.img.xz' },
|
||||||
|
{ key: 'bazzite-deck', label: 'Bazzite Deck UI', file: 'ps5-bazzite-deck.img.xz' },
|
||||||
|
{ key: 'steamos', label: 'SteamOS 3 (Holo)', file: 'ps5-steamos.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}`);
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
name: Dispatch to CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dispatch:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Send repository_dispatch
|
|
||||||
uses: peter-evans/repository-dispatch@v3
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.DISPATCH_TOKEN }}
|
|
||||||
repository: ps5-linux/ps5-linux-updates
|
|
||||||
event-type: image-builder-updated
|
|
||||||
client-payload: '{"sha": "${{ github.sha }}"}'
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
name: Move release tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
commit:
|
||||||
|
description: 'Commit SHA to point `latest` at (defaults to the selected ref)'
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
move-tag:
|
||||||
|
runs-on: self-hosted
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Move latest tag to commit
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const ref = 'tags/latest';
|
||||||
|
const sha = '${{ inputs.commit }}'.trim() || context.sha;
|
||||||
|
try {
|
||||||
|
await github.rest.git.updateRef({
|
||||||
|
...context.repo, ref, sha, force: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status !== 422) throw e;
|
||||||
|
await github.rest.git.createRef({
|
||||||
|
...context.repo, ref: 'refs/tags/latest', sha,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
core.info(`latest -> ${sha}`);
|
||||||
|
|
||||||
|
- name: Refresh release publish date
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
let rel;
|
||||||
|
try {
|
||||||
|
({ data: rel } = await github.rest.repos.getReleaseByTag({
|
||||||
|
...context.repo, tag: 'latest',
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
if (e.status === 404) { core.info('no latest release yet; skipping'); return; }
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// 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}`);
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
name: test uploads
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
matrix:
|
||||||
|
runs-on: self-hosted
|
||||||
|
outputs:
|
||||||
|
distros: ${{ steps.set.outputs.distros }}
|
||||||
|
steps:
|
||||||
|
- id: set
|
||||||
|
run: echo 'distros=["ubuntu2604","arch","cachyos"]' >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: matrix
|
||||||
|
runs-on: self-hosted
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 1
|
||||||
|
matrix:
|
||||||
|
distro: ${{ fromJson(needs.matrix.outputs.distros) }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Reset workspace
|
||||||
|
run: sudo rm -rf ./*
|
||||||
|
|
||||||
|
- name: Create fake image
|
||||||
|
run: |
|
||||||
|
mkdir -p image/output
|
||||||
|
dd if=/dev/urandom of=image/output/ps5-${{ matrix.distro }}.img bs=1M count=8
|
||||||
|
|
||||||
|
- name: Compress image
|
||||||
|
run: |
|
||||||
|
xz -T0 -1 -c image/output/ps5-${{ matrix.distro }}.img \
|
||||||
|
> image/output/ps5-${{ matrix.distro }}.img.xz
|
||||||
|
|
||||||
|
- name: Upload to R2
|
||||||
|
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 }}/test-${{ matrix.distro }}.img.xz" -v
|
||||||
|
|
||||||
|
- 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 "6.x.y-test" > meta/kver
|
||||||
|
echo "abc1234" > meta/psha
|
||||||
|
date -u +%Y%m%d-%H%M%S > meta/ts
|
||||||
|
|
||||||
|
- name: Upload build metadata
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: meta-${{ matrix.distro }}
|
||||||
|
path: meta/
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
if: always()
|
||||||
|
run: rm -rf image/output meta
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: build
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Download build metadata
|
||||||
|
uses: actions/download-artifact@v6
|
||||||
|
with:
|
||||||
|
pattern: meta-*
|
||||||
|
path: meta
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Prepare release body
|
||||||
|
id: body
|
||||||
|
run: |
|
||||||
|
KVER=$(cat meta/kver)
|
||||||
|
PSHA=$(cat meta/psha)
|
||||||
|
TS=$(cat meta/ts)
|
||||||
|
SUMS=$(cat meta/*.sha256)
|
||||||
|
|
||||||
|
{
|
||||||
|
echo 'text<<EOF'
|
||||||
|
echo "PS5 Linux images — built from latest \`main\`."
|
||||||
|
echo ""
|
||||||
|
echo "Kernel: \`$KVER\`"
|
||||||
|
echo "Patches: [\`$PSHA\`](https://github.com/ps5-linux/ps5-linux-patches/commit/$PSHA)"
|
||||||
|
echo "Built: \`$TS\`"
|
||||||
|
echo ""
|
||||||
|
echo "| Image | Download |"
|
||||||
|
echo "|-------|----------|"
|
||||||
|
echo "| Ubuntu 26.04 | [\`ps5-ubuntu2604.img.xz\`](https://pub-561df4012f1a46fbbdf618d5cc5941f6.r2.dev/ps5-ubuntu2604.img.xz) |"
|
||||||
|
echo "| Arch | [\`ps5-arch.img.xz\`](https://pub-561df4012f1a46fbbdf618d5cc5941f6.r2.dev/ps5-arch.img.xz) |"
|
||||||
|
echo "| CachyOS | [\`ps5-cachyos.img.xz\`](https://pub-561df4012f1a46fbbdf618d5cc5941f6.r2.dev/ps5-cachyos.img.xz) |"
|
||||||
|
echo ""
|
||||||
|
echo "**SHA256 checksums:**"
|
||||||
|
echo "\`\`\`"
|
||||||
|
echo "$SUMS"
|
||||||
|
echo "\`\`\`"
|
||||||
|
echo 'EOF'
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Verify release body
|
||||||
|
run: |
|
||||||
|
echo "=== Release body ==="
|
||||||
|
cat <<'EOF'
|
||||||
|
${{ steps.body.outputs.text }}
|
||||||
|
EOF
|
||||||
|
echo ""
|
||||||
|
echo "=== All checksums ==="
|
||||||
|
cat meta/*.sha256
|
||||||
|
|
||||||
|
cleanup-remote:
|
||||||
|
needs: release
|
||||||
|
runs-on: self-hosted
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Clean up R2 test files
|
||||||
|
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: |
|
||||||
|
for d in ubuntu2604 arch cachyos; do
|
||||||
|
rclone delete "r2:${{ secrets.R2_BUCKET }}/test-${d}.img.xz" -v || true
|
||||||
|
done
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request_target:
|
||||||
|
types: [labeled]
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
image:
|
||||||
|
if: github.event.label.name == 'run-ci' || github.event_name != 'pull_request_target'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
uses: ./.github/workflows/build-image.yml
|
||||||
|
with:
|
||||||
|
distro: all
|
||||||
|
secrets:
|
||||||
|
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||||
|
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||||
|
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
|
||||||
|
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
name: Upstream patches watch
|
||||||
|
run-name: Watch ps5-linux-patches for new kernel-* tags
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 4 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
actions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
|
// Read build_image.sh from main via API (no checkout — runner
|
||||||
|
// workspace has root-owned build leftovers EACCES checkout).
|
||||||
|
const file = await github.rest.repos.getContent({
|
||||||
|
owner, repo, path: 'build_image.sh', ref: 'main',
|
||||||
|
});
|
||||||
|
const buf = Buffer.from(file.data.content, 'base64');
|
||||||
|
let body = buf.toString();
|
||||||
|
|
||||||
|
const patchesRepo = (body.match(/^PATCHES_REPO="([^"]+)"/m) || [])[1];
|
||||||
|
if (patchesRepo !== 'https://github.com/ps5-linux/ps5-linux-patches.git') {
|
||||||
|
core.info(`PATCHES_REPO is a fork (${patchesRepo}) — skipping auto-bump.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Releases API returns by published_at (most recent first).
|
||||||
|
// Tags API order is not chronological — hex SHA suffixes sort
|
||||||
|
// alphabetically.
|
||||||
|
const releases = await github.paginate(github.rest.repos.listReleases, {
|
||||||
|
owner: 'ps5-linux', repo: 'ps5-linux-patches', per_page: 100,
|
||||||
|
});
|
||||||
|
const re = /^kernel-\d+\.\d+\.\d+-[a-f0-9]+$/;
|
||||||
|
const latest = releases.find(r => re.test(r.tag_name))?.tag_name;
|
||||||
|
if (!latest) { core.setFailed('no kernel-*-* tags found'); return; }
|
||||||
|
|
||||||
|
const cur = (body.match(/^PATCHES_REF="([^"]+)"/m) || [])[1];
|
||||||
|
core.info(`upstream latest: ${latest}`);
|
||||||
|
core.info(`current ref: ${cur}`);
|
||||||
|
if (latest === cur) { core.info('already current — no bump.'); return; }
|
||||||
|
|
||||||
|
const updated = body.replace(
|
||||||
|
new RegExp(`^PATCHES_REF="${cur}"`, 'm'),
|
||||||
|
`PATCHES_REF="${latest}"`,
|
||||||
|
);
|
||||||
|
await github.rest.repos.createOrUpdateFileContents({
|
||||||
|
owner, repo, path: 'build_image.sh', branch: 'main',
|
||||||
|
message: `kernel: auto-bump ${cur} -> ${latest}`,
|
||||||
|
content: Buffer.from(updated).toString('base64'),
|
||||||
|
sha: file.data.sha,
|
||||||
|
committer: { name: 'ps5-linux-bot', email: 'ps5-linux-bot@users.noreply.github.com' },
|
||||||
|
author: { name: 'ps5-linux-bot', email: 'ps5-linux-bot@users.noreply.github.com' },
|
||||||
|
});
|
||||||
|
core.info(`pushed bump to ${latest}`);
|
||||||
|
|
||||||
|
// Pushes authored by GITHUB_TOKEN don't fire workflow_run /
|
||||||
|
// push events, so trigger-builds.yml won't auto-run. Kick off
|
||||||
|
// the matrix build directly.
|
||||||
|
await github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner, repo, workflow_id: 'build-image.yml', ref: 'main',
|
||||||
|
inputs: { distro: 'all' },
|
||||||
|
});
|
||||||
|
core.info('dispatched build-image.yml distro=all');
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# PS5 Linux Image Builder
|
# PS5 Linux Image Builder
|
||||||
|
|
||||||
Builds bootable Linux USB images for PlayStation 5 using Docker containers. Supports Ubuntu 26.04, Arch, CachyOS (Gamescope + Steam), and Alpine, individually or as a multi-distro image with kexec switching.
|
Builds bootable Linux USB images for PlayStation 5 using Docker containers. Supports Ubuntu 26.04, Arch, CachyOS (Gamescope + Steam), Fedora (GNOME), individually or as a multi-distro image with kexec switching.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Docker (with permission to run `--privileged` containers) — install as per your distro's instructions
|
- Docker (with permission to run `--privileged` containers) — install as per your distro's instructions
|
||||||
- ~30GB free disk space
|
- ~30GB free disk space for Ubuntu, Arch, or CachyOS
|
||||||
|
|
||||||
Once Docker is installed, add your user to the docker group and apply it without logging out:
|
Once Docker is installed, add your user to the docker group and apply it without logging out:
|
||||||
|
|
||||||
@@ -27,7 +27,14 @@ OR
|
|||||||
|
|
||||||
OR
|
OR
|
||||||
|
|
||||||
# Build a multi-distro image (ubuntu2604 + arch + alpine + cachyos)
|
OR
|
||||||
|
|
||||||
|
# Build Fedora (GNOME desktop)
|
||||||
|
./build_image.sh --distro fedora
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
# Build a multi-distro image (ubuntu2604 + arch + cachyos)
|
||||||
./build_image.sh --distro all
|
./build_image.sh --distro all
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -43,12 +50,12 @@ sudo dd if=output/ps5-ubuntu2604.img of=/dev/sdX bs=4M status=progress
|
|||||||
|
|
||||||
| Flag | Description | Default |
|
| Flag | Description | Default |
|
||||||
|------|-------------|---------|
|
|------|-------------|---------|
|
||||||
| `--distro` | `ubuntu2604`, `arch`, `cachyos`, `alpine`, or `all` | `ubuntu2604` |
|
| `--distro` | `ubuntu2604`, `arch`, `cachyos`, `fedora`, or `all` | `ubuntu2604` |
|
||||||
| `--kernel` | Path to kernel source directory | auto-clone `v6.19.10` |
|
| `--kernel` | Path to kernel source directory | auto-clone version selected by PS5 patch set |
|
||||||
| `--img-size` | Disk image size in MB | `12000` (`32000` for `all`) |
|
| `--img-size` | Disk image size in MB | `12000` (`32000` for `all`) |
|
||||||
| `--clean` | Remove all cached build artifacts and start fresh | off |
|
| `--clean` | Remove all cached build artifacts and start fresh | off |
|
||||||
| `--kernel-only` | Build and package the kernel only, then exit | off |
|
| `--kernel-only` | Build and package the kernel only, then exit | off |
|
||||||
| `--patches-ref` | Branch, tag, or commit SHA for patches | `v1.0` |
|
| `--patches-ref` | Branch, tag, or commit SHA for patches | `v1.2` |
|
||||||
|
|
||||||
## Caching
|
## Caching
|
||||||
|
|
||||||
@@ -66,7 +73,7 @@ Use `--clean` to wipe everything and rebuild from scratch. The build will also s
|
|||||||
PS5 Linux Image Builder
|
PS5 Linux Image Builder
|
||||||
=======================
|
=======================
|
||||||
Distro: all
|
Distro: all
|
||||||
(ubuntu2604 arch alpine cachyos)
|
(ubuntu2604 arch cachyos)
|
||||||
Image size: 32000MB
|
Image size: 32000MB
|
||||||
Kernel src: /path/to/work/linux
|
Kernel src: /path/to/work/linux
|
||||||
|
|
||||||
@@ -91,19 +98,17 @@ All verbose output goes to `build.log`. The terminal shows a spinner with live p
|
|||||||
| Ubuntu 26.04 (Resolute) | GNOME | `.deb` | systemd |
|
| Ubuntu 26.04 (Resolute) | GNOME | `.deb` | systemd |
|
||||||
| Arch | Sway | `.pkg.tar.zst` | systemd |
|
| Arch | Sway | `.pkg.tar.zst` | systemd |
|
||||||
| CachyOS | Gamescope + Steam Big Picture (Arch + `[cachyos]` repo, no v3 migration in image build) | `.pkg.tar.zst` | systemd |
|
| CachyOS | Gamescope + Steam Big Picture (Arch + `[cachyos]` repo, no v3 migration in image build) | `.pkg.tar.zst` | systemd |
|
||||||
| Alpine (3.21) | GNOME | extracted from `.deb` | OpenRC |
|
|
||||||
|
|
||||||
## Multi-distro Image
|
## Multi-distro Image
|
||||||
|
|
||||||
`--distro all` builds a 32GB image with 5 partitions (one EFI boot partition plus four root filesystems):
|
`--distro all` builds a 32GB image with 4 partitions (one EFI boot partition plus three root filesystems):
|
||||||
|
|
||||||
| Partition | Type | Label | Content |
|
| Partition | Type | Label | Content |
|
||||||
|-----------|------|-------|---------|
|
|-----------|------|-------|---------|
|
||||||
| p1 | FAT32 | boot | Shared kernel, per-distro initrds, kexec scripts |
|
| p1 | FAT32 | boot | Shared kernel, per-distro initrds, kexec scripts |
|
||||||
| p2 | ext4 | ubuntu2604 | Ubuntu 26.04 rootfs |
|
| p2 | ext4 | ubuntu2604 | Ubuntu 26.04 rootfs |
|
||||||
| p3 | ext4 | arch | Arch rootfs |
|
| p3 | ext4 | arch | Arch rootfs |
|
||||||
| p4 | ext4 | alpine | Alpine rootfs |
|
| p4 | ext4 | cachyos | CachyOS rootfs |
|
||||||
| p5 | ext4 | cachyos | CachyOS rootfs |
|
|
||||||
|
|
||||||
The boot partition contains kexec scripts to switch between distros at runtime. Ubuntu 26.04 is the default boot target.
|
The boot partition contains kexec scripts to switch between distros at runtime. Ubuntu 26.04 is the default boot target.
|
||||||
|
|
||||||
@@ -130,7 +135,7 @@ sudo dpkg -i linux-bin/linux-ps5_*.deb
|
|||||||
build_image.sh # Image builder (also supports --kernel-only)
|
build_image.sh # Image builder (also supports --kernel-only)
|
||||||
docker/
|
docker/
|
||||||
kernel-builder/ # Kernel compilation container
|
kernel-builder/ # Kernel compilation container
|
||||||
kernel-builder-arch/ # Repackages .deb kernel as .pkg.tar.zst
|
kernel-builder-arch/ # Repackages .deb kernel as .pkg.tar.zst
|
||||||
image-builder/
|
image-builder/
|
||||||
Dockerfile # Image building container (distrobuilder)
|
Dockerfile # Image building container (distrobuilder)
|
||||||
entrypoint.sh # Single-distro build logic
|
entrypoint.sh # Single-distro build logic
|
||||||
@@ -139,12 +144,11 @@ distros/
|
|||||||
ubuntu2604/ # Ubuntu 26.04 (Resolute)
|
ubuntu2604/ # Ubuntu 26.04 (Resolute)
|
||||||
arch/ # Arch Linux
|
arch/ # Arch Linux
|
||||||
cachyos/ # CachyOS repos + Gamescope/Steam
|
cachyos/ # CachyOS repos + Gamescope/Steam
|
||||||
alpine/ # Alpine 3.21
|
|
||||||
shared/ # Kernel postinst hooks (single + multi)
|
shared/ # Kernel postinst hooks (single + multi)
|
||||||
boot/
|
boot/
|
||||||
cmdline.txt # Kernel cmdline template (__DISTRO__ placeholder)
|
cmdline.txt # Kernel cmdline template (__DISTRO__ placeholder)
|
||||||
vram.txt # VRAM allocation
|
vram.txt # VRAM allocation
|
||||||
kexec-{ubuntu2604,arch,alpine,cachyos}.sh
|
kexec-{ubuntu2604,arch,cachyos}.sh
|
||||||
work/ # Build artifacts (auto-created)
|
work/ # Build artifacts (auto-created)
|
||||||
linux-bin/ # Compiled kernel packages
|
linux-bin/ # Compiled kernel packages
|
||||||
output/ # Final .img files
|
output/ # Final .img files
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Switch to Alpine Linux via kexec
|
|
||||||
set -e
|
|
||||||
BOOT=/boot/efi
|
|
||||||
kexec -l "$BOOT/bzImage" --initrd="$BOOT/initrd-alpine.img" --command-line="$(cat $BOOT/cmdline-alpine.txt)"
|
|
||||||
kexec -e
|
|
||||||
+48
-10
@@ -9,20 +9,21 @@ KERNEL_SRC=""
|
|||||||
CLEAN=false
|
CLEAN=false
|
||||||
IMG_SIZE=12000
|
IMG_SIZE=12000
|
||||||
KERNEL_ONLY=false
|
KERNEL_ONLY=false
|
||||||
PATCHES_REF="v1.0"
|
PATCHES_REF="daa2e496086ae0c9fe22205f703925a2e2596185"
|
||||||
|
|
||||||
MULTI_DISTROS="ubuntu2604 arch alpine cachyos"
|
MULTI_DISTROS="ubuntu2604 arch cachyos"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "Usage: $0 [--distro <distro>] [--kernel <path>] [--img-size <MB>] [--clean]"
|
echo "Usage: $0 [--distro <distro>] [--kernel <path>] [--img-size <MB>] [--clean]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --distro Distribution to build: ubuntu2604, arch, cachyos, alpine, all (default: ubuntu2604)"
|
echo " --distro Distribution to build: ubuntu2604, arch, cachyos, fedora, proxmox, debian, bazzite, bazzite-deck, batocera, all (default: ubuntu2604)"
|
||||||
echo " --kernel Path to kernel source directory (default: auto-clone to work/linux/)"
|
echo " --kernel Path to kernel source directory (default: auto-clone to work/linux/)"
|
||||||
echo " --img-size Disk image size in MB (default: 12000, 32000 for --distro all)"
|
echo " --img-size Disk image size in MB (default: 12000, 32000 for --distro all)"
|
||||||
echo " --clean Remove all cached build artifacts and start from scratch"
|
echo " --clean Remove all cached build artifacts and start from scratch"
|
||||||
|
echo " --clean-only Remove all cached build artifacts and exit"
|
||||||
echo " --kernel-only Build and package the kernel only, then exit"
|
echo " --kernel-only Build and package the kernel only, then exit"
|
||||||
echo " --patches-ref Branch, tag, or commit SHA for patches (default: v1.0)"
|
echo " --patches-ref Branch, tag, or commit SHA for patches (default: v1.2)"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--kernel) KERNEL_SRC="$2"; shift 2 ;;
|
--kernel) KERNEL_SRC="$2"; shift 2 ;;
|
||||||
--img-size) IMG_SIZE="$2"; shift 2 ;;
|
--img-size) IMG_SIZE="$2"; shift 2 ;;
|
||||||
--clean) CLEAN=true; shift ;;
|
--clean) CLEAN=true; shift ;;
|
||||||
|
--clean-only) CLEAN=true; CLEAN_EXIT=true; shift ;;
|
||||||
--kernel-only) KERNEL_ONLY=true; shift ;;
|
--kernel-only) KERNEL_ONLY=true; shift ;;
|
||||||
--patches-ref) [ -n "$2" ] && PATCHES_REF="$2"; shift 2 ;;
|
--patches-ref) [ -n "$2" ] && PATCHES_REF="$2"; shift 2 ;;
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
@@ -63,8 +65,31 @@ if [ "$DISTRO" = "all" ] && [ "$IMG_SIZE" = "12000" ]; then
|
|||||||
IMG_SIZE=32000
|
IMG_SIZE=32000
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Bazzite assembles the OCI rootfs + an embedded /sysroot/ostree/repo/objects
|
||||||
|
# (a deduplicated second copy of the same content) + the linux-ps5 kernel —
|
||||||
|
# 12 GB is not enough headroom. Bump the default for any bazzite* target.
|
||||||
|
# Batocera unsquashes to ~6 GB; 12 GB is tight once kernel + initrd +
|
||||||
|
# /userdata defaults are added. Bump to 16 GB.
|
||||||
|
case "$DISTRO" in
|
||||||
|
bazzite*)
|
||||||
|
if [ "$IMG_SIZE" = "12000" ]; then
|
||||||
|
IMG_SIZE=24000
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
batocera*)
|
||||||
|
if [ "$IMG_SIZE" = "12000" ]; then
|
||||||
|
IMG_SIZE=16000
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
if [ -z "$FORMAT" ]; then
|
if [ -z "$FORMAT" ]; then
|
||||||
case "$DISTRO" in arch) FORMAT="arch" ;; all) FORMAT="all" ;; *) FORMAT="deb" ;; esac
|
case "$DISTRO" in
|
||||||
|
arch|cachyos|steamos) FORMAT="arch" ;;
|
||||||
|
fedora|bazzite*) FORMAT="rpm" ;;
|
||||||
|
all) FORMAT="all" ;;
|
||||||
|
*) FORMAT="deb" ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
KERNEL_BUILDER_PLATFORM="linux/amd64"
|
KERNEL_BUILDER_PLATFORM="linux/amd64"
|
||||||
@@ -76,14 +101,14 @@ esac
|
|||||||
BUILD_PID=""
|
BUILD_PID=""
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
trap - INT TERM EXIT
|
||||||
echo ""
|
echo ""
|
||||||
echo "Interrupted. Cleaning up..."
|
echo "Cleaning up..."
|
||||||
docker kill "$DOCKER_NAME" 2>/dev/null || true
|
docker kill "$DOCKER_NAME" 2>/dev/null || true
|
||||||
[ -n "$BUILD_PID" ] && kill "$BUILD_PID" 2>/dev/null || true
|
[ -n "$BUILD_PID" ] && kill "$BUILD_PID" 2>/dev/null || true
|
||||||
wait "$BUILD_PID" 2>/dev/null || true
|
wait "$BUILD_PID" 2>/dev/null || true
|
||||||
exit 130
|
|
||||||
}
|
}
|
||||||
trap cleanup INT TERM
|
trap cleanup INT TERM EXIT
|
||||||
|
|
||||||
# --- Clean ---
|
# --- Clean ---
|
||||||
if [ "$CLEAN" = true ]; then
|
if [ "$CLEAN" = true ]; then
|
||||||
@@ -94,6 +119,7 @@ if [ "$CLEAN" = true ]; then
|
|||||||
alpine rm -rf "/parent/$(basename "$dir")"
|
alpine rm -rf "/parent/$(basename "$dir")"
|
||||||
done
|
done
|
||||||
echo "Done."
|
echo "Done."
|
||||||
|
[ "$CLEAN_EXIT" = true ] && exit 0
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -103,6 +129,7 @@ SKIP_CHROOT=false
|
|||||||
|
|
||||||
case "$FORMAT" in
|
case "$FORMAT" in
|
||||||
arch) ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true ;;
|
arch) ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true ;;
|
||||||
|
rpm) ls "$KERNEL_OUT"/*.rpm 1>/dev/null 2>&1 && SKIP_KERNEL=true ;;
|
||||||
all) ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && \
|
all) ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && \
|
||||||
ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true ;;
|
ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true ;;
|
||||||
*) ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && SKIP_KERNEL=true ;;
|
*) ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && SKIP_KERNEL=true ;;
|
||||||
@@ -273,7 +300,7 @@ else
|
|||||||
|
|
||||||
KERNEL_SRC="$(cd "$KERNEL_SRC" && pwd)"
|
KERNEL_SRC="$(cd "$KERNEL_SRC" && pwd)"
|
||||||
|
|
||||||
rm -f "$KERNEL_OUT"/*.deb "$KERNEL_OUT"/*.pkg.tar.zst
|
rm -f "$KERNEL_OUT"/*.deb "$KERNEL_OUT"/*.pkg.tar.zst "$KERNEL_OUT"/*.rpm
|
||||||
|
|
||||||
run_stage "Build kernel builder image" \
|
run_stage "Build kernel builder image" \
|
||||||
docker build --platform "$KERNEL_BUILDER_PLATFORM" -t ps5-kernel-builder \
|
docker build --platform "$KERNEL_BUILDER_PLATFORM" -t ps5-kernel-builder \
|
||||||
@@ -307,6 +334,16 @@ else
|
|||||||
-v "$KERNEL_OUT":/out \
|
-v "$KERNEL_OUT":/out \
|
||||||
ps5-kernel-packager-arch
|
ps5-kernel-packager-arch
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
case "$FORMAT" in rpm)
|
||||||
|
run_stage "Build rpm packager image" \
|
||||||
|
docker build -t ps5-kernel-packager-rpm \
|
||||||
|
-f "$SCRIPT_DIR/docker/kernel-builder-rpm/Dockerfile" "$SCRIPT_DIR"
|
||||||
|
run_stage "Package kernel (.rpm)" \
|
||||||
|
docker run --rm --name "$DOCKER_NAME" \
|
||||||
|
-v "$KERNEL_OUT":/out \
|
||||||
|
ps5-kernel-packager-rpm
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$KERNEL_ONLY" = true ]; then
|
if [ "$KERNEL_ONLY" = true ]; then
|
||||||
@@ -318,7 +355,8 @@ fi
|
|||||||
|
|
||||||
# --- Step 2: Build distribution image ---
|
# --- Step 2: Build distribution image ---
|
||||||
run_stage "Build image builder image" \
|
run_stage "Build image builder image" \
|
||||||
docker build -t ps5-image-builder -f "$SCRIPT_DIR/docker/image-builder/Dockerfile" "$SCRIPT_DIR"
|
docker build --pull --no-cache \
|
||||||
|
-t ps5-image-builder -f "$SCRIPT_DIR/docker/image-builder/Dockerfile" "$SCRIPT_DIR"
|
||||||
|
|
||||||
if [ "$DISTRO" = "all" ]; then
|
if [ "$DISTRO" = "all" ]; then
|
||||||
DOCKER_ARGS=(
|
DOCKER_ARGS=(
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Grows the root partition and filesystem to fill the disk.
|
|
||||||
# Runs once on first boot, then disables itself.
|
|
||||||
set -e
|
|
||||||
|
|
||||||
ROOT_DEV=$(findmnt -no SOURCE /)
|
|
||||||
DISK=$(lsblk -ndo PKNAME "$ROOT_DEV")
|
|
||||||
PART_NUM=$(cat /sys/class/block/$(basename "$ROOT_DEV")/partition)
|
|
||||||
|
|
||||||
growpart "/dev/$DISK" "$PART_NUM" || true
|
|
||||||
partprobe "/dev/$DISK"
|
|
||||||
resize2fs "$ROOT_DEV" || true
|
|
||||||
|
|
||||||
rc-update del grow-rootfs default
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/sbin/openrc-run
|
|
||||||
|
|
||||||
description="Grow root filesystem to fill disk"
|
|
||||||
|
|
||||||
depend() {
|
|
||||||
need localmount
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
ebegin "Growing root filesystem"
|
|
||||||
/usr/local/sbin/grow-rootfs
|
|
||||||
eend $?
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
image:
|
|
||||||
name: ps5-alpine
|
|
||||||
distribution: alpine
|
|
||||||
release: "3.21"
|
|
||||||
description: Alpine Linux with Weston
|
|
||||||
architecture: x86_64
|
|
||||||
|
|
||||||
source:
|
|
||||||
downloader: alpinelinux-http
|
|
||||||
url: https://dl-cdn.alpinelinux.org/alpine
|
|
||||||
skip_verification: true
|
|
||||||
|
|
||||||
packages:
|
|
||||||
manager: apk
|
|
||||||
update: true
|
|
||||||
cleanup: true
|
|
||||||
|
|
||||||
repositories:
|
|
||||||
- name: repositories
|
|
||||||
url: |-
|
|
||||||
https://dl-cdn.alpinelinux.org/alpine/v3.21/main
|
|
||||||
https://dl-cdn.alpinelinux.org/alpine/v3.21/community
|
|
||||||
|
|
||||||
sets:
|
|
||||||
- packages:
|
|
||||||
# Wayland compositor + basics
|
|
||||||
- weston
|
|
||||||
- weston-shell-desktop
|
|
||||||
- weston-backend-drm
|
|
||||||
- weston-xwayland
|
|
||||||
- weston-terminal
|
|
||||||
- foot
|
|
||||||
- xwayland
|
|
||||||
|
|
||||||
# Wayland support
|
|
||||||
- dbus
|
|
||||||
- eudev
|
|
||||||
- elogind
|
|
||||||
- polkit-elogind
|
|
||||||
- seatd
|
|
||||||
|
|
||||||
# Audio / media
|
|
||||||
- pipewire
|
|
||||||
- wireplumber
|
|
||||||
- pipewire-pulse
|
|
||||||
|
|
||||||
# Display / GPU
|
|
||||||
- mesa-gbm
|
|
||||||
- mesa-egl
|
|
||||||
- mesa-gl
|
|
||||||
- mesa-dri-gallium
|
|
||||||
- mesa-va-gallium
|
|
||||||
- mesa-vulkan-ati
|
|
||||||
- libinput
|
|
||||||
- xkeyboard-config
|
|
||||||
|
|
||||||
# Networking
|
|
||||||
- networkmanager
|
|
||||||
- networkmanager-wifi
|
|
||||||
- wpa_supplicant
|
|
||||||
- linux-firmware
|
|
||||||
- linux-firmware-amdgpu
|
|
||||||
- xf86-video-amdgpu
|
|
||||||
|
|
||||||
# System
|
|
||||||
- openrc
|
|
||||||
- openssh
|
|
||||||
- sudo
|
|
||||||
- shadow
|
|
||||||
- nano
|
|
||||||
- bash
|
|
||||||
- coreutils
|
|
||||||
- util-linux
|
|
||||||
- e2fsprogs
|
|
||||||
- e2fsprogs-extra
|
|
||||||
- cloud-utils-growpart
|
|
||||||
- mkinitfs
|
|
||||||
- kmod
|
|
||||||
- kexec-tools
|
|
||||||
- syslog-ng
|
|
||||||
|
|
||||||
# Fonts
|
|
||||||
- font-ubuntu
|
|
||||||
- font-liberation
|
|
||||||
- font-dejavu
|
|
||||||
- fontconfig
|
|
||||||
action: install
|
|
||||||
|
|
||||||
files:
|
|
||||||
- path: /etc/hostname
|
|
||||||
generator: hostname
|
|
||||||
|
|
||||||
- path: /etc/hosts
|
|
||||||
generator: hosts
|
|
||||||
|
|
||||||
- path: /etc/kernel/postinst.d/zz-update-boot
|
|
||||||
generator: copy
|
|
||||||
source: /tmp/build-staging/zz-update-boot
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- path: /etc/fstab
|
|
||||||
generator: copy
|
|
||||||
source: /tmp/build-staging/fstab
|
|
||||||
|
|
||||||
- path: /usr/local/sbin/grow-rootfs
|
|
||||||
generator: copy
|
|
||||||
source: /tmp/build-staging/grow-rootfs
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- path: /etc/init.d/grow-rootfs
|
|
||||||
generator: copy
|
|
||||||
source: /tmp/build-staging/grow-rootfs.openrc
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
actions:
|
|
||||||
- trigger: post-unpack
|
|
||||||
action: |-
|
|
||||||
#!/bin/sh
|
|
||||||
set -eux
|
|
||||||
# Retry up to 10 times on transient network errors
|
|
||||||
echo "retries = 10" >> /etc/apk/apk.conf 2>/dev/null || true
|
|
||||||
# dbus post-install chokes on an empty machine-id
|
|
||||||
rm -f /etc/machine-id
|
|
||||||
|
|
||||||
|
|
||||||
- trigger: post-packages
|
|
||||||
action: |-
|
|
||||||
#!/bin/sh
|
|
||||||
set -eux
|
|
||||||
fc-cache -f -v
|
|
||||||
|
|
||||||
# SSH
|
|
||||||
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
|
|
||||||
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
|
|
||||||
rc-update add syslog-ng default
|
|
||||||
rc-update add udev sysinit
|
|
||||||
rc-update add udev-trigger sysinit
|
|
||||||
rc-update add udev-settle sysinit
|
|
||||||
rc-update add sshd default
|
|
||||||
rc-update add dbus default
|
|
||||||
rc-update add elogind default
|
|
||||||
rc-update add seatd default
|
|
||||||
rc-update add networkmanager default
|
|
||||||
|
|
||||||
- trigger: post-files
|
|
||||||
action: |-
|
|
||||||
#!/bin/sh
|
|
||||||
set -eux
|
|
||||||
rc-update add grow-rootfs default
|
|
||||||
|
|
||||||
# Create default user (ps5/ps5) in groups needed for Wayland/input
|
|
||||||
addgroup ps5
|
|
||||||
adduser -D -s /bin/bash -G ps5 ps5
|
|
||||||
echo "ps5:ps5" | chpasswd
|
|
||||||
addgroup ps5 wheel
|
|
||||||
addgroup ps5 video
|
|
||||||
addgroup ps5 input
|
|
||||||
addgroup ps5 seat
|
|
||||||
echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/wheel
|
|
||||||
|
|
||||||
# Auto-start weston on tty1 login
|
|
||||||
{
|
|
||||||
echo 'if [ "$(tty)" = "/dev/tty1" ] && [ -z "$WAYLAND_DISPLAY" ]; then'
|
|
||||||
echo ' export XDG_RUNTIME_DIR="/tmp/xdg-runtime-$(id -u)"'
|
|
||||||
echo ' mkdir -p "$XDG_RUNTIME_DIR"'
|
|
||||||
echo ' chmod 700 "$XDG_RUNTIME_DIR"'
|
|
||||||
echo ' exec weston --backend=drm-backend.so'
|
|
||||||
echo 'fi'
|
|
||||||
} >> /home/ps5/.bash_profile
|
|
||||||
chown -R ps5:ps5 /home/ps5
|
|
||||||
|
|
||||||
mkdir -p /boot/efi
|
|
||||||
|
|
||||||
mappings:
|
|
||||||
architecture_map: alpinelinux
|
|
||||||
@@ -1,14 +1,91 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Grows the root partition and filesystem to fill the disk.
|
# Grows the root partition + filesystem to fill the disk on first boot.
|
||||||
# Runs once on first boot, then disables itself.
|
# Self-disables after success.
|
||||||
|
#
|
||||||
|
# Uses sfdisk + partx + resize2fs (all in util-linux + e2fsprogs, both
|
||||||
|
# present in the minimal Arch/SteamOS base). Earlier versions called
|
||||||
|
# growpart from cloud-utils — but that package isn't in the SteamOS
|
||||||
|
# recovery image, so the unit failed with "growpart: command not found"
|
||||||
|
# and rootfs stayed at the dd'd image size (~14 GB) on any disk larger
|
||||||
|
# than that.
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
ROOT_DEV=$(findmnt -no SOURCE /)
|
ROOT_DEV=$(findmnt -no SOURCE /)
|
||||||
DISK="/dev/$(lsblk -ndo PKNAME "$ROOT_DEV")"
|
DISK="/dev/$(lsblk -ndo PKNAME "$ROOT_DEV")"
|
||||||
|
|
||||||
|
# PS5 M.2 drive appear as /dev/nvme*. Do not modify partition table,
|
||||||
|
# because it contains m2_init PS5 offset. Only USB drive (/dev/sd*)
|
||||||
|
# are safe for automatic grow root partition.
|
||||||
|
if [[ "$(basename "$DISK")" == nvme* ]]; then
|
||||||
|
echo "grow-rootfs: M.2 detected, skipping"
|
||||||
|
systemctl disable grow-rootfs.service
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
PART_NUM=$(cat /sys/class/block/$(basename "$ROOT_DEV")/partition)
|
PART_NUM=$(cat /sys/class/block/$(basename "$ROOT_DEV")/partition)
|
||||||
|
|
||||||
parted -s "$DISK" resizepart "$PART_NUM" 100%
|
# Our images dd at their built size (~14 GB) onto a much larger USB. The GPT
|
||||||
partprobe "$DISK"
|
# backup header is therefore at byte ~built-size, not at end of disk. Move
|
||||||
|
# the backup header to end-of-disk first so partition table growth works.
|
||||||
|
sgdisk -e "$DISK"
|
||||||
|
|
||||||
|
# If a previous flash left behind partitions BEYOND our rootfs + boot pair
|
||||||
|
# (e.g. a stale `linux-home` partset at part 3 from a prior SteamOS install
|
||||||
|
# on the same drive), the partition extend refuses to grow into the space
|
||||||
|
# already claimed by the leftover partition. Delete every partition with a
|
||||||
|
# number higher than 2 — we only ever create partitions 1 (rootfs) + 2 (boot).
|
||||||
|
# Important: sgdisk only rewrites the on-disk GPT; we must ALSO drop those
|
||||||
|
# stale entries from the kernel's in-memory partition table via partx -d,
|
||||||
|
# or BLKPG_RESIZE_PARTITION below returns EBUSY because the kernel still
|
||||||
|
# thinks sda1 would have to grow into space owned by phantom sda3.
|
||||||
|
for n in $(parted -ms "$DISK" print 2>/dev/null \
|
||||||
|
| awk -F: 'NR>2 && $1+0>2 {print $1}'); do
|
||||||
|
sgdisk -d "$n" "$DISK" 2>/dev/null || true
|
||||||
|
partx -d --nr "$n" "$DISK" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
# sfdisk `,+` extends partition $PART_NUM to consume all available free
|
||||||
|
# space. Works on a mounted/live partition because sfdisk only rewrites
|
||||||
|
# the GPT entry (kernel re-read of the partition table comes next).
|
||||||
|
echo ",+" | sfdisk --no-reread -N "$PART_NUM" "$DISK"
|
||||||
|
|
||||||
|
# Make the kernel pick up the new partition size without unmounting via
|
||||||
|
# BLKPG_RESIZE_PARTITION. partx -u also uses BLKPG, but on some images
|
||||||
|
# (notably SteamOS recovery loop-mounted btrfs) partx returns an error
|
||||||
|
# updating the mounted partition; the direct ioctl is more reliable.
|
||||||
|
python3 - "$DISK" "$PART_NUM" <<'PY'
|
||||||
|
import sys, os, fcntl, ctypes, subprocess
|
||||||
|
disk, pno = sys.argv[1], int(sys.argv[2])
|
||||||
|
out = subprocess.run(["sfdisk","--bytes","-q","-l",disk],
|
||||||
|
capture_output=True, text=True, check=True).stdout
|
||||||
|
start = sectors = None
|
||||||
|
for line in out.splitlines():
|
||||||
|
if line.startswith(f"{disk}{pno} ") or line.startswith(f"{disk}p{pno} "):
|
||||||
|
f = line.split()
|
||||||
|
start, sectors = int(f[1]), int(f[3])
|
||||||
|
break
|
||||||
|
if start is None:
|
||||||
|
sys.exit(f"could not parse partition {pno} from sfdisk -l {disk}")
|
||||||
|
class P(ctypes.Structure):
|
||||||
|
_fields_=[("start",ctypes.c_longlong),("length",ctypes.c_longlong),
|
||||||
|
("pno",ctypes.c_int),("devname",ctypes.c_char*64),
|
||||||
|
("volname",ctypes.c_char*64)]
|
||||||
|
class A(ctypes.Structure):
|
||||||
|
_fields_=[("op",ctypes.c_int),("flags",ctypes.c_int),
|
||||||
|
("datalen",ctypes.c_int),("data",ctypes.c_void_p)]
|
||||||
|
p = P(start=start*512, length=sectors*512, pno=pno)
|
||||||
|
a = A(op=3, flags=0, datalen=ctypes.sizeof(p), data=ctypes.addressof(p))
|
||||||
|
fd = os.open(disk, os.O_RDONLY)
|
||||||
|
try:
|
||||||
|
fcntl.ioctl(fd, 0x1269, a) # BLKPG, BLKPG_RESIZE_PARTITION
|
||||||
|
finally:
|
||||||
|
os.close(fd)
|
||||||
|
PY
|
||||||
|
|
||||||
|
# Belt-and-suspenders: also try partx in case BLKPG via python failed.
|
||||||
|
partx -u --nr "$PART_NUM" "$DISK" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Online ext4 grow.
|
||||||
resize2fs "$ROOT_DEV"
|
resize2fs "$ROOT_DEV"
|
||||||
|
|
||||||
systemctl disable grow-rootfs.service
|
systemctl disable grow-rootfs.service
|
||||||
|
|||||||
+34
-10
@@ -35,6 +35,10 @@ packages:
|
|||||||
- wl-clipboard
|
- wl-clipboard
|
||||||
- xorg-xwayland
|
- xorg-xwayland
|
||||||
|
|
||||||
|
# Bluetooth
|
||||||
|
- bluez
|
||||||
|
- bluez-utils
|
||||||
|
|
||||||
# Audio / media
|
# Audio / media
|
||||||
- pipewire
|
- pipewire
|
||||||
- wireplumber
|
- wireplumber
|
||||||
@@ -118,13 +122,19 @@ actions:
|
|||||||
action: |-
|
action: |-
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -eux
|
set -eux
|
||||||
|
# Pacman 7 Landlock sandbox is unsupported under QEMU; disable it.
|
||||||
|
sed -i '/^\[options\]/a DisableSandbox' /etc/pacman.conf
|
||||||
|
|
||||||
# Initialise pacman keyring
|
# Initialise pacman keyring
|
||||||
pacman-key --init
|
pacman-key --init
|
||||||
pacman-key --populate archlinux
|
pacman-key --populate archlinux
|
||||||
|
|
||||||
# Fetch up-to-date HTTPS mirrors ranked by score, force-refresh package DBs
|
# Keep CI on a small stable mirror set. The full active global list can
|
||||||
curl -fsSL "https://archlinux.org/mirrorlist/?country=all&protocol=https&use_mirror_status=on" \
|
# select slow mirrors and fail pacman's low-speed download timeout.
|
||||||
| sed 's/^#Server/Server/' > /etc/pacman.d/mirrorlist
|
cat > /etc/pacman.d/mirrorlist <<'EOF'
|
||||||
|
Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
|
||||||
|
Server = https://mirrors.kernel.org/archlinux/$repo/os/$arch
|
||||||
|
EOF
|
||||||
pacman -Syy --noconfirm
|
pacman -Syy --noconfirm
|
||||||
|
|
||||||
|
|
||||||
@@ -132,21 +142,35 @@ actions:
|
|||||||
action: |-
|
action: |-
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -eux
|
set -eux
|
||||||
fc-cache -f -v
|
# fc-cache / systemctl can SIGSEGV under QEMU when image plat is linux/amd64 on arm64 hosts.
|
||||||
|
fc-cache -f 2>/dev/null || true
|
||||||
|
|
||||||
|
symlink_wants() {
|
||||||
|
local src=$1
|
||||||
|
local target=$2
|
||||||
|
[ -e "$src" ] || return 0
|
||||||
|
mkdir -p "/etc/systemd/system/${target}.wants"
|
||||||
|
ln -sf "$src" "/etc/systemd/system/${target}.wants/$(basename "$src")"
|
||||||
|
}
|
||||||
|
|
||||||
# SSH
|
|
||||||
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
|
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
|
||||||
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
|
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
|
||||||
systemctl enable sshd
|
symlink_wants /usr/lib/systemd/system/sshd.service multi-user.target
|
||||||
systemctl enable NetworkManager
|
symlink_wants /usr/lib/systemd/system/NetworkManager.service multi-user.target
|
||||||
systemctl enable seatd
|
symlink_wants /usr/lib/systemd/system/seatd.service multi-user.target
|
||||||
systemctl enable pipewire pipewire-pulse wireplumber || true
|
for s in pipewire pipewire-pulse wireplumber; do
|
||||||
|
symlink_wants "/usr/lib/systemd/system/${s}.service" default.target || true
|
||||||
|
done
|
||||||
|
|
||||||
- trigger: post-files
|
- trigger: post-files
|
||||||
action: |-
|
action: |-
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -eux
|
set -eux
|
||||||
systemctl enable grow-rootfs.service
|
mkdir -p /etc/systemd/system/local-fs.target.wants
|
||||||
|
ln -sf /etc/systemd/system/grow-rootfs.service \
|
||||||
|
/etc/systemd/system/local-fs.target.wants/grow-rootfs.service
|
||||||
|
|
||||||
|
systemctl enable bluetooth.service
|
||||||
|
|
||||||
# Create default user (ps5/ps5)
|
# Create default user (ps5/ps5)
|
||||||
useradd -m -G wheel,seat -s /bin/bash ps5
|
useradd -m -G wheel,seat -s /bin/bash ps5
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# batocera
|
||||||
|
|
||||||
|
Adds support for [Batocera](https://batocera.org/) (Buildroot-based
|
||||||
|
retro-emulation distro) on PS5 hardware.
|
||||||
|
|
||||||
|
Batocera ships as an `.img.gz` with FAT32 boot + ext4 SHARE partitions; the
|
||||||
|
OS itself lives in a squashfs at `/boot/batocera`.
|
||||||
|
`distros/batocera/build-rootfs.sh` downloads + unsquashes that image and
|
||||||
|
swaps in the linux-ps5 kernel:
|
||||||
|
|
||||||
|
1. Download `https://mirrors.o2switch.fr/batocera/x86_64/stable/last/`
|
||||||
|
2. Loop-mount the FAT32, find the embedded squashfs
|
||||||
|
3. `unsquashfs` to `$CHROOT`
|
||||||
|
4. Extract the linux-ps5 `.deb`'s `vmlinuz` → `/boot/bzImage`
|
||||||
|
5. Patch `libretroControllers.py` (PS5 controller-mapping fix)
|
||||||
|
6. Set up first-boot SHARE partition creator (`ps5-share-init`)
|
||||||
|
7. Write fstab with `/boot vfat` (NOT `/boot/efi` — batocera-part's
|
||||||
|
SHARE auto-detection greps `/proc/mounts` for `/boot`)
|
||||||
|
|
||||||
|
## Build locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build_image.sh --distro batocera
|
||||||
|
```
|
||||||
|
|
||||||
|
Image size: 16 GB default (Batocera unsquashes to ~6 GB; headroom for
|
||||||
|
/userdata). Override the batocera release with `BATOCERA_VER` /
|
||||||
|
`BATOCERA_BUILD` envs (defaults track the upstream "last" channel).
|
||||||
Executable
+369
@@ -0,0 +1,369 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# distros/batocera/build-rootfs.sh — download upstream batocera image, extract
|
||||||
|
# its squashfs to $CHROOT, swap in our PS5 kernel + modules. Called from
|
||||||
|
# docker/image-builder/entrypoint.sh for DISTRO=batocera*.
|
||||||
|
#
|
||||||
|
# Batocera is Buildroot-based, shipping as a single .img.gz with FAT32 boot +
|
||||||
|
# ext4 SHARE partitions; the OS itself lives in a squashfs file at
|
||||||
|
# /boot/batocera on the FAT32. We unsquash, swap kernel + modules in, and
|
||||||
|
# the rest of the standard image-builder flow packs it onto ext4.
|
||||||
|
#
|
||||||
|
# Expects in env: DISTRO, CHROOT, KVER, ROOT_LABEL, EFI_LABEL
|
||||||
|
# Expects on disk: /kernel-debs/*.deb (linux-ps5 .deb to extract bzImage from)
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
: "${CHROOT:?ERROR: \$CHROOT unset/empty}"
|
||||||
|
[ -d "$CHROOT" ] || { echo "ERROR: \$CHROOT=$CHROOT not a directory"; exit 2; }
|
||||||
|
case "$CHROOT" in /) echo "ERROR: refuse to operate on /"; exit 2 ;; esac
|
||||||
|
|
||||||
|
# Batocera is a Buildroot-based emulation distro. It ships as a
|
||||||
|
# single .img.gz with FAT32 boot + ext4 SHARE partitions; the OS
|
||||||
|
# itself lives in a squashfs file (`/boot/batocera`) on the FAT32.
|
||||||
|
# We unsquash, swap our PS5 kernel + modules in, and let the rest
|
||||||
|
# of the standard image-builder flow pack everything onto ext4.
|
||||||
|
# `last/` always points at the current build, but the .img.gz filename
|
||||||
|
# inside it bakes in the version + date (e.g. batocera-x86_64-43.1-20260529.img.gz),
|
||||||
|
# and the mirror rotates older builds out. Scrape the index to discover
|
||||||
|
# whatever's there today rather than hardcoding (the previous hardcoded
|
||||||
|
# 43-20260430 went 404 within ~4 weeks).
|
||||||
|
BATOCERA_INDEX_URL="${BATOCERA_INDEX_URL:-https://mirrors.o2switch.fr/batocera/x86_64/stable/last/}"
|
||||||
|
if [ -z "${BATOCERA_URL:-}" ]; then
|
||||||
|
IMG_NAME=$(wget -qO- "$BATOCERA_INDEX_URL" \
|
||||||
|
| grep -oE 'batocera-x86_64-[0-9.]+-[0-9]+\.img\.gz' \
|
||||||
|
| head -1)
|
||||||
|
if [ -z "$IMG_NAME" ]; then
|
||||||
|
echo "ERROR: couldn't find a batocera-x86_64-*.img.gz link at $BATOCERA_INDEX_URL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
BATOCERA_URL="${BATOCERA_INDEX_URL}${IMG_NAME}"
|
||||||
|
else
|
||||||
|
IMG_NAME="$(basename "$BATOCERA_URL")"
|
||||||
|
fi
|
||||||
|
# Pull VER + BUILD out of the discovered (or overridden) filename so the
|
||||||
|
# cache key + log lines stay informative.
|
||||||
|
BATOCERA_VER=$(echo "$IMG_NAME" | sed -E 's/^batocera-x86_64-([0-9.]+)-[0-9]+\.img\.gz$/\1/')
|
||||||
|
BATOCERA_BUILD=$(echo "$IMG_NAME" | sed -E 's/^batocera-x86_64-[0-9.]+-([0-9]+)\.img\.gz$/\1/')
|
||||||
|
|
||||||
|
echo "=== Batocera: locate / download $BATOCERA_VER ($BATOCERA_BUILD) ==="
|
||||||
|
# /build/cache is per-run temp. The workflow symlinks /build/cache/
|
||||||
|
# persistent -> /data/cache/ps5/downloads, so the .img.gz can be
|
||||||
|
# pre-staged or survive between runs. The mirror rate-limits per-IP
|
||||||
|
# to ~250KB/s sustained (4MB/s burst), so re-downloading every run
|
||||||
|
# is unacceptably slow.
|
||||||
|
# The workflow hard-links /data/cache/ps5/downloads/* into
|
||||||
|
# image/work/cache before the build container starts, so the
|
||||||
|
# image appears as /build/cache/batocera-*.img.gz inside.
|
||||||
|
CACHED="/build/cache/batocera-${BATOCERA_VER}-${BATOCERA_BUILD}.img.gz"
|
||||||
|
if [ ! -s "$CACHED" ]; then
|
||||||
|
echo ">> No cached image, downloading (this will be slow due to mirror rate-limiting)"
|
||||||
|
wget --tries=3 -O "$CACHED.part" "$BATOCERA_URL"
|
||||||
|
mv "$CACHED.part" "$CACHED"
|
||||||
|
fi
|
||||||
|
echo ">> Using $CACHED ($(du -h "$CACHED" | cut -f1))"
|
||||||
|
|
||||||
|
echo "=== Batocera: decompress + loop ==="
|
||||||
|
BAT_IMG=/build/batocera-src.img
|
||||||
|
gunzip -c "$CACHED" > "$BAT_IMG"
|
||||||
|
BATLOOP=$(losetup -Pf --show "$BAT_IMG")
|
||||||
|
sleep 1
|
||||||
|
# kpartx fallback in case partition kernel events didn't fire
|
||||||
|
[ -e "${BATLOOP}p1" ] || kpartx -av "$BATLOOP"
|
||||||
|
BAT_MNT=$(mktemp -d)
|
||||||
|
BAT_PART1=""
|
||||||
|
for p in "${BATLOOP}p1" "/dev/mapper/$(basename "$BATLOOP")p1"; do
|
||||||
|
[ -e "$p" ] && BAT_PART1="$p" && break
|
||||||
|
done
|
||||||
|
mount -o ro "$BAT_PART1" "$BAT_MNT"
|
||||||
|
|
||||||
|
BAT_SQUASH=""
|
||||||
|
for c in /boot/batocera /batocera /boot/batocera.update; do
|
||||||
|
[ -f "$BAT_MNT$c" ] && BAT_SQUASH="$BAT_MNT$c" && break
|
||||||
|
done
|
||||||
|
if [ -z "$BAT_SQUASH" ]; then
|
||||||
|
echo "ERROR: squashfs not found in batocera image:"
|
||||||
|
find "$BAT_MNT" -maxdepth 3 -type f | head -30
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "=== Batocera: unsquashfs $BAT_SQUASH -> $CHROOT ==="
|
||||||
|
unsquashfs -f -d "$CHROOT" "$BAT_SQUASH"
|
||||||
|
|
||||||
|
# Batocera ships a SECOND squashfs (boot/rufomaculata) with the
|
||||||
|
# libretro cores, mame binary, and other emulator assets. At
|
||||||
|
# runtime it's mounted as a second overlayfs layer on top of the
|
||||||
|
# main batocera squashfs. We don't do overlay — just unsquash
|
||||||
|
# rufomaculata on top of $CHROOT so the unified view is realised
|
||||||
|
# on the ext4 root. Without this, /usr/lib/libretro/ doesn't
|
||||||
|
# exist and EmulationStation reports "no games start" because
|
||||||
|
# retroarch fails to load any core.
|
||||||
|
if [ -f "$BAT_MNT/boot/rufomaculata" ]; then
|
||||||
|
echo "=== Batocera: unsquashfs boot/rufomaculata (libretro + mame) -> $CHROOT ==="
|
||||||
|
unsquashfs -f -d "$CHROOT" "$BAT_MNT/boot/rufomaculata"
|
||||||
|
else
|
||||||
|
echo "WARN: boot/rufomaculata not found — emulator cores will be missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
umount "$BAT_MNT"
|
||||||
|
rmdir "$BAT_MNT"
|
||||||
|
kpartx -dv "$BATLOOP" 2>/dev/null || true
|
||||||
|
losetup -d "$BATLOOP"
|
||||||
|
rm -f "$BAT_IMG"
|
||||||
|
|
||||||
|
echo "=== Batocera: install linux-ps5 kernel + modules ==="
|
||||||
|
KSTAGE=/tmp/bat-kernel-staging
|
||||||
|
rm -rf "$KSTAGE"; mkdir -p "$KSTAGE"
|
||||||
|
# The kernel-builder ships a single combined linux-ps5_*.deb
|
||||||
|
# (Provides: linux-image-X) — there is no linux-image-*.deb on
|
||||||
|
# disk, so target the actual filename pattern.
|
||||||
|
shopt -s nullglob
|
||||||
|
for deb in /kernel-debs/linux-ps5*.deb /kernel-debs/linux-image-*.deb; do
|
||||||
|
[ -f "$deb" ] && dpkg-deb -x "$deb" "$KSTAGE"
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
KVER=$(ls -1 "$KSTAGE/lib/modules" 2>/dev/null | head -1)
|
||||||
|
if [ -z "$KVER" ]; then
|
||||||
|
echo "ERROR: no kernel modules found after dpkg-deb -x of /kernel-debs/*.deb"
|
||||||
|
ls -la /kernel-debs/
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -rf "$CHROOT"/lib/modules/*
|
||||||
|
cp -a "$KSTAGE/lib/modules/$KVER" "$CHROOT/lib/modules/"
|
||||||
|
mkdir -p "$CHROOT/boot/efi"
|
||||||
|
cp "$KSTAGE/boot/vmlinuz-$KVER" "$CHROOT/boot/efi/bzImage"
|
||||||
|
# depmod -b runs from outside the chroot — Batocera's busybox
|
||||||
|
# depmod may not be present, and host depmod handles -b cleanly.
|
||||||
|
depmod -a -b "$CHROOT" "$KVER" || true
|
||||||
|
# Stage WLAN firmware loader + module autoload (same files the
|
||||||
|
# debian/fedora paths get from /kernel-debs/staging via .deb).
|
||||||
|
for src in usr/local/sbin etc/modules-load.d etc/systemd/system; do
|
||||||
|
[ -d "$KSTAGE/$src" ] || continue
|
||||||
|
mkdir -p "$CHROOT/$src"
|
||||||
|
cp -an "$KSTAGE/$src/." "$CHROOT/$src/" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== Batocera: PS5 modprobe quirks ==="
|
||||||
|
mkdir -p "$CHROOT/etc/modprobe.d" "$CHROOT/etc/modules-load.d"
|
||||||
|
cat > "$CHROOT/etc/modprobe.d/ps5-amdgpu.conf" <<MODPROBE
|
||||||
|
options amdgpu dpm=0 gpu_recovery=0
|
||||||
|
MODPROBE
|
||||||
|
# uinput is needed by Batocera's hotkeygen (for virtual keyboard
|
||||||
|
# events when launching games). It's not autoloaded by default on
|
||||||
|
# PS5, so hotkeygen crashes with 'UInputError: /dev/uinput does
|
||||||
|
# not exist'. Force-load on boot.
|
||||||
|
cat > "$CHROOT/etc/modules-load.d/uinput.conf" <<MODPROBE
|
||||||
|
uinput
|
||||||
|
MODPROBE
|
||||||
|
|
||||||
|
echo "=== Batocera: build initrd via host mkinitramfs ==="
|
||||||
|
# Host (image-builder, ubuntu:24.04) has initramfs-tools. Trick
|
||||||
|
# it into building for our PS5 kernel by symlinking the chroot's
|
||||||
|
# modules into /lib/modules/$KVER, then unlinking after.
|
||||||
|
#
|
||||||
|
# initramfs-tools default behaviour: autodetect kernel modules
|
||||||
|
# from /sys on the BUILD HOST — which is a docker container with
|
||||||
|
# no USB, no amdgpu, no real disks. The resulting initrd would
|
||||||
|
# ship without xhci_pci / usb_storage / ext4 / amdgpu drivers,
|
||||||
|
# and the PS5 hangs silently when the kernel tries to find the
|
||||||
|
# USB root partition. Override with an explicit modules list +
|
||||||
|
# MODULES=most so initramfs-tools includes everything the PS5
|
||||||
|
# actually needs at boot.
|
||||||
|
mkdir -p /lib/modules
|
||||||
|
ln -sfn "$CHROOT/lib/modules/$KVER" "/lib/modules/$KVER"
|
||||||
|
|
||||||
|
cat > /etc/initramfs-tools/modules <<'INITMODS'
|
||||||
|
# USB host controllers (PS5 boot drive is on USB 3 — xhci is the must-have).
|
||||||
|
xhci_pci
|
||||||
|
xhci_hcd
|
||||||
|
ehci_pci
|
||||||
|
ehci_hcd
|
||||||
|
ohci_pci
|
||||||
|
ohci_hcd
|
||||||
|
# USB storage class + UAS (faster path).
|
||||||
|
usb_storage
|
||||||
|
uas
|
||||||
|
sd_mod
|
||||||
|
# Filesystems for root + EFI.
|
||||||
|
ext4
|
||||||
|
vfat
|
||||||
|
nls_iso8859-1
|
||||||
|
nls_cp437
|
||||||
|
# Common HID so a USB keyboard works at the initramfs shell if we drop there.
|
||||||
|
usbhid
|
||||||
|
hid_generic
|
||||||
|
INITMODS
|
||||||
|
# Force MODULES=most (curated full driver set, no autodetect).
|
||||||
|
sed -i 's/^MODULES=.*/MODULES=most/' /etc/initramfs-tools/initramfs.conf
|
||||||
|
|
||||||
|
mkinitramfs -k "$KVER" -o "$CHROOT/boot/efi/initrd.img"
|
||||||
|
rm -f "/lib/modules/$KVER"
|
||||||
|
rm -rf "$KSTAGE"
|
||||||
|
|
||||||
|
echo "=== Batocera: patch configgen to bind HOTKEY combos on gamepad ==="
|
||||||
|
# Upstream Batocera's libretroControllers.py only sets
|
||||||
|
# input_enable_hotkey_btn — the hotkey "enable" button — and never
|
||||||
|
# binds input_exit_emulator_btn / input_menu_toggle_btn /
|
||||||
|
# input_save_state_btn / input_load_state_btn. The keyboard-side
|
||||||
|
# bindings (escape = exit, f1 = menu) work fine but on a DualSense
|
||||||
|
# there's no way out of a game without sshing in and pkill'ing
|
||||||
|
# retroarch. Patch the function to also bind start/select/L1/R1.
|
||||||
|
PYFILE="$CHROOT/usr/lib/python3.12/site-packages/configgen/generators/libretro/libretroControllers.py"
|
||||||
|
if [ -f "$PYFILE" ]; then
|
||||||
|
python3 - "$PYFILE" <<'PYPATCH'
|
||||||
|
import sys
|
||||||
|
p = sys.argv[1]
|
||||||
|
src = open(p).read()
|
||||||
|
old = " retroconfig.save('input_enable_hotkey_btn', controllers[0].inputs['hotkey'].id)"
|
||||||
|
extra = '''
|
||||||
|
# PS5: map HOTKEY combos to gamepad — upstream sets only the
|
||||||
|
# enable button, leaving exit-emulator unbound on gamepad. Without
|
||||||
|
# this, gamepad users can't exit a retroarch game without sshing
|
||||||
|
# in and pkill'ing retroarch.
|
||||||
|
for batocera_key, retroarch_key in [
|
||||||
|
('start', 'input_exit_emulator_btn'),
|
||||||
|
('select', 'input_menu_toggle_btn'),
|
||||||
|
('pageup', 'input_load_state_btn'),
|
||||||
|
('pagedown', 'input_save_state_btn'),
|
||||||
|
]:
|
||||||
|
if batocera_key in controllers[0].inputs:
|
||||||
|
retroconfig.save(retroarch_key, controllers[0].inputs[batocera_key].id)'''
|
||||||
|
if old in src and extra not in src:
|
||||||
|
open(p, 'w').write(src.replace(old, old + extra))
|
||||||
|
print(' patched libretroControllers.py')
|
||||||
|
else:
|
||||||
|
print(' skipped (line not found or already patched)')
|
||||||
|
PYPATCH
|
||||||
|
else
|
||||||
|
echo " WARN: $PYFILE missing — configgen patch skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Batocera: fstab + users ==="
|
||||||
|
# NOTE the FAT32 boot partition is mounted at /boot (not
|
||||||
|
# /boot/efi like the other distros) because batocera-part —
|
||||||
|
# which S11share uses to autodetect the SHARE partition by
|
||||||
|
# 'partition next to /boot' — greps /proc/mounts for /boot.
|
||||||
|
# If we mount at /boot/efi the SHARE auto-detection silently
|
||||||
|
# fails and S11share falls back to a 256 MB tmpfs at
|
||||||
|
# /userdata, which won't fit Steam / save data / anything.
|
||||||
|
# PS5 loader reads bzImage / cmdline.txt from the FAT32
|
||||||
|
# partition's root regardless of where Linux mounts it.
|
||||||
|
mkdir -p "$CHROOT/boot"
|
||||||
|
cat > "$CHROOT/etc/fstab" <<FSTAB
|
||||||
|
LABEL=$ROOT_LABEL / ext4 defaults 0 1
|
||||||
|
LABEL=$EFI_LABEL /boot vfat defaults 0 1
|
||||||
|
LABEL=SHARE /userdata ext4 defaults 0 2
|
||||||
|
FSTAB
|
||||||
|
|
||||||
|
echo "=== Batocera: first-boot SHARE partition creator ==="
|
||||||
|
# Batocera's design splits the disk into:
|
||||||
|
# sda1 = rootfs (this image, ~15 GB)
|
||||||
|
# sda2 = /boot FAT32
|
||||||
|
# sda3 = /userdata SHARE (everything user-facing — games,
|
||||||
|
# BIOS, Steam flatpak, screenshots, saves)
|
||||||
|
# The image only ships sda1+sda2. On first boot, expand the
|
||||||
|
# GPT backup header to the actual disk end (so parted/sgdisk
|
||||||
|
# see the full free space) then carve sda3 = SHARE out of
|
||||||
|
# the remainder. Self-disables after running.
|
||||||
|
cat > "$CHROOT/usr/local/sbin/ps5-share-init" <<'PS5SHARE'
|
||||||
|
#!/bin/sh
|
||||||
|
# First-boot: create the SHARE partition + fs if missing, so /userdata
|
||||||
|
# is a real disk-backed mount (916 GB on a 1 TB drive) instead of the
|
||||||
|
# 256 MB tmpfs fallback in /etc/init.d/S11share.
|
||||||
|
set -e
|
||||||
|
ROOT_DEV=$(findmnt -no SOURCE /)
|
||||||
|
DISK=$(/usr/bin/batocera-part prefix "$ROOT_DEV")
|
||||||
|
SHARE_NUM=$(/usr/bin/batocera-part share_internal_num)
|
||||||
|
SHARE_DEV="${DISK}${SHARE_NUM}"
|
||||||
|
[ -b "$DISK" ] || exit 0
|
||||||
|
# already created on a previous boot?
|
||||||
|
if [ -b "$SHARE_DEV" ] && blkid -L SHARE >/dev/null 2>&1; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "ps5-share-init: extending GPT + creating $SHARE_DEV"
|
||||||
|
sgdisk -e "$DISK"
|
||||||
|
partprobe "$DISK"
|
||||||
|
sleep 1
|
||||||
|
sgdisk -n "$SHARE_NUM":0:0 -c "$SHARE_NUM":share -t "$SHARE_NUM":8300 "$DISK"
|
||||||
|
partprobe "$DISK"
|
||||||
|
sleep 1
|
||||||
|
mkfs.ext4 -L SHARE -F "$SHARE_DEV"
|
||||||
|
PS5SHARE
|
||||||
|
chmod +x "$CHROOT/usr/local/sbin/ps5-share-init"
|
||||||
|
# Hook into Batocera's init order: run BEFORE S11share so
|
||||||
|
# S11share's batocera-part share_internal call finds the
|
||||||
|
# partition we just created.
|
||||||
|
cat > "$CHROOT/etc/init.d/S07ps5share" <<'INITSHARE'
|
||||||
|
#!/bin/sh
|
||||||
|
# First-boot SHARE partition creator — see /usr/local/sbin/ps5-share-init
|
||||||
|
case "$1" in
|
||||||
|
start|"") /usr/local/sbin/ps5-share-init >> /tmp/ps5-share-init.log 2>&1 ;;
|
||||||
|
stop|restart|reload|*) ;;
|
||||||
|
esac
|
||||||
|
INITSHARE
|
||||||
|
chmod +x "$CHROOT/etc/init.d/S07ps5share"
|
||||||
|
|
||||||
|
# First-boot defaults for /userdata/system/batocera.conf — set
|
||||||
|
# display.empty=1 so every system (PSP, PS1, PS2, PS3, PS4,
|
||||||
|
# Switch, etc) is visible in EmulationStation even before
|
||||||
|
# ROMs are loaded. S12 runs after S11share has populated
|
||||||
|
# /userdata. Idempotent: only sets a key if not already
|
||||||
|
# present, so the user remains free to flip it back.
|
||||||
|
cat > "$CHROOT/etc/init.d/S12ps5defaults" <<'INITDEF'
|
||||||
|
#!/bin/sh
|
||||||
|
case "$1" in
|
||||||
|
start|"")
|
||||||
|
CONF=/userdata/system/batocera.conf
|
||||||
|
[ -f "$CONF" ] || exit 0
|
||||||
|
grep -qE '^display\.empty=' "$CONF" || echo 'display.empty=1' >> "$CONF"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
INITDEF
|
||||||
|
chmod +x "$CHROOT/etc/init.d/S12ps5defaults"
|
||||||
|
|
||||||
|
# Batocera ships root passwordless. Leave root usable (a lot of
|
||||||
|
# Batocera scripts assume root) but ALSO add a ps5 user so the
|
||||||
|
# release-page convention works.
|
||||||
|
if ! grep -q "^ps5:" "$CHROOT/etc/passwd"; then
|
||||||
|
echo "ps5:x:1000:1000:PS5:/home/ps5:/bin/sh" >> "$CHROOT/etc/passwd"
|
||||||
|
echo "ps5:!::0:99999:7:::" >> "$CHROOT/etc/shadow"
|
||||||
|
echo "ps5:x:1000:" >> "$CHROOT/etc/group"
|
||||||
|
mkdir -p "$CHROOT/home/ps5"
|
||||||
|
chroot "$CHROOT" /bin/sh -c "chown -R 1000:1000 /home/ps5" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
# Both root and ps5 get pw 'ps5' — Batocera's chpasswd is busybox.
|
||||||
|
chroot "$CHROOT" /bin/sh -c "printf 'ps5\nps5\n' | passwd ps5 2>/dev/null; printf 'ps5\nps5\n' | passwd root 2>/dev/null" || true
|
||||||
|
|
||||||
|
echo "=== Batocera: grow-rootfs first-boot service ==="
|
||||||
|
mkdir -p "$CHROOT/usr/local/sbin" "$CHROOT/etc/systemd/system"
|
||||||
|
cat > "$CHROOT/usr/local/sbin/grow-rootfs" <<'GROW'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
ROOT=$(findmnt -no SOURCE / || mount | awk '$3=="/"{print $1; exit}')
|
||||||
|
DISK=$(lsblk -no PKNAME "$ROOT" 2>/dev/null | head -1)
|
||||||
|
PARTNUM=$(echo "$ROOT" | grep -oE '[0-9]+$' || true)
|
||||||
|
[ -z "$DISK" ] || [ -z "$PARTNUM" ] && exit 0
|
||||||
|
growpart "/dev/$DISK" "$PARTNUM" || true
|
||||||
|
resize2fs "$ROOT" || true
|
||||||
|
GROW
|
||||||
|
chmod +x "$CHROOT/usr/local/sbin/grow-rootfs"
|
||||||
|
cat > "$CHROOT/etc/systemd/system/grow-rootfs.service" <<SVC
|
||||||
|
[Unit]
|
||||||
|
Description=Grow rootfs to fill disk (first boot)
|
||||||
|
ConditionPathExists=/usr/local/sbin/grow-rootfs
|
||||||
|
ConditionFirstBoot=yes
|
||||||
|
After=local-fs.target
|
||||||
|
Before=basic.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/sbin/grow-rootfs
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sysinit.target
|
||||||
|
SVC
|
||||||
|
# Batocera switched to systemd in v33+. Try systemctl enable;
|
||||||
|
# tolerate buildroot quirks where /etc/systemd/system layout
|
||||||
|
# differs.
|
||||||
|
mkdir -p "$CHROOT/etc/systemd/system/sysinit.target.wants"
|
||||||
|
ln -sf ../grow-rootfs.service \
|
||||||
|
"$CHROOT/etc/systemd/system/sysinit.target.wants/grow-rootfs.service"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../bazzite/build-rootfs.sh
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../bazzite/grow-rootfs
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user