From 88af19a7d24cb9b16ecc5cf450ce6d519ca0db65 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 16 Apr 2026 08:19:59 +0200 Subject: [PATCH 1/4] host: add TUF-aware layer download, resolv.conf symlink fix, and bin-overrides - mountSquashfs: detect TUF URLs with ?target= parameter and use tufutil.Download via the TUF client instead of raw HTTP GET, fixing 404 errors on GitHub Pages which uses consistent snapshots with hash-prefixed filenames - Initialize TUF client in libcontainer backend from configured DB path and repo URL - Fix resolv.conf symlink issue on Debian 13+: systemd-resolved makes /etc/resolv.conf a symlink, which breaks bind-mount in overlayfs containers. Write resolv.conf directly into overlay upperdir before bind-mounting - Add bin-overrides mechanism: files in /etc/flynn/bin-overrides/ on the host are copied into container /bin/, allowing fixed binaries to be deployed without rebuilding squashfs images --- host/host.go | 2 + host/libcontainer_backend.go | 100 ++++++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/host/host.go b/host/host.go index 0f359d0f30..38b00aeed7 100755 --- a/host/host.go +++ b/host/host.go @@ -318,6 +318,8 @@ func runDaemon(args *docopt.Args) { PartitionCGroups: partitionCGroups, Logger: logger.New("host.id", hostID, "component", "backend", "backend", "libcontainer"), EnableDHCP: enableDHCP, + TufDBPath: "/etc/flynn/tuf.db", + TufRepository: "https://consolving.github.io/flynn-tuf-repo/repository", }) case "mock": backend = MockBackend{} diff --git a/host/libcontainer_backend.go b/host/libcontainer_backend.go index 04a4a5c644..505175a508 100755 --- a/host/libcontainer_backend.go +++ b/host/libcontainer_backend.go @@ -39,7 +39,9 @@ import ( "github.com/flynn/flynn/pkg/shutdown" "github.com/flynn/flynn/pkg/syslog/rfc5424" "github.com/flynn/flynn/pkg/term" + "github.com/flynn/flynn/pkg/tufutil" "github.com/flynn/flynn/pkg/verify" + tuf "github.com/flynn/go-tuf/client" "github.com/golang/groupcache/singleflight" "github.com/inconshreveable/log15" dhcp "github.com/krolaw/dhcp4" @@ -69,6 +71,8 @@ type LibcontainerConfig struct { PartitionCGroups map[string]int64 Logger log15.Logger EnableDHCP bool + TufDBPath string + TufRepository string } func NewLibcontainerBackend(config *LibcontainerConfig) (Backend, error) { @@ -108,6 +112,13 @@ func NewLibcontainerBackend(config *LibcontainerConfig) (Backend, error) { globalState: &libcontainerGlobalState{}, defaultTmpfs: defaultTmpfs, } + if config.TufDBPath != "" && config.TufRepository != "" { + tufClient, err := newTufClient(config.TufDBPath, config.TufRepository) + if err != nil { + return nil, fmt.Errorf("error initializing TUF client: %s", err) + } + l.tufClient = tufClient + } l.httpClient = &http.Client{Transport: &http.Transport{ Dial: dialer.RetryDial(l.discoverdDial), }} @@ -144,6 +155,7 @@ type LibcontainerBackend struct { layerLoader singleflight.Group httpClient *http.Client discoverdClient *discoverd.Client + tufClient *tuf.Client } type Container struct { @@ -625,6 +637,65 @@ func (l *LibcontainerBackend) Run(job *host.Job, runConfig *RunConfig, rateLimit return err } + // On Debian 13+ (and other systemd-resolved systems), /etc/resolv.conf + // in the base image is a symlink to /run/systemd/resolve/resolv.conf. + // Bind-mounting over a symlink doesn't work correctly with overlayfs: + // libcontainer's securejoin resolves the symlink to a different path, + // so the container ends up using the host's resolv.conf instead of + // discoverd's DNS. Fix this by replacing any symlink in the overlay + // upperdir with a copy of our resolv.conf, so the merged view has a + // regular file that the bind mount can target correctly. + containerResolvConf := filepath.Join(diffDir, "etc", "resolv.conf") + if err := os.MkdirAll(filepath.Join(diffDir, "etc"), 0755); err != nil { + log.Error("error creating container /etc in overlay upperdir", "err", err) + return err + } + resolvData, err := os.ReadFile(l.resolvConf) + if err != nil { + log.Error("error reading resolv.conf", "err", err) + return err + } + // Remove any existing symlink before writing the regular file + os.Remove(containerResolvConf) + if err := os.WriteFile(containerResolvConf, resolvData, 0644); err != nil { + log.Error("error writing resolv.conf to overlay upperdir", "err", err) + return err + } + + // Inject binary overrides from host into the container's overlay upperdir. + // This allows deploying fixed versions of component binaries (e.g. flynn-postgres, + // flannel) without rebuilding the squashfs images. Any file placed in + // /etc/flynn/bin-overrides/ on the host will replace the corresponding + // file in the container's /bin/ directory. + binOverridesDir := "/etc/flynn/bin-overrides" + if entries, err := os.ReadDir(binOverridesDir); err == nil { + containerBinDir := filepath.Join(diffDir, "bin") + if err := os.MkdirAll(containerBinDir, 0755); err != nil { + log.Error("error creating container /bin in overlay upperdir", "err", err) + return err + } + for _, entry := range entries { + if entry.IsDir() { + continue + } + srcPath := filepath.Join(binOverridesDir, entry.Name()) + dstPath := filepath.Join(containerBinDir, entry.Name()) + data, err := os.ReadFile(srcPath) + if err != nil { + log.Error("error reading bin override", "file", entry.Name(), "err", err) + continue + } + info, _ := entry.Info() + mode := info.Mode() + os.Remove(dstPath) + if err := os.WriteFile(dstPath, data, mode); err != nil { + log.Error("error writing bin override", "file", entry.Name(), "err", err) + continue + } + log.Info("injected bin override", "file", entry.Name()) + } + } + config.Mounts = append(config.Mounts, bindMount(l.InitPath, "/.containerinit", false), bindMount(l.resolvConf, "/etc/resolv.conf", false), @@ -878,15 +949,28 @@ func (l *LibcontainerBackend) mountSquashfs(m *host.Mountspec) (string, error) { defer f.Close() layer = f case "http", "https": - res, err := l.httpClient.Get(m.URL) - if err != nil { - return "", fmt.Errorf("error getting squashfs layer from %s: %s", m.URL, err) - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("error getting squashfs layer from %s: unexpected HTTP status %s", m.URL, res.Status) + // Check if this is a TUF URL with a ?target= parameter. + // TUF URLs use consistent snapshots with hash-prefixed filenames + // that can't be fetched directly from a static file server. + // Use the TUF client to resolve and download these layers. + if target := u.Query().Get("target"); target != "" && l.tufClient != nil { + tmp, err := tufutil.Download(l.tufClient, target) + if err != nil { + return "", fmt.Errorf("error getting squashfs layer via TUF from %s: %s", m.URL, err) + } + defer tmp.Close() + layer = tmp + } else { + res, err := l.httpClient.Get(m.URL) + if err != nil { + return "", fmt.Errorf("error getting squashfs layer from %s: %s", m.URL, err) + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return "", fmt.Errorf("error getting squashfs layer from %s: unexpected HTTP status %s", m.URL, res.Status) + } + layer = res.Body } - layer = res.Body default: return "", fmt.Errorf("unknown layer URI scheme: %s", u.Scheme) } From adb6ec012559ee306f8d2a11b35928b4ea47604e Mon Sep 17 00:00:00 2001 From: root Date: Thu, 16 Apr 2026 10:40:04 +0200 Subject: [PATCH 2/4] builder: migrate base images from Ubuntu 18.04/16.04 to Ubuntu 24.04 LTS (Noble) Replace ubuntu-bionic and ubuntu-xenial with ubuntu-noble across all component images, build scripts, and Go source code. This consolidates all non-legacy components onto a single supported LTS base. Key changes: - New ubuntu-noble.sh base image script (debootstrap-based) - New heroku-24 stack replacing heroku-18 (runtime + build) - PostgreSQL 11 -> 16, MariaDB 10.1 -> 10.11 LTS, MongoDB 3.2 -> 7.0, Redis from PPA -> Ubuntu main repos - Go toolchain in builder: 1.13.1 -> 1.22.12 - CI Dockerfile and workflow: debian:bookworm-slim -> ubuntu:24.04 - ubuntu-setup.sh handles both DEB822 (Noble) and legacy sources.list - host/img/packages.sh: remove xenial kernel pinning, update jq to 1.7.1 - All slug env vars: SLUGBUILDER/SLUGRUNNER_18 -> _24 - Cedar-14 (ubuntu-trusty) kept unchanged for legacy buildpack compat --- .github/workflows/ci.yml | 8 +- Dockerfile.ci | 2 +- appliance/mariadb/img/packages.sh | 16 +-- appliance/mongodb/img/packages.sh | 11 +- .../postgresql/cmd/flynn-postgres/main.go | 2 +- appliance/postgresql/img/packages.sh | 34 +++-- appliance/postgresql/process.go | 2 +- appliance/redis/img/packages.sh | 5 +- bootstrap/manifest_template.json | 16 +-- builder/build.go | 2 +- builder/img/go.sh | 8 +- builder/img/heroku-24-build.sh | 82 ++++++++++++ builder/img/heroku-24.sh | 123 ++++++++++++++++++ builder/img/ubuntu-noble.sh | 55 ++++++++ builder/manifest.json | 65 ++++----- builder/ubuntu-setup.sh | 13 +- cli/export.go | 4 +- controller/examples/examples.go | 6 +- gitreceive/receiver/flynn-receive.go | 8 +- host/cli/bootstrap.go | 16 +-- host/img/packages.sh | 12 +- script/export-tuf/main.go | 44 +++---- test/test_backup.go | 6 +- test/test_gitreceive.go | 2 +- test/test_release.go | 2 +- util/release/images_template.json | 4 +- 26 files changed, 402 insertions(+), 146 deletions(-) create mode 100755 builder/img/heroku-24-build.sh create mode 100755 builder/img/heroku-24.sh create mode 100755 builder/img/ubuntu-noble.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d081c4fecb..d27daa334e 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,9 +11,9 @@ name: CI on: push: - branches: [tuf-rebuild, master, go-1.22-upgrade] + branches: [tuf-rebuild, master, go-1.22-upgrade, debian13-cgroups-v2-bootstrap] pull_request: - branches: [tuf-rebuild, master] + branches: [tuf-rebuild, master, debian13-cgroups-v2-bootstrap] workflow_dispatch: jobs: @@ -21,7 +21,7 @@ jobs: name: Build all components runs-on: ubuntu-latest container: - image: debian:bookworm-slim + image: ubuntu:24.04 steps: - name: Install system dependencies @@ -64,7 +64,7 @@ jobs: name: Unit tests runs-on: ubuntu-latest container: - image: debian:bookworm-slim + image: ubuntu:24.04 steps: - name: Install system dependencies diff --git a/Dockerfile.ci b/Dockerfile.ci index 42f7413e41..9407d4b9ba 100755 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -10,7 +10,7 @@ # For unit tests: # docker run --rm -v $(pwd):/go/src/github.com/flynn/flynn -w /go/src/github.com/flynn/flynn flynn-ci make test-unit-standalone -FROM debian:bookworm-slim +FROM ubuntu:24.04 # Avoid interactive prompts during package installation ENV DEBIAN_FRONTEND=noninteractive diff --git a/appliance/mariadb/img/packages.sh b/appliance/mariadb/img/packages.sh index a3ffe70bfc..37f34bb970 100755 --- a/appliance/mariadb/img/packages.sh +++ b/appliance/mariadb/img/packages.sh @@ -3,15 +3,15 @@ export DEBIAN_FRONTEND=noninteractive apt-get update -apt-get install -y software-properties-common apt-transport-https -apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 \ - 4D1BB29D63D98E422B2113B19334A25F8507EFA5 \ - 177F4010FE56CA3336300305F1656F24C74CD1D8 \ +apt-get install -y curl ca-certificates gnupg software-properties-common apt-transport-https + +# Add MariaDB 10.11 LTS repository for Noble +curl -fsSL https://mariadb.org/mariadb_release_signing_key.pgp \ + -o /etc/apt/keyrings/mariadb-keyring.pgp +echo "deb [signed-by=/etc/apt/keyrings/mariadb-keyring.pgp] https://dlm.mariadb.com/repo/mariadb-server/10.11/repo/ubuntu noble main" \ + > /etc/apt/sources.list.d/mariadb.list -add-apt-repository 'deb http://nyc2.mirrors.digitalocean.com/mariadb/repo/10.1/ubuntu bionic main' -add-apt-repository 'deb http://repo.percona.com/apt bionic main' apt-get update -apt-get install -y sudo -apt-get install -y mariadb-server percona-xtrabackup +apt-get install -y sudo mariadb-server mariadb-backup apt-get clean apt-get autoremove -y diff --git a/appliance/mongodb/img/packages.sh b/appliance/mongodb/img/packages.sh index 7784a285c9..bec0af2059 100755 --- a/appliance/mongodb/img/packages.sh +++ b/appliance/mongodb/img/packages.sh @@ -2,8 +2,15 @@ export DEBIAN_FRONTEND=noninteractive -apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 42F3E95A2C4F08279C4960ADD68FA50FEA312927 -echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" > /etc/apt/sources.list.d/mongodb-org-3.2.list +apt-get update +apt-get install -y curl ca-certificates gnupg + +# Add MongoDB 7.0 repository for Noble +curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc \ + -o /etc/apt/keyrings/mongodb-server-7.0.gpg +echo "deb [signed-by=/etc/apt/keyrings/mongodb-server-7.0.gpg] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/7.0 multiverse" \ + > /etc/apt/sources.list.d/mongodb-org-7.0.list + apt-get update apt-get install -y sudo mongodb-org apt-get clean diff --git a/appliance/postgresql/cmd/flynn-postgres/main.go b/appliance/postgresql/cmd/flynn-postgres/main.go index ed0aa0722d..08942a9706 100755 --- a/appliance/postgresql/cmd/flynn-postgres/main.go +++ b/appliance/postgresql/cmd/flynn-postgres/main.go @@ -63,7 +63,7 @@ func main() { ID: id, Singleton: singleton, DataDir: filepath.Join(dataDir, "db"), - BinDir: "/usr/lib/postgresql/11/bin/", + BinDir: "/usr/lib/postgresql/16/bin/", Password: password, Logger: log.New("component", "postgres"), TimescaleDB: false, diff --git a/appliance/postgresql/img/packages.sh b/appliance/postgresql/img/packages.sh index 09deb5a2dd..c7cfd5ce22 100755 --- a/appliance/postgresql/img/packages.sh +++ b/appliance/postgresql/img/packages.sh @@ -2,25 +2,31 @@ export DEBIAN_FRONTEND=noninteractive -apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 -echo "deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main" >> /etc/apt/sources.list.d/postgresql.list +# Install prerequisites apt-get update -apt-get dist-upgrade -y -apt-get -qy --fix-missing --force-yes install language-pack-en software-properties-common -update-locale LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LC_ALL=en_US.UTF-8 -dpkg-reconfigure locales -apt-get -y install curl sudo -add-apt-repository ppa:timescale/timescaledb-ppa +apt-get install -y curl ca-certificates gnupg lsb-release + +# Add PostgreSQL APT repository (PGDG) for Noble +install -d /usr/share/postgresql-common/pgdg +curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ + -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc +echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] http://apt.postgresql.org/pub/repos/apt/ noble-pgdg main" \ + > /etc/apt/sources.list.d/pgdg.list + apt-get update +apt-get dist-upgrade -y apt-get install -y -q \ + language-pack-en \ less \ - postgresql-11 \ - postgresql-contrib-11 \ - postgresql-11-pgextwlist \ - postgresql-11-postgis-2.5 \ - postgresql-11-pgrouting \ - timescaledb-postgresql-11 + sudo \ + postgresql-16 \ + postgresql-contrib \ + postgresql-16-postgis-3 \ + postgresql-16-pgrouting apt-get clean apt-get autoremove -y +update-locale LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LC_ALL=en_US.UTF-8 +dpkg-reconfigure locales + echo "\set HISTFILE /dev/null" > /root/.psqlrc diff --git a/appliance/postgresql/process.go b/appliance/postgresql/process.go index ab74f6d358..4703f4196f 100755 --- a/appliance/postgresql/process.go +++ b/appliance/postgresql/process.go @@ -116,7 +116,7 @@ func NewProcess(c Config) *Process { p.port = "5432" } if p.binDir == "" { - p.binDir = "/usr/lib/postgresql/11/bin/" + p.binDir = "/usr/lib/postgresql/16/bin/" } if p.dataDir == "" { p.dataDir = "/data" diff --git a/appliance/redis/img/packages.sh b/appliance/redis/img/packages.sh index 086534afed..678e9c72fc 100755 --- a/appliance/redis/img/packages.sh +++ b/appliance/redis/img/packages.sh @@ -4,10 +4,7 @@ export DEBIAN_FRONTEND=noninteractive apt-get update apt-get dist-upgrade -y -apt-get install -y curl software-properties-common -add-apt-repository ppa:chris-lea/redis-server -apt-get update apt-get install -y redis-server apt-get clean apt-get autoremove -y -mkdir /data +mkdir -p /data diff --git a/bootstrap/manifest_template.json b/bootstrap/manifest_template.json index eb7d3bdcd8..f92444a810 100755 --- a/bootstrap/manifest_template.json +++ b/bootstrap/manifest_template.json @@ -274,9 +274,9 @@ } }, { - "id": "slugbuilder-18-image", + "id": "slugbuilder-24-image", "action": "create-artifact", - "artifact": $image_artifact[slugbuilder-18] + "artifact": $image_artifact[slugbuilder-24] }, { "id": "slugbuilder-14-image", @@ -284,9 +284,9 @@ "artifact": $image_artifact[slugbuilder-14] }, { - "id": "slugrunner-18-image", + "id": "slugrunner-24-image", "action": "create-artifact", - "artifact": $image_artifact[slugrunner-18] + "artifact": $image_artifact[slugrunner-24] }, { "id": "slugrunner-14-image", @@ -490,9 +490,9 @@ "release": { "env": { "CONTROLLER_KEY": "{{ (index .StepData \"controller-key\").Data }}", - "SLUGBUILDER_18_IMAGE_ID": "{{ (index .StepData \"slugbuilder-18-image\").ID }}", + "SLUGBUILDER_24_IMAGE_ID": "{{ (index .StepData \"slugbuilder-24-image\").ID }}", "SLUGBUILDER_14_IMAGE_ID": "{{ (index .StepData \"slugbuilder-14-image\").ID }}", - "SLUGRUNNER_18_IMAGE_ID": "{{ (index .StepData \"slugrunner-18-image\").ID }}", + "SLUGRUNNER_24_IMAGE_ID": "{{ (index .StepData \"slugrunner-24-image\").ID }}", "SLUGRUNNER_14_IMAGE_ID": "{{ (index .StepData \"slugrunner-14-image\").ID }}" }, "processes": { @@ -643,9 +643,9 @@ "release": { "env": { "CONTROLLER_KEY": "{{ (index .StepData \"controller-key\").Data }}", - "SLUGBUILDER_18_IMAGE_ID": "{{ (index .StepData \"slugbuilder-18-image\").ID }}", + "SLUGBUILDER_24_IMAGE_ID": "{{ (index .StepData \"slugbuilder-24-image\").ID }}", "SLUGBUILDER_14_IMAGE_ID": "{{ (index .StepData \"slugbuilder-14-image\").ID }}", - "SLUGRUNNER_18_IMAGE_ID": "{{ (index .StepData \"slugrunner-18-image\").ID }}", + "SLUGRUNNER_24_IMAGE_ID": "{{ (index .StepData \"slugrunner-24-image\").ID }}", "SLUGRUNNER_14_IMAGE_ID": "{{ (index .StepData \"slugrunner-14-image\").ID }}" } } diff --git a/builder/build.go b/builder/build.go index 968a8f4322..61d4253061 100755 --- a/builder/build.go +++ b/builder/build.go @@ -54,7 +54,7 @@ Build Flynn images using builder/manifest.json. type Builder struct { // baseLayer is used when building an image which has no - // dependencies (e.g. the ubuntu-bionic image) + // dependencies (e.g. the ubuntu-noble image) baseLayer *host.Mountspec // artifacts is a map of built artifacts and is written to diff --git a/builder/img/go.sh b/builder/img/go.sh index d6916d9f64..111e9be7d2 100755 --- a/builder/img/go.sh +++ b/builder/img/go.sh @@ -2,8 +2,8 @@ set -eo pipefail -go_version="1.13.1" -go_shasum="94f874037b82ea5353f4061e543681a0e79657f787437974214629af8407d124" +go_version="1.22.12" +go_shasum="4fa4f869b0f7fc6bb1eb2660e74657fbf04cdd290b5aef905585c86051b34d43" gobin_commit="ef6664e41f0bfe3007869844d318bb2bfa2627f9" dir="/usr/local" @@ -11,8 +11,8 @@ apt-get update apt-get install --yes git build-essential apt-get clean -curl -fsSLo /tmp/go.tar.gz "https://storage.googleapis.com/golang/go${go_version}.linux-amd64.tar.gz" -echo "${go_shasum} /tmp/go.tar.gz" | shasum -c - +curl -fsSLo /tmp/go.tar.gz "https://go.dev/dl/go${go_version}.linux-amd64.tar.gz" +echo "${go_shasum} /tmp/go.tar.gz" | sha256sum -c - rm -rf "${dir}/go" tar xzf /tmp/go.tar.gz -C "${dir}" rm /tmp/go.tar.gz diff --git a/builder/img/heroku-24-build.sh b/builder/img/heroku-24-build.sh new file mode 100755 index 0000000000..36959e26fe --- /dev/null +++ b/builder/img/heroku-24-build.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Heroku-24 build stack — development packages for Noble +# Derived from https://github.com/heroku/stack-images + +set -e +export LC_ALL=C +export DEBIAN_FRONTEND=noninteractive + +apt-get update + +apt-get install -y --no-install-recommends \ + autoconf \ + automake \ + bison \ + build-essential \ + gettext \ + libacl1-dev \ + libapt-pkg-dev \ + libargon2-dev \ + libattr1-dev \ + libaudit-dev \ + libbsd-dev \ + libbz2-dev \ + libcairo2-dev \ + libcap-dev \ + libcurl4-openssl-dev \ + libdb-dev \ + libev-dev \ + libevent-dev \ + libexif-dev \ + libffi-dev \ + libgcrypt20-dev \ + libgd-dev \ + libgdbm-dev \ + libgeoip-dev \ + libglib2.0-dev \ + libgnutls28-dev \ + libgs-dev \ + libicu-dev \ + libidn2-dev \ + libjpeg-dev \ + libkeyutils-dev \ + libkmod-dev \ + libkrb5-dev \ + libldap-dev \ + liblz4-dev \ + libmagic-dev \ + libmagickwand-dev \ + libmemcached-dev \ + libmysqlclient-dev \ + libncurses-dev \ + libnetpbm10-dev \ + libpam0g-dev \ + libpopt-dev \ + libpq-dev \ + librabbitmq-dev \ + libreadline-dev \ + libseccomp-dev \ + libselinux1-dev \ + libsemanage-dev \ + libsodium-dev \ + libssl-dev \ + libsystemd-dev \ + libtool \ + libudev-dev \ + libuv1-dev \ + libwrap0-dev \ + libxml2-dev \ + libxslt1-dev \ + libyaml-dev \ + libzip-dev \ + postgresql-server-dev-16 \ + python3-dev \ + ruby-dev \ + zlib1g-dev + +cd / +rm -rf /root/* +rm -rf /tmp/* +rm -rf /var/cache/apt/archives/*.deb +rm -rf /var/lib/apt/lists/* diff --git a/builder/img/heroku-24.sh b/builder/img/heroku-24.sh new file mode 100755 index 0000000000..d953b89fb7 --- /dev/null +++ b/builder/img/heroku-24.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# Heroku-24 runtime stack based on Ubuntu 24.04 (Noble) +# Derived from https://github.com/heroku/stack-images + +set -e +export LC_ALL=C +export DEBIAN_FRONTEND=noninteractive + +apt-get update +apt-get upgrade -y +apt-get install -y --no-install-recommends \ + apt-transport-https \ + apt-utils \ + bind9-host \ + bzip2 \ + coreutils \ + curl \ + dnsutils \ + ed \ + file \ + fontconfig \ + gcc \ + geoip-database \ + ghostscript \ + git \ + gsfonts \ + imagemagick \ + iproute2 \ + iputils-tracepath \ + language-pack-en \ + less \ + libargon2-1 \ + libcairo2 \ + libcurl4t64 \ + libdatrie1 \ + libev4t64 \ + libevent-2.1-7t64 \ + libevent-core-2.1-7t64 \ + libevent-extra-2.1-7t64 \ + libevent-openssl-2.1-7t64 \ + libevent-pthreads-2.1-7t64 \ + libexif12 \ + libgd3 \ + libgdk-pixbuf-2.0-0 \ + libgdk-pixbuf2.0-common \ + libgnutls-openssl27t64 \ + libgraphite2-3 \ + libgs10 \ + libharfbuzz0b \ + libmagickcore-6.q16-7-extra \ + libmemcached11t64 \ + libpango-1.0-0 \ + libpangocairo-1.0-0 \ + libpangoft2-1.0-0 \ + libpixman-1-0 \ + librabbitmq4 \ + librsvg2-2 \ + librsvg2-common \ + libsasl2-modules \ + libseccomp2 \ + libsodium23 \ + libthai-data \ + libthai0 \ + libuv1t64 \ + libxcb-render0 \ + libxcb-shm0 \ + libxrender1 \ + libxslt1.1 \ + libzip4t64 \ + locales \ + lsb-release \ + make \ + netcat-openbsd \ + openssh-client \ + openssh-server \ + patch \ + postgresql-client-16 \ + rename \ + rsync \ + ruby \ + shared-mime-info \ + socat \ + stunnel4 \ + tar \ + telnet \ + tzdata \ + unzip \ + wget \ + xz-utils \ + zip \ + pigz \ + daemontools \ + vim-tiny + +cat > /etc/ImageMagick-6/policy.xml <<'IMAGEMAGICK_POLICY' + + + + + + + + + + + + + +IMAGEMAGICK_POLICY + +# install the JDK for certificates, then remove it +apt-get install -y --no-install-recommends ca-certificates-java openjdk-21-jre-headless +apt-get remove -y ca-certificates-java +apt-get -y --purge autoremove +apt-get purge -y openjdk-21-jre-headless +test "$(file -b /etc/ssl/certs/java/cacerts)" = "Java KeyStore" + +cd / +rm -rf /root/* +rm -rf /tmp/* +rm -rf /var/cache/apt/archives/*.deb +rm -rf /var/lib/apt/lists/* diff --git a/builder/img/ubuntu-noble.sh b/builder/img/ubuntu-noble.sh new file mode 100755 index 0000000000..8b8e14249b --- /dev/null +++ b/builder/img/ubuntu-noble.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# +# Build an Ubuntu 24.04 (Noble) rootfs as a squashfs layer. +# +# Requires debootstrap on the host: +# apt-get install -y debootstrap +# + +set -e + +TMP="$(mktemp --directory)" + +cleanup() { + # Unmount bind mounts + umount "${TMP}/root/dev/pts" 2>/dev/null || true + umount "${TMP}/root/dev" 2>/dev/null || true + umount "${TMP}/root/proc" 2>/dev/null || true + umount "${TMP}/root/sys" 2>/dev/null || true + # Clear resolv.conf + >"${TMP}/root/etc/resolv.conf" 2>/dev/null || true + rm -rf "${TMP}" +} +trap cleanup EXIT + +mkdir -p "${TMP}/root" + +# Use debootstrap to create a minimal Noble rootfs +if command -v debootstrap >/dev/null 2>&1; then + echo "Building Ubuntu Noble rootfs via debootstrap..." + debootstrap --variant=minbase --arch=amd64 noble "${TMP}/root" http://archive.ubuntu.com/ubuntu +else + # Fallback: download the minimal cloud image root tarball + echo "Building Ubuntu Noble rootfs via cloud image download..." + URL="https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-amd64-root.tar.xz" + curl -fSLo "${TMP}/ubuntu.tar.xz" "${URL}" + tar xf "${TMP}/ubuntu.tar.xz" -C "${TMP}/root" +fi + +# Set up bind mounts for chroot +mount --bind /dev "${TMP}/root/dev" +mount --bind /dev/pts "${TMP}/root/dev/pts" +mount -t proc proc "${TMP}/root/proc" +mount -t sysfs sysfs "${TMP}/root/sys" + +cp "/etc/resolv.conf" "${TMP}/root/etc/resolv.conf" + +chroot "${TMP}/root" bash -e <"builder/ubuntu-setup.sh" + +# Unmount before creating squashfs +umount "${TMP}/root/sys" 2>/dev/null || true +umount "${TMP}/root/proc" 2>/dev/null || true +umount "${TMP}/root/dev/pts" 2>/dev/null || true +umount "${TMP}/root/dev" 2>/dev/null || true + +mksquashfs "${TMP}/root" "/mnt/out/layer.squashfs" -noappend diff --git a/builder/manifest.json b/builder/manifest.json index 9be5f61d8b..4356235082 100755 --- a/builder/manifest.json +++ b/builder/manifest.json @@ -56,27 +56,10 @@ ] }, { - "id": "ubuntu-xenial", + "id": "ubuntu-noble", "layers": [ { - "script": "builder/img/ubuntu-xenial.sh", - "inputs": [ - "builder/ubuntu-setup.sh" - ], - "limits": { - "temp_disk": "2GB" - }, - "linux_capabilities": [ - "CAP_SYS_ADMIN" - ] - } - ] - }, - { - "id": "ubuntu-bionic", - "layers": [ - { - "script": "builder/img/ubuntu-bionic.sh", + "script": "builder/img/ubuntu-noble.sh", "inputs": [ "builder/ubuntu-setup.sh" ], @@ -99,7 +82,7 @@ }, { "id": "go", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "script": "builder/img/go.sh", @@ -182,7 +165,7 @@ }, { "id": "host", - "base": "ubuntu-xenial", + "base": "ubuntu-noble", "layers": [ { "name": "host-packages", @@ -275,7 +258,7 @@ }, { "id": "postgres", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "name": "postgres-packages", @@ -303,7 +286,7 @@ }, { "id": "mariadb", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "name": "mariadb-packages", @@ -331,7 +314,7 @@ }, { "id": "mongodb", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "name": "mongodb-packages", @@ -361,7 +344,7 @@ }, { "id": "redis", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "name": "redis-packages", @@ -391,7 +374,7 @@ }, { "id": "blobstore", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "gobuild": { @@ -433,7 +416,7 @@ }, { "id": "gitreceive", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "name": "gitreceive-packages", @@ -461,7 +444,7 @@ }, { "id": "tarreceive", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "gobuild": { @@ -493,7 +476,7 @@ }, { "id": "taffy", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "name": "taffy-packages", @@ -517,11 +500,11 @@ } }, { - "id": "heroku-18", - "base": "ubuntu-bionic", + "id": "heroku-24", + "base": "ubuntu-noble", "layers": [ { - "script": "builder/img/heroku-18.sh", + "script": "builder/img/heroku-24.sh", "limits": { "temp_disk": "2G" } @@ -529,11 +512,11 @@ ] }, { - "id": "heroku-18-build", - "base": "heroku-18", + "id": "heroku-24-build", + "base": "heroku-24", "layers": [ { - "script": "builder/img/heroku-18-build.sh", + "script": "builder/img/heroku-24-build.sh", "limits": { "temp_disk": "2G" } @@ -553,8 +536,8 @@ ] }, { - "id": "slugbuilder-18", - "base": "heroku-18-build", + "id": "slugbuilder-24", + "base": "heroku-24-build", "layers": [ { "name": "slugbuilder-packages", @@ -615,8 +598,8 @@ } }, { - "id": "slugrunner-18", - "base": "heroku-18", + "id": "slugrunner-24", + "base": "heroku-24", "layers": [ { "copy": { @@ -727,7 +710,7 @@ }, { "id": "test", - "base": "ubuntu-bionic", + "base": "ubuntu-noble", "layers": [ { "name": "test-packages", @@ -771,7 +754,7 @@ "base": "busybox", "layers": [ { - "build_with": "ubuntu-bionic", + "build_with": "ubuntu-noble", "name": "test-apps-minio", "script": "test/apps/minio.sh" }, diff --git a/builder/ubuntu-setup.sh b/builder/ubuntu-setup.sh index 4f47ce860d..152389b92d 100755 --- a/builder/ubuntu-setup.sh +++ b/builder/ubuntu-setup.sh @@ -40,8 +40,17 @@ apt-get install --yes squashfs-tools curl gnupg rm -rf /var/lib/apt/lists/* # this forces "apt-get update" in dependent images, which is also good -# enable the universe -sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list +# enable the universe repository +if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then + # Noble (24.04+) uses DEB822 format — universe is typically already included + # by debootstrap, but ensure it's there + if ! grep -q 'Components:.*universe' /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null; then + sed -i 's/^Components: \(.*\)/Components: \1 universe/' /etc/apt/sources.list.d/ubuntu.sources + fi +elif [ -f /etc/apt/sources.list ]; then + # Bionic/Xenial/Trusty use traditional sources.list format + sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list +fi # make systemd-detect-virt return "docker" # See: https://github.com/systemd/systemd/blob/aa0c34279ee40bce2f9681b496922dedbadfca19/src/basic/virt.c#L434 diff --git a/cli/export.go b/cli/export.go index 89f23bdf6b..3002538ddd 100755 --- a/cli/export.go +++ b/cli/export.go @@ -742,8 +742,8 @@ func runImport(args *docopt.Args, client controller.Client) error { var slugRunnerID string stack := release.Meta["slugrunner.stack"] switch stack { - case "heroku-18": - slugRunnerID = gitreceiveRelease.Env["SLUGRUNNER_18_IMAGE_ID"] + case "heroku-24": + slugRunnerID = gitreceiveRelease.Env["SLUGRUNNER_24_IMAGE_ID"] case "cedar-14", "": slugRunnerID = gitreceiveRelease.Env["SLUGRUNNER_14_IMAGE_ID"] if slugRunnerID == "" { diff --git a/controller/examples/examples.go b/controller/examples/examples.go index 0dee3d06e0..8262cb2cea 100755 --- a/controller/examples/examples.go +++ b/controller/examples/examples.go @@ -178,8 +178,8 @@ func (e *generator) getInitialAppRelease() { if err != nil { return } - if artifact, err := e.client.GetArtifact(appRelease.Env["SLUGRUNNER_18_IMAGE_ID"]); err == nil { - e.resourceIds["SLUGRUNNER_18_IMAGE_URI"] = artifact.URI + if artifact, err := e.client.GetArtifact(appRelease.Env["SLUGRUNNER_24_IMAGE_ID"]); err == nil { + e.resourceIds["SLUGRUNNER_24_IMAGE_URI"] = artifact.URI } } @@ -324,7 +324,7 @@ func (e *generator) createArtifact() { } artifact := &ct.Artifact{ Type: ct.ArtifactTypeFlynn, - URI: e.resourceIds["SLUGRUNNER_18_IMAGE_URI"], + URI: e.resourceIds["SLUGRUNNER_24_IMAGE_URI"], RawManifest: manifest.RawManifest(), Hashes: manifest.Hashes(), Size: int64(len(manifest.RawManifest())), diff --git a/gitreceive/receiver/flynn-receive.go b/gitreceive/receiver/flynn-receive.go index 092df8112e..8df6f7dea0 100755 --- a/gitreceive/receiver/flynn-receive.go +++ b/gitreceive/receiver/flynn-receive.go @@ -86,14 +86,14 @@ Options: return fmt.Errorf("Error getting current app release: %s", err) } - slugbuilderImageID := os.Getenv("SLUGBUILDER_18_IMAGE_ID") - slugrunnerImageID := os.Getenv("SLUGRUNNER_18_IMAGE_ID") - stackName := "heroku-18" + slugbuilderImageID := os.Getenv("SLUGBUILDER_24_IMAGE_ID") + slugrunnerImageID := os.Getenv("SLUGRUNNER_24_IMAGE_ID") + stackName := "heroku-24" cfStackName := "cflinuxfs3" if stack := prevRelease.Env["FLYNN_STACK"]; stack != "" { switch stack { - case "heroku-18": + case "heroku-24": case "cedar-14": fmt.Println("WARNING: The cedar-14 stack is deprecated and does not receive security updates.") fmt.Println("WARNING: Unset FLYNN_STACK to use the default stack.") diff --git a/host/cli/bootstrap.go b/host/cli/bootstrap.go index 7834200136..ddab6a1dd3 100755 --- a/host/cli/bootstrap.go +++ b/host/cli/bootstrap.go @@ -690,19 +690,19 @@ UPDATE artifacts SET uri = '%s', type = 'flynn', manifest = '%s', hashes = '%s', );`, artifact.URI, jsonb(&artifact.RawManifest), jsonb(artifact.Hashes), artifact.Size, artifact.LayerURLTemplate, jsonb(artifact.Meta), step.ID)) } - // create the slugbuilder/slugrunner-18 artifacts if they don't exist + // create the slugbuilder/slugrunner-24 artifacts if they don't exist for _, name := range []string{"slugbuilder", "slugrunner"} { - artifact := artifacts[name+"-18-image"] + artifact := artifacts[name+"-24-image"] sqlBuf.WriteString(fmt.Sprintf(` DO $$ BEGIN - IF (SELECT env->>'%s_18_IMAGE_ID' FROM releases WHERE release_id = (SELECT release_id FROM apps WHERE name = 'gitreceive' AND deleted_at IS NULL)) IS NULL THEN + IF (SELECT env->>'%s_24_IMAGE_ID' FROM releases WHERE release_id = (SELECT release_id FROM apps WHERE name = 'gitreceive' AND deleted_at IS NULL)) IS NULL THEN INSERT INTO artifacts (artifact_id, type, uri, manifest, hashes, size, layer_url_template, meta) VALUES ('%s', 'flynn', '%s', '%s', '%s', %d, '%s', '%s'); END IF; END; $$;`, strings.ToUpper(name), random.UUID(), artifact.URI, jsonb(&artifact.RawManifest), jsonb(artifact.Hashes), artifact.Size, artifact.LayerURLTemplate, jsonb(artifact.Meta))) - // update pre-slugrunner/slugbuilder-18 artifacts currently being referenced by gitreceive + // update pre-slugrunner/slugbuilder-24 artifacts currently being referenced by gitreceive // (which will also update all current user releases to use slugrunner-14) artifact = artifacts[name+"-14-image"] sqlBuf.WriteString(fmt.Sprintf(` @@ -712,14 +712,14 @@ OR uri = (SELECT env->>'%[7]s_IMAGE_URI' FROM releases WHERE release_id = (SELEC artifact.URI, jsonb(&artifact.RawManifest), jsonb(artifact.Hashes), artifact.Size, artifact.LayerURLTemplate, jsonb(artifact.Meta), strings.ToUpper(name))) } - // update pre-slugbuilder-18 releases with a stack tag + // update pre-slugbuilder-24 releases with a stack tag sqlBuf.WriteString(`UPDATE releases SET meta = jsonb_set(meta, '{slugrunner.stack}', '"cedar-14"') WHERE meta->>'slugrunner.stack' IS NULL and meta->>'git' = 'true';`) // update slug artifacts currently being referenced by gitreceive // (which will also update all current user releases to use the // latest slugrunner images) for _, name := range []string{"slugbuilder", "slugrunner"} { - for _, version := range []string{"14", "18"} { + for _, version := range []string{"14", "24"} { artifact := artifacts[fmt.Sprintf("%s-%s-image", name, version)] sqlBuf.WriteString(fmt.Sprintf(` UPDATE artifacts SET uri = '%s', type = 'flynn', manifest = '%s', hashes = '%s', size = %d, layer_url_template = '%s', meta = '%s' @@ -745,9 +745,9 @@ UPDATE releases SET env = jsonb_set(env, '{REDIS_IMAGE_ID}', ('"' || (SELECT art WHERE env->>'REDIS_IMAGE_URI' IS NOT NULL;`, artifacts["redis-image"].URI)) - // ensure recent SLUGBUILDER/SLUGRUNNER_18/14_IMAGE_ID variables are set on the appropriate releases + // ensure recent SLUGBUILDER/SLUGRUNNER_24/14_IMAGE_ID variables are set on the appropriate releases for _, name := range []string{"slugbuilder", "slugrunner"} { - for _, version := range []string{"18", "14"} { + for _, version := range []string{"24", "14"} { artifact := artifacts[fmt.Sprintf("%s-%s-image", name, version)] sqlBuf.WriteString(fmt.Sprintf(` UPDATE releases SET env = jsonb_set(env, '{%[1]s_%[2]s_IMAGE_ID}', ('"' || (SELECT artifact_id::text FROM artifacts WHERE uri = '%[3]s') || '"')::jsonb, true) diff --git a/host/img/packages.sh b/host/img/packages.sh index d86c1301b1..517cca3614 100755 --- a/host/img/packages.sh +++ b/host/img/packages.sh @@ -4,18 +4,12 @@ # either in a container or in a VM export DEBIAN_FRONTEND=noninteractive apt-get update -# explicitly install linux 4.13 as the version of ZFS available on xenial is -# not compatible with linux 4.15 (the 'zfs' command just hangs) -apt-get install --yes linux-image-4.13.0-1019-gcp initramfs-tools systemd udev zfsutils-linux iptables net-tools iproute2 qemu-kvm +apt-get install --yes systemd udev zfsutils-linux iptables net-tools iproute2 apt-get clean -# support 9p rootfs when starting in a VM -printf '%s\n' 9p 9pnet 9pnet_virtio >> /etc/initramfs-tools/modules -update-initramfs -u - # install jq for reading container config files -JQ_URL="https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64" -JQ_SHA="af986793a515d500ab2d35f8d2aecd656e764504b789b66d7e1a0b727a124c44" +JQ_URL="https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64" +JQ_SHA="5942c9b0934e510ee61eb3e30273f1b3fe2590df93933a93d7c58b81d19c8ff5" curl -fsSLo /tmp/jq "${JQ_URL}" echo "${JQ_SHA} /tmp/jq" | sha256sum -c - mv /tmp/jq /usr/local/bin/jq diff --git a/script/export-tuf/main.go b/script/export-tuf/main.go index a2acc92297..34a0255db8 100755 --- a/script/export-tuf/main.go +++ b/script/export-tuf/main.go @@ -188,18 +188,18 @@ func (e *exporter) buildBaseLayers() error { // // Dependency tree: // busybox (standalone) - // ubuntu-bionic (standalone) - // ubuntu-xenial (standalone, needed only for host image) + // ubuntu-noble (standalone) + // ubuntu-noble (standalone, needed only for host image) bases := []struct { name string script string }{ {"busybox", "builder/img/busybox.sh"}, - {"ubuntu-bionic", "builder/img/ubuntu-bionic.sh"}, - // ubuntu-xenial needed for host image but host image also needs + {"ubuntu-noble", "builder/img/ubuntu-noble.sh"}, + // ubuntu-noble needed for host image but host image also needs // kernel packages which require a full apt - skip for now as - // the host image will use ubuntu-bionic in the simplified pipeline + // the host image will use ubuntu-noble in the simplified pipeline } for _, base := range bases { @@ -481,10 +481,10 @@ func (e *exporter) imageSpecs() []imageSpec { }, }, - // --- ubuntu-bionic-based images --- + // --- ubuntu-noble-based images --- { Name: "blobstore", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "flynn-blobstore": "/bin/flynn-blobstore", }, @@ -497,7 +497,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "host", - Base: "ubuntu-bionic", // simplified: using bionic instead of xenial + Base: "ubuntu-noble", Binaries: map[string]string{ "flynn-host": "/usr/local/bin/flynn-host", "flynn-init": "/usr/local/bin/flynn-init", @@ -515,7 +515,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "postgres", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "flynn-postgres": "/bin/flynn-postgres", "flynn-postgres-api": "/bin/flynn-postgres-api", @@ -529,7 +529,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "redis", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "flynn-redis": "/bin/flynn-redis", "flynn-redis-api": "/bin/flynn-redis-api", @@ -545,7 +545,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "mariadb", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "flynn-mariadb": "/bin/flynn-mariadb", "flynn-mariadb-api": "/bin/flynn-mariadb-api", @@ -559,7 +559,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "mongodb", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "flynn-mongodb": "/bin/flynn-mongodb", "flynn-mongodb-api": "/bin/flynn-mongodb-api", @@ -575,7 +575,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "gitreceive", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "gitreceived": "/bin/gitreceived", "flynn-receiver": "/bin/flynn-receiver", @@ -589,7 +589,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "tarreceive", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "tarreceive": "/bin/tarreceive", }, @@ -599,7 +599,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "taffy", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "taffy": "/bin/taffy", "flynn-receiver": "/bin/flynn-receiver", @@ -609,8 +609,8 @@ func (e *exporter) imageSpecs() []imageSpec { }, }, { - Name: "slugbuilder-18", - Base: "ubuntu-bionic", + Name: "slugbuilder-24", + Base: "ubuntu-noble", Binaries: map[string]string{ "create-artifact": "/bin/create-artifact", "slug-migrator": "/bin/slug-migrator", @@ -626,7 +626,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "slugbuilder-14", - Base: "ubuntu-bionic", // simplified: using bionic instead of cedar-14/trusty + Base: "ubuntu-noble", // simplified: using noble instead of cedar-14/trusty Binaries: map[string]string{ "create-artifact": "/bin/create-artifact", "slug-migrator": "/bin/slug-migrator", @@ -641,8 +641,8 @@ func (e *exporter) imageSpecs() []imageSpec { }, }, { - Name: "slugrunner-18", - Base: "ubuntu-bionic", + Name: "slugrunner-24", + Base: "ubuntu-noble", ExtraFiles: map[string]string{ "slugrunner/runner/init": "/runner/init", }, @@ -652,7 +652,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "slugrunner-14", - Base: "ubuntu-bionic", // simplified + Base: "ubuntu-noble", // simplified ExtraFiles: map[string]string{ "slugrunner/runner/init": "/runner/init", }, @@ -662,7 +662,7 @@ func (e *exporter) imageSpecs() []imageSpec { }, { Name: "builder", - Base: "ubuntu-bionic", + Base: "ubuntu-noble", Binaries: map[string]string{ "flynn-builder": "/bin/flynn-builder", }, diff --git a/test/test_backup.go b/test/test_backup.go index 06b60cabb1..5c204e480a 100755 --- a/test/test_backup.go +++ b/test/test_backup.go @@ -48,7 +48,7 @@ func (s *BackupSuite) Test_v20190730_0_nodejs_redis(t *c.C) { } func (s *BackupSuite) testStackRedeploy(t *c.C, x *Cluster) { - // deploy app again, confirm stack is heroku-18 + // deploy app again, confirm stack is heroku-24 r := s.newGitRepo(t, "https://github.com/flynn-examples/nodejs-flynn-example") r.cluster = x t.Assert(r.git("commit", "-m", "second", "--allow-empty"), Succeeds) @@ -56,7 +56,7 @@ func (s *BackupSuite) testStackRedeploy(t *c.C, x *Cluster) { t.Assert(r.git("push", "flynn", "master"), Succeeds) release, err := x.controller.GetAppRelease("nodejs") t.Assert(err, c.IsNil) - t.Assert(release.Meta["slugrunner.stack"], c.Equals, "heroku-18") + t.Assert(release.Meta["slugrunner.stack"], c.Equals, "heroku-24") // deploy app again with stack set to cedar-14 t.Assert(r.git("commit", "-m", "third", "--allow-empty"), Succeeds) @@ -114,7 +114,7 @@ func (s *BackupSuite) testClusterBackupWithFn(t *c.C, name string, fn func(*c.C, debug(t, "getting app release") release, err := x.controller.GetAppRelease("nodejs") t.Assert(err, c.IsNil) - stack := "heroku-18" + stack := "heroku-24" if strings.HasPrefix(name, "v2016") || strings.HasPrefix(name, "v2017") { stack = "cedar-14" } diff --git a/test/test_gitreceive.go b/test/test_gitreceive.go index 3331f66bba..35b60847f0 100755 --- a/test/test_gitreceive.go +++ b/test/test_gitreceive.go @@ -121,6 +121,6 @@ func (s *GitreceiveSuite) TestGitReleaseMeta(t *c.C) { t.Assert(release.Meta, c.DeepEquals, map[string]string{ "git": "true", "git.commit": commit, - "slugrunner.stack": "heroku-18", + "slugrunner.stack": "heroku-24", }) } diff --git a/test/test_release.go b/test/test_release.go index 77db0f3cc9..60d6fc41ba 100755 --- a/test/test_release.go +++ b/test/test_release.go @@ -73,7 +73,7 @@ cd "${ROOT}" # create a slug for testing slug based app updates build/bin/flynn-host run \ --volume /tmp \ - build/image/slugbuilder-18.json \ + build/image/slugbuilder-24.json \ /usr/bin/env \ CONTROLLER_KEY="{{ .ControllerKey }}" \ SLUG_IMAGE_ID="{{ .SlugImageID }}" \ diff --git a/util/release/images_template.json b/util/release/images_template.json index 342a1f3bcf..403016dfb9 100755 --- a/util/release/images_template.json +++ b/util/release/images_template.json @@ -7,9 +7,9 @@ "blobstore": $image_artifact[blobstore], "router": $image_artifact[router], "gitreceive": $image_artifact[gitreceive], - "slugbuilder-18": $image_artifact[slugbuilder-18], + "slugbuilder-24": $image_artifact[slugbuilder-24], "slugbuilder-14": $image_artifact[slugbuilder-14], - "slugrunner-18": $image_artifact[slugrunner-18], + "slugrunner-24": $image_artifact[slugrunner-24], "slugrunner-14": $image_artifact[slugrunner-14], "taffy": $image_artifact[taffy], "updater": $image_artifact[updater], From 4e14a7caeb22b20c49fd7cb458037939ff067c77 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 16 Apr 2026 10:58:49 +0200 Subject: [PATCH 3/4] mariadb: migrate from innobackupex to mariabackup for MariaDB 10.11 LTS --- appliance/mariadb/process.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/appliance/mariadb/process.go b/appliance/mariadb/process.go index c98186f6d4..537170cdee 100755 --- a/appliance/mariadb/process.go +++ b/appliance/mariadb/process.go @@ -284,20 +284,20 @@ func (p *Process) assumePrimary(downstream *discoverd.Instance) (err error) { return nil } -// Backup returns a reader for streaming a backup in xbstream format. +// Backup returns a reader for streaming a backup in mbstream format. func (p *Process) Backup() (io.ReadCloser, error) { r := &backupReadCloser{} cmd := exec.Command( - filepath.Join(p.BinDir, "innobackupex"), + filepath.Join(p.BinDir, "mariabackup"), "--defaults-file="+p.ConfigPath(), + "--backup", "--host=127.0.0.1", "--port="+p.Port, "--user=flynn", "--password="+p.Password, "--socket=", - "--stream=xbstream", - ".", + "--stream=mbstream", ) cmd.Dir = p.DataDir cmd.Stderr = &r.stderr @@ -336,7 +336,7 @@ func (p *Process) extractBackupInfo() (*BackupInfo, error) { return &BackupInfo{LogFile: fields[0], LogPos: fields[1], GTID: fields[2]}, nil } -// Restore restores the database from an xbstream backup. +// Restore restores the database from an mbstream backup. func (p *Process) Restore(r io.Reader) (*BackupInfo, error) { if err := p.writeConfig(configData{}); err != nil { return nil, err @@ -355,11 +355,11 @@ func (p *Process) Restore(r io.Reader) (*BackupInfo, error) { } func (p *Process) unpackXbstream(r io.Reader) error { - cmd := exec.Command(filepath.Join(p.BinDir, "xbstream"), "-x", "--directory="+p.DataDir) + cmd := exec.Command(filepath.Join(p.BinDir, "mbstream"), "-x", "--directory="+p.DataDir) cmd.Stdin = io.NopCloser(r) if buf, err := cmd.CombinedOutput(); err != nil { - p.Logger.Error("xbstream failed", "err", err, "output", string(buf)) + p.Logger.Error("mbstream failed", "err", err, "output", string(buf)) return err } @@ -368,13 +368,13 @@ func (p *Process) unpackXbstream(r io.Reader) error { func (p *Process) restoreApplyLog() error { cmd := exec.Command( - filepath.Join(p.BinDir, "innobackupex"), + filepath.Join(p.BinDir, "mariabackup"), "--defaults-file="+p.ConfigPath(), - "--apply-log", - p.DataDir, + "--prepare", + "--target-dir="+p.DataDir, ) if buf, err := cmd.CombinedOutput(); err != nil { - p.Logger.Error("innobackupex apply-log failed", "err", err, "output", string(buf)) + p.Logger.Error("mariabackup prepare failed", "err", err, "output", string(buf)) return err } return nil @@ -990,7 +990,7 @@ func MySQLErrorNumber(err error) uint16 { return 0 } -// backupReadCloser wraps the Cmd of the innobackupex to perform error handling. +// backupReadCloser wraps the Cmd of mariabackup to perform error handling. type backupReadCloser struct { cmd *exec.Cmd stdout io.ReadCloser @@ -1005,10 +1005,10 @@ func (r *backupReadCloser) Close() error { return err } - // Verify that innobackupex prints "completed OK!" at the end of STDERR. + // Verify that mariabackup prints "completed OK!" at the end of STDERR. if !strings.HasSuffix(strings.TrimSpace(r.stderr.String()), "completed OK!") { r.stderr.WriteTo(os.Stderr) - return errors.New("innobackupex did not complete ok") + return errors.New("mariabackup did not complete ok") } return nil From c0bec711753bd797979e72e458d070e2e3ddec5f Mon Sep 17 00:00:00 2001 From: root Date: Thu, 16 Apr 2026 12:41:02 +0200 Subject: [PATCH 4/4] builder: prefer cloud image download over debootstrap for ubuntu-noble --- builder/img/ubuntu-noble.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/builder/img/ubuntu-noble.sh b/builder/img/ubuntu-noble.sh index 8b8e14249b..8aeeba1684 100755 --- a/builder/img/ubuntu-noble.sh +++ b/builder/img/ubuntu-noble.sh @@ -24,12 +24,12 @@ trap cleanup EXIT mkdir -p "${TMP}/root" -# Use debootstrap to create a minimal Noble rootfs -if command -v debootstrap >/dev/null 2>&1; then +# Build rootfs from cloud image (fast) or debootstrap (slow but more minimal). +# Set USE_DEBOOTSTRAP=1 to force debootstrap. +if [ "${USE_DEBOOTSTRAP:-}" = "1" ] && command -v debootstrap >/dev/null 2>&1; then echo "Building Ubuntu Noble rootfs via debootstrap..." debootstrap --variant=minbase --arch=amd64 noble "${TMP}/root" http://archive.ubuntu.com/ubuntu else - # Fallback: download the minimal cloud image root tarball echo "Building Ubuntu Noble rootfs via cloud image download..." URL="https://cloud-images.ubuntu.com/minimal/releases/noble/release/ubuntu-24.04-minimal-cloudimg-amd64-root.tar.xz" curl -fSLo "${TMP}/ubuntu.tar.xz" "${URL}" @@ -42,6 +42,8 @@ mount --bind /dev/pts "${TMP}/root/dev/pts" mount -t proc proc "${TMP}/root/proc" mount -t sysfs sysfs "${TMP}/root/sys" +# Ensure /etc/resolv.conf is a real file (cloud image may have a symlink) +rm -f "${TMP}/root/etc/resolv.conf" 2>/dev/null || true cp "/etc/resolv.conf" "${TMP}/root/etc/resolv.conf" chroot "${TMP}/root" bash -e <"builder/ubuntu-setup.sh"