@@ -218,26 +218,24 @@ bool UpdateManager::applyUpdate() {
218218 }
219219 lockFile.close ();
220220
221- // HOST SIDE: Check disk space on the host before entering the chroot,
222- // so QStorageInfo can read the available space on mounted partitions.
221+ // Check disk space on the host before entering the chroot.
223222 Q_EMIT updateProgress (5 , " Checking disk space" );
224223 if (!checkDiskSpace ()) {
225224 Logger::instance ().error (" Insufficient disk space for update" );
226225 QFile::remove (lockPath);
227226 return false ;
228227 }
229228
230- // CHROOT SIDE: Everything from here runs inside overlayroot-chroot,
231- // writing to the persistent lower layer — matching the original nuts-cru
232- // architecture where the entire update executes inside the chroot.
229+ // Everything from here runs inside overlayroot-chroot.
233230
234- // Step 1: Mount devtmpfs inside the chroot (required for device nodes
235- // that dpkg maintainer scripts and initramfs-tools depend on).
231+ // Step 1: Prepare chroot environment.
236232 Q_EMIT updateProgress (8 , " Preparing chroot environment" );
237233 {
238234 QString output, error;
239235 m_sysInterface->executeInOverlay ({" /usr/bin/mount" , " -t" , " devtmpfs" , " dev" , " /dev" }, output, error);
240236 // Non-fatal: may already be mounted; proceed regardless.
237+ m_sysInterface->executeInOverlay ({" /usr/bin/mkdir" , " -p" , " /tmp" }, output, error);
238+ m_sysInterface->executeInOverlay ({" /usr/bin/chmod" , " 1777" , " /tmp" }, output, error);
241239 }
242240
243241 Q_EMIT updateProgress (10 , " Mounting partitions" );
@@ -289,6 +287,29 @@ bool UpdateManager::applyUpdate() {
289287 return false ;
290288 }
291289
290+ // Create nx-pkgmgr-policy symlinks.
291+ {
292+ QString output, error;
293+ QStringList aptTools = {" apt" , " apt-cache" , " apt-cdrom" , " apt-config" , " apt-get" , " apt-mark" };
294+ for (const QString& tool : aptTools)
295+ m_sysInterface->executeInOverlay ({" /usr/bin/ln" , " -sf" , " /usr/bin/nx-pkgmgr-policy" , " /usr/bin/" + tool}, output, error);
296+
297+ QStringList dpkgTools = {
298+ " dpkg" , " dpkg-deb" , " dpkg-divert" , " dpkg-maintscript-helper" , " dpkg-query" ,
299+ " dpkg-realpath" , " dpkg-split" , " dpkg-statoverride" , " dpkg-trigger" ,
300+ " dpkg-architecture" , " dpkg-buildapi" , " dpkg-buildflags" , " dpkg-buildpackage" ,
301+ " dpkg-buildtree" , " dpkg-checkbuilddeps" , " dpkg-distaddfile" ,
302+ " dpkg-genbuildinfo" , " dpkg-genchanges" , " dpkg-gencontrol" , " dpkg-gensymbols" ,
303+ " dpkg-mergechangelogs" , " dpkg-name" , " dpkg-parsechangelog" ,
304+ " dpkg-scanpackages" , " dpkg-scansources" , " dpkg-shlibdeps" , " dpkg-source" , " dpkg-vendor"
305+ };
306+ for (const QString& tool : dpkgTools)
307+ m_sysInterface->executeInOverlay ({" /usr/bin/ln" , " -sf" , " /usr/bin/nx-pkgmgr-policy" , " /usr/bin/" + tool}, output, error);
308+
309+ // Flush all pending writes to disk before exiting the chroot.
310+ m_sysInterface->executeInOverlay ({" /usr/bin/sync" }, output, error);
311+ }
312+
292313 cleanup ();
293314 QFile::remove (lockPath);
294315
@@ -302,10 +323,7 @@ bool UpdateManager::applyUpdate() {
302323// ----------------------
303324
304325bool UpdateManager::prepareSystemPartitions () {
305- // All mount operations run inside the chroot (lower layer), matching
306- // the original nuts-cru behaviour. findfs, mount, and mkdir are invoked
307- // via executeInOverlay so the mounts are visible inside the chroot context
308- // where all subsequent work happens.
326+ // All mount operations run inside the chroot (lower layer).
309327 QString output, error;
310328
311329 // Resolve and mount NX_HOME → /home
@@ -336,9 +354,7 @@ bool UpdateManager::prepareSystemPartitions() {
336354}
337355
338356bool UpdateManager::downloadOTAPayload () {
339- // Runs inside the chroot (lower layer), matching the original nuts-cru behaviour.
340- // axel -n 10 opens 10 parallel connections, bypassing SourceForge CDN per-connection
341- // throttling. Each mirror is tried in sequence; the first to pass checksum wins.
357+ // Runs inside the chroot (lower layer).
342358 QString otaPath = Config::instance ().downloadDir () + " /nuts-ota.squashfs" ;
343359 QString output, error;
344360
@@ -409,9 +425,9 @@ bool UpdateManager::mountOTAPayload() {
409425}
410426
411427bool UpdateManager::prepareUpdateTools () {
412- QString workDir = Config::instance (). workDir ();
413- QString appImagePath = workDir + " / dpkg-tooling .AppImage" ;
414- QString extractDir = workDir + " /pkgman-extracted" ;
428+ // Use /tmp inside the chroot.
429+ QString appImagePath = " /tmp/ dpkg-1.22.21-x86_64 .AppImage" ;
430+ QString extractDir = " /tmp /pkgman-extracted" ;
415431 QString appRunPath = extractDir + " /squashfs-root/AppRun" ;
416432
417433 QString appImageUrl = " https://raw.githubusercontent.com/Nitrux/storage/master/Other/AppImages/dpkg-1.22.21-x86_64.AppImage" ;
@@ -457,8 +473,9 @@ bool UpdateManager::prepareUpdateTools() {
457473
458474 m_pkgManagerPath = appRunPath;
459475
460- // Symlink tools into the lower layer.
461- QStringList tools = {" dpkg" , " dpkg-deb" , " dpkg-divert" , " dpkg-query" ,
476+ m_sysInterface->executeInOverlay ({" /usr/bin/ln" , " -svf" , appRunPath, " /usr/bin/dpkg" }, output, error);
477+
478+ QStringList tools = {" dpkg-deb" , " dpkg-divert" , " dpkg-query" ,
462479 " dpkg-realpath" , " dpkg-split" , " dpkg-statoverride" ,
463480 " dpkg-trigger" , " dpkg-maintscript-helper" , " update-alternatives" };
464481
@@ -481,19 +498,21 @@ bool UpdateManager::prepareUpdateTools() {
481498
482499bool UpdateManager::syncPackageData () {
483500 QString url = QString (" https://raw.githubusercontent.com/Nitrux/storage/master/Other/var-lib-dpkg-%1.tar.xz" ).arg (m_minTarget);
484- QString tarPath = Config::instance (). workDir () + " / var-lib-dpkg.tar.xz" ;
501+ QString tarPath = QString ( " /tmp/ var-lib-dpkg-%1 .tar.xz" ). arg (m_minTarget) ;
485502 QString expectedChecksum = m_queryData.value (" VAR_LIB_SUM" );
486503 QString output, error;
487504
488- // Download inside chroot using axel
505+ // Remove any stale file first
506+ m_sysInterface->executeInOverlay ({" /usr/bin/rm" , " -f" , tarPath}, output, error);
507+
489508 Logger::instance ().info (" Downloading package database archive..." );
490509 if (!m_sysInterface->executeInOverlay (
491510 {" /usr/bin/axel" , " -n" , " 10" , " -o" , tarPath, url}, output, error)) {
492511 Logger::instance ().error (" Failed to download package database: " + error);
493512 return false ;
494513 }
495514
496- // Verify checksum inside chroot before extraction
515+ // Verify checksum inside chroot before extraction.
497516 // Extracting an unverified archive to / is extremely dangerous (zip slip / overwrite attacks).
498517 Logger::instance ().info (" Verifying package database archive..." );
499518 QString checksumCmd = QString (" echo '%1 %2' | /usr/bin/sha256sum -c -" )
@@ -504,12 +523,20 @@ bool UpdateManager::syncPackageData() {
504523 return false ;
505524 }
506525
507- // Extract into the lower layer via overlayroot-chroot.
508- if (!m_sysInterface->executeInOverlay ({" /usr/bin/tar" , " -xf" , tarPath, " -C" , " /" }, output, error)) {
526+ // Extract into the lower layer — original uses: cd / && tar -xf $TARFILE
527+ if (!m_sysInterface->executeInOverlay (
528+ {" /bin/sh" , " -c" , " mkdir -p /var/lib/dpkg && cd / && /usr/bin/tar -xf " + tarPath},
529+ output, error)) {
509530 Logger::instance ().error (" Failed to extract package database: " + error);
510531 return false ;
511532 }
512533
534+ // Confirm extraction succeeded.
535+ if (!m_sysInterface->executeInOverlay ({" /usr/bin/test" , " -f" , " /var/lib/dpkg/status" }, output, error)) {
536+ Logger::instance ().error (" Package database extraction failed: /var/lib/dpkg/status not found" );
537+ return false ;
538+ }
539+
513540 return true ;
514541}
515542
@@ -518,59 +545,38 @@ bool UpdateManager::performPackageUpdates() {
518545 QString updatesDir = otaDir + " /updates" ;
519546 QString nvidiaDir = otaDir + " /nvidia" ;
520547
521- // Collect deb file list from inside the chroot — squashfsDir is mounted there
522- QString output, error;
523- m_sysInterface->executeInOverlay (
524- {" /usr/bin/find" , updatesDir, " -name" , " *.deb" , " -type" , " f" }, output, error);
525- QStringList debFiles = output.split (' \n ' , Qt::SkipEmptyParts);
526-
548+ // Detect NVIDIA on the host via /proc (shared with chroot).
527549 bool isNvidia = QDir (" /proc/driver/nvidia" ).exists ();
528550 if (!isNvidia) {
551+ QString output, error;
529552 m_sysInterface->executeCommand (" /usr/bin/lspci" , {}, output, error);
530553 if (output.contains (" NVIDIA" , Qt::CaseInsensitive)) isNvidia = true ;
531554 }
555+ if (isNvidia)
556+ Logger::instance ().info (" NVIDIA hardware detected. Including NVIDIA drivers." );
532557
533- if (isNvidia) {
534- QString nvidiaOutput, nvidiaError;
535- m_sysInterface->executeInOverlay (
536- {" /usr/bin/find" , nvidiaDir, " -name" , " *.deb" , " -type" , " f" }, nvidiaOutput, nvidiaError);
537- QStringList nvidiaDebs = nvidiaOutput.split (' \n ' , Qt::SkipEmptyParts);
538- if (!nvidiaDebs.isEmpty ()) {
539- Logger::instance ().info (" NVIDIA hardware detected. Including NVIDIA drivers." );
540- debFiles << nvidiaDebs;
541- }
542- }
543-
544- if (debFiles.isEmpty ()) {
545- Logger::instance ().warning (" No packages found to update." );
546- return true ;
547- }
548-
549- // Phase 1: Unpack
550- // All dpkg operations run inside overlayroot-chroot so they write to the
551- // lower (persistent) layer, not the upper (ephemeral) overlay layer.
552- const int BATCH_SIZE = 50 ;
553- Logger::instance ().info (QString (" Unpacking %1 packages in batches..." ).arg (debFiles.size ()));
554-
555- for (int i = 0 ; i < debFiles.size (); i += BATCH_SIZE) {
556- QStringList batch = debFiles.mid (i, BATCH_SIZE);
557- Logger::instance ().info (QString (" Unpacking batch %1 of %2..." ).arg ((i/BATCH_SIZE)+1 ).arg ((debFiles.size ()+BATCH_SIZE-1 )/BATCH_SIZE));
558+ // Phase 1: Unpack.
559+ Logger::instance ().info (" Phase 1: Unpacking OTA content..." );
560+ {
561+ QString searchPaths = updatesDir;
562+ if (isNvidia)
563+ searchPaths += " " + nvidiaDir;
558564
559- QStringList args;
560- args << " /usr/bin/env "
561- << " DEBIAN_FRONTEND=noninteractive "
562- << " TMPDIR= " + Config::instance (). workDir ()
563- << m_pkgManagerPath << " --force-all" << " --unpack" ;
564- args << batch ;
565+ QString unpackCmd = QString (
566+ " export DEBIAN_FRONTEND=noninteractive && "
567+ " export TMPDIR=/tmp && "
568+ " find %1 -name '*.deb' -print0 | "
569+ " xargs -0 -n 120 %2 --force-all --unpack"
570+ ). arg (searchPaths, m_pkgManagerPath) ;
565571
566572 QString output, error;
567- if (!m_sysInterface->executeInOverlay (args , output, error)) {
568- Logger::instance ().error (" Failed to unpack batch : " + error);
573+ if (!m_sysInterface->executeInOverlay ({ " /bin/sh " , " -c " , unpackCmd} , output, error)) {
574+ Logger::instance ().error (" Failed to unpack OTA content : " + error);
569575 return false ;
570576 }
571577 }
572578
573- // Phase 2: Configure loop
579+ // Phase 2: Configure loop audit reports clean.
574580 Logger::instance ().info (" Configuring packages..." );
575581 int maxPasses = 15 ;
576582 int pass = 1 ;
@@ -581,10 +587,9 @@ bool UpdateManager::performPackageUpdates() {
581587
582588 QString output, error;
583589 QStringList confArgs;
584- confArgs << " /usr/bin/env"
585- << " DEBIAN_FRONTEND=noninteractive"
586- << " TMPDIR=" + Config::instance ().workDir ()
587- << m_pkgManagerPath << " --force-all" << " --configure" << " -a" ;
590+ confArgs << " /bin/sh" << " -c"
591+ << QString (" export DEBIAN_FRONTEND=noninteractive && export TMPDIR=/tmp && "
592+ " %1 --force-all --configure -a" ).arg (m_pkgManagerPath);
588593 m_sysInterface->executeInOverlay (confArgs, output, error);
589594
590595 QStringList auditArgs;
@@ -594,6 +599,7 @@ bool UpdateManager::performPackageUpdates() {
594599
595600 if (currentAudit.isEmpty ()) {
596601 Logger::instance ().success (" Package configuration converged." );
602+ m_sysInterface->executeInOverlay ({" /usr/bin/rm" , " -rf" , " /tmp/pkgman-extracted" }, output, error);
597603 return true ;
598604 }
599605
@@ -614,7 +620,7 @@ bool UpdateManager::performPackageUpdates() {
614620
615621bool UpdateManager::runCleanupCrew () {
616622 QString ccuChecksum = m_queryData.value (" NUTS_CCU_CHECKSUM" );
617- QString ccuPath = Config::instance (). workDir () + " /nuts-cpp-ccu" ;
623+ QString ccuPath = " /tmp /nuts-cpp-ccu" ;
618624 QString output, error;
619625
620626 // Build the component URL from internal config
@@ -651,19 +657,11 @@ bool UpdateManager::runCleanupCrew() {
651657void UpdateManager::cleanup () {
652658 QString output, error;
653659
654- // Unmount inside chroot — mounts were created inside the chroot
660+ // Unmount inside chroot — mounts were created inside the chroot.
655661 m_sysInterface->executeInOverlay ({" /usr/bin/umount" , Config::instance ().squashfsDir ()}, output, error);
656662 m_sysInterface->executeInOverlay ({" /usr/bin/umount" , " /home" }, output, error);
657663 m_sysInterface->executeInOverlay ({" /usr/bin/umount" , " /var/lib" }, output, error);
658664 m_sysInterface->executeInOverlay ({" /usr/bin/umount" , " /dev" }, output, error);
659-
660- // Remove dpkg symlinks inside chroot
661- QStringList tools = {" dpkg" , " dpkg-deb" , " dpkg-query" , " update-alternatives" ,
662- " dpkg-divert" , " dpkg-realpath" , " dpkg-split" ,
663- " dpkg-statoverride" , " dpkg-trigger" , " dpkg-maintscript-helper" };
664- for (const QString& tool : tools) {
665- m_sysInterface->executeInOverlay ({" /usr/bin/rm" , " -f" , " /usr/bin/" + tool}, output, error);
666- }
667665}
668666
669667
0 commit comments