From bc88860ad19fe327cef448c9ee680b219e106544 Mon Sep 17 00:00:00 2001 From: Bart Oldeman Date: Mon, 13 Apr 2026 15:19:27 +0000 Subject: [PATCH 1/5] Use subdir_software/subdir_modules for --bwrap Using those instead of hardcoded 'software' and 'modules' makes the path inside the bwrap installdir more similar to the external paths, if --subdir-software or --subdir-modules are used. --- easybuild/tools/bwrap.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/bwrap.py b/easybuild/tools/bwrap.py index 8cfd8c8cd2..b70cdc21cd 100644 --- a/easybuild/tools/bwrap.py +++ b/easybuild/tools/bwrap.py @@ -35,7 +35,7 @@ from easybuild.base import fancylogger from easybuild.tools.build_log import EasyBuildError, print_msg -from easybuild.tools.config import install_path +from easybuild.tools.config import install_path, ConfigurationVariables from easybuild.tools.filetools import mkdir, write_file from easybuild.tools.utilities import trace_msg @@ -107,14 +107,15 @@ def prepare_bwrap(bwrap_installpath): set_bwrap_info('installpath_modules', install_path(typ='modules')) - bwrap_modules_installpath = os.path.join(bwrap_installpath, 'modules') + variables = ConfigurationVariables() + bwrap_modules_installpath = os.path.join(bwrap_installpath, variables['subdir_modules']) bwrap_cmd = ['bwrap', '--dev-bind', '/', '/'] # bind mount all software directories for mod in sorted(get_bwrap_info('modules_to_install')): installdir = os.path.join(os.path.realpath(installpath_software), mod) - bwrap_installdir = os.path.join(bwrap_installpath, 'software', mod) + bwrap_installdir = os.path.join(bwrap_installpath, variables['subdir_software'], mod) mkdir(installdir, parents=True) mkdir(bwrap_installdir, parents=True) bwrap_cmd.extend(['--bind', bwrap_installdir, installdir]) From 4d8e6a4f4ec8d8bd39ffbc5cb43c4393ede0aba8 Mon Sep 17 00:00:00 2001 From: Bart Oldeman Date: Mon, 13 Apr 2026 15:52:31 +0000 Subject: [PATCH 2/5] Use overlayfs with bwrap if we cannot create installdir In that case we go up the directory tree until we find a directory that exists, and use overlayfs to avoid hiding the other directories (that could have dependent software!) --- easybuild/tools/bwrap.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/bwrap.py b/easybuild/tools/bwrap.py index b70cdc21cd..83ee257435 100644 --- a/easybuild/tools/bwrap.py +++ b/easybuild/tools/bwrap.py @@ -116,9 +116,24 @@ def prepare_bwrap(bwrap_installpath): for mod in sorted(get_bwrap_info('modules_to_install')): installdir = os.path.join(os.path.realpath(installpath_software), mod) bwrap_installdir = os.path.join(bwrap_installpath, variables['subdir_software'], mod) - mkdir(installdir, parents=True) + use_overlayfs = False + try: + mkdir(installdir, parents=True) + except EasyBuildError: + # if we can't create the external installation directory, try to use overlayfs + use_overlayfs = True mkdir(bwrap_installdir, parents=True) - bwrap_cmd.extend(['--bind', bwrap_installdir, installdir]) + if use_overlayfs: + bwrap_workdir = os.path.join(bwrap_installpath, 'workdir', mod) + mkdir(bwrap_workdir, parents=True) + # go up the tree until we find a directory that exists + while not os.path.exists(installdir): + installdir = os.path.dirname(installdir) + bwrap_installdir = os.path.dirname(bwrap_installdir) + opts = ['--overlay-src', installdir, '--overlay', bwrap_installdir, bwrap_workdir, installdir] + else: + opts = ['--bind', bwrap_installdir, installdir] + bwrap_cmd.extend(opts) set_bwrap_info('bwrap_cmd', bwrap_cmd) bwrap_cmd_str = ' '.join(bwrap_cmd) From d71ac0db328d95b82a2af84983eb0c530c9e20bc Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Sat, 13 Jun 2026 20:59:49 +0200 Subject: [PATCH 3/5] add end-to-end test for bwrap with overlay on ubuntu 26.06 --- .github/workflows/end2end_bwrap.yml | 37 ++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/.github/workflows/end2end_bwrap.yml b/.github/workflows/end2end_bwrap.yml index 1338dc0d2f..91209ada82 100644 --- a/.github/workflows/end2end_bwrap.yml +++ b/.github/workflows/end2end_bwrap.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: end2end: name: End-to-end test (runner VM) - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Check out the repo @@ -14,7 +14,7 @@ jobs: - name: Install system packages run: sudo apt-get install -y lmod bubblewrap - - name: Allow unprivileged user namespaces (Ubuntu 24.04) + - name: Allow unprivileged user namespaces (Ubuntu 26.04) run: | sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0 @@ -64,7 +64,38 @@ jobs: eb --show-config --prefix /tmp | grep "bwrap-installpath [ ]* \(C\) = /tmp/bwrap" # test installation of UnZip + bzip2 dependency - eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils + eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils --try-amend=prebuildopts='export CFLAGS="$CFLAGS -std=gnu89" && ' + + # no module files should be created in configured install path (default: $HOME/.local/easybuild), + # not for UnZip, nor for bzip2 dependency + (test -d $HOME/.local/easybuild/modules/all/UnZip || echo 'no UnZip module dir found: OK') > /tmp/out-UnZip-mod + (test -d $HOME/.local/easybuild/modules/all/bzip2 || echo 'no bzip2 module dir found: OK') > /tmp/out-bzip2-mod + + # software subdirectories in configured install path should be empty + (test -e $HOME/.local/easybuild/software/UnZip/6.0/* || echo 'UnZip software dir in $HOME/.local/easybuild is empty: OK') > /tmp/out-UnZip-sw-dir + (test -e $HOME/.local/easybuild/software/bzip2/1.0.8/* || echo 'bzip2 software dir in $HOME/.local/easybuild is empty: OK') > /tmp/out-bzip2-sw-dir + + grep 'no UnZip module dir found: OK' /tmp/out-UnZip-mod && grep 'no bzip2 module dir found: OK' /tmp/out-bzip2-mod && grep 'UnZip software dir in .* is empty' /tmp/out-UnZip-sw-dir && grep 'bzip2 software dir in .* is empty' /tmp/out-bzip2-sw-dir && ls -l /tmp/bwrap-installpath/modules/all/{bzip2/1.0.8.lua,UnZip/6.0.lua} && ls -l /tmp/bwrap-installpath/software/{UnZip,bzip2}/*/bin/* && ls -l /tmp/bwrap-installpath/software/{UnZip,bzip2}/*/easybuild/*log* + EOF + + - name: End-to-end test of installing UnZip with EasyBuild in bwrap namespace with overlay + shell: bash + run: | + bash -l <=0.10 for --overlay and --overlay-src support + bwrap --version + + source /tmp/eb_env + + # remove previously created install directories + rm -r $HOME/.local/easybuild/software/* /tmp/bwrap-installpath + + # make install directory read-only to force using overlay + chmod -w $HOME/.local/easybuild/software + + # test installation of UnZip + bzip2 dependency + eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils --try-amend=prebuildopts='export CFLAGS="$CFLAGS -std=gnu89" && ' # no module files should be created in configured install path (default: $HOME/.local/easybuild), # not for UnZip, nor for bzip2 dependency From 3c376be648274ae72a3d1ba6839e31b442002039 Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Sun, 14 Jun 2026 14:16:52 +0200 Subject: [PATCH 4/5] remove --try-amend --- .github/workflows/end2end_bwrap.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/end2end_bwrap.yml b/.github/workflows/end2end_bwrap.yml index 91209ada82..1a80ac5cdb 100644 --- a/.github/workflows/end2end_bwrap.yml +++ b/.github/workflows/end2end_bwrap.yml @@ -64,7 +64,7 @@ jobs: eb --show-config --prefix /tmp | grep "bwrap-installpath [ ]* \(C\) = /tmp/bwrap" # test installation of UnZip + bzip2 dependency - eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils --try-amend=prebuildopts='export CFLAGS="$CFLAGS -std=gnu89" && ' + eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils # no module files should be created in configured install path (default: $HOME/.local/easybuild), # not for UnZip, nor for bzip2 dependency @@ -95,7 +95,7 @@ jobs: chmod -w $HOME/.local/easybuild/software # test installation of UnZip + bzip2 dependency - eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils --try-amend=prebuildopts='export CFLAGS="$CFLAGS -std=gnu89" && ' + eb UnZip-6.0.eb --trace --robot --experimental --bwrap --bwrap-installpath /tmp/bwrap-installpath --filter-deps=binutils # no module files should be created in configured install path (default: $HOME/.local/easybuild), # not for UnZip, nor for bzip2 dependency From cd0267cb7d78763fee666db636c44e608e69cc5b Mon Sep 17 00:00:00 2001 From: Samuel Moors Date: Mon, 29 Jun 2026 17:50:44 +0200 Subject: [PATCH 5/5] add bwrap_installpath_software and bwrap_installpath_modules to the json file --- easybuild/tools/bwrap.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/easybuild/tools/bwrap.py b/easybuild/tools/bwrap.py index 83ee257435..da2f5957c6 100644 --- a/easybuild/tools/bwrap.py +++ b/easybuild/tools/bwrap.py @@ -47,6 +47,8 @@ 'bwrap_cmd': [], 'bwrap_eb_options': [], 'bwrap_installpath': '', + 'bwrap_installpath_software': '', + 'bwrap_installpath_modules': '', 'installpath_modules': '', 'installpath_software': '', 'modules_to_install': set(), @@ -108,14 +110,17 @@ def prepare_bwrap(bwrap_installpath): set_bwrap_info('installpath_modules', install_path(typ='modules')) variables = ConfigurationVariables() - bwrap_modules_installpath = os.path.join(bwrap_installpath, variables['subdir_modules']) + bwrap_installpath_software = os.path.join(bwrap_installpath, variables['subdir_software']) + bwrap_installpath_modules = os.path.join(bwrap_installpath, variables['subdir_modules']) + set_bwrap_info('bwrap_installpath_software', bwrap_installpath_software) + set_bwrap_info('bwrap_installpath_modules', bwrap_installpath_modules) bwrap_cmd = ['bwrap', '--dev-bind', '/', '/'] # bind mount all software directories for mod in sorted(get_bwrap_info('modules_to_install')): installdir = os.path.join(os.path.realpath(installpath_software), mod) - bwrap_installdir = os.path.join(bwrap_installpath, variables['subdir_software'], mod) + bwrap_installdir = os.path.join(bwrap_installpath_software, mod) use_overlayfs = False try: mkdir(installdir, parents=True) @@ -139,7 +144,7 @@ def prepare_bwrap(bwrap_installpath): bwrap_cmd_str = ' '.join(bwrap_cmd) # disable `--bwrap` to prepare for a real installation (in bwrap namespace) - bwrap_eb_options = ['--disable-bwrap', f'--installpath-modules={bwrap_modules_installpath}'] + bwrap_eb_options = ['--disable-bwrap', f'--installpath-modules={bwrap_installpath_modules}'] set_bwrap_info('bwrap_eb_options', bwrap_eb_options) _log.info(f'Info needed for bwrap: {_bwrap_info}')