Compare commits
5 Commits
pve-9.2.11
...
pve-9.2.18
| Author | SHA1 | Date | |
|---|---|---|---|
| e1d2c00a97 | |||
| 85c47eddf8 | |||
| 612887bf6b | |||
| 5865198240 | |||
| 4719d30359 |
@@ -37,6 +37,9 @@ jobs:
|
|||||||
GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
KEEP_RELEASES: '3'
|
KEEP_RELEASES: '3'
|
||||||
|
# Rebuild even without a Proxmox version change once an image reaches this
|
||||||
|
# age, so Debian base security updates make it into the image.
|
||||||
|
MAX_AGE_DAYS: '30'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out
|
- name: Check out
|
||||||
@@ -58,29 +61,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
cd "$GITHUB_WORKSPACE"
|
cd "$GITHUB_WORKSPACE"
|
||||||
|
./scripts/ci/decide-build.sh "$MAX_AGE_DAYS" > "$GITHUB_OUTPUT"
|
||||||
UPSTREAM=$(./scripts/ci/upstream-version.sh)
|
|
||||||
TAG="pve-${UPSTREAM}"
|
|
||||||
echo "upstream pve-manager: ${UPSTREAM}"
|
|
||||||
|
|
||||||
PUBLISHED=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${GITEA_API}/releases?limit=1" \
|
|
||||||
| python3 -c 'import json,sys; r=json.load(sys.stdin); print(r[0]["tag_name"] if r else "")')
|
|
||||||
echo "latest release: ${PUBLISHED:-<none>}"
|
|
||||||
|
|
||||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "version=${UPSTREAM}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
if [ "${{ inputs.force }}" = "true" ]; then
|
if [ "${{ inputs.force }}" = "true" ]; then
|
||||||
echo "forced by manual trigger"
|
echo "forced by manual trigger"
|
||||||
echo "build=yes" >> "$GITHUB_OUTPUT"
|
sed -i 's/^build=no$/build=yes/' "$GITHUB_OUTPUT"
|
||||||
elif [ "$PUBLISHED" = "$TAG" ]; then
|
|
||||||
echo "${TAG} is already published — nothing to do"
|
|
||||||
echo "build=no" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "new version — building"
|
|
||||||
echo "build=yes" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
fi
|
||||||
|
cat "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: steps.decide.outputs.build == 'yes'
|
if: steps.decide.outputs.build == 'yes'
|
||||||
|
|||||||
44
Makefile
44
Makefile
@@ -31,6 +31,9 @@ PVE_EXTRA_PACKAGES ?= ifupdown2 open-iscsi chrony postfix lvm2 thin-provisioning
|
|||||||
ethtool bridge-utils ipmitool nvme-cli lsscsi sudo
|
ethtool bridge-utils ipmitool nvme-cli lsscsi sudo
|
||||||
|
|
||||||
# ---------------------------------------------------------------- imaj ayarlari
|
# ---------------------------------------------------------------- imaj ayarlari
|
||||||
|
# amd64 | arm64. arm64 is wired up but has never been built or deployed -
|
||||||
|
# see "Verified status" in the README. Building arm64 on an x86_64 host means
|
||||||
|
# TCG emulation with no KVM, which is very slow; prefer a native arm64 builder.
|
||||||
ARCH ?= amd64
|
ARCH ?= amd64
|
||||||
SUBARCH ?= generic
|
SUBARCH ?= generic
|
||||||
BOOT ?= uefi
|
BOOT ?= uefi
|
||||||
@@ -85,8 +88,21 @@ MAAS_IMAGE_NAME ?= custom/$(IMAGE_NAME)
|
|||||||
# MAAS snap kurulumu icin: /var/snap/maas/current/preseeds
|
# MAAS snap kurulumu icin: /var/snap/maas/current/preseeds
|
||||||
MAAS_PRESEED_DIR ?= /var/snap/maas/current/preseeds
|
MAAS_PRESEED_DIR ?= /var/snap/maas/current/preseeds
|
||||||
|
|
||||||
OVMF_DIR ?= /usr/share/OVMF
|
# UEFI firmware, keyed on the TARGET architecture - an aarch64 guest needs
|
||||||
OVMF_SFX ?= $(shell test -f $(OVMF_DIR)/OVMF_CODE.fd && echo "" || echo "_4M")
|
# AAVMF whatever the build host is. (Upstream packer-maas keys this on the host
|
||||||
|
# architecture instead, which only works when host and target match.)
|
||||||
|
ifeq ($(strip $(ARCH)),arm64)
|
||||||
|
FW_DIR ?= /usr/share/AAVMF
|
||||||
|
FW ?= AAVMF
|
||||||
|
else
|
||||||
|
FW_DIR ?= /usr/share/OVMF
|
||||||
|
FW ?= OVMF
|
||||||
|
endif
|
||||||
|
FW_SFX ?= $(shell test -f $(FW_DIR)/$(FW)_CODE.fd && echo "" || echo "_4M")
|
||||||
|
|
||||||
|
# KVM is only usable when the host and the guest share an architecture.
|
||||||
|
# Otherwise QEMU falls back to TCG emulation, which is very slow.
|
||||||
|
HOST_IS_ARM := $(shell test "$$(uname -m)" = aarch64 && echo true || echo false)
|
||||||
|
|
||||||
# ---------------------------------------------------------------- hedefler
|
# ---------------------------------------------------------------- hedefler
|
||||||
.PHONY: help deps deps-cache check-upstream print-var checkout overlay customize image verify preseed install-preseed upload clean distclean lint
|
.PHONY: help deps deps-cache check-upstream print-var checkout overlay customize image verify preseed install-preseed upload clean distclean lint
|
||||||
@@ -158,6 +174,17 @@ image: checkout $(CUSTOMIZE)
|
|||||||
sed -i -E 's|^([[:space:]]*disk_size[[:space:]]*=[[:space:]]*).*|\1"$(DISK_SIZE)"|' $(TPL)/debian-cloudimg.pkr.hcl
|
sed -i -E 's|^([[:space:]]*disk_size[[:space:]]*=[[:space:]]*).*|\1"$(DISK_SIZE)"|' $(TPL)/debian-cloudimg.pkr.hcl
|
||||||
sed -i -E 's|^([[:space:]]*cpus[[:space:]]*=[[:space:]]*).*|\1$(BUILD_CPUS)|' $(TPL)/debian-cloudimg.pkr.hcl
|
sed -i -E 's|^([[:space:]]*cpus[[:space:]]*=[[:space:]]*).*|\1$(BUILD_CPUS)|' $(TPL)/debian-cloudimg.pkr.hcl
|
||||||
sed -i -E 's|^([[:space:]]*memory[[:space:]]*=[[:space:]]*).*|\1$(BUILD_MEM)|' $(TPL)/debian-cloudimg.pkr.hcl
|
sed -i -E 's|^([[:space:]]*memory[[:space:]]*=[[:space:]]*).*|\1$(BUILD_MEM)|' $(TPL)/debian-cloudimg.pkr.hcl
|
||||||
|
@echo "==> Build VM apt ayari yamalaniyor (IPv4 zorlama + timeout)"
|
||||||
|
# Every apt fetch over roughly 15 MB inside the build VM used to stall for
|
||||||
|
# exactly 31 seconds regardless of its size, which is a connection timeout
|
||||||
|
# rather than a bandwidth limit: QEMU's user-mode network offers IPv6 that
|
||||||
|
# does not actually work, so apt's parallel connections black-hole on it and
|
||||||
|
# only fall back to IPv4 when the timeout expires. Forcing IPv4 took the
|
||||||
|
# 28.5 MB upstream fetch from 31s to 3s. bootcmd runs in cloud-init's
|
||||||
|
# init-local stage, before SSH is up, so this is in place for the upstream
|
||||||
|
# provisioners too, not just ours.
|
||||||
|
sed -i "/^bootcmd:/a\\ - echo 'Acquire::ForceIPv4 \"true\"; Acquire::http::Timeout \"20\"; Acquire::Retries \"3\";' > /etc/apt/apt.conf.d/99-build-speed" $(TPL)/user-data-cloudimg
|
||||||
|
@sed -n '/^bootcmd:/,+1p' $(TPL)/user-data-cloudimg
|
||||||
ifeq ($(strip $(DEBIAN_IMAGE_CHANNEL)),stable)
|
ifeq ($(strip $(DEBIAN_IMAGE_CHANNEL)),stable)
|
||||||
@echo "==> Kararli Debian cloud image kullanilacak (packer onbellegi isabet eder)"
|
@echo "==> Kararli Debian cloud image kullanilacak (packer onbellegi isabet eder)"
|
||||||
sed -i -E 's|/daily/latest/|/latest/|g; s|-daily\.qcow2|.qcow2|g' $(TPL)/debian-cloudimg.pkr.hcl
|
sed -i -E 's|/daily/latest/|/latest/|g; s|-daily\.qcow2|.qcow2|g' $(TPL)/debian-cloudimg.pkr.hcl
|
||||||
@@ -165,8 +192,13 @@ ifeq ($(strip $(DEBIAN_IMAGE_CHANNEL)),stable)
|
|||||||
endif
|
endif
|
||||||
sed -i -E 's|--best --force|-$(GZIP_LEVEL) --force|' $(PM)/scripts/fuse-tar-root
|
sed -i -E 's|--best --force|-$(GZIP_LEVEL) --force|' $(PM)/scripts/fuse-tar-root
|
||||||
@grep -nE 'disk_size|^ cpus|^ memory' $(TPL)/debian-cloudimg.pkr.hcl
|
@grep -nE 'disk_size|^ cpus|^ memory' $(TPL)/debian-cloudimg.pkr.hcl
|
||||||
cp -v $(OVMF_DIR)/OVMF_CODE$(OVMF_SFX).fd $(TPL)/OVMF_CODE.fd
|
cp -v $(FW_DIR)/$(FW)_CODE$(FW_SFX).fd $(TPL)/OVMF_CODE.fd
|
||||||
cp -v $(OVMF_DIR)/OVMF_VARS$(OVMF_SFX).fd $(TPL)/OVMF_VARS.fd
|
cp -v $(FW_DIR)/$(FW)_VARS$(FW_SFX).fd $(TPL)/OVMF_VARS.fd
|
||||||
|
ifeq ($(strip $(ARCH)),arm64)
|
||||||
|
# AAVMF images must be padded to 64 MiB for QEMU's arm64 "virt" machine.
|
||||||
|
truncate -s 64m $(TPL)/OVMF_CODE.fd
|
||||||
|
truncate -s 64m $(TPL)/OVMF_VARS.fd
|
||||||
|
endif
|
||||||
rm -rf $(TPL)/output-cloudimg $(TPL)/seeds-cloudimg.iso
|
rm -rf $(TPL)/output-cloudimg $(TPL)/seeds-cloudimg.iso
|
||||||
cd $(TPL) && PACKER_LOG=$(PACKER_LOG) packer init .
|
cd $(TPL) && PACKER_LOG=$(PACKER_LOG) packer init .
|
||||||
cd $(TPL) && PACKER_LOG=$(PACKER_LOG) packer build \
|
cd $(TPL) && PACKER_LOG=$(PACKER_LOG) packer build \
|
||||||
@@ -174,8 +206,8 @@ endif
|
|||||||
-var debian_version=$(DEBIAN_VERSION) \
|
-var debian_version=$(DEBIAN_VERSION) \
|
||||||
-var architecture=$(ARCH) \
|
-var architecture=$(ARCH) \
|
||||||
-var boot_mode=$(BOOT) \
|
-var boot_mode=$(BOOT) \
|
||||||
-var ovmf_suffix=$(OVMF_SFX) \
|
-var ovmf_suffix=$(FW_SFX) \
|
||||||
-var host_is_arm=false \
|
-var host_is_arm=$(HOST_IS_ARM) \
|
||||||
-var timeout=$(TIMEOUT) \
|
-var timeout=$(TIMEOUT) \
|
||||||
-var customize_script=$(CUSTOMIZE) \
|
-var customize_script=$(CUSTOMIZE) \
|
||||||
-var filename=$(OUTPUT) \
|
-var filename=$(OUTPUT) \
|
||||||
|
|||||||
149
README.md
149
README.md
@@ -14,6 +14,7 @@ variable change away (see [Moving to a new Proxmox release](#moving-to-a-new-pro
|
|||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
- [What this does](#what-this-does)
|
- [What this does](#what-this-does)
|
||||||
|
- [Prebuilt images](#prebuilt-images)
|
||||||
- [Why Debian + the Proxmox repository, and not the Proxmox ISO](#why-debian--the-proxmox-repository-and-not-the-proxmox-iso)
|
- [Why Debian + the Proxmox repository, and not the Proxmox ISO](#why-debian--the-proxmox-repository-and-not-the-proxmox-iso)
|
||||||
- [How it works](#how-it-works)
|
- [How it works](#how-it-works)
|
||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
@@ -27,6 +28,8 @@ variable change away (see [Moving to a new Proxmox release](#moving-to-a-new-pro
|
|||||||
- [Four traps this image works around](#four-traps-this-image-works-around)
|
- [Four traps this image works around](#four-traps-this-image-works-around)
|
||||||
- [Moving to a new Proxmox release](#moving-to-a-new-proxmox-release)
|
- [Moving to a new Proxmox release](#moving-to-a-new-proxmox-release)
|
||||||
- [Build performance](#build-performance)
|
- [Build performance](#build-performance)
|
||||||
|
- [arm64](#arm64)
|
||||||
|
- [Release automation](#release-automation)
|
||||||
- [Repository layout](#repository-layout)
|
- [Repository layout](#repository-layout)
|
||||||
- [Troubleshooting](#troubleshooting)
|
- [Troubleshooting](#troubleshooting)
|
||||||
- [Verified status](#verified-status)
|
- [Verified status](#verified-status)
|
||||||
@@ -163,6 +166,33 @@ fails is retried on the next boot rather than leaving the node half-configured.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Prebuilt images
|
||||||
|
|
||||||
|
Images are built and published automatically:
|
||||||
|
|
||||||
|
**→ [Download the latest image](https://gitea.mynodes.xyz/ilker/maas-proxmox/releases)**
|
||||||
|
|
||||||
|
Each release carries the image, its SHA-256 checksum, the build's metadata
|
||||||
|
(`image-info.txt`) and `SOURCES.md`, the corresponding-source offer required by the
|
||||||
|
licences of the packages inside it. The builds are **unofficial** and not affiliated
|
||||||
|
with Proxmox Server Solutions GmbH.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -LO https://gitea.mynodes.xyz/ilker/maas-proxmox/releases/download/<tag>/maas-image-pve-<version>-amd64.tar.gz
|
||||||
|
curl -LO https://gitea.mynodes.xyz/ilker/maas-proxmox/releases/download/<tag>/maas-image-pve-<version>-amd64.tar.gz.sha256
|
||||||
|
sha256sum -c maas-image-pve-<version>-amd64.tar.gz.sha256
|
||||||
|
|
||||||
|
maas $PROFILE boot-resources create name='custom/proxmox-ve-9' \
|
||||||
|
title='Proxmox VE 9' architecture='amd64/generic' \
|
||||||
|
filetype='tgz' content@=maas-image-pve-<version>-amd64.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
You still need the curtin preseed on your MAAS region controller — see
|
||||||
|
[Quick start](#quick-start) step 5. Downloading an image skips only steps 1-3.
|
||||||
|
|
||||||
|
Prefer building it yourself if you would rather not trust someone else's binary;
|
||||||
|
that is what the rest of this document is about.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -233,7 +263,7 @@ All of these are `make` variables — `sudo make image DISK_SIZE=24G`, and so on
|
|||||||
| Variable | Default | Meaning |
|
| Variable | Default | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `IMAGE_NAME` | `proxmox-ve-9` | MAAS name (`custom/<name>`) and preseed filename |
|
| `IMAGE_NAME` | `proxmox-ve-9` | MAAS name (`custom/<name>`) and preseed filename |
|
||||||
| `ARCH` / `SUBARCH` | `amd64` / `generic` | Target architecture. *Only amd64 was tested.* |
|
| `ARCH` / `SUBARCH` | `amd64` / `generic` | Target architecture. `arm64` is wired up but **never built or deployed** — see [arm64](#arm64). |
|
||||||
| `BOOT` | `uefi` | Boot mode baked into the image. *Only UEFI was tested.* |
|
| `BOOT` | `uefi` | Boot mode baked into the image. *Only UEFI was tested.* |
|
||||||
| `DISK_SIZE` | `16G` | Build VM disk. Upstream's 4G cannot fit Debian + Proxmox |
|
| `DISK_SIZE` | `16G` | Build VM disk. Upstream's 4G cannot fit Debian + Proxmox |
|
||||||
| `BUILD_CPUS` / `BUILD_MEM` | `4` / `4096` | Build VM resources |
|
| `BUILD_CPUS` / `BUILD_MEM` | `4` / `4096` | Build VM resources |
|
||||||
@@ -246,7 +276,7 @@ All of these are `make` variables — `sudo make image DISK_SIZE=24G`, and so on
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `DEBIAN_IMAGE_CHANNEL` | `stable` | `stable` uses a fixed URL so Packer's cache works; `daily` is upstream's default and changes every day. *Only `stable` was tested.* |
|
| `DEBIAN_IMAGE_CHANNEL` | `stable` | `stable` uses a fixed URL so Packer's cache works; `daily` is upstream's default and changes every day. *Only `stable` was tested.* |
|
||||||
| `GZIP_LEVEL` | `6` | Tarball compression. Upstream uses 9 |
|
| `GZIP_LEVEL` | `6` | Tarball compression. Upstream uses 9 |
|
||||||
| `APT_PROXY` | *(empty)* | Local APT cache, e.g. `http://10.0.2.2:3142` — see `make deps-cache`. *Untested.* |
|
| `APT_PROXY` | *(empty)* | Local APT cache, e.g. `http://10.0.2.2:3142` — see `make deps-cache`. *Untested on this image; measured on maas-samba-ad.* |
|
||||||
|
|
||||||
### MAAS
|
### MAAS
|
||||||
|
|
||||||
@@ -707,9 +737,17 @@ All of them are on by default:
|
|||||||
- **`GZIP_LEVEL=6`** — upstream uses `--best` (9). With `pigz` this is noticeably faster
|
- **`GZIP_LEVEL=6`** — upstream uses `--best` (9). With `pigz` this is noticeably faster
|
||||||
for a few percent more size.
|
for a few percent more size.
|
||||||
|
|
||||||
For repeated builds, a local APT cache should remove roughly 700 MB of downloads.
|
- **Forcing IPv4** — the Makefile patches the build VM's cloud-init seed to write
|
||||||
*This path is untested and the figure is an estimate from package sizes, not a
|
`Acquire::ForceIPv4 "true"` before SSH comes up. Without it, every apt download over
|
||||||
measurement:*
|
roughly 15 MB stalled for *exactly* 31 seconds regardless of its size: QEMU's
|
||||||
|
user-mode network offers IPv6 that does not actually work, so apt's parallel
|
||||||
|
connections black-holed on it and only fell back to IPv4 once the timeout expired.
|
||||||
|
Measured on the sibling [maas-samba-ad](https://github.com/ilkermanap/maas-samba-ad)
|
||||||
|
build, the same 28.5 MB fetch went from 31s (914 kB/s) to 3s (9.2 MB/s). The 31s
|
||||||
|
stall is visible in this project's build logs too, but the fix has not yet been
|
||||||
|
measured here.
|
||||||
|
|
||||||
|
For repeated builds you can also put a local APT cache in front:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo make deps-cache # installs apt-cacher-ng
|
sudo make deps-cache # installs apt-cacher-ng
|
||||||
@@ -718,10 +756,94 @@ sudo make image APT_PROXY=http://10.0.2.2:3142
|
|||||||
|
|
||||||
`10.0.2.2` is the build host as seen from Packer's user-mode network. When a proxy is
|
`10.0.2.2` is the build host as seen from Packer's user-mode network. When a proxy is
|
||||||
configured, Debian repositories are rewritten from `https` to `http` so the cache can
|
configured, Debian repositories are rewritten from `https` to `http` so the cache can
|
||||||
serve them; package signatures are still verified.
|
serve them — a cache cannot see inside a `CONNECT` tunnel — and package signatures are
|
||||||
|
still verified. Note that Debian 13 keeps the real mirror URLs in
|
||||||
|
`/etc/apt/mirrors/*.list` behind the `mirror+file:` method, so rewriting `sources.list`
|
||||||
|
alone is not enough.
|
||||||
|
|
||||||
|
*Do not expect much from the cache on its own.* On the samba-ad build a fully warm cache
|
||||||
|
turned a 26.2 MB fetch from 2s into 0s and a 17.9 MB fetch from 1s into 0s — about three
|
||||||
|
seconds off a 4m40s build. Almost all of the time this project's builds spend on the
|
||||||
|
network is the ~800 MB pulled from the Proxmox repository, which the cache *would* serve
|
||||||
|
on a repeat build; that has not been measured.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## arm64
|
||||||
|
|
||||||
|
Proxmox VE 9.2 added **official** arm64 support — same code base, same
|
||||||
|
repositories, same release lifecycle as x86-64, with full support on NVIDIA Grace
|
||||||
|
and Vera platforms and best-effort on other UEFI Armv8-A/Armv9-A hardware. The
|
||||||
|
`pve-no-subscription` repository carries `proxmox-ve`, `pve-manager`,
|
||||||
|
`proxmox-default-kernel` and `pve-qemu-kvm` for arm64.
|
||||||
|
|
||||||
|
This repository is wired for it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo WITH_ARM64=1 ./scripts/install-deps.sh # adds qemu-system-arm, AAVMF
|
||||||
|
sudo make image ARCH=arm64
|
||||||
|
make preseed ARCH=arm64 # -> ..._arm64_generic_proxmox-ve-9
|
||||||
|
make upload ARCH=arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
The firmware is selected from the **target** architecture (`AAVMF` for arm64,
|
||||||
|
`OVMF` for amd64) and padded to 64 MiB as QEMU's arm64 `virt` machine requires;
|
||||||
|
KVM is used only when the host and target architectures match.
|
||||||
|
|
||||||
|
**This has never been run.** Nothing here has been built for arm64, let alone
|
||||||
|
deployed, and it is listed under [Not verified](#not-verified) for that reason.
|
||||||
|
Two things stand between the code and a usable image:
|
||||||
|
|
||||||
|
- **Build speed.** On an x86_64 builder an arm64 build runs under TCG emulation
|
||||||
|
with no KVM. Expect it to be several times slower than the ~11 minutes an amd64
|
||||||
|
build takes; how much slower has not been measured. The I/O optimisations
|
||||||
|
(`eatmydata`, deferred initramfs) help less when the bottleneck is CPU.
|
||||||
|
- **Somewhere to deploy it.** An arm64 image needs arm64 machines behind MAAS to
|
||||||
|
be worth anything, and none were available to test against.
|
||||||
|
|
||||||
|
If you want arm64 seriously, put a native arm64 builder behind a second runner
|
||||||
|
rather than emulating. That removes the speed problem entirely and lets the same
|
||||||
|
pipeline build both architectures.
|
||||||
|
|
||||||
|
## Release automation
|
||||||
|
|
||||||
|
[`.gitea/workflows/build-image.yml`](.gitea/workflows/build-image.yml) builds the image
|
||||||
|
on a self-hosted runner and publishes it as a release.
|
||||||
|
|
||||||
|
It runs **daily**, but rebuilds only when there is a reason to. Proxmox publishes
|
||||||
|
roughly weekly — the trixie repository currently holds 56 `pve-manager` and 28
|
||||||
|
`proxmox-kernel` versions — so an unconditional daily build would produce about
|
||||||
|
45 GB a month of near-identical artifacts. A check that finds nothing costs about
|
||||||
|
30 seconds.
|
||||||
|
|
||||||
|
A rebuild is triggered when any of these holds:
|
||||||
|
|
||||||
|
| Trigger | Why |
|
||||||
|
|---|---|
|
||||||
|
| `pve-manager` differs from the published image | The obvious one |
|
||||||
|
| `proxmox-default-kernel` differs | Kernel security fixes do not bump `pve-manager`, and those matter most |
|
||||||
|
| The newest release is older than `MAX_AGE_DAYS` (30) | Debian base security updates bump neither of the above; without a floor an image could sit unchanged for months |
|
||||||
|
| Manual dispatch with `force` | Escape hatch |
|
||||||
|
|
||||||
|
The comparison reads `image-info.txt` from the last release rather than guessing from
|
||||||
|
tag names, so it reflects what is actually inside the published image.
|
||||||
|
[`scripts/ci/decide-build.sh`](scripts/ci/decide-build.sh) can be run by hand to see the
|
||||||
|
decision without triggering anything.
|
||||||
|
|
||||||
|
Releases are tagged `pve-<version>-<date>` and pruned to the newest
|
||||||
|
`KEEP_RELEASES` (3) — at ~1.5 GB each, unbounded retention fills the server.
|
||||||
|
|
||||||
|
### Runner
|
||||||
|
|
||||||
|
The runner is registered in **host mode**: steps run directly on the build machine as
|
||||||
|
root, because the build needs `/dev/kvm`, `qemu-nbd`, FUSE and root privileges, all of
|
||||||
|
which a container would have to be granted anyway. The consequence is worth stating
|
||||||
|
plainly: anything able to dispatch a workflow in this repository gets root on the build
|
||||||
|
machine. Do not attach this runner to a repository that accepts outside contributions.
|
||||||
|
|
||||||
|
Checkout is a plain `git clone`, not `actions/checkout` — the latter is a JavaScript
|
||||||
|
action and a host-mode runner has no Node.js runtime.
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -730,6 +852,8 @@ scripts/install-deps.sh build host dependencies
|
|||||||
scripts/customize-proxmox.sh.in template for the script that runs inside the build VM
|
scripts/customize-proxmox.sh.in template for the script that runs inside the build VM
|
||||||
scripts/deploy-cluster.sh deploy a whole cluster through MAAS
|
scripts/deploy-cluster.sh deploy a whole cluster through MAAS
|
||||||
scripts/verify-image.sh check a built image's contents
|
scripts/verify-image.sh check a built image's contents
|
||||||
|
scripts/ci/ release pipeline helpers (decision, artifacts, publish, prune)
|
||||||
|
.gitea/workflows/ Gitea Actions pipeline
|
||||||
overlay/ files baked into the image
|
overlay/ files baked into the image
|
||||||
usr/local/sbin/pve-maas-init first-boot state machine
|
usr/local/sbin/pve-maas-init first-boot state machine
|
||||||
etc/pve-maas/pve-maas.conf defaults, with every option documented
|
etc/pve-maas/pve-maas.conf defaults, with every option documented
|
||||||
@@ -861,12 +985,17 @@ plaintext `PVE_ROOT_PASSWORD`, `PVE_ENABLED=false`, `PVE_FQDN`, and every
|
|||||||
**Build variants**
|
**Build variants**
|
||||||
|
|
||||||
- `pve-enterprise` and `pve-test` repositories
|
- `pve-enterprise` and `pve-test` repositories
|
||||||
- arm64
|
- **arm64** — the Makefile and scripts handle it (firmware selection, MAAS
|
||||||
|
architecture, the Debian-kernel check), but no arm64 image has been built and
|
||||||
|
none deployed. See [arm64](#arm64).
|
||||||
- BIOS boot (`BOOT=bios`)
|
- BIOS boot (`BOOT=bios`)
|
||||||
- `DEBIAN_IMAGE_CHANNEL=daily`
|
- `DEBIAN_IMAGE_CHANNEL=daily`
|
||||||
- `APT_PROXY` and `make deps-cache`. The ~700 MB figure quoted under
|
- `APT_PROXY` and `make deps-cache` *for this image*. The cache was measured end to end
|
||||||
[Build performance](#build-performance) is an estimate from download sizes, not a
|
on the sibling maas-samba-ad build, where it saved about three seconds; this project
|
||||||
measurement.
|
pulls far more from the Proxmox repository, and that case has not been measured.
|
||||||
|
- The `Acquire::ForceIPv4` seed patch *for this image*. It was measured on maas-samba-ad
|
||||||
|
(31s to 3s on the same 28.5 MB fetch); the same 31s stall appears in this project's
|
||||||
|
logs, but no build has been run here since the patch was added.
|
||||||
- A non-default `IMAGE_NAME`
|
- A non-default `IMAGE_NAME`
|
||||||
|
|
||||||
**Make targets** — `make upload` (the image was uploaded with the equivalent `maas`
|
**Make targets** — `make upload` (the image was uploaded with the equivalent `maas`
|
||||||
|
|||||||
96
scripts/ci/decide-build.sh
Executable file
96
scripts/ci/decide-build.sh
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Copyright (C) 2026 Ilker Manap
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# Decides whether a rebuild is worth doing, and prints the decision as
|
||||||
|
# key=value lines suitable for $GITHUB_OUTPUT.
|
||||||
|
#
|
||||||
|
# decide-build.sh [max-age-days] (default 30)
|
||||||
|
#
|
||||||
|
# A rebuild happens when any of these is true:
|
||||||
|
#
|
||||||
|
# 1. pve-manager in the repository differs from the published image
|
||||||
|
# 2. proxmox-default-kernel differs — kernel security fixes do not bump
|
||||||
|
# pve-manager, and those are the ones that matter most
|
||||||
|
# 3. the newest release is older than max-age-days — Debian base security
|
||||||
|
# updates bump neither of the above, so without a floor an image could
|
||||||
|
# sit unchanged for months
|
||||||
|
#
|
||||||
|
# Requires GITEA_API and GITEA_TOKEN in the environment.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
|
||||||
|
MAX_AGE_DAYS="${1:-30}"
|
||||||
|
: "${GITEA_API:?GITEA_API required}"
|
||||||
|
: "${GITEA_TOKEN:?GITEA_TOKEN required}"
|
||||||
|
|
||||||
|
SUITE=$(make -s print-var VAR=DEBIAN_SERIES)
|
||||||
|
COMP=$(make -s print-var VAR=PVE_REPO)
|
||||||
|
URI=$(make -s print-var VAR=PVE_REPO_URI)
|
||||||
|
ARCH=$(make -s print-var VAR=ARCH)
|
||||||
|
|
||||||
|
# Newest version of each package we care about, in one pass over the index.
|
||||||
|
PKGS=$(curl -fsS "${URI}/dists/${SUITE}/${COMP}/binary-${ARCH}/Packages.gz" | gunzip)
|
||||||
|
newest() {
|
||||||
|
printf '%s\n' "$PKGS" \
|
||||||
|
| awk -v want="$1" '$1=="Package:" && $2==want {p=1; next} p && $1=="Version:" {print $2; p=0}' \
|
||||||
|
| sort -V | tail -1
|
||||||
|
}
|
||||||
|
UP_PVE=$(newest pve-manager)
|
||||||
|
UP_KERNEL=$(newest proxmox-default-kernel)
|
||||||
|
[ -n "$UP_PVE" ] || { echo "pve-manager not found in ${URI} ${SUITE}/${COMP}" >&2; exit 1; }
|
||||||
|
[ -n "$UP_KERNEL" ] || { echo "proxmox-default-kernel not found" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "upstream: pve-manager=${UP_PVE} proxmox-default-kernel=${UP_KERNEL}" >&2
|
||||||
|
|
||||||
|
# What the newest published release actually contains. image-info.txt is a few
|
||||||
|
# hundred bytes and is published with every release, so this needs no guessing
|
||||||
|
# from tag names.
|
||||||
|
LATEST=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" "${GITEA_API}/releases?limit=1")
|
||||||
|
INFO_URL=$(printf '%s' "$LATEST" | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
r = json.load(sys.stdin)
|
||||||
|
if r:
|
||||||
|
for a in r[0].get("assets", []):
|
||||||
|
if a["name"] == "image-info.txt":
|
||||||
|
print(a["browser_download_url"]); break
|
||||||
|
')
|
||||||
|
CREATED=$(printf '%s' "$LATEST" | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
r = json.load(sys.stdin); print(r[0]["created_at"] if r else "")')
|
||||||
|
|
||||||
|
PREV_PVE="" PREV_KERNEL=""
|
||||||
|
if [ -n "$INFO_URL" ]; then
|
||||||
|
INFO=$(curl -fsSL "$INFO_URL" || true)
|
||||||
|
PREV_PVE=$(printf '%s' "$INFO" | awk -F= '$1=="pve-manager"{print $2}')
|
||||||
|
PREV_KERNEL=$(printf '%s' "$INFO" | awk -F= '$1=="proxmox-default-kernel"{print $2}')
|
||||||
|
fi
|
||||||
|
echo "published: pve-manager=${PREV_PVE:-<none>} proxmox-default-kernel=${PREV_KERNEL:-<none>}" >&2
|
||||||
|
|
||||||
|
AGE_DAYS=99999
|
||||||
|
if [ -n "$CREATED" ]; then
|
||||||
|
AGE_DAYS=$(CREATED="$CREATED" python3 -c '
|
||||||
|
import datetime, os
|
||||||
|
c = datetime.datetime.fromisoformat(os.environ["CREATED"].replace("Z", "+00:00"))
|
||||||
|
print((datetime.datetime.now(datetime.timezone.utc) - c).days)')
|
||||||
|
echo "newest release is ${AGE_DAYS} day(s) old" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILD=no
|
||||||
|
REASON="up to date"
|
||||||
|
if [ -z "$PREV_PVE" ]; then
|
||||||
|
BUILD=yes; REASON="no published image yet"
|
||||||
|
elif [ "$UP_PVE" != "$PREV_PVE" ]; then
|
||||||
|
BUILD=yes; REASON="pve-manager ${PREV_PVE} -> ${UP_PVE}"
|
||||||
|
elif [ "$UP_KERNEL" != "$PREV_KERNEL" ]; then
|
||||||
|
BUILD=yes; REASON="kernel ${PREV_KERNEL} -> ${UP_KERNEL}"
|
||||||
|
elif [ "$AGE_DAYS" -ge "$MAX_AGE_DAYS" ]; then
|
||||||
|
BUILD=yes; REASON="image is ${AGE_DAYS} days old (limit ${MAX_AGE_DAYS}) — picking up Debian updates"
|
||||||
|
fi
|
||||||
|
echo "decision: ${BUILD} (${REASON})" >&2
|
||||||
|
|
||||||
|
printf 'build=%s\n' "$BUILD"
|
||||||
|
printf 'tag=%s\n' "pve-${UP_PVE}-$(date -u +%Y%m%d)"
|
||||||
|
printf 'version=%s\n' "$UP_PVE"
|
||||||
|
printf 'kernel=%s\n' "$UP_KERNEL"
|
||||||
|
printf 'reason=%s\n' "$REASON"
|
||||||
@@ -13,6 +13,11 @@ set -euo pipefail
|
|||||||
TAG="${1:?tag required}"
|
TAG="${1:?tag required}"
|
||||||
VERSION="${2:?version required}"
|
VERSION="${2:?version required}"
|
||||||
DIST="${3:?dist directory required}"
|
DIST="${3:?dist directory required}"
|
||||||
|
|
||||||
|
# DIST cagiranin dizinine goreli olabilir; repo kokune gecmeden once mutlaklastir.
|
||||||
|
DIST="$(cd "$DIST" && pwd)"
|
||||||
|
# make degiskenlerini okuyabilmek icin depo koku gerekiyor.
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
: "${GITEA_API:?GITEA_API required}"
|
: "${GITEA_API:?GITEA_API required}"
|
||||||
: "${GITEA_TOKEN:?GITEA_TOKEN required}"
|
: "${GITEA_TOKEN:?GITEA_TOKEN required}"
|
||||||
|
|
||||||
@@ -20,6 +25,9 @@ REPO_URL="${GITHUB_SERVER_URL:-}/${GITHUB_REPOSITORY:-}"
|
|||||||
RUN="${GITHUB_RUN_NUMBER:-manual}"
|
RUN="${GITHUB_RUN_NUMBER:-manual}"
|
||||||
SHA="${GITHUB_SHA:-}"
|
SHA="${GITHUB_SHA:-}"
|
||||||
IMAGE=$(basename "$(ls "$DIST"/maas-image-*.tar.gz)")
|
IMAGE=$(basename "$(ls "$DIST"/maas-image-*.tar.gz)")
|
||||||
|
MAAS_ARCH=$(make -s print-var VAR=MAAS_ARCH)
|
||||||
|
PVE_MAJOR=$(make -s print-var VAR=PVE_VERSION)
|
||||||
|
IMAGE_NAME=$(make -s print-var VAR=IMAGE_NAME)
|
||||||
|
|
||||||
BODY=$(cat <<BODYEOF
|
BODY=$(cat <<BODYEOF
|
||||||
Unofficial build of a MAAS-deployable Proxmox VE image.
|
Unofficial build of a MAAS-deployable Proxmox VE image.
|
||||||
@@ -32,8 +40,8 @@ Unofficial build of a MAAS-deployable Proxmox VE image.
|
|||||||
Upload to MAAS:
|
Upload to MAAS:
|
||||||
|
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
maas \$PROFILE boot-resources create name='custom/proxmox-ve-9' \\
|
maas \$PROFILE boot-resources create name='custom/${IMAGE_NAME}' \\
|
||||||
title='Proxmox VE 9' architecture='amd64/generic' \\
|
title='Proxmox VE ${PVE_MAJOR}' architecture='${MAAS_ARCH}' \\
|
||||||
filetype='tgz' content@=${IMAGE}
|
filetype='tgz' content@=${IMAGE}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
@@ -45,6 +53,15 @@ the GPL/AGPL licences of the packages inside the image.
|
|||||||
BODYEOF
|
BODYEOF
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# A forced rebuild on the same day, of the same upstream version, produces the
|
||||||
|
# tag that is already published and the API answers 409. Disambiguate with the
|
||||||
|
# run number rather than overwriting a release someone may already have used.
|
||||||
|
if curl -fsS -o /dev/null -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_API}/releases/tags/${TAG}" 2>/dev/null; then
|
||||||
|
TAG="${TAG}-r${RUN}"
|
||||||
|
echo "tag already exists; publishing as ${TAG} instead"
|
||||||
|
fi
|
||||||
|
|
||||||
PAYLOAD=$(TAG="$TAG" VERSION="$VERSION" BODY="$BODY" SHA="$SHA" python3 -c '
|
PAYLOAD=$(TAG="$TAG" VERSION="$VERSION" BODY="$BODY" SHA="$SHA" python3 -c '
|
||||||
import json, os
|
import json, os
|
||||||
print(json.dumps({
|
print(json.dumps({
|
||||||
|
|||||||
@@ -74,8 +74,11 @@ apt-mark hold cloud-init || true
|
|||||||
# Paket imzalari yine dogrulandigi icin guvenlik kaybi yok.
|
# Paket imzalari yine dogrulandigi icin guvenlik kaybi yok.
|
||||||
if [ -n "${http_proxy:-}" ]; then
|
if [ -n "${http_proxy:-}" ]; then
|
||||||
log "APT onbellegi kullaniliyor (${http_proxy}); Debian depolari http'ye cevriliyor"
|
log "APT onbellegi kullaniliyor (${http_proxy}); Debian depolari http'ye cevriliyor"
|
||||||
sed -i 's|https://deb.debian.org|http://deb.debian.org|g; s|https://security.debian.org|http://security.debian.org|g' \
|
# Debian 13 keeps the actual mirror URLs in /etc/apt/mirrors/*.list, reached
|
||||||
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list 2>/dev/null || true
|
# through the "mirror+file:" method, so rewriting sources.list alone misses them.
|
||||||
|
sed -i 's|https://|http://|g' \
|
||||||
|
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources \
|
||||||
|
/etc/apt/sources.list.d/*.list /etc/apt/mirrors/*.list 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "eatmydata kuruluyor"
|
log "eatmydata kuruluyor"
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ apt-get install -y --no-install-recommends \
|
|||||||
qemu-system-x86 qemu-utils ovmf cloud-image-utils \
|
qemu-system-x86 qemu-utils ovmf cloud-image-utils \
|
||||||
libnbd-bin nbdkit fuse2fs cpu-checker
|
libnbd-bin nbdkit fuse2fs cpu-checker
|
||||||
|
|
||||||
|
# arm64 hedefi icin ek paketler. Varsayilan olarak kurulmaz: x86_64 host'ta
|
||||||
|
# arm64 derlemek TCG emulasyonu demektir (KVM yok) ve cok yavastir. Bu yol
|
||||||
|
# hic denenmedi - README'deki "Verified status" bolumune bakin.
|
||||||
|
if [ "${WITH_ARM64:-0}" = "1" ]; then
|
||||||
|
echo "==> arm64 hedefi icin ek paketler (WITH_ARM64=1)"
|
||||||
|
apt-get install -y --no-install-recommends qemu-system-arm qemu-efi-aarch64
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> HashiCorp APT deposu ekleniyor (packer)"
|
echo "==> HashiCorp APT deposu ekleniyor (packer)"
|
||||||
install -d -m 0755 /etc/apt/keyrings
|
install -d -m 0755 /etc/apt/keyrings
|
||||||
curl -fsSL https://apt.releases.hashicorp.com/gpg \
|
curl -fsSL https://apt.releases.hashicorp.com/gpg \
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ check "iSCSI initiator adi yok (dugumde uretilir)" '! present "etc/iscsi/initia
|
|||||||
check "SSH host anahtarlari yok" '! grep -qE "^\./etc/ssh/ssh_host_.*_key$" "$TMP/list"'
|
check "SSH host anahtarlari yok" '! grep -qE "^\./etc/ssh/ssh_host_.*_key$" "$TMP/list"'
|
||||||
check "networking.service etkin DEGIL" '! present "etc/systemd/system/multi-user.target.wants/networking.service"'
|
check "networking.service etkin DEGIL" '! present "etc/systemd/system/multi-user.target.wants/networking.service"'
|
||||||
check "interfaces.new yok (pvenetcommit ezmesin)" '! present "etc/network/interfaces.new"'
|
check "interfaces.new yok (pvenetcommit ezmesin)" '! present "etc/network/interfaces.new"'
|
||||||
check "Debian cekirdegi yok" '! grep -qE "^\./boot/vmlinuz-.*[^e]-(cloud-)?amd64$" "$TMP/list"'
|
# Mimariden bagimsiz: /boot altinda -pve ile bitmeyen hicbir cekirdek olmamali.
|
||||||
|
# Debian'inki amd64'te *-amd64, arm64'te *-arm64 diye adlandirilir; Proxmox'unki
|
||||||
|
# her zaman -pve ile biter. Hic cekirdek yoksa da gecer - PVE cekirdeginin
|
||||||
|
# varligini asagidaki ayri kontrol dogruluyor.
|
||||||
|
check "Debian cekirdegi yok" '! grep -E "^\./boot/vmlinuz-" "$TMP/list" | grep -qv -- "-pve$"'
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "==> Proxmox cekirdegi"
|
echo "==> Proxmox cekirdegi"
|
||||||
|
|||||||
Reference in New Issue
Block a user