🔒 Repository is read-only – file editing is disabled.

PaganLinux/build-pagan.sh main

9770 linii Raw ← Powrót
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770
#!/bin/bash
# =============================================================================
# build-pagan-complete.sh - Kompletny builder Pagan Linux
# =============================================================================
# Pełna budowa: LFS Ch. 5, 6, 7, 8 + BLFS + Kernel + systemd + GRUB
#              + środowisko graficzne (domyślnie XFCE4) + Calamares (instalator)
#              + PAG/pagsync/pagbuild + fish (powłoka użytkowników i roota)
#              + bootowalny LIVE ISO (BIOS + UEFI) + obraz IMG.
#
# JAK URUCHOMIĆ (jeden skrypt robi wszystko – od pobrania po ISO):
#   sudo ./build-pagan.sh                          # buduj XFCE4 + Calamares do ISO
#   sudo ./build-pagan.sh --de deepin --both       # Deepin + IMG + ISO
#   sudo ./build-pagan.sh --no-calamares           # ISO bez instalatora
#   sudo ./build-pagan.sh --rebuild --repo https://repo.paganlinux.eu/stable/
#
# Konfiguracja: skopiuj build.conf.example → build.conf (lub /etc/pagan-build.conf)
#   cp build.conf.example build.conf
# Pełna pomoc:  ./build-pagan.sh --help
# =============================================================================

set -euo pipefail

# ========================= KONFIGURACJA =========================
# Absolutna ścieżka do katalogu skryptów – działa nawet po `cd /` w main()
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Odcisk pliku skryptu z chwili startu.
#
# PO CO: bash czyta skrypt INKREMENTALNIE (śledzi offset w bajtach), więc jeśli
# plik zmieni się W TRAKCIE działania – np. edytor z auto-zapisem przy zmianie
# fokusu (klik na terminal!) – bash wznawia czytanie pod ZŁYM offsetem
# „wykonuje” fragment przypadkowej linii, podając bzdurny numer:
#   ./build-pagan.sh: line 8458: $'z\303\263w': nie znaleziono polecenia
#   [ERROR] Linia 8458: "zów"
# (tu 'zów' to urwany ogon słowa „Wskazówka”/„obrazów” z zupełnie innej linii).
# Ten odcisk pozwala odróżnić taką sytuację od prawdziwego błędu skryptu.
_PAGAN_SELF_SIG="$(stat -c '%s-%Y' "${BASH_SOURCE[0]}" 2>/dev/null || echo '')"

_pagan_self_changed() {
    [[ -n "$_PAGAN_SELF_SIG" ]] || return 1
    local _now
    _now="$(stat -c '%s-%Y' "${BASH_SOURCE[0]}" 2>/dev/null || echo '')"
    [[ -n "$_now" && "$_now" != "$_PAGAN_SELF_SIG" ]]
}

# ========================= PARSOWANIE ARGUMENTÓW (CLI) =========================
# Precedencja ustawień: CLI > zmienne środowiskowe > plik konfiguracyjny > domyślne.
# Parsowanie na POCZĄTKU skryptu (przed nadaniem domyślnych wartości), inaczej
# flaga z CLI nie miałaby wpływu na build.
_usage_builder() {
    cat <<'EOF'
Użycie: sudo ./build-pagan.sh [OPCJE]

OPCJE:
  --config FILE      Wczytaj plik konfiguracyjny (domyślnie: ./build.conf, /etc/pagan-build.conf)
  --root DIR         Katalog rootfs budowy (PAGAN_ROOT)
  --de NAZWA         Środowisko graficzne: xfce|deepin|budgie|pagande|none
                     (domyślnie: xfce) – none = BAZA bez GUI (instalator z netinstallu / --auto)
                     Uwaga: kde i gnome są tymczasowo wyłączone.
  --kernel WERSJA    Wersja jądra Linux (np. 6.18.43)
  --calamares        Włącz Calamares jako instalator (domyślne)
  --no-calamares     Pomiń Calamares (ISO bez instalatora systemu)
  --no-fish          Nie ustawiaj fish jako powłoki użytkowników (zostaje bash)
  --img              Tylko obraz IMG
  --iso              Tylko obraz ISO (domyślne)
  --both             IMG + ISO
  --no-nvidia        Pomiń budowę sterowników NVIDIA
  --no-rolling-kernel  Nie pakuj jądra jako pakiet rolling (linux) do PAG
  --repo URL         Domyślne repozytorium PAG (trafi do /etc/pag/repos.conf)
  --size MB          Rozmiar obrazu IMG w MB
  --jobs N           Liczba równoległych zadań (make/ninja). Domyślnie ograniczona
                     pamięcią (1 zadanie ≈ 2 GB), by nie zamrozić komputera.
                     Alias środowiskowy: PAGAN_JOBS=N
  --force-pkg=NAZWA  Wymuś przebudowę pakietu mimo zapisu resume
  --repo-pkgs="P1 P2" Doinstaluj pakiety z repo PAG do rootfs przed utworzeniem
                     obrazu (np. --repo-pkgs="xfce4 xfce4-panel thunar lightdm";
                     wielokrotne użycie dopisuje pakiety)
  --rebuild          Czyść i buduj od zera (alias: --clean, --from-scratch)
  --serial-console   Loguj jądro/systemd ZAINSTALOWANEGO systemu także na ttyS0
                     (testy w QEMU/KVM: -serial file:log → pełny log jako tekst).
                     Alias środowiskowy: PAGAN_SERIAL_CONSOLE=1
  --keep-sources     Z --rebuild: zachowaj $PAGAN_ROOT/sources (pobrane archiwa)
                     i nie kasuj ich – oszczędza ponowne pobieranie
  -h, --help         Ta pomoc
EOF
    exit "${1:-0}"
}

parse_args_early() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --config)            export PAGAN_CONFIG_FILE="$2"; shift 2 ;;
            --root)              export PAGAN_ROOT="$2"; shift 2 ;;
            --de)                export PAGAN_DE="$2"; shift 2 ;;
            --kernel)            export KERNEL_VERSION="$2"; shift 2 ;;
            --img)               export BUILD_IMG=1 BUILD_ISO=0 ARTIFACT_MODE=img; shift ;;
            --iso)               export BUILD_IMG=0 BUILD_ISO=1 ARTIFACT_MODE=iso; shift ;;
            --both)              export BUILD_IMG=1 BUILD_ISO=1 ARTIFACT_MODE=both; shift ;;
            --no-nvidia)         export SKIP_NVIDIA=1; shift ;;
            --no-rolling-kernel)  export ROLLING_KERNEL=0; shift ;;
            --serial-console)    export PAGAN_SERIAL_CONSOLE=1; shift ;;
            --calamares)         export PAGAN_CALAMARES=1; shift ;;
            --no-calamares)      export PAGAN_CALAMARES=0; shift ;;
            --no-fish)           export PAGAN_FISH=0; shift ;;
            --repo)              export PAGAN_REPO_URL="$2"; shift 2 ;;
            --size)              export IMAGE_SIZE="$2"; shift 2 ;;
            --jobs)              export PAGAN_JOBS="$2"; shift 2 ;;
            --jobs=*)            export PAGAN_JOBS="${1#*=}"; shift ;;
            --force-pkg=*)       export FORCE_PKGS="${1#*=},${FORCE_PKGS:-}"; shift ;;
            --repo-pkgs=*)       export REPO_PKGS="${REPO_PKGS:+$REPO_PKGS }${1#*=}"; shift ;;
            --rebuild|--clean|--from-scratch) export REBUILD_FROM_SCRATCH=1; shift ;;
            --keep-sources) export REBUILD_KEEP_SOURCES=1; shift ;;
            -h|--help)           _usage_builder ;;
            *) echo "Nieznany argument: $1" >&2; _usage_builder 2 ;;
        esac
    done
}
parse_args_early "$@"

# ========================= PLIK KONFIGURACYJNY =========================
# Wczytuje ./build.conf lub /etc/pagan-build.conf. Zmienne już ustawione przez
# CLI/środowisko mają pierwszeństwo i NIE zostają nadpisane przez plik.
_load_config_file() {
    local _cf="${PAGAN_CONFIG_FILE:-}"
    if [[ -z "$_cf" ]]; then
        for _c in "$SCRIPT_DIR/build.conf" "/etc/pagan-build.conf"; do
            [[ -f "$_c" ]] && { _cf="$_c"; break; }
        done
    fi
    [[ -n "$_cf" && -f "$_cf" ]] || return 0

    local _vars=(PAGAN_ROOT PAGAN_VERSION PAGAN_DE PAGAN_RELEASE LFS_VERSION
                 KERNEL_VERSION MAKEFLAGS OUTPUT_DIR ARTIFACT_MODE
                 REBUILD_FROM_SCRATCH IMAGE_SIZE PAGAN_REPO_URL
                 PAGAN_LOG_LEVEL SKIP_NVIDIA BUILD_IMG BUILD_ISO
                 ROLLING_KERNEL REPO_PKGS PAGAN_CALAMARES PAGAN_FISH PAGAN_JOBS)
    local _v
    declare -A _pre=()
    for _v in "${_vars[@]}"; do
        _pre[$_v]="${!_v:-__UNSET__}"
    done
    # shellcheck disable=SC1090
    source "$_cf" 2>/dev/null || true
    # Przywróć wartości z CLI/env (mają pierwszeństwo nad plikiem)
    for _v in "${_vars[@]}"; do
        if [[ "${_pre[$_v]:-__UNSET__}" != "__UNSET__" ]]; then
            printf -v "$_v" '%s' "${_pre[$_v]}"
        fi
    done
    echo "[config] Wczytano konfigurację: $_cf"
}
_load_config_file

PAGAN_ROOT="${PAGAN_ROOT:-/mnt/pagan}"
PAGAN_VERSION="${PAGAN_VERSION:-0.1.2 Mokosz}"
PAGAN_DE="${PAGAN_DE:-xfce}"                  # wersja podstawowa: xfce | deepin | budgie | pagande | none (none = baza/CLI; kde/gnome wyłączone)
PAGAN_RELEASE="2026.09"
# Tapeta wydania; można nadpisać np. PAGAN_WALLPAPER=paganlinux-marzanna-wallpaper.png.
PAGAN_WALLPAPER="${PAGAN_WALLPAPER:-paganlinux-mokosz-autumn-wallpaper.png}"
LFS_VERSION="13.1"
# Instalator systemu: Calamares (domyślnie włączony). 0 = pomiń instalator.
PAGAN_CALAMARES="${PAGAN_CALAMARES:-1}"
# Powłoka: fish zamiast bash dla użytkowników i roota (domyślnie). 0 = zostaw bash.
PAGAN_FISH="${PAGAN_FISH:-1}"
# Katalog patchy LFS (RC: 13.1-rc2; po wydaniu stabilnym zmień na 13.1)
LFS_PATCH_DIR="${LFS_PATCH_DIR:-13.1-rc2}"

# Kernel (można zmienić: KERNEL_VERSION=6.18.x dla starszego LTS – URL skaluje się sam)
KERNEL_VERSION="${KERNEL_VERSION:-7.2.5}"
KERNEL_URL="https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION%%.*}.x/linux-${KERNEL_VERSION}.tar.xz"

# Rolling kernel (Arch-like): pakuj jądro jako pakiet .pag (linux) i zainstaluj przez PAG,
# żeby `pag upgrade` / `pagan-drivers kernel` mogły je aktualizować bez nowego ISO.
# Ustaw 0 (--no-rolling-kernel), jeśli kernel ma żyć tylko w ISO.
ROLLING_KERNEL="${ROLLING_KERNEL:-1}"

# Domyślne repozytorium PAG – trafi do /etc/pag/repos.conf w budowanym systemie
PAGAN_REPO_URL="${PAGAN_REPO_URL:-https://repo.paganlinux.eu/stable/}"

# Szyfrowany DNS (DoT) przez systemd-resolved w zainstalowanym systemie.
# PAGAN_DNS="" wyłącza (zostawia DNS z DHCP). DNSSEC=allow-downgrade:
# walidacja gdy serwer wspiera, bez łamania sieci lokalnych.
PAGAN_DNS="${PAGAN_DNS:-1.1.1.1}"

# PAGAN_SERIAL_CONSOLE=1 → jądro i systemd ZAINSTALOWANEGO systemu logują także
# na konsolę szeregową (ttyS0). W QEMU/KVM `-serial file:log` daje wtedy PEŁNY
# log startu jako tekst – nie trzeba zrzutów ekranu, a przyczynę widać od razu.
#
# Domyślnie 0 (wyłączone): z „console=ttyS0” jako OSTATNIĄ konsolą /dev/console
# wskazuje port szeregowy, więc na komputerze bez takiego portu komunikaty
# systemd nie pojawiłyby się na ekranie. Włączać tylko do testów/VM.
PAGAN_SERIAL_CONSOLE="${PAGAN_SERIAL_CONSOLE:-0}"
PAGAN_DNS_TLS="${PAGAN_DNS_TLS:-yes}"

# Hardware / równoległość.
# PAGAN_JOBS = liczba równoległych zadań (make/ninja/mksquashfs). Domyślnie
# ograniczamy ją PAMIĘCIĄ (1 zadanie ≈ 2 GB), bo Qt6/KF6 (ciężkie C++ + PCH) przy
# -j$(nproc) potrafią wyczerpać RAM → swap → komputer zamarza. Nadpisz przez
# --jobs N, PAGAN_JOBS=N w środowisku lub w build.conf.
PAGAN_JOBS="${PAGAN_JOBS:-}"
if [[ -z "$PAGAN_JOBS" ]]; then
    _ncpu="$(nproc 2>/dev/null || echo 1)"
    _memgb=$(( $(awk '/^MemTotal:/{print $2}' /proc/meminfo 2>/dev/null || echo 0) / 1048576 ))
    if [[ "$_memgb" -gt 0 ]]; then
        PAGAN_JOBS=$(( _memgb / 2 ))
        if [[ "$PAGAN_JOBS" -lt 1 ]]; then PAGAN_JOBS=1; fi
        if [[ "$PAGAN_JOBS" -gt "$_ncpu" ]]; then PAGAN_JOBS="$_ncpu"; fi
    else
        PAGAN_JOBS="$_ncpu"
    fi
fi
MAKEFLAGS="-j$PAGAN_JOBS"
export PAGAN_JOBS MAKEFLAGS
LFS_TGT="$(uname -m)-pagan-linux-gnu"

# Ścieżki
SRC_DIR="$PAGAN_ROOT/sources"
TOOLS_DIR="$PAGAN_ROOT/tools"
CROSS_BIN="$TOOLS_DIR/bin"
OUTPUT_DIR="${OUTPUT_DIR:-$SCRIPT_DIR/ISO}"
mkdir -p "$OUTPUT_DIR"

# Artefakt końcowy: img | iso | both | ask
ARTIFACT_MODE="${ARTIFACT_MODE:-iso}"
# Wymuś pełne czyszczenie i budowę od zera: 1|true|yes
REBUILD_FROM_SCRATCH="${REBUILD_FROM_SCRATCH:-0}"
# Pomiń budowę sterowników NVIDIA (open-gpu-kernel-modules): 1|true|yes
SKIP_NVIDIA="${SKIP_NVIDIA:-0}"

# Rozmiar obrazu IMG (MB)
IMAGE_SIZE="${IMAGE_SIZE:-16384}"  # 16GB

# ========================= SYSTEM LOGÓW =========================
# Poziomy: 0=DEBUG 1=INFO 2=WARN 3=ERROR
: "${PAGAN_LOG_LEVEL:=0}"

LOG_FILE="/var/log/pagan-build.log"
SUMMARY_FILE="/var/log/pagan-build.summary.log"
mkdir -p "$(dirname "$LOG_FILE")"

# Inicjalizuj plik logu
cat > "$LOG_FILE" <<EOF
╔══════════════════════════════════════════════════════════════════╗
║  Pagan Linux Builder v${PAGAN_VERSION} – Log budowy
║  Start: $(date '+%Y-%m-%d %H:%M:%S')  |  DE: ${PAGAN_DE^^}  |  CPU: $(nproc) rdzeni  |  Zadania: ${PAGAN_JOBS}
╚══════════════════════════════════════════════════════════════════╝
EOF
echo "" > "$SUMMARY_FILE"

# ========================= KOLORY (tylko konsola) =========================
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; MAGENTA='\033[0;35m'; CYAN='\033[0;36m'
BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'

# ========================= INTERFEJS UŻYTKOWNIKA =========================
source "$SCRIPT_DIR/ui.sh"
ui_init

# ========================= STATYSTYKI =========================
PKG_BUILT=0; PKG_FAILED=0; PKG_SKIPPED=0
RESUME_FILE="${RESUME_FILE:-/var/log/pagan-build.done}"

# Rejestry zasobów IMG – pozwalają trapowi EXIT (patrz _show_summary) posprzątać
# po błędzie: bind-mounty i loop device nie mogą zostać zajęte po przerwaniu.
_PAGAN_IMG_LOOPDEV=""
_PAGAN_IMG_MNT=""

# ========================= SYSTEM RESUME =========================
# Sprawdza czy pakiet był już zbudowany
_is_built() {
    local pkg="$1"
    # --force-pkg nadpisuje resume. Drugi warunek: dopasowanie po nazwie-bazie
    # (część przed pierwszym '-'), np. --force-pkg=gcc wymusza także gcc-pass1/pass2.
    # To celowe (przebudowa wszystkich etapów danego pakietu); NIE rozciąga się na
    # inne nazwy – np. 'gcc14' nie pasuje do 'gcc', bo jego baza to 'gcc14'.
    [[ -n "${FORCE_PKGS:-}" ]] && [[ ",${FORCE_PKGS}," == *",${pkg},"* || ",${FORCE_PKGS}," == *",${pkg%%-*},"* ]] && return 1
    [[ -f "$RESUME_FILE" ]] && grep -qxF "$pkg" "$RESUME_FILE" 2>/dev/null
}
# Oznacza pakiet jako zbudowany
_mark_built() {
    local pkg="$1"
    mkdir -p "$(dirname "$RESUME_FILE")"
    echo "$pkg" >> "$RESUME_FILE"
}

_is_true() {
    # Akceptuj też warianty mieszane (True/Yes/On) – normalizacja do małych liter.
    local v="${1:-}"
    case "${v,,}" in
        1|true|yes|on|y) return 0 ;;
        *) return 1 ;;
    esac
}

# Czy adres (hex, np. z readelf/objdump) to dokładnie 0x9000 – link_addr jądra
# GRUB-a i386-pc?
#
# Oba narzędzia piszą ten sam adres RÓŻNYMI formatami:
#   readelf -SW  → „00009000”  (zeruje pole Addr do szerokości ELF-a: 8 dla i386-pc)
#   objdump -f   → „0x00009000” (z prefiksem 0x)
# Dlatego najpierw zdejmujemy opcjonalny prefiks, a potem porównujemy LICZBOWO.
#
# DLACZEGO NIE PORÓWNANIE TEKSTOWE: `[[ "$a" == "9000" ]]` dawało FAŁSZYWY
# ALARM („kernel.img is miscompiled”) dla całkowicie poprawnego pliku (bo
# „00009000” != „9000”), a permissywny glob `!= *9000` przepuszczał realnie
# zepsute 0x19000.
_addr_is_0x9000() {
    local a="${1:-}"
    a="${a#0[xX]}"
    [[ "$a" =~ ^[0-9a-fA-F]+$ ]] || return 1
    (( 16#$a == 0x9000 ))
}

clean_build_state() {
    step "Tryb: zbuduj od nowa"

    if [[ -z "$PAGAN_ROOT" || "$PAGAN_ROOT" == "/" ]]; then
        error "Niebezpieczny PAGAN_ROOT='$PAGAN_ROOT' - odmawiam czyszczenia"
    fi

    if declare -F umount_virtual_fs >/dev/null; then
        umount_virtual_fs >/dev/null 2>&1 || true
    fi

    local _keep=""
    # Odzysk po przerwanym --rebuild --keep-sources: sources odłożone do
    # $PAGAN_ROOT.sources.keep, a rootfs jeszcze nie odtworzony.
    if [[ ! -d "$PAGAN_ROOT/sources" && -d "$PAGAN_ROOT.sources.keep" ]]; then
        warn "Odzyskuję wcześniej odłożone sources/ (przerwany --rebuild --keep-sources)"
        mkdir -p "$PAGAN_ROOT"
        mv "$PAGAN_ROOT.sources.keep" "$PAGAN_ROOT/sources"
    fi
    if _is_true "${REBUILD_KEEP_SOURCES:-}" && [[ -d "$PAGAN_ROOT/sources" ]]; then
        # sources/ (pobrane archiwa, zwykle kilka GB) wynosimy w całości poza
        # rootfs (rename na tym samym FS = natychmiastowe), żeby --rebuild ich
        # nie skasował – po odtworzeniu rootfs wracają na miejsce.
        _keep="$PAGAN_ROOT.sources.keep"
        warn "Zachowuję sources/ (--keep-sources): przenoszę do $_keep"
        rm -rf "$_keep"
        mv "$PAGAN_ROOT/sources" "$_keep" || \
            error "Nie udało się odłożyć sources/ ($_keep) – przerwij i zrób to ręcznie"
    fi

    warn "Czyszczenie katalogu builda: $PAGAN_ROOT"
    rm -rf --one-file-system "$PAGAN_ROOT"
    rm -f "$RESUME_FILE"

    mkdir -p "$PAGAN_ROOT" "$OUTPUT_DIR"
    if [[ -n "$_keep" && -d "$_keep" ]]; then
        mv "$_keep" "$PAGAN_ROOT/sources"
        ok "Przywrócono sources/ do $PAGAN_ROOT/sources"
    fi
    ok "Wyczyszczono rootfs i stan resume; start od zera"
}

# Wykrywa nieaktualny zapis resume. Sytuacja: ktoś ręcznie skasował $PAGAN_ROOT,
# a wpisy w $RESUME_FILE (/var/log/pagan-build.done, POZA rootfs) pozostały –
# przez co build "myśli", że pakiety są zbudowane, choć rootfs jest pusty.
_detect_stale_resume() {
    [[ -s "$RESUME_FILE" ]] || return 0
    local n
    n=$(wc -l < "$RESUME_FILE" 2>/dev/null || echo 0)
    if [[ ! -d "$PAGAN_ROOT/usr" ]] || [[ ! -d "$PAGAN_ROOT/etc" ]]; then
        warn "Wykryto nieaktualny zapis resume ($n pakietów) – $PAGAN_ROOT jest pusty/nieistniejący"
        warn "Resetuję resume (build od początku; usuń ręcznie $RESUME_FILE, aby pominąć to ostrzeżenie)"
        rm -f "$RESUME_FILE"
        return 0
    fi

    # Samo-naprawa ABI libjpeg: host (Arch) i binarki linkowane host-first używają
    # libjpeg.so.8. Stary build targetu dawał .62, a libtiff/tumbler linkowały
    # wlasnie z .62. Jeśli w rootfs jest jeszcze .62 (stary ABI) albo brak .8,
    # przebudowujemy libjpeg (receptura ma -DWITH_JPEG8=1) + zależne pakiety.
    if [[ -d "$PAGAN_ROOT/usr/lib" ]] \
            && { [[ -e "$PAGAN_ROOT/usr/lib/libjpeg.so.62" ]] || [[ ! -e "$PAGAN_ROOT/usr/lib/libjpeg.so.8" ]]; } \
            && grep -qxF 'libjpeg-turbo-3.1.3' "$RESUME_FILE" 2>/dev/null; then
        warn "libjpeg: wykryto stary ABI .62 w targetcie – wymuszam przebudowę libjpeg/libtiff/tumbler (na .8)"
        sed -i -E '/^(libjpeg-turbo-3\.1\.3|libtiff-4\.7\.1|tumbler-4\.20\.2)$/d' "$RESUME_FILE" 2>/dev/null || true
    fi

    # KF6 CoreAddons: metatypes instalowały się do zagnieżdżonego rootfsa
    # (absolutny KDE_INSTALL_QTMETATYPESDIR + DESTDIR), a eksport celuje w
    # $PAGAN_ROOT/usr/metatypes/… → brak pliku = kpmcore się nie skonfiguruje.
    if [[ -e "$PAGAN_ROOT/usr/lib/cmake/KF6CoreAddons/KF6CoreAddonsTargets.cmake" ]] \
       && [[ ! -e "$PAGAN_ROOT/usr/metatypes/qt6kf6coreaddons_metatypes.json" ]]; then
        warn "KF6 CoreAddons: brak pliku metatypes w targetcie – wymuszam przebudowę kcoreaddons"
        sed -i '/^kcoreaddons-6\.28\.0$/d' "$RESUME_FILE" 2>/dev/null || true
    fi

    # KF6: stare configi (z pełnego KF6) eksportują targety QML, których libki
    # usuwamy (budujemy KF6 bez QML) → find_package(KF6...) pada. Wymuś
    # przebudowę danych modułów, żeby ich configi były spójne z libkami.
    local _kf6_pairs=( "kcoreaddons:KF6CoreAddons" "kconfig:KF6Config" "ki18n:KF6I18n" "kwidgetsaddons:KF6WidgetsAddons" )
    local _pp _cm _tp
    for _pp in "${_kf6_pairs[@]}"; do
        _cm="${_pp##*:}"
        _tp="$PAGAN_ROOT/usr/lib/cmake/$_cm/${_cm}Targets.cmake"
        [[ -f "$_tp" ]] || continue
        grep -q "Qml" "$_tp" 2>/dev/null || continue
        [[ -e "$PAGAN_ROOT/usr/lib/lib${_cm}Qml.so.6" ]] && continue
        warn "KF6 $_cm: stary config eksportuje QML bez libki – wymuszam przebudowę ${_pp%%:*}-6.28.0"
        sed -i "/^${_pp%%:*}-6\.28\.0$/d" "$RESUME_FILE" 2>/dev/null || true
    done
}

# ========================= FUNKCJE LOGOWANIA =========================
# _log_raw <kolor> <poziom> <msg> – zapisuje do pliku (bez kolorów) i na konsolę
_log_raw() {
    local color="$1" lvl="$2" msg="$3"
    local ts; ts=$(date '+%H:%M:%S')
    echo -e "${color}[${ts}] [${lvl}]${NC} $msg"
    echo "[${ts}] [${lvl}] $msg" >> "$LOG_FILE"
}

dbg() {
    [[ $PAGAN_LOG_LEVEL -le 0 ]] && _log_raw "${DIM}" "DEBUG" "$*" || true
}
info() {
    [[ $PAGAN_LOG_LEVEL -le 1 ]] && _log_raw "${BLUE}" "INFO " "$*" || true
}
warn() {
    [[ $PAGAN_LOG_LEVEL -le 2 ]] && _log_raw "${YELLOW}" "WARN " "$*" >&2 || true
}
# Brak KRYTYCZNY – nie wystarczy „uwaga”: obraz z takim brakiem nie nadaje się do
# wydania. Tak liczony problem podnosi VERIFY_CRITICAL, a main() PRZERYWA przed
# create_image/create_iso (obejście: PAGAN_ALLOW_BROKEN_ISO=1).
#
# DLACZEGO OSOBNA FUNKCJA: wszystkie braki były wcześniej zwykłymi ostrzeżeniami,
# więc ISO wyszło bez modułów NVIDIA (DESTDIR zamiast INSTALL_MOD_PATH) i z
# niepełnym initramfs – nikt tego nie zatrzymał. Kryterium: „czy z takiego obrazu
# da się zainstalować i wystartować system”.
crit() {
    warn "$*"
    # `fails` jest lokalne w verify_rootfs – dynamiczny zasięg basha sprawia, że
    # trafiamy w tę samą zmienną; poza tą funkcją zmiennej nie ma, więc `-v` chroni
    # przed błędem pod `set -u`.
    [[ -v fails ]] && ((fails++)) || true
    [[ -v VERIFY_CRITICAL ]] && ((VERIFY_CRITICAL++)) || true
    return 0
}
error() {
    _log_raw "${RED}" "ERROR" "$*" >&2
    _dump_stack
    _log_raw "${RED}" "FATAL" "Budowa przerwana. Log: $LOG_FILE" >&2
    exit 1
}
ok() {
    _log_raw "${GREEN}" "OK   " "$*"
    ((PKG_BUILT++)) || true
    echo "✓ $*" >> "$SUMMARY_FILE"
}

step() {
    local title="$*"
    echo ""
    echo -e "${MAGENTA}${BOLD}═══ ${title} ═══${NC}"
    echo "━━━ $(date '+%H:%M:%S') ━━━ ${title} ━━━" >> "$LOG_FILE"
    dbg "START: $title"
}

_dump_stack() {
    local frame=0
    echo "  Stack trace:" >> "$LOG_FILE"
    while caller $frame 2>/dev/null; do
        ((frame++))
    done >> "$LOG_FILE"
}

# Trap błędów – łapie SIGERR
_on_error() {
    local lineno="$1" cmd="$2"
    echo -e "${RED}[ERROR]${NC} Linia ${lineno}: \"${cmd}\"" | tee -a "$LOG_FILE" >&2
    echo "[$(date '+%H:%M:%S')] [ERROR] Linia ${lineno}: ${cmd}" >> "$LOG_FILE"
    # Kluczowa diagnoza: jeśli SAM PLIK skryptu zmienił się w trakcie builda,
    # to komunikaty o „nieznalezionym poleceniu”/bzdurnych numerach linii są
    # artefaktem desynchronizacji odczytu, a nie błędem w skrypcie.
    if _pagan_self_changed; then
        {
            echo -e "${YELLOW}[UWAGA]${NC} build-pagan.sh ZMIENIŁ SIĘ w trakcie działania (rozmiar/mtime)."
            echo "        Bash czyta skrypt z offsetem – po zmianie pliku wykonuje przypadkowe"
            echo "        fragmenty linii i podaje bzdurne numery. Zamknij edytor (Zed zapisuje"
            echo "        plik przy zmianie fokusu – klik na terminal!) i uruchom build ponownie."
            echo "        To NIE jest błąd skryptu – nie zgłaszaj tego jako błąd w kodzie."
        } >&2
    fi
    _dump_stack
}
trap '_on_error "${LINENO}" "${BASH_COMMAND}"' ERR

# Podsumowanie na wyjściu
_show_summary() {
    # Best-effort cleanup: avoid stale mounts after cancel/error.
    if declare -F umount_virtual_fs >/dev/null; then
        umount_virtual_fs >/dev/null 2>&1 || true
    fi
    # Zasoby IMG (bind-mounty + loop) – gdy create_image padnie w połowie.
    if declare -F _cleanup_image_mounts >/dev/null; then
        _cleanup_image_mounts
    fi

    ui_summary
}
trap _show_summary EXIT

# ========================= KONFIGURACJA =========================

# ========================= BANNER =========================
show_banner() {
    cat << 'EOF'
╔═══════════════════════════════════════════════════════════════════╗
║                                                                   ║
║    ██████   █████   █████   █████  ███    ██                      ║
║    ██   ██ ██   ██ ██      ██   ██ ████   ██                      ║
║    ██████  ███████ ██████  ███████ ██ ██  ██                      ║
║    ██      ██   ██ ██   ██ ██   ██ ██  ██ ██                      ║
║    ██      ██   ██  █████  ██   ██ ██   ████                      ║
║                                                                   ║
║    ╔══════════════════════════════════════════════════════════╗   ║
EOF
    # Wiersze ze zmiennymi – padding liczony w locie, żeby ramka
    # zawsze się domykała (niezależnie od długości wersji/architektury).
    local pad entry
    for entry in \
        "  Pagan Linux Builder v${PAGAN_VERSION}" \
        "  LFS ${LFS_VERSION} + BLFS + ${PAGAN_DE^^}" \
        "  Architektura: $(uname -m)" \
        "  Kernel: ${KERNEL_VERSION} $(_is_true "${ROLLING_KERNEL:-1}" && printf '(rolling/PAG)' || printf '(tylko w ISO)')" \
        "  Data: $(date +'%Y-%m-%d %H:%M:%S')"; do
        # Gdy wpis jest dłuższy niż ramka (58), nie licz ujemnej szerokości
        # (printf '%*s' z ujemną szerokością rozjeżdża ramkę).
        if (( ${#entry} > 58 )); then
            pad=""
        else
            pad=$(printf '%*s' "$((58 - ${#entry}))" "")
        fi
        printf '║    ║%s%s║   ║\n' "$entry" "$pad"
    done
    cat << 'EOF'
║    ╚══════════════════════════════════════════════════════════╝   ║
║                                                                   ║
╚═══════════════════════════════════════════════════════════════════╝
EOF
}

# ========================= SPRAWDZANIE ZALEŻNOŚCI =========================
# Zwraca nazwę pakietu dla danego polecenia ($1) i menedżera pakietów ($2: apt|dnf|pacman).
# WAŻNE: nazwy pakietów są RÓŻNE między dystrybucjami (np. Arch: glib-mkenums jest
# w glib2, a nie w „libglib2.0-dev”; xorriso to „libisoburn”; pkg-config to „pkgconf”).
# Błędna nazwa = pacman/apt przerywa CAŁĄ transakcję i nic nie instaluje.
_pm_pkg() {
    local cmd="$1" pm="${2:-apt}"
    case "$cmd" in
        mkfs.fat)   echo dosfstools ;;
        mkfs.ext4)  echo e2fsprogs ;;
        mksquashfs) echo squashfs-tools ;;
        mcopy)      echo mtools ;;
        glib-mkenums)
            case "$pm" in
                dnf)    echo glib2-devel ;;
                pacman) echo glib2 ;;   # Arch: binarka + nagłówki w jednym pakiecie glib2
                *)      echo libglib2.0-dev ;;
            esac ;;
        g-ir-scanner)
            case "$pm" in
                dnf)    echo gobject-introspection-devel ;;
                *)      echo gobject-introspection ;;
            esac ;;
        intltool-update) echo intltool ;;
        losetup|blkid|wipefs) echo util-linux ;;
        parted)     echo parted ;;
        cpio)       echo cpio ;;
        rg)         echo ripgrep ;;
        zstd)       echo zstd ;;
        unzip)      echo unzip ;;
        gpg)
            case "$pm" in dnf) echo gnupg2 ;; *) echo gnupg ;; esac ;;
        pkg-config)
            case "$pm" in
                pacman) echo pkgconf ;;    # Arch nie ma pakietu „pkg-config”
                dnf)    echo pkgconf-pkg-config ;;
                *)      echo pkg-config ;;
            esac ;;
        xorriso)
            case "$pm" in
                pacman) echo libisoburn ;;  # Arch: /usr/bin/xorriso jest w libisoburn
                *)      echo xorriso ;;
            esac ;;
        find)       echo findutils ;;
        diff|cmp)   echo diffutils ;;
        strings)    echo binutils ;;
        gcc)        echo gcc ;;
        g++)
            # g++ to zwykły pakiet tylko w Debianie; Arch/Fedora dają go z gcc / gcc-c++
            case "$pm" in pacman) echo gcc ;; dnf) echo gcc-c++ ;; *) echo g++ ;; esac ;;
        iceauth)
            case "$pm" in pacman) echo xorg-iceauth ;; *) echo iceauth ;; esac ;;
        *)          echo "$cmd" ;;
    esac
}

# ========================= PLIKI STARTOWE ISO (SYSLINUX) =========================
# Do bootowalnego (hybrydowego) ISO potrzeba na HOŚCIE plików syslinux:
#   isolinux.bin   – El Torito boot BIOS (menu startowe ISO),
#   ldlinux.c32    – moduł wymagany przez isolinux.bin,
#   isohdpfx.bin   – 432-bajtowy MBR isohybrid: bez niego sektor 0 ISO to zera,
#                    brak tablicy partycji → balenaEtcher/Rufus odrzucają obraz
#                    („Missing partition table") i pendrive nie bootuje.
# Syslinux bywa rozłożony różnie w zależności od dystrybucji:
#   Arch/EndeavourOS: /usr/lib/syslinux/bios/*
#   Debian/Ubuntu:    /usr/lib/ISOLINUX/*, /usr/lib/syslinux/mbr/*
#   Fedora/openSUSE:  /usr/share/syslinux/*
# Skrypt instaluje pakiet automatycznie (jak pozostałe zależności), a gdy to
# niemożliwe – przerywa z czytelnym komunikatem zamiast po cichu wygenerować
# ISO niebootowalne z USB.
SYSLINUX_DIRS=(
    /usr/lib/syslinux/bios
    /usr/lib/syslinux
    /usr/lib/syslinux/modules/bios
    /usr/share/syslinux
    /usr/lib/ISOLINUX
    /usr/lib/syslinux/mbr
)

# $1 = nazwa pliku (isolinux.bin|ldlinux.c32|isohdpfx.bin) → echo pełnej ścieżki
_find_syslinux_file() {
    local f="$1" d
    for d in "${SYSLINUX_DIRS[@]}"; do
        if [[ -f "$d/$f" ]]; then
            echo "$d/$f"
            return 0
        fi
    done
    return 1
}

# Instaluje syslinux na hoście przez menedżer pakietów (jeśli się da).
_install_syslinux_host() {
    # Menedżer pakietów hosta uruchamiamy bez LD_LIBRARY_PATH/LIBRARY_PATH
    # buildera – inaczej binarka hosta załaduje biblioteki z $PAGAN_ROOT
    # i wywali się na niezgodności SONAME („libcurl.so.4: no version
    # information available (required by libalpm.so.16)”).
    if command -v pacman &>/dev/null; then
        env -u LD_LIBRARY_PATH -u LIBRARY_PATH pacman -Sy --noconfirm --needed syslinux
    elif command -v apt-get &>/dev/null; then
        env -u LD_LIBRARY_PATH -u LIBRARY_PATH apt-get update
        env -u LD_LIBRARY_PATH -u LIBRARY_PATH apt-get install -y isolinux syslinux-common syslinux-utils
    elif command -v dnf &>/dev/null; then
        env -u LD_LIBRARY_PATH -u LIBRARY_PATH dnf install -y syslinux
    else
        return 1
    fi
}

check_dependencies() {
    step "Sprawdzanie zależności systemowych"

    local deps=(
        "gcc" "g++" "make" "bison" "flex" "texinfo" "bc"
        "wget" "curl" "tar" "xz" "bzip2" "gzip" "zstd" "unzip"
        "patch" "gawk" "sed" "grep" "find" "strings"
        "file" "diff" "cmp" "cpio"
        "python3" "perl" "m4" "cmake" "ninja" "meson" "gperf"
        "git" "rsync" "gpg" "pkg-config"
        "xorriso" "mkfs.fat" "mkfs.ext4" "mcopy" "parted" "mksquashfs" "losetup"
        "g-ir-scanner" "intltool-update" "itstool" "iceauth" "rg"
    )

    # Narzędzia glib (glib-mkenums, glib-genmarshal) CELOWO nie są tu
    # sprawdzane: nie potrzebujemy ich z hosta. glib-mkenums powstaje dopiero
    # przy budowie glib2 w targetcie i builder podstawia je sobie sam
    # (_provide_glib_build_tools tworzy wrapper w $TOOLS_DIR/bin i to on jest
    # pierwszy w PATH przy buildach). Wymaganie ich od hosta było fałszywym
    # alarmem, który wołał `pacman -Sy` – czyli CZĘŚCIOWĄ aktualizację
    # systemu, po której host potrafi się rozjechać (klasyk: libalpm.so.16
    # vs libcurl „no version information available”).
    _provide_glib_build_tools

    local missing=()
    for dep in "${deps[@]}"; do
        # Zadowalamy się też narzędziem z TARGETU lub wrapperem buildera –
        # przy wznowionym buildzie rootfs już je ma.
        if command -v "$dep" &>/dev/null \
           || [[ -x "$TOOLS_DIR/bin/$dep" ]] \
           || [[ -x "$PAGAN_ROOT/usr/bin/$dep" ]] \
           || [[ -x "$PAGAN_ROOT/usr/sbin/$dep" ]]; then
            continue
        fi
        # texinfo nie ma binarki „texinfo” (w Arch/Fedora jest makeinfo/texi2any)
        case "$dep" in
            texinfo)
                command -v makeinfo &>/dev/null || command -v texi2any &>/dev/null || missing+=("$dep") ;;
            *) missing+=("$dep") ;;
        esac
    done

    if [[ ${#missing[@]} -gt 0 ]]; then
        warn "Brakujące pakiety: ${missing[*]}"
        info "Instalowanie brakujących pakietów..."

        # Uwaga: menedżer pakietów hosta uruchamiamy w OCZYSZCZONYM środowisku
        # (env -u LD_LIBRARY_PATH -u LIBRARY_PATH). Builder eksportuje
        # LD_LIBRARY_PATH na $PAGAN_ROOT/usr/lib, a wtedy binarka hosta
        # (pacman) potrafi załadować biblioteki z TARGETU i wywalić się na
        # niezgodności SONAME (np. „libcurl.so.4: no version information
        # available (required by libalpm.so.16)”).
        if command -v apt-get &>/dev/null; then
            local apt_pkgs=()
            for dep in "${missing[@]}"; do apt_pkgs+=("$(_pm_pkg "$dep" apt)"); done
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH apt-get update
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH apt-get install -y "${apt_pkgs[@]}" \
                libncurses-dev libssl-dev libreadline-dev \
                libxml2-dev libxslt1-dev libglib2.0-dev \
                libgtk-3-dev libpango1.0-dev libcairo2-dev \
                libx11-dev libxext-dev libxft-dev \
                libfreetype6-dev libfontconfig1-dev \
                libjpeg-dev libpng-dev libtiff-dev \
                libgdk-pixbuf2.0-dev libnotify-dev \
                libdbus-1-dev libsystemd-dev libudev-dev libmnl-dev libevdev-dev \
                libelf-dev libffi-dev libunistring-dev libacl1-dev libattr1-dev \
                libpam0g-dev libgirepository1.0-dev gobject-introspection \
                libqt5svg5-dev qtbase5-dev qtdeclarative5-dev qttools5-dev \
                shared-mime-info intltool xsltproc gperf valac gcc-multilib \
                libc6-dev-i386 python3-pip python3-venv xorg-util-macros
        elif command -v dnf &>/dev/null; then
            local dnf_pkgs=()
            for dep in "${missing[@]}"; do dnf_pkgs+=("$(_pm_pkg "$dep" dnf)"); done
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH dnf groupinstall -y "Development Tools" "Development Libraries"
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH dnf install -y "${dnf_pkgs[@]}" \
                ncurses-devel openssl-devel readline-devel \
                libxml2-devel libxslt-devel glib2-devel \
                gtk3-devel pango-devel cairo-devel \
                libX11-devel libXext-devel libXft-devel \
                freetype-devel fontconfig-devel \
                libjpeg-devel libpng-devel libtiff-devel \
                gdk-pixbuf2-devel libnotify-devel \
                dbus-devel systemd-devel libudev-devel libmnl-devel libevdev-devel \
                elfutils-libelf-devel libffi-devel libacl-devel libattr-devel \
                pam-devel gobject-introspection-devel qt5-qtbase-devel \
                qt5-qtdeclarative-devel qt5-qtsvg-devel shared-mime-info intltool \
                libxslt-devel gperf vala gcc-c++ multilib-rpm-config \
                python3-devel python3-pip xorg-x11-util-macros
        elif command -v pacman &>/dev/null; then
            local arch_pkgs=()
            for dep in "${missing[@]}"; do arch_pkgs+=("$(_pm_pkg "$dep" pacman)"); done
            # Uwaga (Arch): NIE MA pakietów „glib2-devel”/„libglib2.0-dev” – nagłówki
            # i narzędzia (glib-mkenums) są w glib2. Błędna nazwa = pacman przerywa
            # całą transakcję („pakiet nie został znaleziony”) i nic nie instaluje.
            # systemd CELOWO pominięty: na Arch/EndeavourOS próba aktualizacji
            # systemd (np. 260→261) koliduje z pakietami typu systemd-resolvconf /
            # systemd-sysvcompat (wymagają dokładnej wersji systemd=…), a dev-nagłówki
            # libsystemd nie są potrzebne na hoście – systemd buduje się w chrocie.
            if ! env -u LD_LIBRARY_PATH -u LIBRARY_PATH \
                 pacman -Sy --noconfirm --needed base-devel "${arch_pkgs[@]}" \
                    ncurses openssl readline libxml2 libxslt glib2 \
                    gtk3 pango cairo libx11 libxext libxft xtrans xorg-util-macros intltool \
                    freetype2 fontconfig libjpeg-turbo libpng libtiff \
                    gdk-pixbuf2 libnotify dbus \
                    elfutils libffi python python-pip wayland-protocols; then
                # Konflikt na liście dev-libs (np. AUR trzyma starszą wersję czegoś)
                # nie może zabić builda – doinstaluj przynajmniej same narzędzia.
                warn "Pełna lista dev-libs nie przeszła przez pacman – próbuję same narzędzia..."
                env -u LD_LIBRARY_PATH -u LIBRARY_PATH \
                    pacman -Sy --noconfirm --needed "${arch_pkgs[@]}" || \
                    error "Nie udało się zainstalować narzędzi builda przez pacman: ${arch_pkgs[*]}"
            fi
        else
            error "Nieznany menedżer pakietów. Zainstaluj ręcznie: ${missing[*]}"
        fi
    fi

    # Hostowy python3 musi faktycznie DZIAŁAĆ – glibc (i inne pakiety) używa go
    # do generowania nagłówków (gen-as-const). Najczęstsza przyczyna awarii:
    # „partial upgrade” na Arch – python zaktualizowany pod nowsze glibc, a libm
    # została stara → ImportError: „version `GLIBC_x.y' not found”. To NIE jest
    # błąd builda – trzeba najpierw naprawić system (sudo pacman -Syu).
    if ! python3 -c 'import math, sys' >/dev/null 2>&1; then
        error "Hostowy python3 jest uszkodzony (python3 -c 'import math' nie działa). Wykonaj PEŁNĄ aktualizację systemu (np. sudo pacman -Syu), potem uruchom build ponownie."
    fi

    # Pliki startowe ISO (isolinux + isohdpfx MBR) to NIE polecenia – command -v
    # ich nie złapie, więc sprawdzamy pliki wprost i instalujemy pakiet sami.
    if ! _find_syslinux_file isolinux.bin >/dev/null ||
       ! _find_syslinux_file ldlinux.c32 >/dev/null ||
       ! _find_syslinux_file isohdpfx.bin >/dev/null; then
        warn "Brak plików syslinux na hoście (isolinux.bin/ldlinux.c32/isohdpfx.bin) – instaluję..."
        _install_syslinux_host || \
            error "Nieznany menedżer pakietów – zainstaluj syslinux ręcznie (potrzebne: isolinux.bin, ldlinux.c32, isohdpfx.bin)"
        if ! _find_syslinux_file isohdpfx.bin >/dev/null; then
            error "Auto-instalacja syslinux nie powiodła się. Bez isohdpfx.bin ISO nie będzie hybrydowe (balenaEtcher: 'Missing partition table'). Zainstaluj ręcznie: Arch/EndeavourOS 'sudo pacman -S syslinux' | Debian/Ubuntu 'sudo apt install isolinux syslinux-common syslinux-utils' | Fedora 'sudo dnf install syslinux'"
        fi
    fi

    # docbook-xsl (style XSL manpages) – xfce4-terminal 1.1.5+ generuje strony man
    # przez xsltproc z martwego URL http://docbook.sourceforge.net/…
    # („cannot parse …/manpages/docbook.xsl” → ninja: build stopped). Pakiet rejestruje
    # w /etc/xml/catalog rewriteURI dla tego legacy URL → xsltproc bierze style lokalnie.
    # To pakiet DANYCH (bez binarki) – command -v go nie złapie, sprawdzamy pliki.
    if ! find /usr/share/xml/docbook -maxdepth 1 -name 'xsl-stylesheets*' 2>/dev/null | grep -q .; then
        warn "Brak docbook-xsl (strony man XFCE: xfce4-terminal/…) – instaluję..."
        if command -v pacman &>/dev/null; then
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH pacman -Sy --noconfirm --needed docbook-xsl
        elif command -v apt-get &>/dev/null; then
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH apt-get update
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH apt-get install -y docbook-xsl
        elif command -v dnf &>/dev/null; then
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH dnf install -y docbook-xsl
        else
            error "Nieznany menedżer pakietów – zainstaluj ręcznie docbook-xsl (potrzebny do stron man xfce4-terminal)"
        fi
        if ! find /usr/share/xml/docbook -maxdepth 1 -name 'xsl-stylesheets*' 2>/dev/null | grep -q .; then
            error "Instalacja docbook-xsl nie powiodła się – zainstaluj ręcznie (Arch/EndeavourOS: 'sudo pacman -S docbook-xsl')"
        fi
    fi

    # jinja2 (host, python3) – wymagany przez meson systemd (generowanie plików).
    # systemd buduje się NA HOŚCIE hostowym mesonem/pythonem, więc moduł musi być
    # w hostowym python3 (install_pag instaluje go dopiero później – do targetu).
    if ! python3 -c 'import jinja2' >/dev/null 2>&1; then
        warn "Brak hostowego modułu jinja2 (wymagany przez systemd) – instaluję..."
        if command -v pacman &>/dev/null; then
            env -u LD_LIBRARY_PATH -u LIBRARY_PATH pacman -Sy --noconfirm --needed python-jinja
        elif command -v apt-get &>/dev/null; then
            apt-get update && apt-get install -y python3-jinja2
        elif command -v dnf &>/dev/null; then
            dnf install -y python3-jinja2
        else
            python3 -m pip install --break-system-packages jinja2 2>/dev/null || \
                python3 -m pip install jinja2
        fi
        if ! python3 -c 'import jinja2' >/dev/null 2>&1; then
            error "Nie udało się zainstalować jinja2 na hoście – wymagane do builda systemd (Arch: 'sudo pacman -S python-jinja', Debian/Ubuntu: 'sudo apt install python3-jinja2')"
        fi
        ok "jinja2 (host) zainstalowany"
    fi
    ok "Wszystkie zależności spełnione"
}

# ========================= INICJALIZACJA =========================
# ========================= USRMERGE (scalone /usr) =========================
# systemd (261) WYMAGA layoutu z połączonym /usr: /bin, /sbin, /lib muszą być
# symlinkami do odpowiedników w /usr. Wcześniej były to zwykłe katalogi, więc:
#   - /bin/bash nie istniał (bash instaluje się do /usr/bin) → chroot /bin/bash
#     i ldd (skrypt z shebang #!/bin/bash) padały w sanity check,
#   - /bin/sh (tworzony przez build bash jako `ln -sf bash /bin/sh`) wisiał,
#   - systemd odmówiłby startu ("system is not with a merged /usr").
# Funkcja przenosi zawartość istniejących katalogów do /usr (jeśli coś tam jest)
# i zamienia je w symlinki. Bezpieczna do wielokrotnego wywołania (resume).
_usrmerge_rootfs() {
    # Bezpieczeństwo: nigdy nie ruszaj / przy PAGAN_ROOT="/" lub pustym
    if [[ -z "$PAGAN_ROOT" || "$PAGAN_ROOT" == "/" ]]; then
        warn "Usrmerge pominięty – niebezpieczny PAGAN_ROOT='${PAGAN_ROOT:-<pusty>}'"
        return 0
    fi
    # Uwaga: systemd budowany z -Dsplit-usr=false tłumaczy ścieżki /sbin/foo →
    # /usr/bin/foo, więc WSZYSTKO (łącznie z /usr/sbin) musi trafić do /usr/bin.

    # bin → usr/bin
    if [[ ! -L "$PAGAN_ROOT/bin" && -d "$PAGAN_ROOT/bin" ]]; then
        mkdir -p "$PAGAN_ROOT/usr/bin"
        cp -a "$PAGAN_ROOT/bin/." "$PAGAN_ROOT/usr/bin/" 2>/dev/null || true
        rm -rf "$PAGAN_ROOT/bin"
        ln -s usr/bin "$PAGAN_ROOT/bin"
    fi
    # sbin → usr/bin (NIE usr/sbin! inaczej agetty/modprobe są nieosiągalne dla systemd)
    if [[ ! -L "$PAGAN_ROOT/sbin" && -d "$PAGAN_ROOT/sbin" ]]; then
        # SysVinit-owe binarki (halt/shutdown/poweroff/reboot) przegrywają z systemd
        rm -f "$PAGAN_ROOT/sbin/halt" "$PAGAN_ROOT/sbin/shutdown" \
              "$PAGAN_ROOT/sbin/poweroff" "$PAGAN_ROOT/sbin/reboot" "$PAGAN_ROOT/sbin/init"
        mkdir -p "$PAGAN_ROOT/usr/bin"
        cp -a "$PAGAN_ROOT/sbin/." "$PAGAN_ROOT/usr/bin/" 2>/dev/null || true
        rm -rf "$PAGAN_ROOT/sbin"
        ln -s usr/bin "$PAGAN_ROOT/sbin"
    fi
    # usr/sbin → usr/bin (realny katalog scal; puste → od razu symlink, żeby
    # pakiety z --sbindir=/usr/sbin lądowały w /usr/bin)
    if [[ ! -L "$PAGAN_ROOT/usr/sbin" ]]; then
        if [[ -d "$PAGAN_ROOT/usr/sbin" ]]; then
            rm -f "$PAGAN_ROOT/usr/sbin/halt" "$PAGAN_ROOT/usr/sbin/shutdown" \
                  "$PAGAN_ROOT/usr/sbin/poweroff" "$PAGAN_ROOT/usr/sbin/reboot" "$PAGAN_ROOT/usr/sbin/init"
            mkdir -p "$PAGAN_ROOT/usr/bin"
            cp -a "$PAGAN_ROOT/usr/sbin/." "$PAGAN_ROOT/usr/bin/" 2>/dev/null || true
            rm -rf "$PAGAN_ROOT/usr/sbin"
        else
            mkdir -p "$PAGAN_ROOT/usr/bin"
        fi
        ln -s bin "$PAGAN_ROOT/usr/sbin"
    fi
    # lib → usr/lib
    if [[ ! -L "$PAGAN_ROOT/lib" && -d "$PAGAN_ROOT/lib" ]]; then
        mkdir -p "$PAGAN_ROOT/usr/lib"
        cp -a "$PAGAN_ROOT/lib/." "$PAGAN_ROOT/usr/lib/" 2>/dev/null || true
        rm -rf "$PAGAN_ROOT/lib"
        ln -s usr/lib "$PAGAN_ROOT/lib"
    fi
    # lib64 → usr/lib (x86_64 ABI)
    if [[ ! -L "$PAGAN_ROOT/lib64" && -d "$PAGAN_ROOT/lib64" ]]; then
        mkdir -p "$PAGAN_ROOT/usr/lib"
        cp -a "$PAGAN_ROOT/lib64/." "$PAGAN_ROOT/usr/lib/" 2>/dev/null || true
        rm -rf "$PAGAN_ROOT/lib64"
        ln -s usr/lib "$PAGAN_ROOT/lib64"
    fi
    # usr/lib64 → usr/lib: ld.so NIE szuka /usr/lib64 (poza cache); bez scalenia
    # binarki nie znajdą np. libjpeg.so.8 (libtiff!).
    if [[ ! -L "$PAGAN_ROOT/usr/lib64" ]]; then
        if [[ -d "$PAGAN_ROOT/usr/lib64" ]]; then
            mkdir -p "$PAGAN_ROOT/usr/lib"
            # dev-symlink libjpeg.so z /usr/lib (→ .8) jest właściwszy niż stary z lib64 (→ .62)
            rm -f "$PAGAN_ROOT/usr/lib64/libjpeg.so"
            cp -a "$PAGAN_ROOT/usr/lib64/." "$PAGAN_ROOT/usr/lib/" 2>/dev/null || true
            rm -rf "$PAGAN_ROOT/usr/lib64"
        else
            mkdir -p "$PAGAN_ROOT/usr/lib"
        fi
        ln -s lib "$PAGAN_ROOT/usr/lib64"
    fi
    # /var/run i /var/lock muszą być symlinkami do tmpfs /run (inaczej systemd
    # taint var-run-bad, a na RO squashfs zapis do /var/run w ogóle nie działa)
    if [[ ! -L "$PAGAN_ROOT/var/run" ]]; then
        rm -rf "$PAGAN_ROOT/var/run"
        ln -s ../run "$PAGAN_ROOT/var/run"
    fi
    if [[ ! -L "$PAGAN_ROOT/var/lock" ]]; then
        rm -rf "$PAGAN_ROOT/var/lock"
        ln -s ../run/lock "$PAGAN_ROOT/var/lock"
    fi
    info "  ✓ Usrmerge: /bin /sbin /lib /lib64 /usr/sbin /usr/lib64 /var/run /var/lock → /usr"
}

# Wrapper `ninja` w $TOOLS_DIR/bin (pierwszy w PATH): dokłada -j/-l z $PAGAN_JOBS,
# jeśli użytkownik nie podał -j. Ninja domyślnie bierze WSZYSTKIE rdzenie, co przy
# Qt6/KF6 (ciężkie C++ + PCH) zamraża komputer. Współdzielone przez init_build
# (pełny build) i fix-calamares.sh (naprawa pojedynczego pakietu).
_install_ninja_wrapper() {
    mkdir -p "$TOOLS_DIR/bin"
    cat > "$TOOLS_DIR/bin/ninja" <<'NINJA_WRAPPER'
#!/bin/sh
# Pagan: ogranicz równoległość ninja do $PAGAN_JOBS, o ile użytkownik nie podał -j.
real=""
for c in /usr/bin/ninja /bin/ninja /usr/local/bin/ninja; do
    if [ -x "$c" ]; then real="$c"; break; fi
done
if [ -z "$real" ]; then echo "ninja(wrapper): brak prawdziwego ninja" >&2; exit 127; fi
has_j=0; has_l=0
for a in "$@"; do
    case "$a" in
        -j|-j[0-9]*|--jobs|--jobs=*) has_j=1 ;;
        -l|-l[0-9]*|--load-average*) has_l=1 ;;
    esac
done
if [ "$has_j" = 0 ] && [ -n "${PAGAN_JOBS:-}" ]; then
    if [ "$has_l" = 0 ]; then
        exec "$real" -j "$PAGAN_JOBS" -l "$PAGAN_JOBS" "$@"
    fi
    exec "$real" -j "$PAGAN_JOBS" "$@"
fi
exec "$real" "$@"
NINJA_WRAPPER
    chmod 755 "$TOOLS_DIR/bin/ninja"
    info "  ✓ Wrapper ninja: limit zadań = ${PAGAN_JOBS:-auto} (rdzeni: $(nproc 2>/dev/null || echo '?'))"
}

init_build() {
    step "Inicjalizacja środowiska budowy"
    cd /

    mkdir -p "$PAGAN_ROOT" "$SRC_DIR" "$TOOLS_DIR" "$OUTPUT_DIR"

    # Katalogi systemowe
    for dir in usr bin sbin lib lib64 etc var boot home root \
                opt tmp run mnt media srv; do
        mkdir -p "$PAGAN_ROOT/$dir"
    done

    # Złączone /usr – wymagane przez systemd; naprawia /bin/bash, /bin/sh, ldd
    _usrmerge_rootfs

    # Wrapper ninja: ogranicza równoległość (chroni przed zamrożeniem komputera).
    _install_ninja_wrapper

    # Katalogi PAG
    mkdir -p "$PAGAN_ROOT/var/lib/pag"{,/packages,/snapshots}
    mkdir -p "$PAGAN_ROOT/var/cache/pag"

    # Deploymenty
    mkdir -p "$PAGAN_ROOT/.deployments"

    ok "Środowisko zainicjowane w: $PAGAN_ROOT"
}

# ========================= POBIERANIE PAKIETÓW =========================
_spinner() {
    local pid=$1 delay=0.1 spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' text="${2:-}"
    local i=0
    while kill -0 "$pid" 2>/dev/null; do
        i=$(( (i+1) % ${#spinstr} ))
        printf '\r  %s %s' "${spinstr:$i:1}" "$text"
        sleep "$delay"
    done
    printf '\r  ✓ %s\n' "$text"
}

_archive_is_valid() {
    local file="$1"
    [[ -s "$file" ]] || return 1

    case "$file" in
        *.tar.xz) xz -t "$file" >/dev/null 2>&1 ;;
        *.tar.gz|*.tgz) gzip -t "$file" >/dev/null 2>&1 ;;
        *.tar.bz2|*.tbz2) bzip2 -t "$file" >/dev/null 2>&1 ;;
        *.tar.zst)
            if command -v zstd >/dev/null 2>&1; then
                zstd -t "$file" >/dev/null 2>&1
            else
                unzstd -t "$file" >/dev/null 2>&1
            fi
            ;;
        *.tar) tar -tf "$file" >/dev/null 2>&1 ;;
        *.zip) unzip -tqq "$file" >/dev/null 2>&1 ;;
        *) ! file "$file" 2>/dev/null | grep -qE "HTML document|XML document|ASCII text|empty" ;;
    esac
}

# Weryfikuje sumę SHA256 pliku. Zwraca 0 gdy plik pasuje do oczekiwanej sumy
# (lub gdy suma jest "SKIP"/pusta). Zwraca 1 przy niezgodności.
#
# Uwaga: mechanizm weryfikacji jest gotowy – w recepturze wystarczy wpisać
# realną sumę zamiast "SKIP" (4. pole: nazwa:wersja:url:SHA256:komendy).
# Póki wpisy mają "SKIP", archiwa NIE są weryfikowane kryptograficznie (tylko
# test poprawności archiwum). Aby wymusić podawanie sum (tryb CI/safety):
#   PAGAN_STRICT_SHA=1  → każdy pakiet bez sumy SHA256 kończy się błędem.
_sha_ok() {
    local file="$1" expected="${2:-SKIP}"
    if [[ -z "$expected" || "$expected" == "SKIP" ]]; then
        if _is_true "${PAGAN_STRICT_SHA:-0}"; then
            warn "  ✗ Brak sumy SHA256 (SKIP) dla $(basename "$file") – PAGAN_STRICT_SHA=1"
            return 1
        fi
        return 0
    fi
    command -v sha256sum >/dev/null 2>&1 || return 0
    local got
    got=$(sha256sum "$file" 2>/dev/null | awk '{print $1}')
    if [[ "$got" == "$expected" ]]; then
        return 0
    fi
    warn "  ✗ SHA256 niezgodność dla $(basename "$file"):"
    warn "      oczekiwano: $expected"
    warn "      otrzymano:  ${got:-brak}"
    return 1
}

download_pkg() {
    local url="$1" dest="$2" sha="${3:-SKIP}" force="${4:-0}"
    mkdir -p "$(dirname "$dest")"

    if [[ "$force" != "1" && -f "$dest" ]]; then
        if _archive_is_valid "$dest" && _sha_ok "$dest" "$sha"; then
            info "  ⏭ $(basename "$dest") – już pobrany (ok)"
            return 0
        fi
        warn "Uszkodzone/niezgodne archiwum: $(basename "$dest") – pobieram ponownie"
        rm -f "$dest"
    fi

    # Lista adresów do próby (główny + mirror)
    local urls=("$url")
    case "$url" in
        *ftp.gnu.org/gnu/*)
            urls+=("https://mirror.kumi.systems/gnu/${url#https://ftp.gnu.org/gnu/}")
            urls+=("https://mirrors.ocf.berkeley.edu/gnu/${url#https://ftp.gnu.org/gnu/}")
            urls+=("https://ftp.nluug.nl/pub/gnu/${url#https://ftp.gnu.org/gnu/}")
            ;;
        *ftpmirror.gnu.org/*)
            # ftpmirror.gnu.org to usługa przekierowań (bywa 502) – zapas: wprost z ftp.gnu.org
            urls+=("https://ftp.gnu.org/gnu/${url#https://ftpmirror.gnu.org/}")
            ;;
        *cdn.kernel.org/*|*www.kernel.org/*)
            urls+=("https://mirrors.edge.kernel.org/${url#https://cdn.kernel.org/}")
            urls+=("https://mirrors.edge.kernel.org/${url#https://www.kernel.org/}")
            ;;
        *download.savannah.gnu.org*)
            # download.savannah.gnu.org bywa 502 (awarie usługi) – pełne mirror-y Savannah:
            urls+=("$(echo "$url" | sed 's|https://download.savannah.gnu.org|https://www.mirrorservice.org/sites/download.savannah.gnu.org|')")
            urls+=("$(echo "$url" | sed 's|https://download.savannah.gnu.org|https://mirror.rabisu.com/mirrors/savannah|')")
            ;;
        *libbsd.freedesktop.org*)
            # libbsd.freedesktop.org bywa martwy/wisnący (timeout bez odpowiedzi).
            # Debian mirror trzyma ten sam upstream orig tarball (libbsd_<ver>.orig.tar.xz,
            # ten sam katalog libbsd-<ver>/ w środku).
            urls+=("$(echo "$url" | sed 's|https://libbsd.freedesktop.org/releases/libbsd-\(.*\)\.tar\.xz|https://deb.debian.org/debian/pool/main/libb/libbsd/libbsd_\1.orig.tar.xz|')")
            ;;
        *dbus.freedesktop.org*)
            # dbus.freedesktop.org bywa martwy (jak libbsd) – Debian mirror: dbus_<ver>.orig.tar.xz
            urls+=("$(echo "$url" | sed 's|https://dbus.freedesktop.org/releases/dbus/dbus-\(.*\)\.tar\.xz|https://deb.debian.org/debian/pool/main/d/dbus/dbus_\1.orig.tar.xz|')")
            ;;
        *github.com*releases*download*.tar.xz)
            # GitHub: próbuj .tar.gz zamiast .tar.xz (nie zawsze jest .tar.xz)
            urls+=("${url%.tar.xz}.tar.gz")
            ;;
        *github.com*releases*download*.tar.bz2)
            urls+=("${url%.tar.bz2}.tar.gz")
            ;;
        *prdownloads.sourceforge.net*|*downloads.sourceforge.net*|*download.sourceforge.net*)
            urls+=("${url}?download")
            ;;
        *sourceforge.net/projects/*)
            urls+=("$(echo "$url" | sed 's|https://sourceforge.net/projects/|https://downloads.sourceforge.net/|')?download")
            ;;
        *samba.org/ftp/rsync/src/*|*samba.org/pub/rsync/src/*|*rsync.samba.org/*)
            # rsync.samba.org bywa wolny / niedostępny (awarie, geoblokady). Debian
            # mirror trzyma ten sam upstream tarball: rsync_<ver>.orig.tar.gz
            # (w środku ten sam katalog rsync-<ver>/). Wzorzec jak libbsd/dbus wyżej.
            # Kolejność: alternatywny host samba → CDN deb.debian.org → mirror PL.
            urls+=("$(echo "$url" | sed 's|https://www.samba.org/|https://download.samba.org/|')")
            urls+=("$(echo "$url" | sed 's|.*/rsync-\([0-9.]*\)\.tar\.gz|https://deb.debian.org/debian/pool/main/r/rsync/rsync_\1.orig.tar.gz|')")
            urls+=("$(echo "$url" | sed 's|.*/rsync-\([0-9.]*\)\.tar\.gz|https://ftp.pl.debian.org/debian/pool/main/r/rsync/rsync_\1.orig.tar.gz|')")
            ;;
        *download.kde.org/stable/plasma/*polkit-kde-agent-*)
            # Repo invent nazywa się polkit-kde-agent-1 (z -1); bez sufiksu → ślepy
            # adres (GitLab redirect na logowanie). Specjalny mapping do v<ver>.
            urls+=("$(echo "$url" | sed -E 's@https://download\.kde\.org/stable/plasma/([0-9.]+)/polkit-kde-agent-([0-9.]+)\.tar\.xz@https://invent.kde.org/plasma/polkit-kde-agent-1/-/archive/v\2/polkit-kde-agent-1-v\2.tar.bz2@')")
            ;;
        *download.kde.org/stable/frameworks/*|*download.kde.org/stable/plasma/*|*download.kde.org/stable/phonon/*)
            # download.kde.org bywa w awarii (całe stable/ potrafi zwracać 404 – np.
            # 2026-08-24 frameworks/6.28.0 mimo że pliki wcześniej się pobierały).
            # Zapas: invent.kde.org (GitLab) archiwa .tar.bz2. Wzorzec:
            #   .../stable/<kat>/<ver>/<pkg>-<ver>.tar.xz
            #   -> invent.kde.org/<kat>/<pkg>/-/archive/v<ver>/<pkg>-v<ver>.tar.bz2
            urls+=("$(echo "$url" | sed -E 's@https://download\.kde\.org/stable/(frameworks|plasma|phonon)/([0-9.]+)/([^/]+)-([0-9.]+)\.tar\.xz@https://invent.kde.org/\1/\3/-/archive/v\4/\3-v\4.tar.bz2@')")
            ;;
        *download.kde.org/stable/release-service/*)
            # Gear (release-service): grupa w invent zależy od pakietu
            local _kg=utilities
            case "$url" in
                *kpmcore-*) _kg=partitionmanager ;;
            esac
            local _kp _kpn _kver
            _kp=$(basename "$url" .tar.xz); _kpn="${_kp%-*}"; _kver="${_kp##*-}"
            urls+=("https://invent.kde.org/$_kg/$_kpn/-/archive/v$_kver/$_kpn-v$_kver.tar.bz2")
            ;;
    esac

    local ok=0
    for u in "${urls[@]}"; do
        if [[ "$u" != "$url" ]]; then
            info "  ↻ Próbuję mirror: $u"
        fi
        rm -f "$dest"
        # Mirror o innym rozszerzeniu archiwum (np. .tar.bz2 z invent.kde.org zamiast
        # .tar.xz): pobieramy do tymczasowej nazwy z rozszerzeniem mirrora, walidujemy
        # wg niego, potem mv do $dest (tar -xf auto-detektuje kompresję po bajtach).
        local _u_ext="${u##*.}"; _u_ext="${_u_ext%%\?*}"
        local _orig_ext="${url##*.}"; _orig_ext="${_orig_ext%%\?*}"
        local _dl="$dest"
        [[ "$u" != "$url" && "$_u_ext" != "$_orig_ext" ]] && _dl="${dest%.*}.${_u_ext}"
        rm -f "$_dl"
        # Timeoity: martwy serwer (np. libbsd/dbus.freedesktop.org) nie może zawiesić builda.
        # Retry-all-errors: xorg.freedesktop.org bywa flaky (przerywane połączenia) – retry to pokonuje.
        if wget -q --timeout=30 --tries=3 --show-progress -O "$_dl" "$u" 2>/dev/null || \
           curl -f -# --connect-timeout 20 --max-time 600 --retry 8 --retry-all-errors --retry-delay 3 -L -o "$_dl" "$u" 2>/dev/null; then
            # Sprawdź czy archiwum jest poprawne (nie ucięte/nie HTML) i zgadza
            # się suma SHA256 (jeśli podana).
            if _archive_is_valid "$_dl" && _sha_ok "$_dl" "$sha"; then
                if [[ "$_dl" != "$dest" ]]; then
                    mv -f "$_dl" "$dest"
                fi
                ok=1
                break
            fi
            warn "Pobrano uszkodzony/błędny plik z $u – próbuję kolejne źródło"
            rm -f "$_dl"
            continue   # wyraźnie przechodzimy do kolejnego mirrora (u)
        fi
    done

    if [[ $ok -eq 0 ]]; then
        rm -f "$dest"
        error "Nie udało się pobrać: $url"
    fi

    info "  ✓ $(basename "$dest")"
}

# ========================= BUDOWA PAKIETU LFS =========================
# Hostowe buildy autotools/meson wołają z PATH skryptowe narzędzia glib
# (np. glib-mkenums w libxklavier). Host bywa bez nich (zależy od dystrybucji).
# Wystawiamy je w $TOOLS_DIR/bin (pierwszy w PATH), ale TYLKO skrypty z shebangiem
# – binarki targetu linkują libglib i nie uruchomią się na hoście.
_provide_glib_build_tools() {
    local t _src
    mkdir -p "$TOOLS_DIR/bin"
    for t in glib-mkenums glib-genmarshal; do
        _src="$PAGAN_ROOT/usr/bin/$t"
        [[ -x "$_src" ]] || continue
        if command -v "$t" >/dev/null 2>&1; then continue; fi
        [[ "$(head -c2 "$_src" 2>/dev/null)" == "#!" ]] || continue
        printf '#!/bin/sh\nexec "%s" "$@"\n' "$_src" > "$TOOLS_DIR/bin/$t"
        chmod +x "$TOOLS_DIR/bin/$t"
    done
}

# ========================= MANIFEST BAZY Z OBRAZU (ROLLING/ADOPT) =========================
# Buildery zapisują tu KAŻDY pakiet wbudowany ze źródeł (nazwa + wersja), a na
# końcu builda generate_base_manifest() zamienia to na /usr/share/pag/base.json.
#
# Po co: pliki zbudowane w ISO nie są znane pagowi (nie ma ich w installed.json),
# więc `pag upgrade` ich nie widzi. Manifest mówi pagowi, CO stanowi bazę obrazu
# i w jakiej wersji, dzięki czemu `pag base adopt` może dograć je z repo (albo
# przynajmniej powiedzieć, które zostają w wersji z obrazu) – patrz komendy
# `pag base` w projekcie pag.
_base_manifest_file() {
    printf '%s\n' "$PAGAN_ROOT/usr/share/pag/base-packages.txt"
}

_base_manifest_add() {
    local _n="$1" _v="$2"
    [[ -n "$_n" ]] || return 0
    # Pakiety wyłącznie budowlane (toolchain/Ch.5) – nie istnieją w systemie
    # docelowym jako pakiety, więc nie trafiają do manifestu bazy.
    case "$_n" in
        *-pass1|*-pass2|*-tc) return 0 ;;
    esac
    [[ -d "$PAGAN_ROOT/usr" ]] || return 0
    local _f
    _f=$(_base_manifest_file)
    mkdir -p "$(dirname "$_f")" 2>/dev/null || return 0
    printf '%s %s\n' "$_n" "${_v:-?}" >> "$_f" 2>/dev/null || true
}

# Zamienia base-packages.txt na base.json (czytany przez `pag base` w systemie).
# Deduplikacja po nazwie – wygrywa OSTATNI wpis (np. przebudowa gtk w install_xfce
# nadpisuje wersję z build_blfs).
generate_base_manifest() {
    local _txt _json
    _txt=$(_base_manifest_file)
    _json="$PAGAN_ROOT/usr/share/pag/base.json"
    if [[ ! -s "$_txt" ]]; then
        warn "Manifest bazy: brak $_txt – 'pag base adopt' nie będzie miał czego zaadoptować"
        return 0
    fi
    mkdir -p "$(dirname "$_json")"
    python3 - "$_txt" "$_json" "$PAGAN_VERSION" "$PAGAN_DE" "$KERNEL_VERSION" <<'PYEOF' || warn "Manifest bazy: generowanie base.json nie powiodło się"
import json, sys

src, dst, iso_version, de, kernel = sys.argv[1:6]
pairs = {}
with open(src, encoding="utf-8", errors="replace") as fh:
    for line in fh:
        line = line.strip()
        if not line:
            continue
        name, _, ver = line.partition(" ")
        if name:
            pairs[name] = ver or "?"

with open(dst, "w", encoding="utf-8") as fh:
    json.dump({
        "iso_version": iso_version,
        "de": de,
        "kernel": kernel,
        "packages": [{"name": n, "version": v} for n, v in sorted(pairs.items())],
    }, fh, indent=2, ensure_ascii=False)
print(f"  manifest bazy: {len(pairs)} pakietow => {dst}")
PYEOF
}

# Scala „zagnieżdżony rootfs” ($PAGAN_ROOT/mnt/pagan) z powrotem do rootfsu.
#
# SKĄD SIĘ BIERZE: budujemy na hoście z PKG_CONFIG_SYSROOT_DIR="$PAGAN_ROOT", więc
# pkg-config zwraca ścieżki instalacyjne JUŻ z sysrootem — np. NetworkManager
# dostaje `systemdsystemunitdir=/mnt/pagan/usr/lib/systemd/system`
# i `dbus_conf_dir=/mnt/pagan/usr/share/dbus-1/system.d`, a pipewire
# `systemd_user_unit_dir=/mnt/pagan/usr/lib/systemd/user`. Meson dokłada do takiej
# ścieżki jeszcze DESTDIR=$PAGAN_ROOT → plik ląduje w /mnt/pagan/mnt/pagan/usr/...
# (podwójny prefiks), czyli POZA systemem. Meson nie odcina sysrootu, bo nie jest
# mu znany (nie używamy cross-file z [properties] sys_root).
#
# Realne skutki uboczne (zaobserwowane):
#   * NetworkManager: brak NetworkManager.service → w LIVE nikt nie podnosi
#     interfejsu (brak DHCP — w tym w QEMU) i brak polityki
#     org.freedesktop.NetworkManager.conf → NM nie zajmie nazwy na D-Bus,
#   * bluez: brak bluetooth.service,
#   * pipewire/wireplumber: brak jednostek user (brak dźwięku w sesji),
#   * systemd, librsvg: analogicznie (część ratują funkcje _ensure_*).
#
# DLACZEGO SCALAMY, A NIE KASUJEMY: kasowanie $PAGAN_ROOT/mnt (tak było wcześniej
# przy librsvg) gubiło legalne pliki zainstalowane wcześniej przez INNE pakiety.
# Scalanie jest idempotentne: pliki z zagnieżdżonego rootfsu pochodzą z właśnie
# zakończonej instalacji, więc słusznie nadpisują ewentualne starsze kopie.
_merge_nested_rootfs() {
    local _root="${PAGAN_ROOT%/}"
    [[ -n "$_root" && "$_root" != "/" ]] || return 0
    local _nested="$_root/${_root#/}"
    [[ -d "$_nested" ]] || return 0

    local _n
    _n=$(find "$_nested" 2>/dev/null | wc -l)
    cp -a "$_nested/." "$_root/" 2>/dev/null || true
    rm -rf "$_nested"
    # Katalog pośredni ("$PAGAN_ROOT" obcięty z podwójnego prefiksu) ma zostać
    # pusty – rmdir nie ruszy go, gdyby było tam coś jeszcze.
    rmdir "$(dirname "$_nested")" 2>/dev/null || true

    warn "  Scalono zagnieżdżony rootfs: $_nested$_root (${_n} wpisów, błąd DESTDIR+sysroot)"
}

build_lfs_pkg() {
    local pkg_name="$1"
    local pkg_version="$2"
    local pkg_url="$3"
    local pkg_sha="$4"
    local pkg_config="$5"

    # Manifest bazy: rejestrujemy też pakiety POMINIĘTE przez resume (wersja
    # i nazwa są znane z receptury), bo w rootfsie i tak są – a pag musi
    # wiedzieć, że pochodzą z obrazu, nie z repo.
    _base_manifest_add "$pkg_name" "$pkg_version"

    # Prawdziwa nazwa pliku z URL – niezależna od pkg_name
    local archive
    archive=$(basename "$pkg_url" | sed 's/?.*//')

    local archive_path="$SRC_DIR/$archive"
    local pkg_dir="$SRC_DIR/build-${pkg_name}"

    ((UI_PKG_N++)) || true

    # Resume – pomiń jeśli pakiet już zbudowany
    if _is_built "${pkg_name}-${pkg_version}"; then
        ui_skip "${pkg_name}-${pkg_version}"
        ((PKG_SKIPPED++)) || true
        return 0
    fi

    info "  Budowanie: ${pkg_name}-${pkg_version}"

    download_pkg "$pkg_url" "$archive_path" "$pkg_sha"

    rm -rf "$pkg_dir"
    mkdir -p "$pkg_dir"

    # Sprawdź czy archiwum ma katalog nadrzędny (np. gcc-14.2.0/) czy jest płaskie (tzdata)
    local -a _strip=()
    local _tar_first _tar_second
    tar tf "$archive_path" > /tmp/_tar_first.tmp 2>/dev/null || true
    _tar_first=$(sed -n '1p' /tmp/_tar_first.tmp 2>/dev/null)
    _tar_second=$(sed -n '2p' /tmp/_tar_first.tmp 2>/dev/null)
    rm -f /tmp/_tar_first.tmp
    # Niektóre tarballe (np. openssh) listują katalog nadrzędny BEZ końcowego '/',
    # więc jeśli 1. wpis nie ma '/', ale 2. już ma → to katalog nadrzędny.
    if [[ "$_tar_first" == *"/"* ]]; then
        _strip=(--strip-components=1)
    elif [[ -n "$_tar_second" && "$_tar_second" == *"/"* ]]; then
        _strip=(--strip-components=1)
    fi

    case "$archive" in
        *.tar.xz) tar -xf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
        *.tar.gz) tar -xf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
        *.tar.zst) unzstd -d "$archive_path" -o "$SRC_DIR/${archive%.zst}" && \
                    tar -xf "$SRC_DIR/${archive%.zst}" -C "$pkg_dir" "${_strip[@]}" ;;
        *.tar.bz2|*.tbz2) tar -xjf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
        *.tgz) tar -xzf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
        *.tar) tar -xf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
        *.zip) unzip -q "$archive_path" -d "$pkg_dir" ;;
        # Pliki bez archiwum (np. .pem, .crt) – kopiuj bezpośrednio
        *) cp "$archive_path" "$pkg_dir/$(basename "$archive")" ;;
    esac || {
        warn "Rozpakowanie $pkg_name nieudane – wymuszam ponowne pobranie i retry"
        rm -f "$archive_path" "$SRC_DIR/${archive%.zst}"
        download_pkg "$pkg_url" "$archive_path" "$pkg_sha" 1
        case "$archive" in
            *.tar.xz) tar -xf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
            *.tar.gz) tar -xf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
            *.tar.zst) unzstd -d "$archive_path" -o "$SRC_DIR/${archive%.zst}" && tar -xf "$SRC_DIR/${archive%.zst}" -C "$pkg_dir" "${_strip[@]}" ;;
            *.tar.bz2|*.tbz2) tar -xjf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
            *.tgz) tar -xzf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
            *.tar) tar -xf "$archive_path" -C "$pkg_dir" "${_strip[@]}" ;;
            *.zip) unzip -q "$archive_path" -d "$pkg_dir" ;;
            *) cp "$archive_path" "$pkg_dir/$(basename "$archive")" ;;
        esac || error "Rozpakowanie $pkg_name nieudane"
    }

    pushd "$pkg_dir" > /dev/null

    local pkg_log="$UI_LOG_DIR/${pkg_name}-${pkg_version}.log"

    # Udostępnij hostowym buildom skryptowe narzędzia glib z targetu
    # (np. glib-mkenums dla libxklavier) – zanim ruszy configure/make.
    _provide_glib_build_tools

    if [[ -n "$pkg_config" && "$pkg_config" != "SKIP" ]]; then
        ui_build_eval "${pkg_name}-${pkg_version}" "$pkg_log" "$pkg_config" \
            || error "Configure failed for $pkg_name (log: $pkg_log)"
    fi

    # Pomijaj auto make/install jeśli pkg_config już robi wszystko sam
    local _skip_auto_make=0
    case "$pkg_config" in
        *"cp -"*)                _skip_auto_make=1 ;;
        *"cp "*)                 _skip_auto_make=1 ;;
        *"make install"*)        _skip_auto_make=1 ;;
        *"DESTDIR="*)            _skip_auto_make=1 ;;
        *"ninja"*)              _skip_auto_make=1 ;;
        *"pip3"*)               _skip_auto_make=1 ;;
        *"pip install"*)        _skip_auto_make=1 ;;
        *"cmake --install"*)    _skip_auto_make=1 ;;
        *"./bootstrap"*)        _skip_auto_make=1 ;;
        *"install -m"*)          _skip_auto_make=1 ;;
        *"osinfo-db-import"*)   _skip_auto_make=1 ;;
        *"PREFIX="*)             _skip_auto_make=1 ;;
        *"ZONEINFO="*)           _skip_auto_make=1 ;;
        make*)                   _skip_auto_make=1 ;;
    esac

    if [[ $_skip_auto_make -eq 0 ]]; then
        info "  Make: $pkg_name"
        ui_build_eval "${pkg_name}-${pkg_version} (make)" "$pkg_log" "make $MAKEFLAGS && make install" \
            || error "Make/Install failed for $pkg_name (log: $pkg_log)"
    fi

    popd > /dev/null
    rm -rf "$pkg_dir"

    # Zagnieżdżony rootfs (błąd DESTDIR+sysroot, patrz _merge_nested_rootfs) –
    # scalaj NATYCHMIAST po instalacji pakietu, a nie dopiero w finalize_build.
    # Wcześniej robił to tylko finalize, więc każdy `rm -rf "$PAGAN_ROOT/mnt"`
    # w środku builda (np. po librsvg) kasował pliki już zainstalowanych pakietów:
    # tak zginęły NetworkManager.service, bluetooth.service i jednostki user
    # pipewire/wireplumber. Scalanie tutaj sprawia też, że poprawki .la/.pc
    # poniżej obejmują odzyskane pliki.
    _merge_nested_rootfs

    # Remove .la files (libtool archives) – they have hardcoded paths, break cross-building
    find "$PAGAN_ROOT/usr/lib" -name "*.la" -delete 2>/dev/null || true

    # Fix .pc files: rewrite prefix paths so pkg-config finds target without SYSROOT.
    # UWAGA: omijamy SYMLINKI (foo.pc → foo-<ver>.pc). [[ -f ]] łapie też symlink,
    # a sed -i na symlinku ZASTĘPUJE go zwykłym plikiem → przy reinstalacji meson
    # pada: „Destination '.../foo.pc' already exists and is not a symlink”.
    for pc in "$PAGAN_ROOT/usr/lib/pkgconfig/"*.pc "$PAGAN_ROOT/usr/share/pkgconfig/"*.pc; do
        if [[ -f "$pc" && ! -L "$pc" ]]; then
            sed -i 's|^prefix=/usr$|prefix='"$PAGAN_ROOT"'/usr|' "$pc"
            # Remove bare -I${includedir} (without subdir) to avoid exposing target libc headers
            sed -i '/^Cflags:/ { s/ -I\${includedir} / /g; s/^-I\${includedir} //; s/ -I\${includedir}$// }' "$pc"
        fi
    done 2>/dev/null || true

    _mark_built "${pkg_name}-${pkg_version}"
    ok "  ✅ Zbudowano: $pkg_name-$pkg_version"

    # xkeyboard-config: dane lądują w /usr/share/xkeyboard-config-2/, a Xorg czyta
    # je z /usr/share/X11/xkb (symlink). Meson potrafi nie odtworzyć symlinka po
    # przerwanej instalacji – domykamy go tutaj, żeby sanity/live nie zgłaszał
    # „Brak danych xkeyboard-config (/usr/share/X11/xkb)”.
    if [[ "$pkg_name" == "xkeyboard-config" && -d "$PAGAN_ROOT/usr/share/xkeyboard-config-2" ]]; then
        if [[ ! -L "$PAGAN_ROOT/usr/share/X11/xkb" ]]; then
            rm -rf "$PAGAN_ROOT/usr/share/X11/xkb"
            mkdir -p "$PAGAN_ROOT/usr/share/X11"
            ln -s ../xkeyboard-config-2 "$PAGAN_ROOT/usr/share/X11/xkb"
            info "  ✓ xkeyboard-config: /usr/share/X11/xkb → xkeyboard-config-2"
        fi
    fi
}

# ========================= ABI SANITY (TARGET ONLY) =========================
_max_ver_from_strings() {
    local file="$1" prefix="$2"
    strings -a "$file" 2>/dev/null \
        | rg -o "${prefix}_[0-9]+\.[0-9]+" 2>/dev/null \
        | sed "s/^${prefix}_//" \
        | sort -V \
        | tail -n 1 || true
}

_ver_gt() {
    # true jeśli $1 > $2
    [[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n 1)" == "$1" && "$1" != "$2" ]]
}

check_target_abi() {
    step "ABI sanity check (target LFS libs)"

    local target_libc="$PAGAN_ROOT/usr/lib/libc.so.6"
    if [[ ! -f "$target_libc" ]]; then
        warn "Brak $target_libc - pomijam ABI check"
        return 0
    fi

    local target_glibc
    target_glibc=$(_max_ver_from_strings "$target_libc" "GLIBC")
    if [[ -z "$target_glibc" ]]; then
        warn "Nie udało się odczytać wersji GLIBC z targetu - pomijam ABI check"
        return 0
    fi

    info "Target GLIBC (z $target_libc): $target_glibc"

    local bad=0
    while IFS= read -r so; do
        local req
        req=$(_max_ver_from_strings "$so" "GLIBC")
        [[ -z "$req" ]] && continue

        if _ver_gt "$req" "$target_glibc"; then
            warn "ABI mismatch (target): $(basename "$so") wymaga GLIBC_$req > GLIBC_$target_glibc"
            ((bad++)) || true
        fi
    done < <(find "$PAGAN_ROOT/usr/lib" -maxdepth 1 -type f -name '*.so*' 2>/dev/null)

    if [[ $bad -gt 0 ]]; then
        error "Wykryto $bad bibliotek(i) w targetowym $PAGAN_ROOT/usr/lib wymagających nowszego GLIBC niż LFS target. To nie jest host-check, tylko check targetu. Wyczyść $PAGAN_ROOT i zbuduj od nowa."
    fi

    ok "ABI sanity check targetu: OK"
}

# ========================= LFS PATCH CHECK (BEST-EFFORT) =========================
check_lfs_patches() {
    step "Sprawdzanie dostępnych patchy LFS (best-effort)"

    local patch_index_url="https://www.linuxfromscratch.org/patches/lfs/${LFS_PATCH_DIR:-13.1-rc2}/"
    local html
    if ! html=$(curl -fsSL --max-time 20 "$patch_index_url" 2>/dev/null); then
        warn "Nie udało się pobrać indeksu patchy LFS: $patch_index_url"
        warn "Pomijam check patchy (build będzie kontynuowany)."
        return 0
    fi

    local tracked=(
        "bzip2-1.0.8" "coreutils-9.11" "expect-5.45.4" "glibc-2.44"
        "glibc-fhs" "kbd-2.10.0" "Python-3.14.7" "sysvinit-3.14" "tar-1.35"
    )

    local found=0
    for pkg in "${tracked[@]}"; do
        if echo "$html" | rg -qi "${pkg}.*\.patch"; then
            warn "LFS patch dostępny dla: $pkg"
            ((found++)) || true
        fi
    done

    if [[ $found -eq 0 ]]; then
        ok "Nie wykryto oczywistych patchy dla śledzonych pakietów LFS ${LFS_VERSION}"
    else
        warn "Wykryto $found potencjalnych patchy LFS. Rozważ integrację przed release build."
    fi
}

report_artifacts() {
    step "LOKALIZACJE ARTEFAKTÓW"

    local img_file="$OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.img"
    local iso_file="$OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.iso"
    local printed=0

    if [[ -f "$img_file" ]]; then
        info "IMG: $img_file ($(du -h "$img_file" 2>/dev/null | cut -f1))"
        echo "IMG: $img_file" >> "$SUMMARY_FILE"
        printed=1
    fi
    if [[ -f "$iso_file" ]]; then
        info "ISO: $iso_file ($(du -h "$iso_file" 2>/dev/null | cut -f1))"
        echo "ISO: $iso_file" >> "$SUMMARY_FILE"
        printed=1
    fi

    if [[ $printed -eq 0 ]]; then
        warn "Nie znaleziono artefaktów IMG/ISO w $OUTPUT_DIR"
    fi
}

# ========================= BUDOWA TOOLCHAIN (Ch. 5) =========================
build_toolchain() {
    step "ROZDZIAŁ 5: BUDOWA TOOLCHAIN"

    ui_set_total 7

    export LC_ALL=POSIX
    export LFS="$PAGAN_ROOT"
    export LFS_TGT="$LFS_TGT"

    # 5.2 - Linux headers (host make)
    build_lfs_pkg "linux-headers-tc" "$KERNEL_VERSION" \
        "$KERNEL_URL" \
        "SKIP" \
        "make mrproper && make headers && find usr/include -type f ! -name '*.h' -delete && cp -rv usr/include $PAGAN_ROOT/usr"

    # 5.3 - GMP (statycznie – PRZED binutils, czysty host PATH!)
    # GCC 16 (C23) zabija stare testy GMP. -std=gnu89 dla configure (testy),
    # ale build potrzebuje C99 → make CFLAGS='-O2' (nadpisuje Makefile).
    build_lfs_pkg "gmp-tc" "6.3.0" \
        "https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz" \
        "SKIP" \
        "CFLAGS='-std=gnu89 -O2' ./configure --prefix=$TOOLS_DIR --enable-cxx --disable-shared --enable-static && make CFLAGS='-O2' $MAKEFLAGS && make install"

    # 5.4 - MPFR (host PATH)
    build_lfs_pkg "mpfr-tc" "4.2.2" \
        "https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.2.tar.xz" \
        "SKIP" \
        "CFLAGS='-std=gnu89 -O2' ./configure --prefix=$TOOLS_DIR --disable-shared --enable-static --with-gmp=$TOOLS_DIR && make CFLAGS='-O2' $MAKEFLAGS && make install"

    # 5.5 - MPC (host PATH)
    build_lfs_pkg "mpc-tc" "1.4.1" \
        "https://ftp.gnu.org/gnu/mpc/mpc-1.4.1.tar.xz" \
        "SKIP" \
        "CFLAGS='-std=gnu89 -O2' ./configure --prefix=$TOOLS_DIR --disable-shared --enable-static --with-gmp=$TOOLS_DIR --with-mpfr=$TOOLS_DIR && make CFLAGS='-O2' $MAKEFLAGS && make install"

    # 5.6 - Binutils pass 1 (cross-tools – od teraz PATH=$TOOLS_DIR/bin:$PATH)
    export PATH="$TOOLS_DIR/bin:$PATH"
    build_lfs_pkg "binutils-pass1" "2.47" \
        "https://ftp.gnu.org/gnu/binutils/binutils-2.47.tar.xz" \
        "SKIP" \
        "mkdir -v build && cd build && ../configure --prefix=$TOOLS_DIR --with-sysroot=$PAGAN_ROOT --target=$LFS_TGT --disable-nls --enable-gprofng=no --disable-werror --disable-dependency-tracking && make $MAKEFLAGS && make install"

    # 5.7 - GCC pass 1 (cross-compiler)
    # GCC 16.1.1 host kompiluje GCC 16.2.0 źródła jako C++. safe-ctype.h
    # definiuje #define isgraph(c) do_not_use_isgraph..., a GCC16 <cctype>
    # robi using ::isgraph; → makro się rozwija → łamie parser locale_facets.h.
    # Fix: w system.h dodajemy #include <cctype> przed safe-ctype.h i
    #       #undef makr po nim.
    build_lfs_pkg "gcc-pass1" "16.2.0" \
        "https://ftp.gnu.org/gnu/gcc/gcc-16.2.0/gcc-16.2.0.tar.xz" \
        "SKIP" \
        'case $(uname -m) in x86_64) sed -e "/m64=/s/lib64/lib/" -i.orig gcc/config/i386/t-linux64 ;; esac; sed -i "s/__cplusplus != 201103/__cplusplus < 201103/g; s/__cplusplus > 201103/__cplusplus < 201103/g" libcody/configure 2>/dev/null || true; sed -i "/^#include \"safe-ctype.h\"/i #ifdef __cplusplus\n#include <cctype>\n#endif" gcc/system.h 2>/dev/null || true; sed -i "/^#include \"safe-ctype.h\"/a #ifdef __cplusplus\n#undef isalpha\n#undef isalnum\n#undef iscntrl\n#undef isdigit\n#undef isgraph\n#undef islower\n#undef isprint\n#undef ispunct\n#undef isspace\n#undef isupper\n#undef isxdigit\n#undef toupper\n#undef tolower\n#endif" gcc/system.h 2>/dev/null || true; sed -i "1i #include <stdbool.h>" gcc/config/i386/i386.h 2>/dev/null || true; mkdir -v build && cd build && ../configure --target='"$LFS_TGT"' --prefix='"$TOOLS_DIR"' --with-gmp='"$TOOLS_DIR"' --with-mpfr='"$TOOLS_DIR"' --with-mpc='"$TOOLS_DIR"' --with-glibc-version=2.44 --with-sysroot='"$PAGAN_ROOT"' --with-newlib --without-headers --enable-default-pie --enable-default-ssp --disable-nls --disable-shared --disable-multilib --disable-threads --disable-libatomic --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libstdcxx --disable-split-stack --enable-languages=c,c++ --enable-werror=no CXXFLAGS="-std=gnu++20 -O2 -Wno-error=return-type -fno-char8_t -Wno-deprecated-enum-enum-conversion" && make '"$MAKEFLAGS"' && make install && cd .. && ln -sf gcc '"$CROSS_BIN"'/cc'

    # 5.8 - Glibc (cross-kompilowana)
    build_lfs_pkg "glibc-tc" "2.44" \
        "https://ftp.gnu.org/gnu/glibc/glibc-2.44.tar.xz" \
        "SKIP" \
        "rm -rf $PAGAN_ROOT/usr/include/gnu $PAGAN_ROOT/usr/include/features.h $PAGAN_ROOT/usr/include/stdc-predef.h 2>/dev/null; mkdir -v build && cd build && ../configure --prefix=/usr --host=$LFS_TGT --build=\$(../scripts/config.guess) --enable-kernel=4.14 --with-headers=$PAGAN_ROOT/usr/include libc_cv_slibdir=/usr/lib --disable-werror CFLAGS=\"-O2 -Wno-error=maybe-uninitialized\" && make $MAKEFLAGS && make DESTDIR=$PAGAN_ROOT install"
    ok "Toolchain zbudowany!"
}

# ========================= BUDOWA SYSTEMU KOŃCOWEGO (Ch. 6) =========================
build_final_system() {
    step "ROZDZIAŁ 6-8: PEŁNY SYSTEM KOŃCOWY LFS 13.1"

    # Używamy NARZĘDZI HOSTA podczas budowy (nie chroot). $PAGAN_ROOT/usr/bin
    # celowo pomijamy: targetowe binarki (groff 1.23.0, msgfmt) nie działają na
    # hoście (szukają fontów/bibliotek w /usr lub nie mają LD_LIBRARY_PATH).
    # /usr/bin/core_perl+/vendor_perl: sudo resetuje PATH i gubi je (pod2man!).
    export PATH="$TOOLS_DIR/bin:/usr/bin/core_perl:/usr/bin/vendor_perl:$PATH"
    export LC_ALL=POSIX

    # Globalna kompatybilność z GCC 16 (Arch) – wycisza nowe warningi,
    # które stają się błędami przez -Werror w wielu projektach.
    # -std=gnu17: stare pakiety (gnulib: m4/coreutils/grep) nie rozumieją C23 ([[nodiscard]])
    export CFLAGS="-std=gnu17 -O2 -Wno-error=implicit-function-declaration -Wno-error=int-conversion -Wno-error=return-type -Wno-error=maybe-uninitialized -Wno-error=discarded-qualifiers -Wno-error=unterminated-string-initialization -Wno-error=incompatible-pointer-types -Wno-error=null-dereference -Wno-error=override-init"
    export CXXFLAGS="-O2 -Wno-error=return-type -Wno-error=maybe-uninitialized"
    # Coreutils (i inne GNU) odmawiają configure jako root bez tej zmiennej
    export FORCE_UNSAFE_CONFIGURE=1
    # Targetowe metadane mają pierwszeństwo podczas budowy systemu PaganOS.
    export PKG_CONFIG_DIR=""
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/lib/x86_64-linux-gnu/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"
    export PKG_CONFIG_LIBDIR="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/lib/x86_64-linux-gnu/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"
    export PKG_CONFIG_SYSROOT_DIR="$PAGAN_ROOT"
    # Linkuj z targetowymi libami (glibc 2.44, zgodne z hostem)
    # UWAGA: brak globalnego CPPFLAGS=-I$PAGAN_ROOT/usr/include — glibc budując sam siebie
    # nie może mieć -I na zainstalowane headery glibc (guard _LIBC w gnu/stubs-64.h)
    export LDFLAGS="-L$PAGAN_ROOT/usr/lib -L$PAGAN_ROOT/lib64 -Wl,-rpath-link,$PAGAN_ROOT/usr/lib -Wl,-rpath-link,$PAGAN_ROOT/lib64"

    # fish 4.x – domyślna powłoka użytkowników (zamiast bash). To przepisanie na
    # Rusta, więc build ze źródeł wymagałby Rust >= 1.85 + cargo + sieci do
    # crates.io. Zamiast tego bierzemy OFICJALNĄ, statyczną (musl) jednoplikową
    # binarkę z wydania fish – jest samodzielna (funkcje/completions wbudowane
    # w binarkę), bez zależności i bez toolchainu Rusta. Analogicznie do
    # prebuilt Firefox/NVIDIA już używanych w tym buildzie.
    local _fish_url _fish_sha
    case "$(uname -m)" in
        aarch64|arm64)
            _fish_url="https://github.com/fish-shell/fish-shell/releases/download/4.9.3/fish-4.9.3-linux-aarch64.tar.xz"
            _fish_sha="57349f0111728c64b703a84e611aa97ff4d1fa8466385d64e67bd5bc3ffa49fc" ;;
        *)
            _fish_url="https://github.com/fish-shell/fish-shell/releases/download/4.9.3/fish-4.9.3-linux-x86_64.tar.xz"
            _fish_sha="8f643d10ad1abb0ef7072764c01ceb5cf61b2d373fda400cf60f0bad69b7e095" ;;
    esac

    local packages=(
        # ======== ADMINISTRACJA I KONFIGURACJA ========
        "man-pages:6.18:https://www.kernel.org/pub/linux/docs/man-pages/man-pages-6.18.tar.xz:SKIP:make -R prefix=/usr install"
        "iana-etc:20260805:https://github.com/Mic92/iana-etc/releases/download/20260805/iana-etc-20260805.tar.gz:SKIP:cp services protocols \$PAGAN_ROOT/etc"
        "tzdata:2026c:https://www.iana.org/time-zones/repository/releases/tzdata2026c.tar.gz:SKIP:mkdir -p \$PAGAN_ROOT/usr/share/zoneinfo/posix \$PAGAN_ROOT/usr/share/zoneinfo/right && for tz in etcetera southamerica northamerica europe africa antarctica asia australasia backward; do zic -L /dev/null -d \$PAGAN_ROOT/usr/share/zoneinfo \$tz && zic -L /dev/null -d \$PAGAN_ROOT/usr/share/zoneinfo/posix \$tz && zic -L leapseconds -d \$PAGAN_ROOT/usr/share/zoneinfo/right \$tz; done && cp -f zone.tab zone1970.tab iso3166.tab leapseconds leap-seconds.list \$PAGAN_ROOT/usr/share/zoneinfo/ && zic -d \$PAGAN_ROOT/usr/share/zoneinfo -p Europe/Warsaw"

        # ======== BIBLIOTEKI BAZOWE ========
        "glibc:2.44:https://ftp.gnu.org/gnu/glibc/glibc-2.44.tar.xz:SKIP:mkdir bld && cd bld && ../configure --prefix=/usr --enable-kernel=4.14 --enable-stack-protector=strong libc_cv_slibdir=/usr/lib --disable-werror CFLAGS=\"-O2 -Wno-error=maybe-uninitialized\" && make && make install DESTDIR=\$PAGAN_ROOT"
        "zlib:1.3.2:https://zlib.net/fossils/zlib-1.3.2.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "bzip2:1.0.8:https://www.sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz:SKIP:make -f Makefile-libbz2_so && make install PREFIX=\$PAGAN_ROOT/usr"
        "xz:5.8.3:https://github.com/tukaani-project/xz/releases/download/v5.8.3/xz-5.8.3.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "zstd:1.5.7:https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz:SKIP:make prefix=/usr && make prefix=/usr DESTDIR=\$PAGAN_ROOT install"
        "lz4:1.10.0:https://github.com/lz4/lz4/releases/download/v1.10.0/lz4-1.10.0.tar.gz:SKIP:make && make PREFIX=/usr DESTDIR=\$PAGAN_ROOT install"
        "readline:8.3:https://ftp.gnu.org/gnu/readline/readline-8.3.tar.gz:SKIP:./configure --prefix=/usr --disable-static --with-curses && make SHLIB_LIBS=-lncurses && make install DESTDIR=\$PAGAN_ROOT"
        "gmp:6.3.0:https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz:SKIP:./configure --prefix=/usr --enable-cxx --disable-static CFLAGS=\"-O2 -std=gnu89\" && make CFLAGS=\"-O2\" && make install DESTDIR=\$PAGAN_ROOT"
        "mpfr:4.2.2:https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.2.tar.xz:SKIP:./configure --prefix=/usr --disable-static --enable-thread-safe && make && make install DESTDIR=\$PAGAN_ROOT"
        "mpc:1.4.1:https://ftp.gnu.org/gnu/mpc/mpc-1.4.1.tar.xz:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "isl:0.27:https://libisl.sourceforge.io/isl-0.27.tar.bz2:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "attr:2.6.0:https://download.savannah.gnu.org/releases/attr/attr-2.6.0.tar.gz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "acl:2.4.0:https://download.savannah.gnu.org/releases/acl/acl-2.4.0.tar.xz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "libcap:2.78:https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-2.78.tar.xz:SKIP:make prefix=/usr lib=lib && make prefix=/usr lib=lib DESTDIR=\$PAGAN_ROOT install"
        "libxcrypt:4.5.2:https://github.com/besser82/libxcrypt/releases/download/v4.5.2/libxcrypt-4.5.2.tar.xz:SKIP:./configure --prefix=/usr --enable-hashes=strong,glibc --disable-static CFLAGS=\"-O2 -Wno-error=discarded-qualifiers -Wno-error=unterminated-string-initialization\" && make && make install DESTDIR=\$PAGAN_ROOT"
        "pcre2:10.47:https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.bz2:SKIP:./configure --prefix=/usr --enable-unicode --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "elfutils:0.195:https://sourceware.org/ftp/elfutils/0.195/elfutils-0.195.tar.bz2:SKIP:./configure --prefix=/usr --disable-debuginfod --disable-libdebuginfod --disable-werror && make && make -C libelf install DESTDIR=\$PAGAN_ROOT"
        "libffi:3.8.0:https://github.com/libffi/libffi/releases/download/v3.8.0/libffi-3.8.0.tar.gz:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"

        # ======== KOMPILATOR GCC (z C i C++) ========
        "gcc:16.2.0:https://ftp.gnu.org/gnu/gcc/gcc-16.2.0/gcc-16.2.0.tar.xz:SKIP:sed -i 's/__cplusplus != 201103/__cplusplus < 201103/g; s/__cplusplus > 201103/__cplusplus < 201103/g' libcody/configure 2>/dev/null || true; sed -i '/^#include \"safe-ctype.h\"/i #ifdef __cplusplus\n#include <cctype>\n#endif' gcc/system.h 2>/dev/null || true; case \$(uname -m) in x86_64) sed -e '/m64=/s/lib64/lib/' -i.orig gcc/config/i386/t-linux64 ;; esac; sed -i '1i #include <stdbool.h>' gcc/config/i386/i386.h 2>/dev/null || true; mkdir -v build && cd build && ../configure --prefix=/usr --enable-languages=c,c++ --disable-multilib --disable-bootstrap --with-system-zlib --enable-default-pie --enable-default-ssp --disable-libsanitizer --disable-split-stack --enable-werror=no CXXFLAGS=\"-O2 -Wno-error=return-type -fno-char8_t -Wno-deprecated-enum-enum-conversion\" && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT && ln -sf gcc \$PAGAN_ROOT/usr/bin/cc"

        # ======== NARZĘDZIA SYSTEMOWE ========
        "file:5.48:https://astron.com/pub/file/file-5.48.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "m4:1.4.21:https://ftp.gnu.org/gnu/m4/m4-1.4.21.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "bc:7.0.3:https://github.com/gavinhoward/bc/releases/download/7.0.3/bc-7.0.3.tar.xz:SKIP:CC=gcc CFLAGS=\"-std=c99 -O2\" ./configure --prefix=/usr -G -O3 && make && make install DESTDIR=\$PAGAN_ROOT"
        "flex:2.6.4:https://github.com/westes/flex/releases/download/v2.6.4/flex-2.6.4.tar.gz:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT && ln -sf flex \$PAGAN_ROOT/usr/bin/lex"
        "bison:3.8.2:https://ftp.gnu.org/gnu/bison/bison-3.8.2.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "tcl:8.6.18:https://downloads.sourceforge.net/tcl/tcl8.6.18-src.tar.gz:SKIP:cd unix && ./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        # tk – opcjonalne wsparcie Tk (python buduje się z --with-tcltk).
        # Wersja „8.6.18-xft”: wymusza przebudowę w resume (stary wpis tk-8.6.18
        # był zbudowany BEZ Xft). `make install` (bez DESTDIR) instaluje Tk na
        # HOSTIE: Python 3.14 wykrywa tcl/tk przez pkg-config (tcl.pc/tk.pc)
        # i buduje moduł _tkinter; drugie `make install DESTDIR` dostarcza Tk do
        # live (libtk8.6.so, skrypty tk8.6/, tk.h, tkConfig.sh). Wymaga nagłówków
        # X11 na hoście (/usr/include/X11).
        # --enable-xft (jak BLFS): BEZ tego Tk używa X core fontów – brak polskich
        # glifów (ą/ę/ł/ż) i wolne renderowanie (live ma libXft + fontconfig + DejaVu).
        "tk:8.6.18-xft:https://downloads.sourceforge.net/tcl/tk8.6.18-src.tar.gz:SKIP:test -f /usr/include/X11/Xlib.h || { echo 'Brak nagłówków X11 (Xlib.h) na hoście – zainstaluj libx11/xorgproto-dev'; exit 1; }; cd unix && ./configure --prefix=/usr --enable-xft && make && make install && make install DESTDIR=\$PAGAN_ROOT"
        "expect:5.45.4:https://prdownloads.sourceforge.net/expect/expect5.45.4.tar.gz:SKIP:cp -f pty_termios.c pty_.c 2>/dev/null || true; ./configure --prefix=/usr --with-tcl=/usr/lib --enable-shared && sed -e 's/^#undef HAVE_TERMIOS/#define HAVE_TERMIOS 1/' -e 's/^#undef HAVE_UNISTD_H/#define HAVE_UNISTD_H 1/' -e 's/^#undef HAVE_SYS_FCNTL_H/#define HAVE_SYS_FCNTL_H 1/' -e 's/^#undef HAVE_SYS_SELECT_H/#define HAVE_SYS_SELECT_H 1/' -e 's/^#undef HAVE_SYS_TIME_H/#define HAVE_SYS_TIME_H 1/' -e 's/^#undef HAVE_SYS_TYPES_H/#define HAVE_SYS_TYPES_H 1/' -e 's/^#undef HAVE_SYS_STAT_H/#define HAVE_SYS_STAT_H 1/' -e 's/^#undef HAVE_STDLIB_H/#define HAVE_STDLIB_H 1/' -e 's/^#undef HAVE_STRING_H/#define HAVE_STRING_H 1/' -e 's/^#undef HAVE_MEMORY_H/#define HAVE_MEMORY_H 1/' -e 's/^#undef HAVE_INTTYPES_H/#define HAVE_INTTYPES_H 1/' -e 's/^#undef HAVE_LIMITS_H/#define HAVE_LIMITS_H 1/' -e 's/^#undef HAVE_TCGETS_OR_TCGETA_IN_TERMIOS_H/#define HAVE_TCGETS_OR_TCGETA_IN_TERMIOS_H 1/' -e 's/^#undef HAVE_TIOCGWINSZ_IN_TERMIOS_H/#define HAVE_TIOCGWINSZ_IN_TERMIOS_H 1/' -e 's/^#undef HAVE_SYSMACROS_H/#define HAVE_SYSMACROS_H 1/' -e 's/^#undef HAVE_STDARG_H/#define HAVE_STDARG_H 1/' -e 's/^#undef TIME_WITH_SYS_TIME/#define TIME_WITH_SYS_TIME 1/' expect_cf.h.in > expect_cf.h && sed -i '\$a#define NO_UNION_WAIT 1\n#define SETPGRP_VOID 1\n#define POSIX 1\n#define RETSIGTYPE void\n#define HAVE_OPENPTY 1\n#define HAVE_TCSETATTR 1\n#define HAVE_SYS_WAIT_H 1\n#define HAVE_SYSCONF 1\n#define HAVE_STRFTIME 1\n#define HAVE_MEMMOVE 1\n#define HAVE_MEMCPY 1\n#define HAVE_TIMEZONE 1\n#define HAVE_SIGLONGJMP 1\n#define HAVE_STRCHR 1\n#define HAVE_LONG_FILE_NAMES 1\n#define HAVE_TIME_H 1\n#define REARM_SIG 1\n#define WNOHANG_REQUIRES_POSIX_SOURCE 1' expect_cf.h && make && make install DESTDIR=\$PAGAN_ROOT"
        "dejagnu:1.6.3:https://ftp.gnu.org/gnu/dejagnu/dejagnu-1.6.3.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "autoconf:2.73:https://ftp.gnu.org/gnu/autoconf/autoconf-2.73.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "automake:1.18.1:https://ftp.gnu.org/gnu/automake/automake-1.18.1.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "libtool:2.6.2:https://ftp.gnu.org/gnu/libtool/libtool-2.6.2.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "gdbm:1.26:https://ftp.gnu.org/gnu/gdbm/gdbm-1.26.tar.gz:SKIP:./configure --prefix=/usr --disable-static --enable-libgdbm-compat && make && make install DESTDIR=\$PAGAN_ROOT"
        "gperf:3.3:https://ftp.gnu.org/gnu/gperf/gperf-3.3.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "expat:2.8.3:https://github.com/libexpat/libexpat/releases/download/R_2_8_3/expat-2.8.3.tar.xz:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "gettext:1.0:https://ftp.gnu.org/gnu/gettext/gettext-1.0.tar.xz:SKIP:./configure --prefix=/usr --disable-static --disable-acl && make && make install DESTDIR=\$PAGAN_ROOT"
        "check:0.15.2:https://github.com/libcheck/check/releases/download/0.15.2/check-0.15.2.tar.gz:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "groff:1.24.1:https://ftp.gnu.org/gnu/groff/groff-1.24.1.tar.gz:SKIP:./configure --prefix=/usr --with-gs=missing --with-urw-fonts-dir=/nonexistent && make && make install DESTDIR=\$PAGAN_ROOT"
        "texinfo:7.3:https://ftp.gnu.org/gnu/texinfo/texinfo-7.3.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "intltool:0.51.0:https://deb.debian.org/debian/pool/main/i/intltool/intltool_0.51.0.orig.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"

        # ======== NARZĘDZIA GNU CORE ========
        "ncurses:6.6:https://invisible-mirror.net/archives/ncurses/ncurses-6.6.tar.gz:SKIP:./configure --prefix=/usr --with-shared --without-debug --without-normal --enable-widec --with-versioned-syms && make && make install DESTDIR=\$PAGAN_ROOT && for lib in ncurses form panel menu; do ln -sfv lib\${lib}w.so \$PAGAN_ROOT/usr/lib/lib\${lib}.so 2>/dev/null; done && ln -sfv libncursesw.so \$PAGAN_ROOT/usr/lib/libtinfo.so 2>/dev/null"
        "dialog:1.3-20240619:https://invisible-island.net/archives/dialog/dialog-1.3-20240619.tgz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "bash:5.3:https://ftp.gnu.org/gnu/bash/bash-5.3.tar.gz:SKIP:./configure --prefix=/usr --without-bash-malloc --with-installed-readline && make && make install DESTDIR=\$PAGAN_ROOT && ln -sf bash \$PAGAN_ROOT/bin/sh"
        "coreutils:9.11:https://ftp.gnu.org/gnu/coreutils/coreutils-9.11.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "diffutils:3.12:https://ftp.gnu.org/gnu/diffutils/diffutils-3.12.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "findutils:4.11.0:https://ftp.gnu.org/gnu/findutils/findutils-4.11.0.tar.xz:SKIP:./configure --prefix=/usr --localstatedir=/var/lib/locate && make && make install DESTDIR=\$PAGAN_ROOT"
        "gawk:5.4.1:https://ftp.gnu.org/gnu/gawk/gawk-5.4.1.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "grep:3.12:https://ftp.gnu.org/gnu/grep/grep-3.12.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "gzip:1.14:https://ftp.gnu.org/gnu/gzip/gzip-1.14.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "make:4.4.1:https://ftp.gnu.org/gnu/make/make-4.4.1.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "patch:2.8:https://ftp.gnu.org/gnu/patch/patch-2.8.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "sed:4.10:https://ftp.gnu.org/gnu/sed/sed-4.10.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "tar:1.35:https://ftp.gnu.org/gnu/tar/tar-1.35.tar.xz:SKIP:./configure --prefix=/usr --without-posix-acls && make && make install DESTDIR=\$PAGAN_ROOT"
        "libpipeline:1.5.8:https://download.savannah.gnu.org/releases/libpipeline/libpipeline-1.5.8.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "man-db:2.13.1:https://download.savannah.gnu.org/releases/man-db/man-db-2.13.1.tar.xz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --disable-setuid && make && make install DESTDIR=\$PAGAN_ROOT"
        "vim:9.2.0954:https://github.com/vim/vim/archive/v9.2.0954/vim-9.2.0954.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT && ln -sf vim \$PAGAN_ROOT/usr/bin/vi"
        # busybox (statyczny) – potrzebny do INITRAMFS: samowystarczalny /bin/sh
        # i narzędzia (mount/switch_root/losetup/...). Bez niego initramfs kopiował
        # binarki HOSTA bez /bin/sh i bibliotek → kernel: "Failed to execute /init".
        # Instalujemy TYLKO binarkę (bez symlinków appletów – nie nadpisujemy coreutils).
        "busybox:1.37.0:https://busybox.net/downloads/busybox-1.37.0.tar.bz2:SKIP:make defconfig && sed -i -e 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' -e 's/^CONFIG_TC=y/# CONFIG_TC is not set/' .config && make -j\$PAGAN_JOBS && install -m755 busybox \$PAGAN_ROOT/usr/bin/busybox"

        # ======== BEZPIECZEŃSTWO ========
        "openssl:4.0.1:https://github.com/openssl/openssl/releases/download/openssl-4.0.1/openssl-4.0.1.tar.gz:SKIP:./Configure --prefix=/usr --openssldir=/etc/ssl --libdir=lib shared zlib-dynamic && make && make install DESTDIR=\$PAGAN_ROOT"
        "libbsd:0.12.2:https://libbsd.freedesktop.org/releases/libbsd-0.12.2.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "shadow:4.20.2:https://github.com/shadow-maint/shadow/releases/download/4.20.2/shadow-4.20.2.tar.xz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --with-group-name-max-length=32 && make && make install DESTDIR=\$PAGAN_ROOT"
        "linux-pam:1.7.2:https://github.com/linux-pam/linux-pam/releases/download/v1.7.2/Linux-PAM-1.7.2.tar.xz:SKIP:meson setup build --prefix=/usr --sysconfdir=/etc -Ddocs=disabled -Daudit=disabled -Deconf=disabled -Dlogind=disabled -Dselinux=disabled -Dnis=disabled -Dopenssl=disabled -Dexamples=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "sudo:1.9.17p2:https://www.sudo.ws/dist/sudo-1.9.17p2.tar.gz:SKIP:sed -i 's/current_name->d.iPAddress->length/ASN1_STRING_length(current_name->d.iPAddress)/g' lib/iolog/hostcheck.c && ./configure --prefix=/usr --sysconfdir=/etc --with-secure-path && make && make install DESTDIR=\$PAGAN_ROOT"

        # ======== SIEC ========
        # libtirpc – Xorg 21.x linkuje się z nim (RPC/Xtrans). Bez builda w
        # rootfs configure łapał .pc z HOSTA i w live /usr/bin/X padał:
        # „libtirpc.so.3: cannot open shared object file”.
        # Źródło wg BLFS (SourceForge). Jeśli gcc16 będzie marudzić, dojdzie patch.
        "libtirpc:1.3.7:https://downloads.sourceforge.net/libtirpc/libtirpc-1.3.7.tar.bz2:SKIP:./configure --prefix=/usr --sysconfdir=/etc --disable-static --disable-gssapi && make && make install DESTDIR=\$PAGAN_ROOT"
        "curl:8.18.0:https://curl.se/download/curl-8.18.0.tar.xz:SKIP:./configure --prefix=/usr --with-openssl --disable-ldap --disable-ldaps --without-libidn2 --without-brotli --without-nghttp2 --without-librtmp --without-libpsl CPPFLAGS=\"-DOPENSSL_NO_UI_CONSOLE\" --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "inetutils:2.8:https://ftp.gnu.org/gnu/inetutils/inetutils-2.8.tar.gz:SKIP:./configure --prefix=/usr --disable-logger --disable-whois --disable-servers && make && make install DESTDIR=\$PAGAN_ROOT"
        "iproute2:7.1.0:https://www.kernel.org/pub/linux/utils/net/iproute2/iproute2-7.1.0.tar.xz:SKIP:sed -i /ARPD/d Makefile && make && make DESTDIR=\$PAGAN_ROOT SBINDIR=/usr/sbin install"
        "dhcpcd:10.0.8:https://github.com/NetworkConfiguration/dhcpcd/releases/download/v10.0.8/dhcpcd-10.0.8.tar.xz:SKIP:./configure --prefix=/usr --sysconfdir=/etc && make && make install DESTDIR=\$PAGAN_ROOT"
        "libnl:3.12.0:https://github.com/thom311/libnl/releases/download/libnl3_12_0/libnl-3.12.0.tar.gz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        # libmnl + libnftnl – zależności iptables-nft (nowoczesny firewall/nftables)
        "libmnl:1.0.5:https://www.netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "libnftnl:1.3.1:https://www.netfilter.org/projects/libnftnl/files/libnftnl-1.3.1.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "iptables:1.8.12:https://www.netfilter.org/projects/iptables/files/iptables-1.8.12.tar.xz:SKIP:./configure --prefix=/usr --sysconfdir=/etc && make && make install DESTDIR=\$PAGAN_ROOT"

        # ======== SYSTEM I USŁUGI ========
        "util-linux:2.42.2:https://www.kernel.org/pub/linux/utils/util-linux/v2.42/util-linux-2.42.2.tar.xz:SKIP:./configure --bindir=/usr/bin --libdir=/usr/lib --sbindir=/usr/sbin --disable-chfn-chsh --disable-login --disable-nologin --disable-su --disable-runuser --disable-pylibmount --disable-liblastlog2 --without-python --without-systemd && make && make install DESTDIR=\$PAGAN_ROOT"
        "e2fsprogs:1.47.4:https://downloads.sourceforge.net/project/e2fsprogs/e2fsprogs/v1.47.4/e2fsprogs-1.47.4.tar.gz:SKIP:mkdir bld && cd bld && ../configure --prefix=/usr --sysconfdir=/etc --enable-elf-shlibs --disable-fuse2fs && make && make install DESTDIR=\$PAGAN_ROOT"
        # Narzędzia systemów plików dla instalatora (Calamares):
        #   • dosfstools (mkfs.vfat) – BEZ niego instalacja EFI pada po wyborze
        #     dysku („mkfs.vfat: command not found” → set -e ubija instalator).
        #   • xfsprogs / btrfs-progs – gdy użytkownik wybierze xfs/btrfs w instalatorze.
        "dosfstools:4.2:https://github.com/dosfstools/dosfstools/releases/download/v4.2/dosfstools-4.2.tar.gz:SKIP:./configure --prefix=/usr --enable-compat-symlinks && make && make install DESTDIR=\$PAGAN_ROOT"
        "xfsprogs:7.1.1:https://www.kernel.org/pub/linux/utils/fs/xfs/xfsprogs/xfsprogs-7.1.1.tar.xz:SKIP:./configure --prefix=/usr --disable-static --disable-scrub && make && make install DESTDIR=\$PAGAN_ROOT"
        # btrfs-progs: --disable-libudev, bo libudev (z systemd/udev) buduje się w tej
        # liście PÓŹNIEJ niż btrfs-progs, a configure bez tej opcji pada na
        # „Package 'libudev' not found". libudev służy tu tylko do wykrywania
        # urządzeń MULTIPATH – zbędne w live/instalatorze.
        "btrfs-progs:7.1:https://www.kernel.org/pub/linux/kernel/people/kdave/btrfs-progs/btrfs-progs-v7.1.tar.xz:SKIP:./configure --prefix=/usr --disable-documentation --disable-python --disable-libudev && make && make install DESTDIR=\$PAGAN_ROOT"

        # ======== SZYFROWANIE (LUKS) – cryptsetup + zależności ========
        # Kolejność ma znaczenie: libdevmapper (lvm2) → json-c → popt → argon2 → cryptsetup.
        # lvm2: libdevmapper wymagany przez cryptsetup; daje też LVM w instalatorze (auto+LVM).
        "lvm2:2.03.42:https://sourceware.org/pub/lvm2/releases/LVM2.2.03.42.tgz:SKIP:./configure --prefix=/usr --disable-selinux --disable-systemd-journal && make && make install DESTDIR=\$PAGAN_ROOT"
        "json-c:0.19:https://github.com/json-c/json-c/releases/download/json-c-0.19-20260627/json-c-0.19.tar.gz:SKIP:mkdir bld && cd bld && cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_STATIC_LIBS=OFF -DCMAKE_BUILD_TYPE=Release && make && make install DESTDIR=\$PAGAN_ROOT"
        "popt:1.19:https://github.com/rpm-software-management/popt/archive/refs/tags/popt-1.19-release.tar.gz:SKIP:./autogen.sh && ./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "argon2:20190702:https://github.com/P-H-C/phc-winner-argon2/archive/refs/tags/20190702.tar.gz:SKIP:make && make install PREFIX=/usr DESTDIR=\$PAGAN_ROOT"
        # cryptsetup 2.8 – backend openssl (openssl jest budowany wcześniej),
        # bez tokena ssh i bez bindingu Pythona; man wymaga asciidoctor → off.
        "cryptsetup:2.8.7:https://www.kernel.org/pub/linux/utils/cryptsetup/v2.8/cryptsetup-2.8.7.tar.xz:SKIP:./configure --prefix=/usr --with-crypto_backend=openssl --disable-ssh-token --disable-asciidoc && make && make install DESTDIR=\$PAGAN_ROOT"
        "procps-ng:4.0.7:https://sourceforge.net/projects/procps-ng/files/Production/procps-ng-4.0.7.tar.xz:SKIP:./configure --prefix=/usr --disable-static --disable-kill --without-ncurses && make && make install DESTDIR=\$PAGAN_ROOT"
        "psmisc:23.7:https://sourceforge.net/projects/psmisc/files/psmisc/psmisc-23.7.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "kmod:34.2:https://www.kernel.org/pub/linux/utils/kernel/kmod/kmod-34.2.tar.xz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --disable-manpages --with-openssl --with-xz --with-zstd --with-zlib && make && make install DESTDIR=\$PAGAN_ROOT"
        "kbd:2.10.0:https://www.kernel.org/pub/linux/utils/kbd/kbd-2.10.0.tar.xz:SKIP:./configure --prefix=/usr --disable-vlock && make && make install DESTDIR=\$PAGAN_ROOT"
        "less:704:https://www.greenwoodsoftware.com/less/less-704.tar.gz:SKIP:./configure --prefix=/usr --sysconfdir=/etc && make && make install DESTDIR=\$PAGAN_ROOT"
        "sysklogd:2.7.2:https://github.com/troglobit/sysklogd/releases/download/v2.7.2/sysklogd-2.7.2.tar.gz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var && make && make install DESTDIR=\$PAGAN_ROOT"
        "dbus:1.16.2:https://dbus.freedesktop.org/releases/dbus/dbus-1.16.2.tar.xz:SKIP:CC=gcc CXX=g++ cmake -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_SYSCONFDIR=/etc -DCMAKE_INSTALL_LOCALSTATEDIR=/var -DDBUS_BUILD_TESTS=OFF -DDBUS_SYSTEMD_USER_SERVICE_DIR=/usr/lib/systemd/user && make -C build && make -C build DESTDIR=\$PAGAN_ROOT install"
        # wpa_supplicant po dbus (defconfig ma CONFIG_CTRL_IFACE_DBUS_NEW=y – potrzebuje dbus/dbus.h) i po libnl
        # BINDIR=/usr/sbin: domyślnie wpa_supplicant instaluje się do /usr/local/sbin
        # (Makefile: export BINDIR ?= /usr/local/sbin), a NetworkManager szuka
        # helpera w standardowych ścieżkach systemowych. Dodatkowo `make install`
        # NIE instaluje plików D-Bus (fi.w1.wpa_supplicant1) ani jednostki
        # systemd – dokłada je _ensure_wifi_supplicant().
        "wpa_supplicant:2.11:https://w1.fi/releases/wpa_supplicant-2.11.tar.gz:SKIP:cd wpa_supplicant && cp defconfig .config && sed -i 's/^CONFIG_SMARTCARD=y/#CONFIG_SMARTCARD=y/' .config && sed -i -E 's/([A-Za-z_][A-Za-z0-9_]*)->d.(ia5|dNSName|iPAddress|uniformResourceIdentifier|rfc822Name)->data/ASN1_STRING_get0_data(\1->d.\2)/g; s/([A-Za-z_][A-Za-z0-9_]*)->d.(ia5|dNSName|iPAddress|uniformResourceIdentifier|rfc822Name)->length/ASN1_STRING_length(\1->d.\2)/g; s/cn->data/ASN1_STRING_get0_data(cn)/g; s/cn->length/ASN1_STRING_length(cn)/g' ../src/crypto/tls_openssl.c && make BINDIR=/usr/sbin && make DESTDIR=\$PAGAN_ROOT BINDIR=/usr/sbin install"
        # systemd: konflikt net/if.h (glibc) z linux/if.h (kernel UAPI). Build kompiluje
        # NA HOŚCIE z nagłówkami TARGETA (-I$PAGAN_ROOT/usr/include), które przesłaniają
        # vendorowane UAPI systemd (src/include/uapi) → <linux/netdevice.h> wchodzi do TU
        # PRZED jakimkolwiek headerem glibc, libc-compat.h nie widzi __GLIBC__ i kernel
        # definiuje pełne enumy IFF_*/struct ifreq → potem <net/if.h> glibc →
        # „redeclaration of enumerator”. Kernelowy linux/libc-compat.h wyłącza własne
        # definicje TYLKO gdy glibc wszedł pierwszy (warunek _NET_IF_H && __USE_MISC).
        # Dlatego wymuszamy glibcowy <net/if.h> na starcie KAŻDEJ TU przez -include:
        # wtedy kernel (defs=0) nie dubluje enuma IFF_*/struct ifreq i build przechodzi
        # jak na dystrybucjach. UWAGA: ten -include ładuje features.h PRZED -include
        # config.h (który niesie _GNU_SOURCE), więc dorzucamy -D_GNU_SOURCE – inaczej
        # features.h blokuje __USE_GNU/__USE_LARGEFILE64 → „O_LARGEFILE not defined”
        # (-Werror=undef) i „implicit declaration of gettid”. (Pomysł -D__GLIBC__ NIE
        # działa – libc-compat.h i tak ustawia __UAPI_DEF_IF_*=1 bez _NET_IF_H.)
        "systemd:261.2:https://github.com/systemd/systemd/archive/v261.2/systemd-261.2.tar.gz:SKIP:sed -i 's|#include <stddef.h>|&\n#ifndef __key_serial_t_defined\ntypedef int key_serial_t;\n#define __key_serial_t_defined\n#endif|' src/include/override/sys/keyctl.h && sed -i '/^#include <netinet\/ip\(6\)\?\.h>$/d' src/shared/firewall-util.c && sed -i 's|^#include <endian.h>$|&\n#include <netinet/ip.h>\n#include <netinet/ip6.h>|' src/shared/firewall-util.c && cp -f src/include/uapi/linux/liveupdate.h \$PAGAN_ROOT/usr/include/linux/liveupdate.h && sed -i -e 's/ifr->ifr_data = &ecmd;/ifr->ifr_data = (void*) \&ecmd;/' -e 's/ifr->ifr_data = settings;/ifr->ifr_data = (void*) settings;/' -e 's/ifr\.ifr_data = settings;/ifr.ifr_data = (void*) settings;/' src/shared/ethtool-util.c && sed -i '/#include <linux\/fs.h>/a #include <linux/ext4.h>' src/shared/resize-fs.c && sed -i '/^#endif/i #define MQUEUE_MAGIC 0x19800202' \$PAGAN_ROOT/usr/include/linux/magic.h && CFLAGS=\"\$CFLAGS -D_GNU_SOURCE -include \$PAGAN_ROOT/usr/include/net/if.h -Dopen_tree_attr=open_tree_attr_shim -Dquotactl_fd=quotactl_fd_shim -DTASK_COMM_LEN=16 -Wno-redundant-decls -Wno-error=redundant-decls -Wno-error\" CC=gcc CXX=g++ meson setup build --prefix=/usr --sysconfdir=/etc --localstatedir=/var --buildtype=release -Ddefault-dnssec=no -Dfirstboot=false -Dinstall-tests=false -Dldconfig=false -Dsysusers=false -Drpmmacrosdir=no -Dhomed=disabled -Dman=disabled -Dmode=release -Dpamconfdir=no -Ddev-kvm-mode=0660 -Dnobody-group=nogroup -Dsysupdate=disabled -Dukify=disabled -Dwerror=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
                # GRUB i386-pc: WYMUSZAMY `-Wl,-Ttext` jako opcję adresu bazowego jądra.
                # GRUB sam wykrywa ją w configure.ac (AX_CHECK_LINK_FLAG na
                # "-Wl,--image-base,0x400000"): gdy linker przyjmie tę opcję, wybiera
                # --image-base, w przeciwnym razie -Ttext. Problem: dzisiejszy GNU ld
                # PRZYJMUJE --image-base także dla celów ELF (to opcja PE/EFI, ld tylko
                # ostrzega), więc probe „się udaje” i kernel.exec jest linkowany z
                # --image-base,0x9000. Wtedy .text NIE trafia na 0x9000, tylko na
                # 0x9000 + rozmiar nagłówka ELF (0x74) = 0x9074, bo jądro i386-pc musi
                # być jednym segmentem RWX (-Wl,-N/OMAGIC) i nagłówki siedzą w nim.
                # Skutek: grub-mkimage/grub-install: „kernel.img is miscompiled: its
                # start address is not 0x9000” → instalacja na BIOS nie działa.
                # Odtworzone i zmierzone lokalnie (binutils hosta, GRUB 2.14):
                #   --image-base,0x9000 -> .text 0x9074   (zepsute)
                #   -Ttext,0x9000       -> .text 0x9000   (dobre)
                # Dlatego podajemy wynik probe-a z góry (zmienna cache AX_CHECK_LINK_FLAG)
                # TYLKO dla builda pc – build EFI zostaje przy --image-base, bo tam (PE)
                # to jest właściwa opcja.
                # UWAGA: -Wl,-z,noseparate-code nie ma tu znaczenia (testowane: sam nie
                # naprawia, a z -Ttext jest zbędny) – zostawiony, bo cały pozostały
                # build (moduły, grub-mkimage) jest z nim przetestowany.
                "grub:2.14:https://ftp.gnu.org/gnu/grub/grub-2.14.tar.xz:SKIP:mkdir -p build-pc build-efi && cd build-pc && ../configure --prefix=/usr --sysconfdir=/etc --disable-werror --with-platform=pc TARGET_CFLAGS=\"-Os -fno-pie -fno-PIE\" TARGET_LDFLAGS=\"-no-pie -Wl,-z,noseparate-code\" ax_cv_check_ldflags___Wl___image_base_0x400000=no && touch grub-core/extra_deps.lst && make && make install DESTDIR=\$PAGAN_ROOT && cd ../build-efi && ../configure --prefix=/usr --sysconfdir=/etc --disable-werror --with-platform=efi TARGET_CFLAGS=\"-Os -fno-pie -fno-PIE\" TARGET_LDFLAGS=\"-no-pie -Wl,-z,noseparate-code\" && touch grub-core/extra_deps.lst && make && make install DESTDIR=\$PAGAN_ROOT"

        # efivar + efibootmgr: moduł "bootloader" Calamaresa woła efibootmgr
        # w ścieżce UEFI (efi_boot_next) BEZ zabezpieczenia — gdy binarki nie ma,
        # subprocess.check_output rzuca FileNotFoundError, a Calamares pokazuje
        # „Bad main script file … for python job bootloader raised an exception”.
        # efibootmgr wymaga libefivar (popt jest już wyżej w liście).
        # ENABLE_DOCS=0: strony man efivara generuje MANDOC (docs/Makefile),
        # którego nie ma w systemie budującym — bez tej flagi `make all` pada na
        # „mandoc: command not found”. Biblioteki i narzędzia budują się bez docs.
        # (efibootmgr nie ma tego problemu: %.8 powstaje z %.8.in przez sed.)
        "efivar:39:https://github.com/rhboot/efivar/archive/refs/tags/39.tar.gz:SKIP:unset MAKEFLAGS && make ERRORS='' ENABLE_DOCS=0 all && make DESTDIR=\$PAGAN_ROOT/ LIBDIR=/usr/lib/ PCDIR=/usr/lib/pkgconfig INCLUDEDIR=/usr/include/ BINDIR=/usr/bin/ ENABLE_DOCS=0 install -j1 V=1"
        "efibootmgr:18:https://github.com/rhboot/efibootmgr/archive/edc8b9b6ec1c7751ccb9a483405c99141ba237fc.tar.gz:e951ce9e0534c63bb71ba8b2a3830d4402e51440cb4d524d18c1ef40ae5ee218:make EXTRA_CFLAGS=\"-Os\" EFIDIR=\"/boot/EFI\" && install -D -m0755 src/efibootmgr \$PAGAN_ROOT/usr/bin/efibootmgr && install -D -m0644 src/efibootmgr.8 \$PAGAN_ROOT/usr/share/man/man8/efibootmgr.8"
        "sysvinit:3.14:https://github.com/slicer69/sysvinit/releases/download/3.14/sysvinit-3.14.tar.xz:SKIP:curl -fsSL -o /tmp/sysvinit.patch 'https'\$'\72''//www.linuxfromscratch.org/patches/lfs/13.1-rc2/sysvinit-3.14-consolidated-1.patch' 2>/dev/null || true; patch -Np1 < /tmp/sysvinit.patch 2>/dev/null || true; rm -f /tmp/sysvinit.patch; make && make install DESTDIR=\$PAGAN_ROOT && rm -f \$PAGAN_ROOT/sbin/init"

        # ======== PERL I PYTHON ========
        "sqlite:3530400:https://sqlite.org/2026/sqlite-autoconf-3530400.tar.gz:SKIP:CC=gcc ./configure --prefix=/usr --disable-static --enable-fts5 && make && make install DESTDIR=\$PAGAN_ROOT"
        "perl:5.44.0:https://www.cpan.org/src/5.0/perl-5.44.0.tar.xz:SKIP:sh Configure -des -Dcc=gcc -Dprefix=/usr -Duseshrplib && make && make install DESTDIR=\$PAGAN_ROOT"
        "mpdecimal:4.0.1:https://www.bytereef.org/software/mpdecimal/releases/mpdecimal-4.0.1.tar.gz:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        # python – wymagany przez cały system (pag, pagsync/pagbuild, narzędzia GUI).
        # OpenSSL w rootfs to 4.x (libssl.so.4): build musi używać NAGŁÓWKÓW targetu
        # (CPPFLAGS=-I$PAGAN_ROOT/usr/include), bo inaczej _ssl.c kompiluje się przeciw
        # hostowi (openssl 3.x z TLSv1_method) i linkuje do libssl.so.4 bez tej funkcji
        # → „undefined symbol: TLSv1_method” i make usuwa moduł. Dodatkowo:
        #   • sed usuwa legacy wywołania TLSv1/1_1/1_2_method dla openssl >= 4.0
        #     (patch LFS openssl_4-1 nie pokrywa tej strażnika w 3.14.7);
        #   • LDFLAGS z -Wl,-rpath do rootfs – test importu modułów w trakcie make
        #     (RUNSHARED nadpisuje LD_LIBRARY_PATH) musi znaleźć libssl.so.4.
        "python:3.14.7:https://www.python.org/ftp/python/3.14.7/Python-3.14.7.tar.xz:SKIP:curl -fsSL -o /tmp/python-openssl.patch 'https'\$'\72''//www.linuxfromscratch.org/patches/lfs/13.1-rc2/Python-3.14.7-openssl_4-1.patch' 2>/dev/null || true; patch -Np1 < /tmp/python-openssl.patch 2>/dev/null || true; rm -f /tmp/python-openssl.patch; sed -i 's/!defined(OPENSSL_NO_TLS1_METHOD))/!defined(OPENSSL_NO_TLS1_METHOD) \&\& OPENSSL_VERSION_NUMBER < 0x40000000L)/; s/!defined(OPENSSL_NO_TLS1_1_METHOD))/!defined(OPENSSL_NO_TLS1_1_METHOD) \&\& OPENSSL_VERSION_NUMBER < 0x40000000L)/; s/!defined(OPENSSL_NO_TLS1_2_METHOD))/!defined(OPENSSL_NO_TLS1_2_METHOD) \&\& OPENSSL_VERSION_NUMBER < 0x40000000L)/' Modules/_ssl.c && sed -i 's|^@MODULE__CRYPT_TRUE@_crypt|#_crypt|' Modules/Setup.stdlib.in && ac_cv_lib_crypt_crypt_r=no ac_cv_func_crypt_r=no TCLTK_CFLAGS=\"-I/usr/include\" TCLTK_LIBS=\"-L/usr/lib -ltk8.6 -ltcl8.6\" CPPFLAGS=\"-I\$PAGAN_ROOT/usr/include\" ./configure --prefix=/usr --enable-shared && CPPFLAGS=\"-I\$PAGAN_ROOT/usr/include\" make LDFLAGS=\"\$LDFLAGS -Wl,-rpath,\$PAGAN_ROOT/usr/lib\" && CPPFLAGS=\"-I\$PAGAN_ROOT/usr/include\" make install DESTDIR=\$PAGAN_ROOT"
        "ninja:1.13.2:https://github.com/ninja-build/ninja/archive/v1.13.2/ninja-1.13.2.tar.gz:SKIP:python3 configure.py --bootstrap && install -m755 ninja \$PAGAN_ROOT/usr/bin/"
        "meson:1.12.0:https://github.com/mesonbuild/meson/releases/download/1.12.0/meson-1.12.0.tar.gz:SKIP:pip3 wheel -w dist --no-build-isolation --no-deps \$PWD && pip3 install --no-index --find-links dist --root=\$PAGAN_ROOT meson"
        "pkgconf:3.0.5:https://distfiles.ariadne.space/pkgconf/pkgconf-3.0.5.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "cmake:4.2.3:https://cmake.org/files/v4.2/cmake-4.2.3.tar.gz:SKIP:sed -i 's/j < psig->length/j < ASN1_STRING_length(psig)/; s/psig->data\\[j\\]/ASN1_STRING_get0_data(psig)[j]/; s/num->type == V_ASN1_NEG_INTEGER/ASN1_STRING_get0_data(num)[0] >= 0x80/; s/j < num->length/j < ASN1_STRING_length(num)/; s/num->data\\[j\\]/ASN1_STRING_get0_data(num)[j]/' Utilities/cmcurl/lib/vtls/openssl.c && cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF && cmake --build build $MAKEFLAGS && DESTDIR=\$PAGAN_ROOT cmake --install build"

        # ======== NARZĘDZIA DEWELOPERSKIE ========
        "git:2.53.0:https://www.kernel.org/pub/software/scm/git/git-2.53.0.tar.xz:SKIP:sed -i -E 's/([A-Za-z_][A-Za-z0-9_]*)->d\.(ia5|dNSName|iPAddress|uniformResourceIdentifier|rfc822Name)->data/ASN1_STRING_get0_data(\1->d.\2)/g; s/([A-Za-z_][A-Za-z0-9_]*)->d\.(ia5|dNSName|iPAddress|uniformResourceIdentifier|rfc822Name)->length/ASN1_STRING_length(\1->d.\2)/g' imap-send.c && ./configure --prefix=/usr --with-openssl --with-curl --with-expat && make && make install DESTDIR=\$PAGAN_ROOT"
        # gnupg 2.5 linkuje się przeciw libassuan z HOSTA (target nie buduje libassuan).
        # Archowy/upstreamowy assuan.h 3.0 bywa uszkodzony: zamiast
        # „#define ASSUAN_NO_NPTH_SYSTEM_HOOKS_ANY_MORE 1” zawiera literówkę „#defined”
        # (invalid preprocessing directive). Samouzdrawianie: poprawiamy ten JEDEN token
        # w /usr/include (idempotentne; no-op gdy pliku/znaku brak).
        "gnupg:2.5.17:https://www.gnupg.org/ftp/gcrypt/gnupg/gnupg-2.5.17.tar.bz2:SKIP:sed -i 's/#defined ASSUAN_NO_NPTH_SYSTEM_HOOKS_ANY_MORE/#define ASSUAN_NO_NPTH_SYSTEM_HOOKS_ANY_MORE/' /usr/include/assuan.h 2>/dev/null || true; ./configure --prefix=/usr --sysconfdir=/etc && make && make install DESTDIR=\$PAGAN_ROOT"
        "openssh:10.2p1:https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-10.2p1.tar.gz:SKIP:sed -i 's/octet->data/ASN1_STRING_get0_data(octet)/; s/octet->length/ASN1_STRING_length(octet)/' ssh-pkcs11.c && CPPFLAGS=\"-I\$PAGAN_ROOT/usr/include\" LDFLAGS=\"\$LDFLAGS -Wl,-rpath,\$PAGAN_ROOT/usr/lib\" ./configure --prefix=/usr --sysconfdir=/etc/ssh --with-md5-passwords && make && make install DESTDIR=\$PAGAN_ROOT"
        "rsync:3.4.1:https://www.samba.org/ftp/rsync/src/rsync-3.4.1.tar.gz:SKIP:rm -f config.cache && LDFLAGS=\"\$LDFLAGS -Wl,-rpath,\$PAGAN_ROOT/usr/lib\" ./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "htop:3.3.0:https://github.com/htop-dev/htop/releases/download/3.3.0/htop-3.3.0.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "nano:8.7.1:https://www.nano-editor.org/dist/v8/nano-8.7.1.tar.xz:SKIP:./configure --prefix=/usr --enable-utf8 && make && make install DESTDIR=\$PAGAN_ROOT"
        # fish 4.x – domyślna powłoka użytkowników (zamiast bash). Statyczna
        # binarka z wydania 4.9.3 została przygotowana wyżej (_fish_url/_fish_sha).
        # Usuwamy resztki instalacji 3.7.1 (share/fish), żeby nie mieszały się z
        # wbudowanymi w binarkę funkcjami/completions.
        "fish:4.9.3:${_fish_url}:${_fish_sha}:rm -rf \$PAGAN_ROOT/usr/share/fish && cp -f fish \$PAGAN_ROOT/usr/bin/fish && chmod 755 \$PAGAN_ROOT/usr/bin/fish"

        # ======== DODATKOWE NARZĘDZIA ========
        "ca-certificates:2024:https://curl.se/ca/cacert-2024-07-02.pem:SKIP:mkdir -p \$PAGAN_ROOT/etc/ssl/certs && cp cacert-2024-07-02.pem \$PAGAN_ROOT/etc/ssl/certs/ca-certificates.crt"
        "icu:75.1:https://github.com/unicode-org/icu/releases/download/release-75-1/icu4c-75_1-src.tgz:SKIP:( export CFLAGS=\"-fno-stack-protector -std=gnu17 -O2 -Wno-error=override-init\"; export CXXFLAGS=\"-fno-stack-protector -O2\"; S=\$'\72'; cd source && ./configure --prefix=/usr && sed -i \"s#\\\$(LIBRARY_PATH_PREFIX)\\\$(LIBDIR)\${S}\\\$(top_builddir)/stubdata\${S}\\\$(top_builddir)/tools/ctestfw#\$PWD/lib\${S}\$PWD/stubdata\${S}\$PWD/tools/ctestfw#g; s#\\\$(top_builddir)/stubdata\${S}\\\$(top_builddir)/tools/ctestfw\${S}\\\$(LIBRARY_PATH_PREFIX)\\\$(LIBDIR)#\$PWD/stubdata\${S}\$PWD/tools/ctestfw\${S}\$PWD/lib#g\" icudefs.mk && make && make install DESTDIR=\$PAGAN_ROOT && ln -sf libicudata.so.75.1 \$PAGAN_ROOT/usr/lib/libicudata.so.75 && ln -sf libicudata.so.75.1 \$PAGAN_ROOT/usr/lib/libicudata.so )"
        "shared-mime-info:2.4:https://gitlab.freedesktop.org/xdg/shared-mime-info/-/archive/2.4/shared-mime-info-2.4.tar.bz2:SKIP:PKG_CONFIG_SYSROOT_DIR= PKG_CONFIG_PATH=/usr/lib/pkgconfig meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"

        # ======== FONTY I FIRMWARE ========
        "dejavu-fonts:2.37:https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-fonts-ttf-2.37.tar.bz2:SKIP:mkdir -p \$PAGAN_ROOT/usr/share/fonts/dejavu && install -m644 ttf/*.ttf \$PAGAN_ROOT/usr/share/fonts/dejavu/"
        "linux-firmware:20260810:https://cdn.kernel.org/pub/linux/kernel/firmware/linux-firmware-20260810.tar.xz:SKIP:mkdir -p \$PAGAN_ROOT/usr/lib/firmware && cp -a . \$PAGAN_ROOT/usr/lib/firmware/"
    )

    ui_set_total "${#packages[@]}"
    # tk (nowy pakiet, wymagany przez tkinter instalatora GUI) wymusza przebudowę
    # Pythona: resume-build zostawiłby stary Python zbudowany BEZ Tk (brak _tkinter)
    # → instalator GUI nie wystartuje mimo obecności pakietu tk.
    # Uwaga: klucz resume to „tk-8.6.18-xft” (nowa wersja z Xft) – po udanej
    # przebudowie warunek przestaje działać i Python NIE jest przebudowywany co build.
    if ! _is_built "tk-8.6.18-xft" && _is_built "python-3.14.7"; then
        sed -i '/^python-3\.14\.7$/d' "$RESUME_FILE" 2>/dev/null || true
        warn "tk-8.6.18-xft jest nowy → usuwam python-3.14.7 z resume (przebudowa Pythona z tkinter)"
    fi
    # ncurses MUSI mieć wersjonowane symbole (@NCURSES6_TINFO_...): readline i inne
    # pakiety buildowane PRZED ncurses (kolejność listy) linkowały się z HOSTOWYM
    # ncurses (wersjonowanym) → bez wersjonowania w targetcie link pada:
    # „undefined reference to tgetent@NCURSES6_TINFO_5.0.19991023”. Wymuś przebudowę.
    if [[ -f "$PAGAN_ROOT/usr/lib/libncursesw.so.6" ]] && \
       ! nm -D "$PAGAN_ROOT/usr/lib/libncursesw.so.6" 2>/dev/null | grep -q 'NCURSES6_TINFO'; then
        warn "ncurses w targetcie bez wersjonowanych symboli – usuwam z resume (przebudowa)"
        sed -i '/^ncurses-6.6$/d' "$RESUME_FILE" 2>/dev/null || true
    fi

    # GRUB i386-pc: kernel.exec MUSI mieć start 0x9000, inaczej grub-mkimage pada
    # z „kernel.img is miscompiled: its start address is not 0x9000” i instalacja
    # na BIOS nie działa. Prawdziwą przyczynę (GRUB wybiera -Wl,--image-base
    # zamiast -Wl,-Ttext, bo nowy ld przyjmuje --image-base także dla ELF)
    # i naprawę opisano przy wpisie `grub:` na liście pakietów.
    # Ten check jest samonaprawczy: gdy w rootfsie został stary/zepsuty kernel.exec
    # z wcześniejszego przebiegu, zdejmujemy grub z resume i przebudowujemy go.
    if command -v objdump >/dev/null 2>&1 && \
       [[ -f "$PAGAN_ROOT/usr/lib/grub/i386-pc/kernel.exec" ]]; then
        _gaddr=$(objdump -f "$PAGAN_ROOT/usr/lib/grub/i386-pc/kernel.exec" 2>/dev/null \
                 | awk '/start address/{print $NF}')
        if [[ -n "$_gaddr" ]] && ! _addr_is_0x9000 "$_gaddr"; then
            warn "GRUB i386-pc: kernel.exec start=$_gaddr (ma być 0x…9000) – usuwam z resume (przebudowa)"
            sed -i '/^grub-2\.14$/d' "$RESUME_FILE" 2>/dev/null || true
        fi
    fi

    for pkg_info in "${packages[@]}"; do
        name="${pkg_info%%:*}"; rest="${pkg_info#*:}"
        ver="${rest%%:*}"; rest="${rest#*:}"
        config="${rest##*:}"; rest_no_cfg="${rest%:*}"
        sha="${rest_no_cfg##*:}"
        url="${rest_no_cfg%:*}"
        build_lfs_pkg "$name" "$ver" "$url" "$sha" "$config"
    done

    # TWARDA weryfikacja po przebudowie: zły kernel.img = dysk nie wstanie.
    # Najpierw próbujemy importu spójnego gruba z hosta; dopiero gdy to
    # niemożliwe – zatrzymujemy build z błędem (zamiast pakować złe ISO).
    #
    # WAŻNE, KTÓRY PLIK SPRAWDZAĆ: dla i386-pc `grub-mkimage` czyta plik
    # **kernel.img** (util/mkimage.c: `kernel_path = grub_util_get_path(dir,
    # "kernel.img")` bezwarunkowo), a test „is miscompiled: its start address”
    # z util/grub-mkimagexx.c porównuje adres sekcji .text właśnie z link_addr
    # (0x9000) tego pliku. `kernel.exec` to plik tylko pomocniczy
    # (symbole/debug_info) – sam jego zły adres niczego nie psuje i jest
    # NORMALNY po imporcie gruba z hosta, bo paczki dystrybucyjne nie zawierają
    # kernel.exec wcale. Dlatego sprawdzamy kernel.img, a .exec tylko
    # raportujemy jako informację (wcześniej fałszywy alarm z .exec wymuszał
    # niepotrzebny import).
    if command -v readelf >/dev/null 2>&1 && \
       [[ -f "$PAGAN_ROOT/usr/lib/grub/i386-pc/kernel.img" ]]; then
        _gaddr=$(readelf -SW "$PAGAN_ROOT/usr/lib/grub/i386-pc/kernel.img" 2>/dev/null \
                 | sed -n 's/^ *\[ *[0-9]*\] *\.text  *[A-Z]* *\([0-9a-fA-F]*\).*/\1/p' | head -1)
        if [[ -n "$_gaddr" ]] && ! _addr_is_0x9000 "$_gaddr"; then
            warn "GRUB i386-pc: kernel.img .text na 0x$_gaddr (ma być 0x9000) – grub-install padnie"
            if ! import_host_grub; then
                warn "Brak gruba na hoście do importu – NIE przerywam builda: instalator użyje gruba z repo (netinstall → grub → grub-install). Uwaga: source-grub w ISO jest zepsuty – instalacja BIOS wymaga dostępu do repo."
            fi
        else
            info "  ✓ GRUB i386-pc: kernel.img .text=0x${_gaddr:-?} (OK)"
            # Diagnostyka: rozbieżność .exec/.img oznacza mieszankę buildów
            # (kernel.img z hosta + kernel.exec z builda). Dla i386-pc to
            # nieszkodliwe, ale warto wiedzieć, co jest w rootfsie.
            if command -v objdump >/dev/null 2>&1 && \
               [[ -f "$PAGAN_ROOT/usr/lib/grub/i386-pc/kernel.exec" ]]; then
                _gexec=$(objdump -f "$PAGAN_ROOT/usr/lib/grub/i386-pc/kernel.exec" 2>/dev/null \
                         | awk '/start address/{print $NF}')
                if [[ -n "$_gexec" ]] && ! _addr_is_0x9000 "$_gexec"; then
                    info "  · grub: kernel.exec start=$_gexec (nieużywany przez grub-mkimage dla i386-pc – ignoruję)"
                fi
            fi
        fi
    fi

    rebuild_dbus_with_systemd

    _configure_basic_system
    ok "System końcowy zbudowany!"
}

# Jednostki D-Busa w kanonicznej, działającej postaci (idempotentne).
#
# DLACZEGO OSOBNA FUNKCJA: rebuild_dbus_with_systemd() kończy się wcześnie,
# gdy dbus-daemon ma już --systemd-activation – a wtedy stare, wadliwe symlinki
# z /etc zostają. Ta funkcja jest wołana też z finalize_build i fix-calamares.sh,
# więc naprawia istniejący rootfs bez pełnej przebudowy.
#
# BŁĄD, KTÓRY NAPRAWIA (cykl zależności, systemd usuwa zadanie i D-Bus nie wstaje):
#   sockets.target: Found ordering cycle: dbus.service/start after
#     basic.target/start after sockets.target/start - after dbus.service
#   Job dbus.service/start deleted to break ordering cycle starting with sockets.target/start
# Przyczyna: dbus.service (DefaultDependencies=yes ⇒ After=basic.target) był
# dowiązany w sockets.target.wants, a basic.target ma After=… sockets.target.
# Skutek: brak D-Busa ⇒ brak polkitd (Calamares nie widzi dysków), brak
# NetworkManager i brak lightdm.
_ensure_dbus_units() {
    local _us="$PAGAN_ROOT/usr/lib/systemd/system"
    [[ -f "$_us/dbus.socket" || -f "$_us/dbus.service" ]] || return 0

    cat > "$_us/dbus.socket" <<'DBUSEOF'
[Unit]
Description=D-Bus System Message Bus Socket

[Socket]
ListenStream=/run/dbus/system_bus_socket
SocketMode=0666

[Install]
WantedBy=sockets.target
DBUSEOF

    cat > "$_us/dbus.service" <<'DBUSEOF'
[Unit]
Description=D-Bus System Message Bus
Documentation=man:dbus-daemon(1)
Requires=dbus.socket
After=dbus.socket

[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
ExecReload=/usr/bin/dbus-send --print-reply --system --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig
ExecStartPre=/usr/bin/dbus-uuidgen --ensure
RuntimeDirectory=dbus
RuntimeDirectoryMode=0755
Restart=on-failure
RestartSec=1s
OOMScoreAdjust=-900

[Install]
WantedBy=multi-user.target
Also=dbus.socket
DBUSEOF

    # Gniazdo nasłuchuje wcześnie (aktywacja socketowa), usługa startuje
    # w multi-user.target. Usługa w sockets.target.wants = cykl (patrz wyżej).
    rm -f "$PAGAN_ROOT/etc/systemd/system/sockets.target.wants/dbus.service"
    mkdir -p "$PAGAN_ROOT/etc/systemd/system/sockets.target.wants" \
             "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants"
    ln -sfn /usr/lib/systemd/system/dbus.socket \
            "$PAGAN_ROOT/etc/systemd/system/sockets.target.wants/dbus.socket"
    ln -sfn /usr/lib/systemd/system/dbus.service \
            "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/dbus.service"

    ok "  dbus: jednostki bez cyklu zależności (socket → sockets.target, usługa → multi-user.target)"
}

# WiFi: wpa_supplicant gotowy dla NetworkManagera.
#
# Trzy niezależne braki blokowały WiFi (także na USB):
#  1. binarka lądowała w /usr/local/sbin, bo Makefile ma
#     „export BINDIR ?= /usr/local/sbin” – NM szuka helpera w ścieżkach
#     systemowych,
#  2. brak /usr/share/dbus-1/system-services/fi.w1.wpa_supplicant1.service →
#     D-Bus nie może aktywować demona, a libnm komunikuje się z nim po D-Bus,
#  3. brak polityki /etc/dbus-1/system.d/wpa_supplicant.conf i jednostki systemd.
#
# `make install` wpa_supplicanta instaluje WYŁĄCZNIE binarki – punkty 2 i 3
# trzeba dołożyć. Idempotentne; wołane z finalize_build i fix-calamares.sh.
_ensure_wifi_supplicant() {
    # 1. Sprowadź binarkę do kanonicznego /usr/sbin/wpa_supplicant.
    #
    # Zależnie od wersji i opcji build wpa_supplicant ląduje w /usr/local/sbin
    # (Makefile ma „export BINDIR ?= /usr/local/sbin”) ALBO w /usr/bin.
    # Pliki aktywacji D-Bus i jednostka systemd poniżej wskazują
    # /usr/sbin/wpa_supplicant – jeśli tam nic nie ma, NetworkManager nie ma
    # czym zarządzać kartą (także na USB), a `systemctl start wpa_supplicant`
    # kończy się błędem 203/EXEC. Dlatego przenosimy albo dowiązujemy.
    local _b _src
    mkdir -p "$PAGAN_ROOT/usr/sbin"
    for _b in wpa_supplicant wpa_cli wpa_passphrase; do
        [[ -e "$PAGAN_ROOT/usr/sbin/$_b" ]] && continue
        for _src in "$PAGAN_ROOT/usr/local/sbin/$_b" "$PAGAN_ROOT/usr/bin/$_b" \
                    "$PAGAN_ROOT/sbin/$_b" "$PAGAN_ROOT/bin/$_b"; do
            [[ -x "$_src" ]] || continue
            if [[ "$_src" == "$PAGAN_ROOT/usr/bin/$_b" ]]; then
                # /usr/bin jest w PATH tak samo jak /usr/sbin – dowiązanie
                # wystarcza i nie psuje innych instalacji.
                ln -sfn "/usr/bin/$_b" "$PAGAN_ROOT/usr/sbin/$_b"
            else
                mv -f "$_src" "$PAGAN_ROOT/usr/sbin/$_b"
            fi
            break
        done
    done
    if [[ ! -x "$PAGAN_ROOT/usr/sbin/wpa_supplicant" ]]; then
        warn "  WiFi: brak binarki wpa_supplicant w rootfsie – pomijam konfigurację D-Bus"
        return 0
    fi
    chmod 0755 "$PAGAN_ROOT/usr/sbin/wpa_supplicant" 2>/dev/null || true
    [[ -e "$PAGAN_ROOT/usr/sbin/wpa_cli" ]] && chmod 0755 "$PAGAN_ROOT/usr/sbin/wpa_cli" 2>/dev/null || true

    # 2. Aktywacja przez D-Bus (bez tego NM nie wystartuje wpa_supplicanta).
    mkdir -p "$PAGAN_ROOT/usr/share/dbus-1/system-services" \
             "$PAGAN_ROOT/etc/dbus-1/system.d" \
             "$PAGAN_ROOT/usr/lib/systemd/system" \
             "$PAGAN_ROOT/etc/wpa_supplicant"

    cat > "$PAGAN_ROOT/usr/share/dbus-1/system-services/fi.w1.wpa_supplicant1.service" <<'WPAEOF'
[D-BUS Service]
Name=fi.w1.wpa_supplicant1
Exec=/usr/sbin/wpa_supplicant -u
User=root
WPAEOF

    cat > "$PAGAN_ROOT/etc/dbus-1/system.d/wpa_supplicant.conf" <<'WPAEOF'
<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
        <policy user="root">
                <allow own="fi.w1.wpa_supplicant1"/>

                <allow send_destination="fi.w1.wpa_supplicant1"/>
                <allow send_interface="fi.w1.wpa_supplicant1"/>
                <allow receive_sender="fi.w1.wpa_supplicant1" receive_type="signal"/>
        </policy>
        <policy context="default">
                <deny own="fi.w1.wpa_supplicant1"/>
                <deny send_destination="fi.w1.wpa_supplicant1"/>
                <deny receive_sender="fi.w1.wpa_supplicant1" receive_type="signal"/>
        </policy>
</busconfig>
WPAEOF

    # 3. Jednostka systemd (dodatkowa droga startu, gdyby D-Bus nie aktywował).
    cat > "$PAGAN_ROOT/usr/lib/systemd/system/wpa_supplicant.service" <<'WPAEOF'
[Unit]
Description=WPA supplicant
Before=network.target
After=dbus.service
Wants=network.target

[Service]
Type=dbus
BusName=fi.w1.wpa_supplicant1
ExecStart=/usr/sbin/wpa_supplicant -u

[Install]
WantedBy=multi-user.target
Alias=dbus-fi.w1.wpa_supplicant1.service
WPAEOF

    # /etc/wpa_supplicant/wpa_supplicant.conf nie jest wymagany (NM steruje przez
    # D-Bus), ale wiele narzędzi go oczekuje.
    if [[ ! -f "$PAGAN_ROOT/etc/wpa_supplicant/wpa_supplicant.conf" ]]; then
        printf '# Zarządzane przez NetworkManager (config_methods przez D-Bus)\n' \
            > "$PAGAN_ROOT/etc/wpa_supplicant/wpa_supplicant.conf"
    fi

    ok "  wpa_supplicant: /usr/sbin + aktywacja D-Bus dla NetworkManagera"
}

# Netinstall: backend `pag` w module packages Calamaresa.
#
# Moduł `netinstall` sam nic nie instaluje – zbiera wybór użytkownika do
# globalstorage (packageOperations) i przekazuje go modułowi `packages`.
# `packages/main.py` obsługuje backendy apk/apt/dnf/pacman/…, ale nie zna
# naszego `pag`. Dopisujemy więc klasę backendu na SAMYM KOŃCU pliku
# (po utworzeniu listy backend_managers) i rejestrujemy ją w tej liście.
#
# Dodatkowo pilnujemy /etc/resolv.conf w systemie docelowym: w rootfsie leży
# on po NetworkManagerze z hosta budującego (np. nameserver 192.168.73.85),
# więc w chroocie `pag sync` nie mógłby rozwiązać DNS.
_install_pag_package_backend() {
    local _m="$PAGAN_ROOT/usr/lib/calamares/modules/packages/main.py"
    [[ -f "$_m" ]] || return 0
    grep -q 'PAGAN: backend pag' "$_m" 2>/dev/null && return 0

    cat >> "$_m" <<'PAGEOF'

# PAGAN: backend pag (PaganLinux) – dopisane przez build-pagan.sh
class PMPag(PackageManager):
    backend = "pag"

    @property
    def name(self):
        return "pag"

    def _ensure_target_dns(self):
        # W chroocie /etc/resolv.conf bywa nieużywalny (stary wpis z buildera
        # albo symlink do /run/systemd/resolve, którego w chroocie nie ma),
        # a bez DNS `pag sync` nie pobierze indeksu.
        target = libcalamares.globalstorage.value("rootMountPoint")
        if not target:
            return
        dst = os.path.join(target, "etc/resolv.conf")
        try:
            if os.path.exists(dst) and os.path.getsize(dst) > 0:
                with open(dst, "r", errors="ignore") as handle:
                    body = handle.read()
                if "nameserver" in body:
                    return
        except OSError:
            pass
        for src in ("/run/NetworkManager/resolv.conf",
                    "/etc/resolv.conf",
                    "/run/systemd/resolve/stub-resolv.conf"):
            try:
                if os.path.exists(src) and os.path.getsize(src) > 0:
                    shutil.copyfile(os.path.realpath(src), dst)
                    return
            except OSError:
                continue
        try:
            with open(dst, "w") as handle:
                handle.write("nameserver 1.1.1.1\nnameserver 8.8.8.8\n")
        except OSError:
            pass

    def update_db(self):
        self._ensure_target_dns()
        try:
            check_target_env_call(["pag", "sync"])
        except subprocess.CalledProcessError as exc:
            libcalamares.utils.warning(
                "pag sync nie powiódł się ({}) – próbuję instalować mimo to".format(exc.returncode))

    def update_system(self):
        self._ensure_target_dns()
        check_target_env_call(["pag", "upgrade", "-y"])

    def install(self, pkgs, from_local=False):
        self._ensure_target_dns()
        check_target_env_call(["pag", "install", "-y"] + pkgs)

    def remove(self, pkgs):
        check_target_env_call(["pag", "remove", "-y"] + pkgs)


backend_managers.append(("pag", PMPag))
PAGEOF

    # Wymagane importy: base main.py importuje abc/subprocess/libcalamares, ale
    # nie os/shutil (używa ich nasz backend). Dodajemy przed „import subprocess”.
    if ! grep -qE '^import os$' "$_m"; then
        sed -i '0,/^import subprocess$/s//import os\nimport shutil\nimport subprocess/' "$_m"
    elif ! grep -qE '^import shutil$' "$_m"; then
        sed -i '0,/^import os$/s//import os\nimport shutil/' "$_m"
    fi

    if python3 -c "import ast,sys; ast.parse(open(sys.argv[1]).read())" "$_m" 2>/dev/null; then
        ok "  Calamares: moduł packages – dodany backend pag (netinstall)"
    else
        error "Calamares packages/main.py: patch backendu pag jest niepoprawny składniowo"
    fi
}

# Systemd nie ma w rootfsie SWOJEJ polityki D-Bus.
#
# Bez plików /usr/share/dbus-1/system.d/org.freedesktop.*.conf obowiązuje
# domyślna polityka z /usr/share/dbus-1/system.conf:
#     <policy context="default">
#       <deny own="*"/>              ← nikt nie może zająć nazwy
#       <deny send_type="method_call"/>
# więc PID1 NIE MOŻE zarejestrować org.freedesktop.systemd1, a logind
# org.freedesktop.login1. Skutki (dokładnie widać je w logu startu):
#   systemd[1]: Got disconnect on API bus.
#   dbus-daemon: Failed to activate service 'org.freedesktop.systemd1': timed out
#   pam_systemd: Varlink call io.systemd.Login.CreateSession failed
#   systemd-logind: Failed to start user service …: Transport endpoint is not connected
# → sesje nie powstają → lightdm/XFCE nie startują → system „wisi”.
#
# `src/core/meson.build` robi install_data('org.freedesktop.systemd1.conf'),
# ale w tym buildzie pliki nie trafiły do rootfsu (meson policzył dbuspolicydir
# z pkg-config dbus-1). Wstawiamy je wprost ze źródeł systemd — deterministycznie.
_ensure_systemd_dbus_policy() {
    local _pol="$PAGAN_ROOT/usr/share/dbus-1/system.d"
    local _svc="$PAGAN_ROOT/usr/share/dbus-1/system-services"
    local _ses="$PAGAN_ROOT/usr/share/dbus-1/services"

    [[ -d "$PAGAN_ROOT/usr/lib/systemd" ]] || return 0
    # Już jest? (idempotencja – nie nadpisujemy, gdy systemd zainstalował sam.)
    # Sprawdzamy też symlink aktywacji systemd --user z magistrali SESJI – on też
    # ginął do zagnieżdżonego rootfsu (patrz _merge_nested_rootfs), a bez niego
    # XFCE przy starcie woła `dbus-update-activation-environment --systemd` i loguje:
    #   „error sending to systemd: … The name org.freedesktop.systemd1 was not
    #    provided by any .service files”.
    if [[ -f "$_pol/org.freedesktop.systemd1.conf" && -f "$_svc/org.freedesktop.login1.service" \
          && -e "$_ses/org.freedesktop.systemd1.service" ]]; then
        return 0
    fi

    mkdir -p "$_pol" "$_svc"

    # Źródło: rozpakowane drzewo systemd (SRC_DIR/systemd-*) albo tarball.
    local _src="" _tb="" _d
    for _d in "$SRC_DIR"/systemd-* "$SRC_DIR"/build-systemd; do
        [[ -d "$_d/src/core" ]] && { _src="$_d"; break; }
    done
    if [[ -z "$_src" ]]; then
        for _tb in "$SRC_DIR"/systemd-*.tar.gz "$SRC_DIR"/systemd-*.tar.xz; do
            [[ -f "$_tb" ]] && break
            _tb=""
        done
        [[ -n "$_tb" ]] || { warn "  dbus: brak źródeł systemd – nie mogę dołożyć polityki D-Bus (PID1 nie zajmie nazwy!)"; return 0; }
        _src="$(mktemp -d)"
        tar -xf "$_tb" -C "$_src" --strip-components=1 \
            --wildcards '*/src/*/org.freedesktop.*.conf' '*/src/*/org.freedesktop.*.service' 2>/dev/null || true
        if [[ ! -d "$_src/src/core" ]]; then
            warn "  dbus: nie udało się rozpakować polityki D-Bus z $_tb"
            rm -rf "$_src"
            return 0
        fi
    fi

    local _n_conf=0 _n_svc=0 _f
    for _f in "$_src"/src/*/org.freedesktop.*.conf; do
        [[ -f "$_f" ]] || continue
        cp -f "$_f" "$_pol/" && _n_conf=$((_n_conf + 1))
    done
    for _f in "$_src"/src/*/org.freedesktop.*.service; do
        [[ -f "$_f" ]] || continue
        cp -f "$_f" "$_svc/" && _n_svc=$((_n_svc + 1))
    done

    # Sprzątanie tylko własnego katalogu tymczasowego.
    [[ "$_src" == /tmp/* ]] && rm -rf "$_src"

    # Aktywacja systemd --user z magistrali SESJI (plik instalowany przez systemd
    # jako symlink do ../system-services/…). Bez niego ostrzeżenie z xinitrc XFCE
    # (patrz komentarz na początku funkcji).
    if [[ -f "$_svc/org.freedesktop.systemd1.service" ]]; then
        mkdir -p "$_ses"
        ln -sfn ../system-services/org.freedesktop.systemd1.service \
            "$_ses/org.freedesktop.systemd1.service"
    fi

    if [[ -f "$_pol/org.freedesktop.systemd1.conf" ]]; then
        ok "  dbus: polityka systemd ($_n_conf plików .conf, $_n_svc .service) – PID1 może zająć org.freedesktop.systemd1"
    else
        warn "  dbus: nie udało się dołożyć org.freedesktop.systemd1.conf – systemd nie zarejestruje się na magistrali"
    fi
}

# Udev: baza hwdb (/etc/udev/hwdb.bin).
#
# Bez niej generator systemd-imds-generator (systemd 261) kończy się błędem:
#   systemd-imds-generator: Failed to open hwdb: No such file or directory
#   '/usr/lib/systemd/system-generators/systemd-imds-generator' failed with exit status 1
# hwdb poprawia też rozpoznawanie urządzeń przez udev (klawiatury, tablety,
# kamery), więc generujemy ją przy każdym buildzie/respinie.
_ensure_hwdb() {
    [[ -x "$PAGAN_ROOT/usr/bin/systemd-hwdb" ]] || return 0

    # Brak źródeł = nie ma z czego zbudować bazy (kompilatory hwdb są w /usr/lib).
    if [[ ! -d "$PAGAN_ROOT/usr/lib/udev/hwdb.d" && ! -d "$PAGAN_ROOT/etc/udev/hwdb.d" ]]; then
        return 0
    fi

    mkdir -p "$PAGAN_ROOT/etc/udev"

    # Najpierw chroot (identyczne środowisko jak w systemie), potem --root.
    if chroot "$PAGAN_ROOT" /usr/bin/systemd-hwdb update >/dev/null 2>&1 || \
       "$PAGAN_ROOT/usr/bin/systemd-hwdb" --root="$PAGAN_ROOT" update >/dev/null 2>&1; then
        if [[ -s "$PAGAN_ROOT/etc/udev/hwdb.bin" ]]; then
            ok "  hwdb: /etc/udev/hwdb.bin wygenerowane ($(stat -c %s "$PAGAN_ROOT/etc/udev/hwdb.bin") B)"
            return 0
        fi
    fi
    warn "  hwdb: nie udało się wygenerować /etc/udev/hwdb.bin (generator imds będzie zgłaszał błąd)"
}

# dbus w tablicy build_final_system buduje się PRZED systemd (brak libsystemd
# w pkg-config rootfs) → ENABLE_SYSTEMD=AUTO wyłącza wsparcie systemd i cmake
# nie instaluje dbus.socket/dbus.service. To zmuszało nas do niestandardowego
# wywołania dbus-daemon (bez aktywacji socketowej), które powodowało wyścigi
# i zerwane połączenia („Failed to parse bus message", logind ENOTCONN).
# Tu przebudowujemy dbus PO systemd z ENABLE_SYSTEMD=ON → standardowe jednostki
# aktywacji socketowej (dbus.socket + dbus.service z cmake).
rebuild_dbus_with_systemd() {
    step "DBUS: PRZEBUDOWA Z WSPARCIEM SYSTEMD (aktywacja socketowa)"

    # Idempotencja (resume): jeśli binarka wspiera systemd ORAZ jednostki są
    # poprawne, nie rób nic. (Uwaga: dbus-daemon --help NIE pokazuje tej opcji,
    # więc sprawdzamy przez strings.)
    if [[ -f "$PAGAN_ROOT/usr/bin/dbus-daemon" ]] && \
       strings "$PAGAN_ROOT/usr/bin/dbus-daemon" | grep -q -- "--systemd-activation" && \
       grep -q '^ListenStream=/run/dbus/system_bus_socket$' "$PAGAN_ROOT/usr/lib/systemd/system/dbus.socket" 2>/dev/null; then
        ok "dbus: aktywacja socketowa już aktywna"
        return 0
    fi

    # Wymuś przebudowę mimo wpisu resume z pierwszej (niesystemdowej) budowy.
    sed -i '/^dbus-1.16.2$/d' "$RESUME_FILE" 2>/dev/null || true

    build_lfs_pkg "dbus" "1.16.2" \
        "https://dbus.freedesktop.org/releases/dbus/dbus-1.16.2.tar.xz" \
        "SKIP" \
        "CC=gcc CXX=g++ cmake -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_SYSCONFDIR=/etc -DCMAKE_INSTALL_LOCALSTATEDIR=/var -DDBUS_BUILD_TESTS=OFF -DENABLE_SYSTEMD=ON -DDBUS_SYSTEMD_USER_SERVICE_DIR=/usr/lib/systemd/user && make -C build && make -C build DESTDIR=\$PAGAN_ROOT install"

    # cmake z dbus 1.16.2 generuje jednostki z PUSTYMI @EXPANDED_BINDIR@/
    # @DBUS_SYSTEM_SOCKET@ (bug: te zmienne są zdefiniowane tylko w meson.build;
    # patrz bus/dbus.service.in i bus/CMakeLists.txt) → nadpisujemy poprawnymi.
    # System bus MUSI działać jako root (bez User=) – inaczej nie utworzy
    # /run/dbus i nie przeczyta /etc/dbus-1/system.conf.
    # Kanoniczne jednostki + symlinki (bez cyklu zależności) ustawia
    # _ensure_dbus_units() – jedno źródło prawdy, używane też przez finalize_build
    # i fix-calamares.sh.
    _ensure_dbus_units

    if [[ -f "$PAGAN_ROOT/usr/lib/systemd/system/dbus.socket" ]]; then
        ok "dbus: jednostki systemd (dbus.socket + dbus.service) naprawione"
    else
        warn "dbus: brak dbus.socket po przebudowie – sprawdź log budowy dbus"
    fi
}

_configure_basic_system() {
    # /etc/passwd
    cat > "$PAGAN_ROOT/etc/passwd" <<'EOF'
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:nobody:/:/bin/false
systemd-timesync:x:103:103:systemd Time Synchronization:/:/usr/sbin/nologin
systemd-network:x:104:104:systemd Network Management:/:/usr/sbin/nologin
systemd-resolve:x:105:105:systemd Resolver:/:/usr/sbin/nologin
systemd-oom:x:106:106:systemd Userspace OOM Killer:/:/usr/sbin/nologin
systemd-coredump:x:107:107:systemd Core Dumper:/:/usr/sbin/nologin
systemd-journal-remote:x:108:108:systemd Journal Remote:/:/usr/sbin/nologin
polkitd:x:110:110:polkit PolicyKit daemon:/:/usr/sbin/nologin
man:x:12:12:Manuals Reader:/var/cache/man:/usr/sbin/nologin
messagebus:x:81:81:system message bus:/:/usr/sbin/nologin
EOF

    # /etc/group
    cat > "$PAGAN_ROOT/etc/group" <<'EOF'
root:x:0:
bin:x:1:
sys:x:2:
kmem:x:3:
tty:x:4:
tape:x:5:
daemon:x:6:
floppy:x:7:
disk:x:8:
lp:x:9:
dialout:x:10:
audio:x:11:
video:x:12:
utmp:x:13:
usb:x:14:
cdrom:x:15:
mail:x:34:
nogroup:x:65534:
wheel:x:97:
plugdev:x:98:
input:x:99:
render:x:100:
kvm:x:101:
systemd-journal:x:102:
systemd-timesync:x:103:
systemd-network:x:104:
systemd-resolve:x:105:
systemd-oom:x:106:
systemd-coredump:x:107:
systemd-journal-remote:x:108:
polkitd:x:110:
man:x:12:
clock:x:35:
sgx:x:974:
messagebus:x:81:
# Grupy wymagane przez Calamares (users.defaultGroups/sudoersGroup/autologinGroup)
# oraz standardowe grupy sprzętowe (BLFS).
optical:x:21:
scanner:x:22:
storage:x:23:
power:x:24:
users:x:25:
autologin:x:26:
EOF

    # /etc/fstab
    cat > "$PAGAN_ROOT/etc/fstab" <<'EOF'
# /etc/fstab
/dev/sda1       /               ext4    defaults        0       1
proc            /proc           proc    nosuid,noexec,nodev 0       0
sysfs           /sys            sysfs   nosuid,noexec,nodev 0       0
devtmpfs        /dev            devtmpfs mode=0755,nosuid 0       0
tmpfs           /tmp            tmpfs   defaults        0       0
devpts          /dev/pts        devpts  gid=5,mode=620  0       0
	shm             /dev/shm        tmpfs   defaults        0       0
EOF

    # /etc/systemd/system.conf
    cat > "$PAGAN_ROOT/etc/systemd/system.conf" <<'EOF'
[Manager]
DefaultTimeoutStopSec=10s
DefaultTimeoutStartSec=10s
DefaultStartLimitIntervalSec=10s
DefaultStartLimitBurst=5
EOF

    # /etc/systemd/logind.conf
    cat > "$PAGAN_ROOT/etc/systemd/logind.conf" <<'EOF'
[Login]
HandlePowerKey=poweroff
HandleSuspendKey=suspend
HandleHibernateKey=hibernate
HandleLidSwitch=suspend
HandleLidSwitchExternalPower=suspend
HandleLidSwitchDocked=ignore
PowerKeyIgnoreInhibited=no
SuspendKeyIgnoreInhibited=no
HibernateKeyIgnoreInhibited=no
LidSwitchIgnoreInhibited=yes
HoldoffTimeoutSec=30s
IdleAction=ignore
IdleActionSec=30min
RuntimeDirectorySize=10%
InhibitDelayMaxSec=5
KillUserProcesses=no
KillExcludeUsers=root
RemoveIPC=yes
EOF

    # dbus: aktywacja socketowa. Gniazdo (dbus.socket) nasłuchuje wcześnie
    # (sockets.target), a usługa startuje w multi-user.target – NIE w
    # sockets.target.wants, bo to zamyka cykl zależności:
    #   dbus.service (After=basic.target) → basic.target (After=sockets.target)
    #   → sockets.target (pulls dbus.service) → cykl → zadanie usuwane
    #   i D-Bus nie wstaje (brak polkitd/NetworkManager/lightdm).
    _ensure_dbus_units

    # systemd przejmuje kontrolę nad shutdown/halt/reboot/poweroff/init.
    # (sysvinit przez /sbin → usr/bin nadpisałby symlinki systemd realnymi binarkami)
    for _ctl in halt poweroff reboot shutdown; do
        ln -sf systemctl "$PAGAN_ROOT/usr/bin/$_ctl"
    done
    ln -sf ../lib/systemd/systemd "$PAGAN_ROOT/usr/bin/init"

    # Locale UTF-8: Qt (Calamares/KF6) wymaga UTF-8 – bez wygenerowanych locale
    # Qt ostrzega: "Qt depends on a UTF-8 locale, but has failed to switch".
    # UWAGA: /usr/lib/locale musi istnieć, inaczej localedef pada z ENOENT.
    mkdir -p "$PAGAN_ROOT/usr/lib/locale"
    for _loc in en_US.UTF-8 pl_PL.UTF-8; do
        chroot "$PAGAN_ROOT" /usr/bin/localedef -i "${_loc%.*}" -f UTF-8 "$_loc" || true
    done

    # /etc/hostname
    echo "pagan" > "$PAGAN_ROOT/etc/hostname"

    # /etc/locale.conf
    cat > "$PAGAN_ROOT/etc/locale.conf" <<'EOF'
LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8
EOF

    # /etc/vconsole.conf
    cat > "$PAGAN_ROOT/etc/vconsole.conf" <<'EOF'
KEYMAP=pl
FONT=Lat2-Terminus16
EOF

    # Symlink init systemd
    ln -sf /usr/lib/systemd/systemd "$PAGAN_ROOT/sbin/init" 2>/dev/null || true

    # Enable podstawowych usług (best-effort, może nie działać przy DESTDIR)
    chroot "$PAGAN_ROOT" systemctl enable systemd-logind 2>/dev/null || true
    chroot "$PAGAN_ROOT" systemctl enable systemd-timesyncd 2>/dev/null || true
}

# ========================= BUDOWA BLFS (PEŁNE BIBLIOTEKI) =========================
build_blfs() {
    step "BLFS: BIBLIOTEKI I ZALEŻNOŚCI"

    # Sprawdzamy WYŁĄCZNIE biblioteki targetu LFS w $PAGAN_ROOT/usr/lib
    # zanim zaczniemy BLFS, żeby nie kontynuować z uszkodzonym ABI.
    check_target_abi

    export PATH="$TOOLS_DIR/bin:/usr/bin/core_perl:/usr/bin/vendor_perl:$PATH"
    export PKG_CONFIG_DIR=""
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/lib/x86_64-linux-gnu/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"
    export PKG_CONFIG_SYSROOT_DIR="$PAGAN_ROOT"
    export PKG_CONFIG_LIBDIR="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/lib/x86_64-linux-gnu/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"
    export CPPFLAGS="-I$PAGAN_ROOT/usr/include"
    export LDFLAGS="-L$PAGAN_ROOT/usr/lib -L$PAGAN_ROOT/lib64 -Wl,-rpath-link,$PAGAN_ROOT/usr/lib -Wl,-rpath-link,$PAGAN_ROOT/lib64"

    # Wykryj architekturę dla cross-file (działa na x86_64, aarch64, armv7l...)
    local ARCH
    ARCH=$(uname -m)
    case "$ARCH" in
        x86_64|amd64)  ARCH=x86_64 ;;
        aarch64|arm64) ARCH=aarch64 ;;
        armv7l|armv7*) ARCH=arm ;;
    esac

    # Meson cross-file + stub .pc files
    # Wrapper kompilatora: dodaje -isystem $PAGAN_ROOT/usr/include. Inaczej cross-gcc
    # rozwiązuje <limits.h> do WEWNĘTRZNEGO limits.h gcc (jego include_next nie sięga
    # $sysroot/usr/include) i giną makra glibc: LINE_MAX, _POSIX_HOST_NAME_MAX itd.
    # (env CFLAGS ani c_args z cross-filesa NIE docierają do kompilatora cross – meson
    # traktuje je per-machine; wrapper to omija.)
    # rm -f przed zapisem: /tmp bywa sticky, a obcy plik (inny właściciel) zablokuje cat.
    rm -f /tmp/pagan-cross-gcc /tmp/pagan-cross-g++ /tmp/pagan-cross.ini
    cat > /tmp/pagan-cross-gcc <<WRAP_EOF
#!/bin/sh
exec '$CROSS_BIN/$LFS_TGT-gcc' -isystem '$PAGAN_ROOT/usr/include' "\$@"
WRAP_EOF
    chmod +x /tmp/pagan-cross-gcc
    # C++: tools-g++ z ch5 nie ma libstdc++ (--disable-libstdcxx), więc <iostream> itd.
    # bierzemy z libstdc++ TARGETA (finalny gcc). Wymagana pełna ścieżka -isystem:
    #   katalog multilib (bits/c++config.h) + backward + główny + /usr/include
    # (sam -isystem /usr/include NIE wystarczy – iostream leży w c++/<ver>/).
    _gxx_ver_dir=$(ls -d "$PAGAN_ROOT"/usr/include/c++/*/ 2>/dev/null | head -1)
    _gxx_ver_dir=${_gxx_ver_dir%/}
    _gxx_multi=""
    for _d in "$_gxx_ver_dir"/*/; do
        [[ -f "${_d}bits/c++config.h" ]] && { _gxx_multi="${_d%/}"; break; }
    done
    cat > /tmp/pagan-cross-g++ <<WRAP_EOF2
#!/bin/sh
exec '$CROSS_BIN/$LFS_TGT-g++' -isystem '$_gxx_multi' -isystem '$_gxx_ver_dir/backward' -isystem '$_gxx_ver_dir' -isystem '$PAGAN_ROOT/usr/include' "\$@"
WRAP_EOF2
    chmod +x /tmp/pagan-cross-g++
    cat > /tmp/pagan-cross.ini <<CROSS_EOF
[binaries]
c = '/tmp/pagan-cross-gcc'
cpp = '/tmp/pagan-cross-g++'
ar = '$CROSS_BIN/$LFS_TGT-ar'
strip = '$CROSS_BIN/x86_64-pagan-linux-gnu-strip'
pkg-config = 'pkg-config'
glib_genmarshal = '/usr/bin/glib-genmarshal'
glib_mkenums = '/usr/bin/glib-mkenums'
gdbus_codegen = '/usr/bin/gdbus-codegen'
glib_compile_schemas = '/usr/bin/glib-compile-schemas'

[host_machine]
system = 'linux'
cpu_family = '$ARCH'
cpu = '$ARCH'
endian = 'little'

[properties]
iconv = 'libc'
pkg_config_libdir = '$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig'

[built-in options]
pkg_config_path = '$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig'
# Targetowe nagłówki jako -isystem (NIE -I): inaczej <limits.h> rozwiązuje się do
# WEWNĘTRZNEGO limits.h gcc (include_next nie sięga katalogów -I) i giną makra
# _POSIX_* (glib: „_POSIX_HOST_NAME_MAX undeclared” w ghostutils.c).
c_args = ['-isystem', '$PAGAN_ROOT/usr/include']
CROSS_EOF
    mkdir -p "$PAGAN_ROOT/usr/lib/pkgconfig" "$PAGAN_ROOT/usr/include/sysprof-6"
    cat > "$PAGAN_ROOT/usr/include/sysprof-6/sysprof-capture.h" <<'SYSPROFH'
#ifndef SYSPROF_CAPTURE_H
#define SYSPROF_CAPTURE_H
#include <stdint.h>
typedef struct _SysprofCaptureWriter SysprofCaptureWriter;
typedef struct _SysprofCaptureReader SysprofCaptureReader;
#endif
SYSPROFH
    for stub in iconv sysprof-capture-4; do
        [[ -f "$PAGAN_ROOT/usr/lib/pkgconfig/$stub.pc" ]] || \
            cat > "$PAGAN_ROOT/usr/lib/pkgconfig/$stub.pc" <<EOF
prefix=$PAGAN_ROOT/usr
exec_prefix=\${prefix}
libdir=\${prefix}/lib
includedir=\${prefix}/include
Name: $stub
Description: $stub (stub for cross-build)
Version: 1.0
Libs: -L\${libdir} -lc
Cflags:
EOF
    done
    # duktape (polkit: silnik reguł JS) – brak .pc w targetcie. prefix=/usr, żeby
    # PKG_CONFIG_SYSROOT_DIR dodał /mnt/pagan (stuby wyżej używają prefix=$PAGAN_ROOT,
    # ale one nie mają Cflags; tu -I musi być po sysroot, inaczej podwójny prefix).
    cat > "$PAGAN_ROOT/usr/lib/pkgconfig/duktape.pc" <<'DUKTAPE'
prefix=/usr
exec_prefix=${prefix}
libdir=${prefix}/lib
includedir=${prefix}/include
Name: duktape
Description: duktape embedded JS engine
Version: 2.7.0
Libs: -L${libdir} -lduktape
Cflags: -I${includedir}
DUKTAPE
    info "  ✓ Meson cross-file + stubs"

    _pipewire_flag_if_supported() {
        local opt="$1" val="$2"
        local opt_file=""
        if [[ -f meson.options ]]; then
            opt_file="meson.options"
        elif [[ -f meson_options.txt ]]; then
            opt_file="meson_options.txt"
        fi
        [[ -z "$opt_file" ]] && return 0
        if rg -q "option\\(['\"]${opt}['\"]" "$opt_file"; then
            printf ' -D%s=%s' "$opt" "$val"
        fi
    }

    _configure_pipewire() {
        local cross_arg="$1"
        local flags=""

        flags+=$(_pipewire_flag_if_supported "man" "disabled")
        flags+=$(_pipewire_flag_if_supported "session-managers" "[]")
        flags+=$(_pipewire_flag_if_supported "examples" "disabled")
        flags+=$(_pipewire_flag_if_supported "tools" "disabled")
        flags+=$(_pipewire_flag_if_supported "tests" "disabled")

        sed -i '/spa-acp-tool/,+5d; /test-timer/,+5d; /test-hw-params/,+5d' spa/plugins/alsa/meson.build || true
        eval "meson setup build $cross_arg --prefix=/usr${flags}"
    }

    local xc="--cross-file=/tmp/pagan-cross.ini"

    # CFLAGS dla CROSS GCC (nie host GCC). UWAGA: -isystem $PAGAN_ROOT/usr/include jest
    # tu KLUCZOWE: cross-gcc rozwiązuje <limits.h> do WEWNĘTRZNEGO limits.h gcc (jego
    # include_next nie sięga katalogów -I), przez co giną makra _POSIX_* z glibc
    # (np. „_POSIX_HOST_NAME_MAX undeclared” w glib). -isystem kieruje łańcuch do
    # glibcowego limits.h targetu. Meson w cross-buildu bierze c_args z env CFLAGS
    # (nadpisuje c_args z cross-filesa) – dlatego dodajemy tu, a nie w pagan-cross.ini.
    export CFLAGS="-O2 -Wno-error=implicit-function-declaration -Wno-error=int-conversion -Wno-error=return-type -Wno-error=maybe-uninitialized -Wno-error=discarded-qualifiers -Wno-error=incompatible-pointer-types -isystem $PAGAN_ROOT/usr/include"
    # C++ dla pakietów AUTOTOOLS: env CXXFLAGS (pakiety meson dostają ścieżki przez
    # wrapper pagan-cross-g++). Ścieżki libstdc++ TARGETA wyliczone w bloku wrappera.
    if [[ -n "$_gxx_ver_dir" && -n "$_gxx_multi" ]]; then
        export CXXFLAGS="-O2 -Wno-error=return-type -Wno-error=maybe-uninitialized -Wno-error=deprecated-declarations -isystem $_gxx_multi -isystem $_gxx_ver_dir/backward -isystem $_gxx_ver_dir -isystem $PAGAN_ROOT/usr/include"
    else
        export CXXFLAGS="-O2 -Wno-error=return-type -Wno-error=maybe-uninitialized -Wno-error=deprecated-declarations"
    fi
    export FORCE_UNSAFE_CONFIGURE=1

    local libs=(
        # ======== FUNDAMENTY (glib2/libxml2/atk – muszą być pierwsze!) ========
        "glib:2.86.4:https://download.gnome.org/sources/glib/2.86/glib-2.86.4.tar.xz:SKIP:sed -i 's/return _POSIX_HOST_NAME_MAX;/return 255; \/\/ fallback (cross-gcc nie widzi makra _POSIX_HOST_NAME_MAX)/' glib/ghostutils.c && rm -rf build && meson setup build $xc --prefix=/usr -Dselinux=disabled -Dman-pages=disabled -Dlibmount=enabled -Dtests=false -Dintrospection=disabled -Dsysprof=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        # polkit wymaga gio/glib (target) – musi iść PO glib. Cross-build z $xc;
        # LIBRARY_PATH = target żeby cc.find_library('pam') znalazł libpam z linux-pam.
        # SONAME jest konieczny: bez niego linker wpisuje absolutne /mnt/pagan/usr/lib/libduktape.so do polkitd.
        "duktape:2.7.0:https://duktape.org/duktape-2.7.0.tar.xz:SKIP:cd src && gcc -O2 -fPIC -shared -Wl,-soname,libduktape.so -o libduktape.so duktape.c && install -m 755 libduktape.so \$PAGAN_ROOT/usr/lib/libduktape.so && install -m 644 duktape.h \$PAGAN_ROOT/usr/include/duktape.h && install -m 644 duk_config.h \$PAGAN_ROOT/usr/include/duk_config.h"
        "polkit:127:https://github.com/polkit-org/polkit/archive/127/polkit-127.tar.gz:SKIP:( export LIBRARY_PATH=\"\$PAGAN_ROOT/usr/lib\"; meson setup build $xc --prefix=/usr -Dintrospection=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install )"
        "libxml2:2.15.1:https://download.gnome.org/sources/libxml2/2.15/libxml2-2.15.1.tar.xz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --without-python --disable-static && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
        "atk:2.58.3:https://download.gnome.org/sources/at-spi2-core/2.58/at-spi2-core-2.58.3.tar.xz:SKIP:meson setup build $xc --prefix=/usr -Dintrospection=disabled -Ddocs=false -Dx11=disabled -Ddbus_broker=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "alsa-lib:1.2.15.3:https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.15.3.tar.bz2:SKIP:./configure --host=$LFS_TGT --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "libogg:1.3.6:https://downloads.xiph.org/releases/ogg/libogg-1.3.6.tar.xz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "libvorbis:1.3.7:https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "flac:1.5.0:https://github.com/xiph/flac/releases/download/1.5.0/flac-1.5.0.tar.xz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --disable-static --disable-cpplibs && make && make install DESTDIR=\$PAGAN_ROOT"
        "opus:1.6.1:https://downloads.xiph.org/releases/opus/opus-1.6.1.tar.gz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "pipewire:1.6.0:https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/1.6.0/pipewire-1.6.0.tar.bz2:SKIP:_configure_pipewire \"$xc\" && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install && perl \"\$SCRIPT_DIR/patches/spa-json-core-cxx.pl\" \"\$PAGAN_ROOT/usr/include/spa-0.2/spa/utils/json-core.h\""
        # wireplumber potrzebuje Lua (5.4) do modułów. Domyślnie meson ściąga ją
        # jako subproject-wrap (lua.wrap), ale Pythona mesona nie weryfikuje
        # certyfikatów w tym środowisku ([SSL: CERTIFICATE_VERIFY_FAILED]).
        # Podkładamy więc źródło Lua + patch z wrapdb do subprojects/packagecache
        # przez wget (on działa) i blokujemy sieć mesona (--wrap-mode=nodownload).
        # Meson i tak zweryfikuje oba pliki swoimi hashami z lua.wrap.
        "wireplumber:0.5.13:https://gitlab.freedesktop.org/pipewire/wireplumber/-/archive/0.5.13/wireplumber-0.5.13.tar.bz2:SKIP:mkdir -p subprojects/packagecache && wget -q -O subprojects/packagecache/lua-5.4.4.tar.gz 'https'\$'\72''//www.lua.org/ftp/lua-5.4.4.tar.gz' && wget -q -O subprojects/packagecache/lua_5.4.4-1_patch.zip 'https'\$'\72''//wrapdb.mesonbuild.com/v2/lua_5.4.4-1/get_patch' && meson setup build $xc --prefix=/usr --wrap-mode=nodownload && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "nasm:3.01:https://www.nasm.us/pub/nasm/releasebuilds/3.01/nasm-3.01.tar.xz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "sdl2:2.30.7:https://www.libsdl.org/release/SDL2-2.30.7.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"

        # ======== GRAFIKA 2D/3D ========
        "libpng:1.6.55:https://downloads.sourceforge.net/libpng/libpng-1.6.55.tar.xz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        # WITH_JPEG8=1 → SONAME libjpeg.so.8, zgodny z hostem (Arch) i z binarkami
        # linkowanymi host-first. Bez tego target buduje .62, a część binarek
        # (gdk-pixbuf/gd/openraw…) zapisuje NEEDED .8 → runtime „cannot open
        # shared object file: libjpeg.so.8”. Usuwamy też stare .62 z poprzedniej
        # budowy, żeby nie został mieszany ABI.
        "libjpeg-turbo:3.1.3:https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/3.1.3/libjpeg-turbo-3.1.3.tar.gz:SKIP:rm -f \$PAGAN_ROOT/usr/lib/libjpeg.so \$PAGAN_ROOT/usr/lib/libjpeg.so.62 \$PAGAN_ROOT/usr/lib/libjpeg.so.62.4.0 || true; cmake -B build -DCMAKE_INSTALL_PREFIX=/usr -DWITH_JPEG8=1 -G Ninja && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "libtiff:4.7.1:https://download.osgeo.org/libtiff/tiff-4.7.1.tar.gz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "freetype:2.14.1:https://downloads.sourceforge.net/freetype/freetype-2.14.1.tar.xz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "fontconfig:2.17.1:https://gitlab.freedesktop.org/api/v4/projects/890/packages/generic/fontconfig/2.17.1/fontconfig-2.17.1.tar.xz:SKIP:./configure --host=$LFS_TGT --prefix=/usr --sysconfdir=/etc --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "harfbuzz:12.3.2:https://github.com/harfbuzz/harfbuzz/releases/download/12.3.2/harfbuzz-12.3.2.tar.xz:SKIP:meson setup build --prefix=/usr -Dintrospection=disabled -Ddocs=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "fribidi:1.0.16:https://github.com/fribidi/fribidi/releases/download/v1.0.16/fribidi-1.0.16.tar.xz:SKIP:meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        # pixman – TWARDA zależność cairo (dependency('pixman-1')). Bez systemowego
        # pixmana cairo cofa się do subproject-wrappa (pixman.wrap) i pada na
        # weryfikacji certyfikatów Pythona mesona. Budowane przed cairo.
        "pixman:0.43.4:https://www.cairographics.org/releases/pixman-0.43.4.tar.gz:SKIP:meson setup build --prefix=/usr -Dtests=disabled -Ddemos=disabled -Dgtk=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "cairo:1.18.4:https://www.cairographics.org/releases/cairo-1.18.4.tar.xz:SKIP:meson setup build --prefix=/usr -Dxlib=auto -Dxcb=auto && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "pango:1.57.0:https://download.gnome.org/sources/pango/1.57/pango-1.57.0.tar.xz:SKIP:meson setup build --prefix=/usr -Dgtk_doc=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "gdk-pixbuf:2.44.5:https://download.gnome.org/sources/gdk-pixbuf/2.44/gdk-pixbuf-2.44.5.tar.xz:SKIP:meson setup build --prefix=/usr -Dman=false -Dglycin=disabled -Dintrospection=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "libepoxy:1.5.10:https://github.com/anholt/libepoxy/archive/refs/tags/1.5.10.tar.gz:SKIP:meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "libdrm:2.4.131:https://dri.freedesktop.org/libdrm/libdrm-2.4.131.tar.xz:SKIP:meson setup build --prefix=/usr -Dudev=true && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        # ======== WAYLAND (przed mesa/gtk! mesa wymaga wayland-*-pkgconfig,
        # a X11/xcb w tej fazie NIE istnieje – dlatego mesa/gtk bez platformy x11) ========
        "wayland:1.24.0:https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.24.0/downloads/wayland-1.24.0.tar.xz:SKIP:meson setup build --prefix=/usr -Ddocumentation=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "wayland-protocols:1.47:https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/1.47/downloads/wayland-protocols-1.47.tar.xz:SKIP:meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "libxkbcommon:1.13.1:https://github.com/lfs-book/libxkbcommon/archive/v1.13.1/libxkbcommon-1.13.1.tar.gz:SKIP:meson setup build --prefix=/usr -Denable-x11=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "mesa:25.3.5:https://mesa.freedesktop.org/archive/mesa-25.3.5.tar.xz:SKIP:( export CFLAGS=\"-DHAVE_THRD_CREATE \${CFLAGS}\"; meson setup build --prefix=/usr -Dgallium-drivers=softpipe -Dvulkan-drivers= -Dplatforms=wayland -Dglx=disabled -Dllvm=disabled && sed -i 's/^timespec_sub_saturate/__attribute__((used)) timespec_sub_saturate/' src/util/timespec.h && sed -i 's|#include \"loader_wayland_helper.h\"|#include \"util/timespec.h\"\\n#include \"loader_wayland_helper.h\"|' src/loader/loader_wayland_helper.c && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install )"

        # ======== GTK I POZOSTAŁE (wayland; X11 buduje się dopiero w fazie DE) ========
        "gtk:3.24.51:https://download.gnome.org/sources/gtk/3.24/gtk-3.24.51.tar.xz:SKIP:meson setup build --prefix=/usr -Dx11_backend=false -Dwayland_backend=true -Dintrospection=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        # libinput wymaga libevdev (nie było go w targetcie)
        "libevdev:1.13.3:https://www.freedesktop.org/software/libevdev/libevdev-1.13.3.tar.xz:SKIP:meson setup build --prefix=/usr -Ddocumentation=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        "mtdev:1.1.6:https://bitmath.org/code/mtdev/mtdev-1.1.6.tar.gz:SKIP:./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"
        "libinput:1.31.0:https://gitlab.freedesktop.org/libinput/libinput/-/archive/1.31.0/libinput-1.31.0.tar.gz:SKIP:meson setup build --prefix=/usr -Ddocumentation=false -Dtests=false -Dlibwacom=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"

        # ======== SIEC ========
        "libndp:1.9:http://libndp.org/files/libndp-1.9.tar.gz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        # NetworkManager: `systemdsystemunitdir` i `dbus_conf_dir` podajemy JAWNIE.
        # Domyślnie NM bierze je z pkg-config, a pkg-config przy
        # PKG_CONFIG_SYSROOT_DIR zwraca ścieżki JUŻ z sysrootem (/mnt/pagan/usr/...).
        # Meson dokłada wtedy jeszcze DESTDIR → jednostki i polityka D-Bus lądują
        # w /mnt/pagan/mnt/pagan/usr/... i w live nie ma NetworkManager.service
        # (brak DHCP, brak sieci) ani org.freedesktop.NetworkManager.conf
        # (NM nie zajmie nazwy na D-Bus). Uwaga: dbussystemservicedir NIE jest
        # opcją mesonową NM, dlatego plik aktywacji dispatchera i tak dokłada
        # _ensure_network_manager (oraz _merge_nested_rootfs po każdym pakiecie).
        "networkmanager:1.56.0:https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/releases/1.56.0/downloads/NetworkManager-1.56.0.tar.xz:SKIP:meson setup build --prefix=/usr --sysconfdir=/etc --localstatedir=/var -Dsystemdsystemunitdir=/usr/lib/systemd/system -Ddbus_conf_dir=/usr/share/dbus-1/system.d -Dwrap_mode=nodownload -Dovs=false -Dteamdctl=false -Diwd=false -Dofono=false -Dpolkit=false -Dselinux=false -Dlibaudit=no -Dcrypto=null -Dmodem_manager=false -Dlibpsl=false -Dconcheck=false -Dnm_cloud_setup=false -Dnbft=false -Dnmcli=true -Dnmtui=false -Dppp=false -Dwext=false -Dintrospection=false -Ddocs=false -Dtests=no -Dvapi=false -Dqt=false && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"
        "iw:6.9:https://www.kernel.org/pub/software/network/iw/iw-6.9.tar.xz:SKIP:make && make install DESTDIR=\$PAGAN_ROOT"
        "libical:3.0.20:https://github.com/libical/libical/releases/download/v3.0.20/libical-3.0.20.tar.gz:SKIP:( export ICU_ROOT=\"\$PAGAN_ROOT/usr\"; cmake -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DICAL_BUILD_DOCS=OFF -DLIBICAL_BUILD_TESTING=OFF -DICAL_GLIB=OFF -DSHARED_ONLY=ON && cmake --build build && DESTDIR=\$PAGAN_ROOT cmake --install build )"
        "bluez:5.79:https://www.kernel.org/pub/linux/bluetooth/bluez-5.79.tar.xz:SKIP:./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-library --disable-manpages && make && make install DESTDIR=\$PAGAN_ROOT"
        "libarchive:3.8.5:https://github.com/libarchive/libarchive/releases/download/v3.8.5/libarchive-3.8.5.tar.xz:SKIP:./configure --prefix=/usr --disable-static && make && make install DESTDIR=\$PAGAN_ROOT"
        "libnspr:4.38.2:https://archive.mozilla.org/pub/nspr/releases/v4.38.2/src/nspr-4.38.2.tar.gz:SKIP:cd nspr && ./configure --prefix=/usr --disable-static --enable-64bit && make && make install DESTDIR=$PAGAN_ROOT"
    )

    # duktape: nowsza receptura instaluje też duk_config.h (wymagany przez <duktape.h>
    # u konsumentów, np. polkit). Stare przebiegi mają wpis w resume bez duk_config.h
    # → wymuś przebudowę, żeby poprawka faktycznie dotarła do targeta.
    if [[ -f "$PAGAN_ROOT/usr/include/duktape.h" && ! -f "$PAGAN_ROOT/usr/include/duk_config.h" ]]; then
        warn "duktape w targetcie bez duk_config.h – usuwam z resume (przebudowa)"
        sed -i '/^duktape-2\.7\.0$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
    # Poprzednia wersja była zbudowana bez SONAME i powodowała absolutny DT_NEEDED.
    if [[ -f "$PAGAN_ROOT/usr/lib/libduktape.so" ]] && \
       ! readelf -d "$PAGAN_ROOT/usr/lib/libduktape.so" 2>/dev/null | grep -q 'SONAME.*libduktape\.so'; then
        warn "duktape bez SONAME – usuwam z resume (przebudowa, inaczej polkitd dostaje ścieżkę builda)"
        sed -i '/^duktape-2\.7\.0$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
    # Napraw także stare polkitd już zapisane w rootfsie. Bez tego resume pominie
    # pakiet mimo poprawionego duktape i wadliwy absolutny DT_NEEDED pozostanie.
    if [[ -x "$PAGAN_ROOT/usr/lib/polkit-1/polkitd" ]] && \
       readelf -d "$PAGAN_ROOT/usr/lib/polkit-1/polkitd" 2>/dev/null | grep -q '/mnt/pagan/'; then
        warn "polkitd zawiera ścieżkę /mnt/pagan – wymuszam przebudowę polkit"
        sed -i '/^polkit-127$/d' "$RESUME_FILE" 2>/dev/null || true
    fi

    # Pakiety, którym błąd DESTDIR+sysroot zabrał pliki do zagnieżdżonego rootfsu
    # ($PAGAN_ROOT/mnt/pagan, zob. _merge_nested_rootfs) – a kasowanie $PAGAN_ROOT/mnt
    # w środku builda (przy librsvg) gubiło je bezpowrotnie. Poprawka
    # (_merge_nested_rootfs po KAŻDYM pakiecie) ratuje tylko nowo budowane pakiety,
    # a resume by je pominął – dlatego przy braku pliku zdejmujemy pakiet z resume
    # i przebudowujemy go. Warunki są celowo zawężone (sentinel binarki + brak pliku),
    # żeby nie wymuszać przebudowy na zdrowym rootfsie.
    # NetworkManager nie potrzebuje tu przebudowy: jego brakujące pliki pisze
    # _ensure_network_manager (wierne kopie plików z NetworkManager-1.56.0).
    if [[ -f "$PAGAN_ROOT/usr/bin/bluetoothctl" && \
          ! -f "$PAGAN_ROOT/usr/lib/systemd/system/bluetooth.service" ]]; then
        warn "bluez bez bluetooth.service (błąd DESTDIR+sysroot) – usuwam z resume (przebudowa)"
        sed -i '/^bluez-5\.79$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
    if [[ -e "$PAGAN_ROOT/usr/lib/libpipewire-0.3.so.0" && \
          ! -f "$PAGAN_ROOT/usr/lib/systemd/user/pipewire.service" ]]; then
        warn "pipewire bez jednostek user (błąd DESTDIR+sysroot) – usuwam z resume (przebudowa)"
        sed -i '/^pipewire-1\.6\.0$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
    if [[ -x "$PAGAN_ROOT/usr/bin/wireplumber" && \
          ! -f "$PAGAN_ROOT/usr/lib/systemd/user/wireplumber.service" ]]; then
        warn "wireplumber bez jednostek user (błąd DESTDIR+sysroot) – usuwam z resume (przebudowa)"
        sed -i '/^wireplumber-0\.5\.13$/d' "$RESUME_FILE" 2>/dev/null || true
    fi

    # wayland/wayland-protocols (i inni) generują .pc z pkgdatadir=\${pc_sysrootdir}\${datarootdir}/…
    # – przy wgryzionym prefix=$PAGAN_ROOT/usr do .pc ORAZ PKG_CONFIG_SYSROOT_DIR=$PAGAN_ROOT
    # daje to podwójną ścieżkę (/mnt/pagan/mnt/pagan/...), jak przy libxkbcommon. .pc są
    # już absolutne (prefix=$PAGAN_ROOT/usr), więc \${pc_sysrootdir} jest zbędny → usuń.
    _heal_pc_sysrootdir() {
        local _pc
        while IFS= read -r _pc; do
            sed -i 's/\${pc_sysrootdir}//g' "$_pc"
        done < <(grep -rl '\${pc_sysrootdir}' \
            "$PAGAN_ROOT/usr/lib/pkgconfig" "$PAGAN_ROOT/usr/share/pkgconfig" 2>/dev/null)
    }
    _heal_pc_sysrootdir

    ui_set_total "${#libs[@]}"
    for pkg_info in "${libs[@]}"; do
        name="${pkg_info%%:*}"; rest="${pkg_info#*:}"
        ver="${rest%%:*}"; rest="${rest#*:}"
        config="${rest##*:}"; rest_no_cfg="${rest%:*}"
        sha="${rest_no_cfg##*:}"
        url="${rest_no_cfg%:*}"
        build_lfs_pkg "$name" "$ver" "$url" "$sha" "$config"
        # świeżo zainstalowane .pc też mogą nieść \${pc_sysrootdir} (wayland itd.)
        _heal_pc_sysrootdir
    done

    # Nie pozwól wygenerować obrazu z nieprzenośnym DT_NEEDED. Taki błąd
    # objawia się dopiero po instalacji jako kod 127 polkitd.
    if [[ -x "$PAGAN_ROOT/usr/lib/polkit-1/polkitd" ]] && \
       readelf -d "$PAGAN_ROOT/usr/lib/polkit-1/polkitd" 2>/dev/null | grep -q '/mnt/pagan/'; then
        error "polkitd ma absolutną zależność do /mnt/pagan – przebudowa polkit/duktape nie naprawiła linkowania"
    fi

    ok "BLFS – kompletny zestaw bibliotek!"
}

# ========================= BUDOWA KERNELA =========================
build_kernel() {
    step "BUDOWA KERNELA ${KERNEL_VERSION}"

    export PATH="$TOOLS_DIR/bin:$PATH"
    unset PKG_CONFIG_PATH PKG_CONFIG_SYSROOT_DIR PKG_CONFIG_LIBDIR
    unset CFLAGS CXXFLAGS CPPFLAGS

    local kernel_src="$SRC_DIR/linux-${KERNEL_VERSION}"

    if [[ ! -d "$kernel_src" ]]; then
        download_pkg "$KERNEL_URL" "$SRC_DIR/linux-${KERNEL_VERSION}.tar.xz" "SKIP"
        tar -xf "$SRC_DIR/linux-${KERNEL_VERSION}.tar.xz" -C "$SRC_DIR"
    fi

    pushd "$kernel_src" > /dev/null

    info "  ⚙ Konfiguracja jądra (defconfig + tweaks Pagan)..."

    # Generuj pelna konfiguracje domyslna dla x86_64 (zawsze aktualna dla wersji kernela)
    make defconfig || error "Kernel defconfig failed"

    # === PaganOS overrides ===
    ./scripts/config --set-str CONFIG_LOCALVERSION "-pagan"
    ./scripts/config --disable CONFIG_LOCALVERSION_AUTO
    ./scripts/config --disable CONFIG_WERROR

    # Preemption i scheduling (dla desktop/pulpitu)
    ./scripts/config --enable CONFIG_PREEMPT
    ./scripts/config --disable CONFIG_PREEMPT_VOLUNTARY
    ./scripts/config --set-val CONFIG_HZ 1000
    ./scripts/config --enable CONFIG_SCHED_HRTICK

    # Moduly
    ./scripts/config --enable CONFIG_MODULES
    ./scripts/config --enable CONFIG_MODULE_UNLOAD
    ./scripts/config --enable CONFIG_MODULE_FORCE_UNLOAD
    ./scripts/config --enable CONFIG_MODVERSIONS
    ./scripts/config --disable CONFIG_MODULE_SIG

    # EFI (obowiazkowe dla nowoczesnego sprzetu)
    ./scripts/config --enable CONFIG_EFI
    ./scripts/config --enable CONFIG_EFI_STUB

    # === Pamięć masowa ===
    # NVMe (typowy dysk realnego sprzętu i nowych VM) – bez tego instalator
    # pokazuje pustą listę dysków (lsblk nie widzi /dev/nvme*) na NVMe.
    ./scripts/config --enable CONFIG_BLK_DEV_NVME
    ./scripts/config --disable CONFIG_NVME_FC
    ./scripts/config --disable CONFIG_NVME_TCP
    # exFAT: domyślny FS partycji danych Ventoy – bez tego initramfs nie odczyta
    # ISO z pendrive Ventoy (mount exfat kończy się błędem).
    # (Samo CONFIG_EXFAT_FS włączamy niżej, razem z IOCHARSET=utf8.)
    ./scripts/config --disable CONFIG_EXFAT_DONT_MOUNT_VOLMGS
    ./scripts/config --enable CONFIG_EXFAT_DEFAULT_IOCHARSET_NLS

    # KVM / wirtualizacja
    ./scripts/config --enable CONFIG_VIRTUALIZATION
    ./scripts/config --enable CONFIG_KVM
    ./scripts/config --enable CONFIG_KVM_INTEL
    ./scripts/config --enable CONFIG_KVM_AMD

    # WiFi (jako moduly)
    ./scripts/config --enable CONFIG_WIRELESS
    ./scripts/config --module CONFIG_CFG80211
    ./scripts/config --module CONFIG_MAC80211
    ./scripts/config --module CONFIG_IWLWIFI
    ./scripts/config --module CONFIG_IWLMVM
    ./scripts/config --module CONFIG_ATH9K
    ./scripts/config --module CONFIG_ATH10K
    ./scripts/config --module CONFIG_RTW88
    ./scripts/config --module CONFIG_BRCMFMAC
    # USB Wi-Fi (karty na USB – bez tych modułów instalator/live nie widzi
    # zewnętrznych kart, bo nie powstaje w ogóle interfejs wlan*):
    #   • ath9k_htc – Atheros AR9271/AR7010 (np. TP-Link TL-WN722N v1)
    #   • rtl8187 / rtl8xxxu / rtl8192cu – Realtek USB (8187L, 8188EU, 8192CU, 8723AU)
    #   • mt7601u / mt76x0u / mt76x2u – MediaTek MT7601U/MT7610U/MT7612U (tanie karty)
    #   • rt2x00 – Ralink/MediaTek RT2500USB/RT2800USB/RT3070 (USB)
    #   • mwifiex_usb + brcmfmac (USB) – Marvell/Broadcom
    ./scripts/config --module CONFIG_ATH9K_HTC
    ./scripts/config --module CONFIG_RTL8187
    ./scripts/config --module CONFIG_RTL8XXXU
    ./scripts/config --module CONFIG_RTLWIFI
    ./scripts/config --module CONFIG_RTL8192CU
    ./scripts/config --module CONFIG_MT7601U
    ./scripts/config --module CONFIG_MT76x0U
    ./scripts/config --module CONFIG_MT76x2U
    ./scripts/config --module CONFIG_RT2X00
    ./scripts/config --module CONFIG_RT2500USB
    ./scripts/config --module CONFIG_RT73USB
    ./scripts/config --module CONFIG_RT2800USB
    # Uwaga: RT2800USB_RT33XX/35XX/53XX to BOOL (pod-opcje rt2800usb), nie tristate –
    # wartość 'm' jest nieprawidłowa (warning kconfig „symbol value 'm' invalid”).
    ./scripts/config --enable CONFIG_RT2800USB_RT33XX
    ./scripts/config --enable CONFIG_RT2800USB_RT35XX
    ./scripts/config --enable CONFIG_RT2800USB_RT53XX
    ./scripts/config --module CONFIG_MWIFIEX
    ./scripts/config --module CONFIG_MWIFIEX_USB
    ./scripts/config --enable CONFIG_BRCMFMAC_USB

    # === Live ISO (squashfs + overlay + ISO9660 + loop) – WYMAGANE w jądrze
    # (nie jako moduł), żeby initramfs mógł zamontować system live bez ładowania
    # modułów.
    ./scripts/config --enable CONFIG_BLK_DEV_LOOP
    ./scripts/config --enable CONFIG_SQUASHFS
    ./scripts/config --enable CONFIG_SQUASHFS_XZ
    ./scripts/config --enable CONFIG_SQUASHFS_ZSTD
    ./scripts/config --enable CONFIG_OVERLAY_FS
    ./scripts/config --enable CONFIG_ISO9660_FS
    ./scripts/config --enable CONFIG_JOLIET
    ./scripts/config --enable CONFIG_ZISOFS
    ./scripts/config --enable CONFIG_UDF_FS
    ./scripts/config --enable CONFIG_EFI_FS
    ./scripts/config --enable CONFIG_EFI_PARTITION
    ./scripts/config --enable CONFIG_NLS_UTF8
    ./scripts/config --enable CONFIG_NLS_CODEPAGE_437

    # exFAT – partycja danych Ventoy (domyślnie exFAT). Bez tego initramfs nie
    # zamontuje nośnika i nie znajdzie ISO („nie znaleziono nośnika LIVE”).
    ./scripts/config --enable CONFIG_EXFAT_FS
    ./scripts/config --set-str CONFIG_EXFAT_DEFAULT_IOCHARSET utf8
    # UAS – część zewnętrznych USB3/SATA w obudowach zgłasza się tylko przez UAS.
    ./scripts/config --enable CONFIG_USB_UAS
    ./scripts/config --enable CONFIG_DEVTMPFS
    ./scripts/config --enable CONFIG_DEVTMPFS_MOUNT
    ./scripts/config --enable CONFIG_TMPFS

    # XATTR/ACL – wymagane dla live ISO i tmpfs:
    #   • CONFIG_SQUASHFS_XATTR: bez niego kernel loguje
    #     "SQUASHFS error: Xattrs in filesystem, these will be ignored"
    #     (mksquashfs domyślnie zapisuje xatrry do obrazu)
    #   • CONFIG_TMPFS_POSIX_ACL: bez niego systemd-tmpfiles nie może ustawić
    #     ACL na /var/log/journal (overlay/tmpfs) → "Failed to parse ACL ... Invalid argument"
    ./scripts/config --enable CONFIG_SQUASHFS_XATTR
    ./scripts/config --enable CONFIG_TMPFS_POSIX_ACL

    # === Dyski dla QEMU/KVM, sprzętu fizycznego i instalatora ===
    # Wszystko jest wbudowane w kernel (nie jako moduł), ponieważ live ISO
    # musi wykryć dysk przed uruchomieniem Calamares.
    # Podstawa SCSI i narzędzia działające na urządzeniach SG.
    ./scripts/config --enable CONFIG_SCSI
    ./scripts/config --enable CONFIG_BLK_DEV_SD
    ./scripts/config --enable CONFIG_CHR_DEV_SG
    # Kontrolery SATA/IDE – QEMU z if=ide, płyty główne i większość komputerów.
    ./scripts/config --enable CONFIG_ATA
    ./scripts/config --enable CONFIG_SATA_AHCI
    ./scripts/config --enable CONFIG_ATA_PIIX
    # NVMe – współczesne dyski SSD.
    ./scripts/config --enable CONFIG_NVME_CORE
    ./scripts/config --enable CONFIG_BLK_DEV_NVME
    # VirtIO – QEMU/KVM/Proxmox. CONFIG_VIRTIO_BLK tworzy /dev/vda.
    ./scripts/config --enable CONFIG_VIRTIO
    ./scripts/config --enable CONFIG_VIRTIO_PCI
    ./scripts/config --enable CONFIG_VIRTIO_BLK
    ./scripts/config --enable CONFIG_VIRTIO_NET
    ./scripts/config --enable CONFIG_VIRTIO_CONSOLE
    # Systemy plików używane przez instalator, EFI i zamontowane partycje.
    ./scripts/config --enable CONFIG_EXT4_FS
    ./scripts/config --enable CONFIG_FAT_FS
    ./scripts/config --enable CONFIG_VFAT_FS
    ./scripts/config --enable CONFIG_ISO9660_FS
    ./scripts/config --enable CONFIG_SQUASHFS

    # === DRM: maszyny wirtualne + fallback ekranu ===
    # Bez tych opcji Xorg/modesetting nie ma czym sterować ekranu:
    #   • QEMU/KVM: domyślna karta "std" (bochs-drm), QXL (Spice), Cirrus;
    #     bez CONFIG_DRM_VIRTIO_GPU Xorg loguje "virtio_gpu: driver missing".
    #   • CONFIG_DRM_SIMPLEDRM: fallback z framebuffera EFI – ekran działa nawet
    #     gdy brak natywnego sterownika GPU (typowy „pada Xorg" na nieznanym sprzęcie).
    #   • VirtualBox (vboxvideo) i VMware (vmwgfx).
    #   • amdgpu: najpopularniejsze GPU desktopu (firmware jest w linux-firmware).
    ./scripts/config --enable CONFIG_DRM_VIRTIO_GPU
    ./scripts/config --module CONFIG_DRM_BOCHS
    ./scripts/config --module CONFIG_DRM_QXL
    ./scripts/config --module CONFIG_DRM_CIRRUS_QEMU
    ./scripts/config --module CONFIG_DRM_VMWGFX
    ./scripts/config --module CONFIG_DRM_VBOXVIDEO
    ./scripts/config --enable CONFIG_DRM_SIMPLEDRM
    # SYSFB_SIMPLEFB: zamienia framebuffer EFI (GOP) na platformowe
    # "simple-framebuffer" – bez tego simpledrm nie dostaje urządzenia.
    ./scripts/config --enable CONFIG_SYSFB_SIMPLEFB
    # amdgpu jako moduł (firmware jest w linux-firmware); FBDEV-EMULATION – żeby
    # xf86-video-fbdev też miał czym sterować (/dev/fb0); wymaga CONFIG_FB.
    ./scripts/config --module CONFIG_DRM_AMDGPU
    ./scripts/config --enable CONFIG_FB
    ./scripts/config --enable CONFIG_DRM_FBDEV_EMULATION

    # === Utwardzanie jądra (security) ===
    # KASLR (RANDOMIZE_BASE) już jest z defconfig; dokładamy resztę:
    #   • STACKPROTECTOR_STRONG/FORTIFY_SOURCE/HARDENED_USERCOPY – ochrona
    #     przed przepełnieniami bufora w jądrze/usermode.
    #   • SLAB_FREELIST_* + SHUFFLE_PAGE_ALLOCATOR + INIT_ON_* – utrudniają
    #     exploity wykorzystujące niezerowaną pamięć/kinetykę alokatora.
    #   • SECURITY_YAMA – kernel.yama.ptrace_scope (ogranicza ptrace).
    #   • SECURITY_DMESG_RESTRICT – kernel.dmesg_restrict.
    ./scripts/config --enable CONFIG_STACKPROTECTOR_STRONG
    ./scripts/config --enable CONFIG_FORTIFY_SOURCE
    ./scripts/config --enable CONFIG_HARDENED_USERCOPY
    ./scripts/config --enable CONFIG_SLAB_FREELIST_RANDOM
    ./scripts/config --enable CONFIG_SLAB_FREELIST_HARDENED
    ./scripts/config --enable CONFIG_SHUFFLE_PAGE_ALLOCATOR
    ./scripts/config --enable CONFIG_INIT_ON_ALLOC_DEFAULT_ON
    ./scripts/config --enable CONFIG_INIT_ON_FREE_DEFAULT_ON
    ./scripts/config --enable CONFIG_STRICT_KERNEL_RWX
    ./scripts/config --enable CONFIG_STRICT_MODULE_RWX
    ./scripts/config --enable CONFIG_SECURITY_YAMA
    ./scripts/config --enable CONFIG_SECURITY_DMESG_RESTRICT

    # Uzupelnij wszystkie brakujace opcje domyslynymi
    # === Systemy plików dla instalatora i systemu docelowego ===
    # Calamares oferuje ext4/xfs/btrfs – bez CONFIG_BTRFS_FS/CONFIG_XFS_FS
    # mount -t btrfs/xfs pada („unknown filesystem type”) i instalacja wysypuje
    # się przy wyborze tych FS. =y (wbudowane) – root na btrfs/xfs musi wstać
    # zanim initramfs załaduje moduły.
    ./scripts/config --enable CONFIG_BTRFS_FS
    ./scripts/config --enable CONFIG_XFS_FS

    # === LUKS / device-mapper (szyfrowanie dysku przez Calamares) ===
    # =y (wbudowane), bo initramfs musi odblokować root ZANIM załaduje moduły.
    ./scripts/config --enable CONFIG_BLK_DEV_DM
    ./scripts/config --enable CONFIG_DM_CRYPT
    # Kryptografia używana przez LUKS2 (aes-xts-plain64 + sha256/sha512)
    ./scripts/config --enable CONFIG_CRYPTO_XTS
    ./scripts/config --enable CONFIG_CRYPTO_AES
    ./scripts/config --enable CONFIG_CRYPTO_SHA256
    ./scripts/config --enable CONFIG_CRYPTO_SHA512

    # Uzupelnij wszystkie brakujace opcje domyslynymi
    info "  ⚙ olddefconfig (uzupelnianie configu)..."
    make olddefconfig || error "Kernel olddefconfig failed"

    info "  Kompilacja jądra..."
    make $MAKEFLAGS bzImage || error "Kernel build failed"
    make $MAKEFLAGS modules || error "Kernel modules build failed"

    info "  Instalacja jądra..."
    make INSTALL_MOD_PATH="$PAGAN_ROOT" modules_install || error "Modules install failed"
    cp arch/x86/boot/bzImage "$PAGAN_ROOT/boot/vmlinuz-${KERNEL_VERSION}-pagan"
    cp System.map "$PAGAN_ROOT/boot/System.map-${KERNEL_VERSION}-pagan"
    cp .config "$PAGAN_ROOT/boot/config-${KERNEL_VERSION}-pagan"

    # Symlink
    ln -sf "vmlinuz-${KERNEL_VERSION}-pagan" "$PAGAN_ROOT/boot/vmlinuz-linux"
    ln -sf "System.map-${KERNEL_VERSION}-pagan" "$PAGAN_ROOT/boot/System.map"

    popd > /dev/null

    ok "Kernel ${KERNEL_VERSION} zbudowany!"
}

# ========================= WIRELESS-REGDB (regulatory.db dla WiFi) =========================
# cfg80211 z CONFIG_CFG80211_REQUIRE_SIGNED_REGDB=y (domyślne od 5.19) WYMAGA
# podpisanego regulatory.db w /usr/lib/firmware. Bez niego kernel loguje:
#   "Direct firmware load for regulatory.db failed with error -2"
#   "cfg80211: failed to load regulatory.db"
# a WiFi nie ma prawidłowego domyślnego kraju → problemy z kanałami 5 GHz.
install_wireless_regdb() {
    step "WIRELESS-REGDB (regulatory.db)"

    local fw_dir="$PAGAN_ROOT/usr/lib/firmware"
    mkdir -p "$fw_dir"

    # Już jest? (np. ręcznie dołożone) – nic nie rób
    if [[ -s "$fw_dir/regulatory.db" && -s "$fw_dir/regulatory.db.p7s" ]]; then
        ok "regulatory.db + .p7s już obecne"
        return 0
    fi

    # 1) Najszybsze: z hosta buildera
    local src
    for src in /usr/lib/firmware/regulatory.db /lib/firmware/regulatory.db; do
        if [[ -s "$src" && -s "${src}.p7s" ]]; then
            cp -a "$src" "${src}.p7s" "$fw_dir/" 2>/dev/null && {
                ok "regulatory.db + .p7s (z hosta: $src)"
                return 0
            }
        fi
    done

    # 2) Pobierz wireless-regdb z kernel.org (host-independently)
    local _ver="${WIRELESS_REGDB_VERSION:-2026.05.30}"
    local _url="https://www.kernel.org/pub/software/network/wireless-regdb/wireless-regdb-${_ver}.tar.xz"
    local _arch="$SRC_DIR/wireless-regdb-${_ver}.tar.xz"
    local _dir="$SRC_DIR/wireless-regdb-${_ver}"

    download_pkg "$_url" "$_arch" "SKIP"
    rm -rf "$_dir" && mkdir -p "$_dir"
    tar -xf "$_arch" -C "$_dir" --strip-components=1 2>/dev/null || {
        warn "Nie udało się rozpakować wireless-regdb – WiFi bez regulatory.db"
        return 0
    }
    if [[ -s "$_dir/regulatory.db" ]]; then
        install -Dm644 "$_dir/regulatory.db" "$fw_dir/regulatory.db"
        install -Dm644 "$_dir/regulatory.db.p7s" "$fw_dir/regulatory.db.p7s" 2>/dev/null || true
        ok "regulatory.db + .p7s zainstalowane (wireless-regdb-${_ver})"
        return 0
    fi

    warn "Nie udało się zdobyć regulatory.db – WiFi może nie działać (cfg80211)"
}

# ========================= PAGAN-WIFI (konsolowy asystent Wi-Fi, PL/EN) =========================
# Pomocnik dla LIVE/instalatora i systemu bez DE: wykrywa kartę Wi-Fi i łączy
# z siecią WPA/WPA2. Woli NetworkManager (nmcli – używa go też instalator GUI),
# a gdy go nie ma – wpa_supplicant + dhcpcd/udhcpc. Dwujęzyczny (PL/EN wg $LANG).
install_pagan_wifi() {
    step "PAGAN-WIFI (konsolowy asystent Wi-Fi)"
    cat > "$PAGAN_ROOT/usr/bin/pagan-wifi" <<'WIFIEOF'
#!/bin/bash
# pagan-wifi – połącz z siecią Wi-Fi (WPA/WPA2) – komunikaty PL/EN wg $LANG
# Użycie:  sudo pagan-wifi [SSID] [hasło]     (puste = zapyta)
set -u
is_pl() { [[ "${LANG:-C}" == pl* ]]; }
msg()   { if is_pl; then echo "$1"; else echo "$2"; fi; }

IFACE=$(ip -o link 2>/dev/null | sed -n 's/^[0-9]*: \(w[^:@]*\):.*/\1/p' | head -n1)
if [[ -z "$IFACE" ]]; then
    msg "[!] Nie znaleziono żadnego interfejsu Wi-Fi (wlan*/wlp*)." \
        "[!] No Wi-Fi interface found (wlan*/wlp*)."
    msg "    Sprawdź: lsusb | grep -i -E 'wireless|realtek|mediatek|atheros'" \
        "    Check:   lsusb | grep -i -E 'wireless|realtek|mediatek|atheros'"
    msg "    oraz:    dmesg | grep -i firmware   (brak firmware = karta niewidoczna)" \
        "    and:     dmesg | grep -i firmware   (missing firmware = card invisible)"
    exit 1
fi
msg "[*] Znaleziono interfejs: $IFACE" "[*] Interface found: $IFACE"

SSID="${1:-}"; PASS="${2:-}"
if [[ -z "$SSID" ]]; then
    read -r -p "$(msg 'SSID: ' 'SSID: ')" SSID
fi
if [[ -z "${2+x}" && -z "$PASS" ]]; then
    read -r -s -p "$(msg 'Hasło (puste = sieć otwarta): ' 'Password (empty = open network): ')" PASS
    echo ""
fi
[[ -n "$SSID" ]] || { msg "[!] Brak SSID." "[!] No SSID."; exit 1; }

ip link set "$IFACE" up 2>/dev/null

# 1) NetworkManager (jak instalator GUI)
if command -v nmcli >/dev/null 2>&1; then
    if [[ -n "$PASS" ]]; then
        nmcli dev wifi connect "$SSID" password "$PASS"
    else
        nmcli dev wifi connect "$SSID"
    fi
    if [[ $? -eq 0 ]]; then
        msg "[+] Połączono przez NetworkManager." "[+] Connected via NetworkManager."
        exit 0
    fi
    msg "[.] nmcli nie połączył – próbuję wpa_supplicant..." "[.] nmcli failed – trying wpa_supplicant..."
fi

# 2) wpa_supplicant (+ dhcp)
command -v wpa_supplicant >/dev/null 2>&1 || {
    msg "[!] Brak wpa_supplicant – doinstaluj: pag install wpa_supplicant iw" \
        "[!] wpa_supplicant missing – install: pag install wpa_supplicant iw"
    exit 1
}
_CFG=/tmp/pagan-wpa.conf
rm -f "$_CFG"
if [[ -n "$PASS" ]]; then
    wpa_passphrase "$SSID" "$PASS" > "$_CFG"
else
    printf 'network={\n    ssid="%s"\n    key_mgmt=NONE\n}\n' "$SSID" > "$_CFG"
fi
pkill -f "wpa_supplicant -B -i $IFACE" 2>/dev/null
wpa_supplicant -B -i "$IFACE" -c "$_CFG" 2>/dev/null
sleep 2

if command -v dhcpcd >/dev/null 2>&1; then
    dhcpcd "$IFACE"
elif command -v udhcpc >/dev/null 2>&1; then
    udhcpc -i "$IFACE" -q
elif command -v systemctl >/dev/null 2>&1; then
    systemctl restart systemd-networkd 2>/dev/null
fi
msg "[+] Gotowe. Test: ping -c2 1.1.1.1" "[+] Done. Test: ping -c2 1.1.1.1"
WIFIEOF
    chmod 755 "$PAGAN_ROOT/usr/bin/pagan-wifi"
    ok "pagan-wifi zainstalowany (/usr/bin/pagan-wifi, PL/EN)"
}

# ========================= STEROWNIKI NVIDIA (open-gpu-kernel-modules) =========================
# Buduje moduły NVIDIA z oficjalnego open-source repo NVIDIA
# (https://github.com/NVIDIA/open-gpu-kernel-modules) przeciw zbudowanemu kernelowi.
install_nvidia_drivers() {
    step "INSTALACJA STEROWNIKÓW NVIDIA (open-gpu-kernel-modules)"

    local nv_ver="${NVIDIA_VERSION:-main}"
    local nv_url="https://github.com/NVIDIA/open-gpu-kernel-modules/archive/refs/heads/${nv_ver}.tar.gz"
    local nv_arch="$SRC_DIR/nvidia-open-gpu-kernel-modules-${nv_ver}.tar.gz"
    local nv_dir="$SRC_DIR/build-nvidia-open"

    # Kernel źródła muszą być zbudowane (po build_kernel)
    if [[ ! -f "$SRC_DIR/linux-${KERNEL_VERSION}/.config" ]]; then
        warn "Brak zbudowanego kernela – pomijam NVIDIA (zbuduj kernel najpierw)"
        return 0
    fi

    download_pkg "$nv_url" "$nv_arch" "SKIP" || return 0

    rm -rf "$nv_dir"
    mkdir -p "$nv_dir"
    tar -xf "$nv_arch" -C "$nv_dir" --strip-components=1 2>/dev/null || { warn "Rozpakowanie NVIDIA nieudane"; return 0; }

    pushd "$nv_dir" > /dev/null

    info "  Budowa modułów NVIDIA (to potrwa)..."
    # Czyścimy flagi z build_blfs (LDFLAGS -Wl,-rpath-link,$PAGAN_ROOT/usr/lib trafia
    # w Kbuild do ld WPROST -> "ld: unrecognized option '-Wl,-rpath-link,...'")
    if ( unset LDFLAGS CFLAGS CXXFLAGS CPPFLAGS; \
         make -j"$PAGAN_JOBS" modules \
            SYSSRC="$SRC_DIR/linux-${KERNEL_VERSION}" \
            SYSOUT="$SRC_DIR/linux-${KERNEL_VERSION}" ) 2>/tmp/nvidia-build.log; then
        info "  Instalacja modułów NVIDIA do systemu..."
        # UWAGA na DESTDIR vs INSTALL_MOD_PATH: to modules_install z Kbuild, a on
        # używa INSTALL_MOD_PATH (DESTDIR jest ignorowany!). Wcześniej przekazywaliśmy
        # DESTDIR="$PAGAN_ROOT", więc moduły NVIDIA lądowały w /usr/lib/modules/...
        # HOSTA (i tam je widzieliśmy: /usr/lib/modules/7.2.5-pagan/), a w ISO nie
        # było ICH WCALE – na realnym PC z NVIDIA nie dało się uruchomić Xorga
        # akcelerowanego (brak nvidia.ko). DEPMOD=true, żeby nie ruszać modułów
        # hosta; depmod w rootfsie robi hook pakietu kernel / pagan-initramfs.
        ( unset LDFLAGS CFLAGS CXXFLAGS CPPFLAGS; \
          make modules_install \
            SYSSRC="$SRC_DIR/linux-${KERNEL_VERSION}" \
            SYSOUT="$SRC_DIR/linux-${KERNEL_VERSION}" \
            INSTALL_MOD_PATH="$PAGAN_ROOT/usr" \
            INSTALL_MOD_STRIP=1 \
            DEPMOD=true ) 2>/tmp/nvidia-install.log || \
            warn "NVIDIA modules_install nieudane"
        if [[ -n "$(find "$PAGAN_ROOT/usr/lib/modules/${KERNEL_VERSION}-pagan" -name 'nvidia.ko*' 2>/dev/null | head -n1)" ]]; then
            ok "NVIDIA open-gpu-kernel-modules zbudowane i zainstalowane (usr/lib/modules/${KERNEL_VERSION}-pagan)"

            # Same moduły w obrazie to za mało – nikt ich nie załaduje:
            #   * kernel NIE ma nouveau (CONFIG_DRM_NOUVEAU is not set),
            #   * w ISO nie ma sterownika userspace (DDX „nvidia”), który normalnie
            #     robi modprobe,
            #   * bez reguł udev nie powstają węzły /dev/nvidia*, /dev/nvidia-uvm…
            # Dokładamy więc: reguły udev (ten sam plik co pakiet `nvidia` z repo –
            # jedna definicja, bez konfliktów przy późniejszej instalacji) oraz regułę
            # AUTOLOADU dla kart NVIDIA (PCI vendor 10de, klasa VGA/3D).
            # Efekt: karta dostaje KMS przez nvidia-drm i pulpit działa na modesetting;
            # akceleracja GL dochodzi po zainstalowaniu pakietu `nvidia` z repo
            # (jego 20-nvidia.conf przełącza Xorg na DDX nvidia).
            local _nvrules="$SCRIPT_DIR/recipes/gui/nvidia/nvidia.rules"
            install -dm0755 "$PAGAN_ROOT/usr/lib/udev/rules.d"
            if [[ -f "$_nvrules" ]]; then
                install -Dm0644 "$_nvrules" "$PAGAN_ROOT/usr/lib/udev/rules.d/60-nvidia.rules"
            else
                warn "  brak $_nvrules – węzły /dev/nvidia* mogą się nie utworzyć"
            fi
            cat > "$PAGAN_ROOT/usr/lib/udev/rules.d/99-pagan-nvidia-autoload.rules" <<'NVEOF'
# PaganLinux: automatyczne załadowanie sterownika NVIDIA (open kernel modules)
# na kartach NVIDIA. Powód: kernel nie ma nouveau, a w obrazie (ISO/instalacja)
# nie ma DDX „nvidia”, który normalnie robi modprobe – bez tej reguły moduły
# nvidia.ko/nvidia-drm.ko leżą w /usr/lib/modules i nikt ich nie używa.
# modeset=1 → nvidia-drm daje KMS (Xorg/modesetting działa bez userspace).
# WYŁĄCZENIE: usuń ten plik (lub `pag remove nvidia` po instalacji userspace).
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", RUN+="/usr/sbin/modprobe nvidia-drm modeset=1"
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", RUN+="/usr/sbin/modprobe nvidia-drm modeset=1"
NVEOF
            info "  NVIDIA: reguły udev (/dev/nvidia*) + autoload dla kart 10de"
        else
            warn "NVIDIA: brak nvidia.ko w rootfsie po instalacji – sprawdź /tmp/nvidia-install.log"
        fi
    else
        warn "NVIDIA modules build nieudane – pomijam (log: /tmp/nvidia-build.log)"
    fi

    popd > /dev/null
}

# ========================= INSTALACJA XFCE =========================
# Minimalny stos X dla instalatora (tryb BAZA – live bez środowiska graficznego).
# Calamares/GUI startujemy przez startx (profile.d/pagan-live.sh → .xinitrc),
# więc live musi mieć Xorg + xinit.
# Lista pakietów = sprawdzona warstwa X11 (te same wersje co stos Qt6/KF6).
install_x_installer() {
    step "INSTALACJA X11 (DLA INSTALATORA GUI – TRYB BAZA)"

    # Środowisko builda jak przy budowie Qt6/KF6: hostowe narzędzia pierwsze, ale
    # pkg-config/LD_LIBRARY_PATH muszą widzieć libki staged w $PAGAN_ROOT.
    export PATH="$TOOLS_DIR/bin:$PATH"
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig:$PAGAN_ROOT/usr/lib/x86_64-linux-gnu/pkgconfig:$PAGAN_ROOT/usr/lib64/pkgconfig"
    local _old_ld="${LD_LIBRARY_PATH:-}"
    export LD_LIBRARY_PATH="/usr/lib:$PAGAN_ROOT/usr/lib${_old_ld:+:$_old_ld}"

    local xorg_url="https://xorg.freedesktop.org/archive/individual"
    local x_autotools="./configure --prefix=/usr --disable-static && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    build_lfs_pkg "xorgproto" "2025.1" \
        "$xorg_url/proto/xorgproto-2025.1.tar.gz" "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
    build_lfs_pkg "libXau" "1.0.12" "$xorg_url/lib/libXau-1.0.12.tar.gz" "SKIP" "$x_autotools"
    build_lfs_pkg "libXdmcp" "1.1.5" "$xorg_url/lib/libXdmcp-1.1.5.tar.gz" "SKIP" "$x_autotools"
    build_lfs_pkg "libxcb" "1.17.0" "$xorg_url/lib/libxcb-1.17.0.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "libX11" "1.8.13" "$xorg_url/lib/libX11-1.8.13.tar.xz" "SKIP" "$x_autotools"

    # libXext / libXrender / libXss – Tk (python/tkinter) linkuje je, gdy configure
    # wykryje X na HOŚCIE builda. Bez nich w rootfs import tkinter w chrocie pada:
    # „ImportError: libXss.so.1: cannot open shared object file”. Budujemy je do
    # rootfs PRZED Tk, żeby runtime (chroot) miał komplet zależności libtk.
    build_lfs_pkg "libXext" "1.3.6" "$xorg_url/lib/libXext-1.3.6.tar.xz" "SKIP" "$x_autotools"
    # libXrender/libXss – runtime'owe zależności Tk (python/tkinter). Build ze źródeł
    # potyka się o GCC 16 (stary X.org: Glyph.c – „expected ';' before 'elt'”, błąd
    # istnieje też z -std=gnu17). Tk i tak linkował się przeciw HOSTOWYM libkom,
    # a host ma ten sam glibc (2.44/x86_64) – więc kopiujemy działające biblioteki
    # z hosta do rootfs (SONAME libXrender.so.1 / libXss.so.1 wystarcza runtime'owo).
    for _xlib in libXrender.so.1 libXss.so.1; do
        if [[ ! -e "$PAGAN_ROOT/usr/lib/$_xlib" && -e "/usr/lib/$_xlib" ]]; then
            cp -a /usr/lib/${_xlib}* "$PAGAN_ROOT/usr/lib/" 2>/dev/null || true
            [[ -e "$PAGAN_ROOT/usr/lib/$_xlib" ]] && info "  ✓ $_xlib (skopiowane z hosta)"
        fi
        if [[ ! -e "$PAGAN_ROOT/usr/lib/$_xlib" ]]; then
            warn "  ⚠ brak $_xlib w rootfs i na hoście – tkinter może nie zadziałać"
        fi
    done

    # libXft + PRZEBUDOWA Tk z obsługą Xft – bez tego Tk (python/tkinter) używa
    # X core fontów i nie ma polskich znaków (ą/ę/ł/ż): fonty DejaVu (TTF) działają
    # tylko przez Xft/fontconfig. Bazowy Tk budował się przed X11 (configure: xft=no);
    # tu mamy już libX11, więc dorabiamy libXft i przebudowujemy Tk (klucz -xft2 = nowy wpis resume).
    build_lfs_pkg "libXft" "2.3.9" "$xorg_url/lib/libXft-2.3.9.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "tk" "8.6.18-xft2" "https://downloads.sourceforge.net/tcl/tk8.6.18-src.tar.gz" "SKIP" \
        "cd unix && ./configure --prefix=/usr --enable-xft && make && make install DESTDIR=\$PAGAN_ROOT"
    if [[ -x "$PAGAN_ROOT/usr/bin/python3" ]]; then
        if chroot "$PAGAN_ROOT" /usr/bin/python3 -c 'import tkinter' >/dev/null 2>&1; then
            info "  ✓ tkinter import OK (Tk z Xft)"
        else
            warn "  ⚠ tkinter nie importuje się w chrocie – sprawdź ldd $PAGAN_ROOT/usr/lib/libtk8.6.so (brak libek X w rootfs?)"
        fi
    fi

    build_lfs_pkg "libxkbfile" "1.1.3" "$xorg_url/lib/libxkbfile-1.1.3.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xkbcomp" "1.4.7" "$xorg_url/app/xkbcomp-1.4.7.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xauth" "1.1.3" "$xorg_url/app/xauth-1.1.3.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xinit" "1.4.2" "$xorg_url/app/xinit-1.4.2.tar.xz" "SKIP" \
        "./configure --prefix=/usr --with-xkbdefaultdir=/usr/share/X11/xkb && make && make install DESTDIR=\$PAGAN_ROOT"
    # xkeyboard-config: instalacja chce /usr/share/X11/xkb jako SYMLINK do
    # xkeyboard-config-2/xkb – po przerwanej przebudowie zostaje zwykły katalog
    # → „Destination '.../X11/xkb' already exists and is not a symlink”.
    # Analogicznie usuwamy realny plik pkgconfig/xkeyboard-config.pc – mezony robi
    # z niego symlink do xkeyboard-config-2.pc (stary „Fix .pc” potrafił go zniszczyć).
    rm -rf "$PAGAN_ROOT/usr/share/X11/xkb"
    rm -f "$PAGAN_ROOT/usr/share/pkgconfig/xkeyboard-config.pc"
    build_lfs_pkg "xkeyboard-config" "2.48" \
        "$xorg_url/data/xkeyboard-config/xkeyboard-config-2.48.tar.xz" "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
    build_lfs_pkg "xorg-server" "21.1.24" \
        "$xorg_url/xserver/xorg-server-21.1.24.tar.xz" "SKIP" \
        "./configure --prefix=/usr --enable-glamor --enable-dri3 --enable-xcsecurity && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    build_lfs_pkg "xf86-input-libinput" "1.5.0" \
        "$xorg_url/driver/xf86-input-libinput-1.5.0.tar.xz" "SKIP" \
        "./configure --prefix=/usr --with-sdkdir=/usr/include/xorg && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    ok "X11 dla instalatora GUI zainstalowane (xorg-server + xinit)"
}

# Cache motywów ikon dla wszystkich motywów z index.theme w rootfs. GTK na świeżo
# zamontowanym squashfs (overlay) potrafi nie znaleźć ikon bez icon-theme.cache.
_refresh_icon_caches() {
    local _t _dir _rc
    for _t in "$PAGAN_ROOT"/usr/share/icons/*/; do
        [ -d "$_t" ] || continue
        [ -f "$_t/index.theme" ] || continue
        _dir="$(basename "$_t")"
        local _err
        _rc=0
        _err=$(chroot "$PAGAN_ROOT" env -u LD_LIBRARY_PATH /usr/bin/gtk-update-icon-cache -f -q \
            "/usr/share/icons/$_dir" 2>&1) || _rc=1
        if [[ $_rc -eq 0 ]]; then
            info "  ✓ icon-theme.cache: $_dir"
        else
            warn "  ⚠ gtk-update-icon-cache nie zadziałało dla $_dir${_err%%$'\n'*}"
        fi
    done
}

# Włącza usługi systemowe potrzebne pulpitowi. Symlinki piszemy wprost –
# `systemctl enable` w chroot bez PID 1 potrafi nie zadziałać (ten sam wzorzec,
# co przy polkit/NetworkManagerze). Idempotentne.
_ensure_desktop_units() {
    local _us="$PAGAN_ROOT/usr/lib/systemd/system"
    local _w="$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants"
    local _u
    mkdir -p "$_w"
    # upower.service  → xfce4-power-manager: stan baterii/zasilania (bez niego
    #                   loguje „up_client_get_devices failed: The name
    #                   org.freedesktop.UPower was not provided by any .service
    #                   files”). UWAGA: to NIE usuwa ostrzeżenia Calamaresa
    #                   „Could not inhibit sleep” – ono dotyczy starszego
    #                   org.freedesktop.PowerManagement.Inhibit, które zapewnia
    #                   menedżer zasilania SESJI (xfce4-power-manager).
    # udisks2.service → montowanie nośników (backend menedżera nośników)
    for _u in upower.service udisks2.service; do
        [[ -f "$_us/$_u" ]] || continue
        ln -sfn "/usr/lib/systemd/system/$_u" "$_w/$_u"
    done
}

# Dodatki pulpitu z repo PAG + naprawa bibliotek z rozjechanym ABI.
#
# To nie są zależności BUDOWY, tylko braki W OBRAZIE, które wyszły dopiero
# w sesji live (log serial z QEMU). Każdy wpis ma konkretny dowód z tego logu:
#
#  * xorg-xrdb     – /etc/xdg/xfce4/xinitrc:80 „xrdb: command not found”
#                    (XFCE ładuje Xresources; bez xrdb część ustawień X nie działa)
#  * upower        – xfce4-power-manager: „up_client_get_devices failed:
#                    The name org.freedesktop.UPower was not provided by any
#                    .service files” (stan baterii/zasilania)
#  * libnotify     – dostarcza `notify-send` (skrypty, instalator)
#  * xfce4-notifyd – bez niego libnotify kończy „Failed to connect to proxy”
#                    i żadne powiadomienie nie dociera do użytkownika
#  * udisks2       – warstwa montowania nośników (backend menedżera nośników)
#
# Dodatkowo NAPRAWA ABI: w rootfsie potrafią zostać biblioteki z wcześniejszych
# przebiegów (resume), zbudowane w innym środowisku niż reszta obrazu:
#   * libgsf-1.so.114  → brak xmlNanoHTTPClose  → „Failed to load plugin
#     tumbler-odf-thumbnailer.so” (libxml2 2.15 nie eksportuje już nanohttp)
#   * libavcodec.so.63 → brak rsvg_handle_render_document → „Failed to load plugin
#     tumbler-ffmpeg-thumbnailer.so” (librsvg 2.40.21 nie ma API 2.46+)
# Wersje z repo PAG są spójne z tym obrazem (zweryfikowane `nm -D`), więc
# wymuszamy reinstalację (`pag install -f`). Warunek zostaje na stałe – gdyby
# rozjazd wrócił, build naprawi się sam.
install_desktop_extras() {
    step "DODATKI PULPITU + NAPRAWA ABI (repo PAG)"
    if [[ ! -x "$PAGAN_ROOT/usr/bin/pag" ]]; then
        warn "  brak /usr/bin/pag w rootfs – pomijam dodatki pulpitu"
        return 0
    fi

    local _p _ok=0 _fail=0
    local -a _want=()
    # Ogólne (każde DE na X11):
    [[ -x "$PAGAN_ROOT/usr/bin/xrdb" ]]          || _want+=(xorg-xrdb)
    [[ -x "$PAGAN_ROOT/usr/bin/upower" ]]        || _want+=(upower)
    [[ -x "$PAGAN_ROOT/usr/bin/notify-send" ]]   || _want+=(libnotify)
    [[ -x "$PAGAN_ROOT/usr/bin/udisksctl" ]]     || _want+=(udisks2)
    # Demon powiadomień tylko dla XFCE – Deepin/Budgie mają własne, a obecność
    # xfce4-notifyd (autostart/aktywacja D-Bus) mogłaby im podebrać nazwę
    # org.freedesktop.Notifications na magistrali.
    # UWAGA: pakiet z repo instaluje demona w libexecdir – /usr/lib/xfce4/notifyd/
    # xfce4-notifyd – a w /usr/bin jest tylko xfce4-notifyd-config. Sprawdzamy oba
    # miejsca (wcześniej sentinel na /usr/bin/xfce4-notifyd dawał fałszywy alarm
    # „brak powiadomień” mimo poprawnie zainstalowanego pakietu).
    if [[ "${PAGAN_DE,,}" == "xfce" && \
          ! -x "$PAGAN_ROOT/usr/bin/xfce4-notifyd" && \
          ! -x "$PAGAN_ROOT/usr/lib/xfce4/notifyd/xfce4-notifyd" ]]; then
        _want+=(xfce4-notifyd)
    fi

    for _p in "${_want[@]:-}"; do
        [[ -n "$_p" ]] || continue
        if _pag_install "$_p"; then
            ok "  ✓ z repo: $_p"; ((_ok++)) || true
        else
            warn "  ✗ nie udało się doinstalować: $_p (sprawdź: pag search $_p)"
            ((_fail++)) || true
        fi
    done
    if [[ ${#_want[@]} -eq 0 ]]; then
        info "  wszystkie dodatki pulpitu już są w rootfs"
    fi

    # Naprawa rozjechanego ABI (szczegóły w komentarzu nad funkcją).
    local _abi=0
    if _lib_missing_symbol "/usr/lib/libgsf-1.so.114*" "xmlNanoHTTPClose"; then
        warn "  libgsf: rozjazd ABI (brak xmlNanoHTTPClose) – reinstalacja z repo"
        _pag_install libgsf -f && ((_abi++)) || true
    fi
    if _lib_missing_symbol "/usr/lib/libavcodec.so.63*" "rsvg_handle_render_document"; then
        warn "  libavcodec: rozjazd ABI (brak rsvg_handle_render_document) – reinstalacja z repo"
        _pag_install ffmpeg -f && ((_abi++)) || true
    fi
    if [[ $_abi -gt 0 ]]; then
        ok "  naprawiono biblioteki z rozjechanym ABI: $_abi (tumbler: ODF + ffmpeg)"
    fi

    _ensure_desktop_units

    if [[ $_fail -eq 0 ]]; then
        ok "  dodatki pulpitu gotowe (doinstalowano: $_ok)"
    fi
}

# Uzupełnienie TOOLCHAINU DOCELOWEGO o binutils.
#
# DLACZEGO: ISO buduje gcc (LFS rozdz. 6), ale binutils wchodzi tylko jako „pass1”
# do /tools – w /usr/bin NIE MA `ld`, `as`, `objcopy`, `nm`, `readelf`. Efekt:
# na zainstalowanym systemie nie da się nic skompilować, a tego wymaga DKMS
# (pakiet `nvidia-dkms` buduje moduły NVIDIA u użytkownika) i każda budowa ze
# źródeł – `pag install nvidia-dkms` kończyłby się błędem linkowania (brak ld).
#
# Repo PAG ma binutils 2.47 (w środku ld/as/objcopy/nm/readelf; wymaga maks.
# GLIBC_2.38, więc działa na glibc 2.44 z tego obrazu – sprawdzone objdump -T).
# DOCELOWO: zbudować binutils natywnie w obrazie (jak gcc) i wyłączyć ten krok.
install_target_toolchain_extras() {
    step "TOOLCHAIN DOCELOWY (binutils)"
    if [[ -x "$PAGAN_ROOT/usr/bin/ld" ]]; then
        info "  binutils już w obrazie (ld obecny)"
        return 0
    fi
    if _pag_install binutils; then
        ok "  ✓ binutils z repo (ld/as/objcopy) – DKMS i budowa ze źródeł zadziałają"
    else
        warn "  ✗ binutils nie doinstalowane – DKMS na docelowym systemie nie zadziała (brak linkera ld)"
    fi
}

install_xfce() {
    step "INSTALACJA XFCE"

    export PATH="$TOOLS_DIR/bin:$PATH"
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"
    # Target libs first for rpath-link: host pangoft2 is ABI-newer than target
    # pango, causing symbol mismatch when host pangoft2 resolves against target pango.
    export LDFLAGS="-L$PAGAN_ROOT/usr/lib -L/usr/lib -Wl,-rpath-link,$PAGAN_ROOT/usr/lib -Wl,-rpath-link,/usr/lib"
    # Hostowe binarki budowane w trakcie XFCE (g-ir-scanner/introspection itp.) linkują
    # się z libkami ROOTFS (np. libxklavier.so.16) i MUSZĄ je znaleźć w runtime – bez
    # tego pada: „error while loading shared libraries: libxklavier.so.16”. Kolejność
    # HOST-PIERWSZY (jak przy budowie Qt6/KF6): hostowe libc/libm wygrywają, brakujące targetowe
    # libki dobierane są z rootfs.
    local _old_ld="${LD_LIBRARY_PATH:-}"
    export LD_LIBRARY_PATH="/usr/lib:$PAGAN_ROOT/usr/lib${_old_ld:+:$_old_ld}"

    # Xorg
    # UWAGA: receptura MUSI mieć pełny „make install DESTDIR=…”, inaczej
    # build_lfs_pkg sam odtwarza „make && make install” na HOŚCIE (prefix=/usr)
    # i nadpisuje pliki systemu builda zamiast rootfs.
    build_lfs_pkg "xorg-server" "21.1.13" \
        "https://www.x.org/releases/individual/xserver/xorg-server-21.1.13.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr --enable-glamor --enable-dri3 --enable-xcsecurity --disable-systemd-logind && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # Klawiatura X11: xkbcomp + xkeyboard-config (+ setxkbmap). Bez nich X serwer
    # pada przy starcie: „/usr/bin/xkbcomp: No such file … Failed to activate
    # virtual core keyboard” (Fatal server error).
    local xorg_url="https://xorg.freedesktop.org/archive/individual"
    build_lfs_pkg "xkbcomp" "1.4.7" \
        "$xorg_url/app/xkbcomp-1.4.7.tar.xz" "SKIP" \
        "./configure --prefix=/usr && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    build_lfs_pkg "setxkbmap" "1.3.4" \
        "$xorg_url/app/setxkbmap-1.3.4.tar.xz" "SKIP" \
        "./configure --prefix=/usr && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    # xkeyboard-config: po przerwanej przebudowie /usr/share/X11/xkb bywa zwykłym
    # katalogiem, a meson-install chce tam SYMLINK → czyścimy przed instalacją.
    # (także realny pkgconfig/xkeyboard-config.pc – ma być symlinkiem).
    rm -rf "$PAGAN_ROOT/usr/share/X11/xkb"
    rm -f "$PAGAN_ROOT/usr/share/pkgconfig/xkeyboard-config.pc"
    build_lfs_pkg "xkeyboard-config" "2.48" \
        "$xorg_url/data/xkeyboard-config/xkeyboard-config-2.48.tar.xz" "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
    # startx/xinit + xauth – bez nich profile.d/pagan-live.sh nie ma czym
    # podnieść X w live („startx” nie istnieje) i live spada do TTY.
    build_lfs_pkg "xauth" "1.1.3" \
        "$xorg_url/app/xauth-1.1.3.tar.xz" "SKIP" \
        "./configure --prefix=/usr && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    build_lfs_pkg "xinit" "1.4.2" \
        "$xorg_url/app/xinit-1.4.2.tar.xz" "SKIP" \
        "./configure --prefix=/usr --with-xkbdefaultdir=/usr/share/X11/xkb && make && make install DESTDIR=\$PAGAN_ROOT"

    # iceauth – wymagany przez ICE w sesji X11 (xfce4-session/xfwm4 bez niego:
    # „/usr/bin/iceauth: No such file” → „Authentication Rejected” i brak sesji).
    build_lfs_pkg "iceauth" "1.0.11" \
        "$xorg_url/app/iceauth-1.0.11.tar.xz" "SKIP" \
        "./configure --prefix=/usr && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # Xorg drivers
    # xf86-input-libinput instaluje libinput-properties.h do $(sdkdir). Domyślnie
    # sdkdir bierze z xorg-server.pc, a nasza pętla .pc ma prefix="$PAGAN_ROOT/usr",
    # więc sdkdir byłby już z rootfs → DESTDIR doklejałby go DRUGI raz
    # (/mnt/pagan/mnt/pagan/...). Wymuszamy sdkdir bezwzględnie na /usr/include/xorg
    # (nagłówka wymaga xfce4-settings).
    build_lfs_pkg "xf86-input-libinput" "1.4.0" \
        "https://www.x.org/releases/individual/driver/xf86-input-libinput-1.4.0.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr --with-sdkdir=/usr/include/xorg && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    build_lfs_pkg "xf86-video-fbdev" "0.5.1" \
        "https://www.x.org/releases/individual/driver/xf86-video-fbdev-0.5.1.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # Cairo z BLFS budowany z -Dxlib=auto → bez X11 w rootfs nie powstał
    # cairo-xlib.h, a GDK-X11 (GTK poniżej) go wymaga. X11 już jest w rootfs,
    # więc przebudowujemy cairo z xlib=enabled (usuwamy marker resume).
    if [[ -f "$RESUME_FILE" ]]; then
        sed -i '/^cairo-1\.18\.4$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
    build_lfs_pkg "cairo" "1.18.4" \
        "https://www.cairographics.org/releases/cairo-1.18.4.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr -Dxlib=enabled -Dxcb=auto && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
    info "  ✓ Cairo przebudowany z backendem Xlib (cairo-xlib.h dostępny)"

    # GTK w BLFS budowany jest z -Dx11_backend=false → w rootfs NIE MA gdk/gdkx.h,
    # a libwnck i reszta XFCE (DE X11) tego wymagają. Teraz X11 jest już w rootfs
    # (xorg-server powyżej), więc przebudowujemy GTK z backendem X11 (usuwamy marker
    # resume, żeby build_lfs_pkg nie pominął przebudowy).
    if [[ -f "$RESUME_FILE" ]]; then
        sed -i '/^gtk-3\.24\.51$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
    build_lfs_pkg "gtk" "3.24.51" \
        "https://download.gnome.org/sources/gtk/3.24/gtk-3.24.51.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr -Dx11_backend=true -Dwayland_backend=true -Dintrospection=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
    info "  ✓ GTK3 przebudowany z backendem X11 (gdk/gdkx.h dostępny)"

    # SVG dla ikon: Adwaita (domyślny motyw GTK/XFCE) jest SVG-owy, a gdk-pixbuf
    # sam SVG nie dekoduje → xfce4-session ginie „Wnck:ERROR: default_icon_at_size:
    # assertion (base)”. Budujemy libcroco (zależność) + librsvg 2.40.21 (ostatnia
    # wersja C/autotools, bez toolchainu Rusta).
    build_lfs_pkg "libcroco" "0.6.13" \
        "https://download.gnome.org/sources/libcroco/0.6/libcroco-0.6.13.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr --disable-static && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    build_lfs_pkg "librsvg" "2.40.21" \
        "https://download.gnome.org/sources/librsvg/2.40/librsvg-2.40.21.tar.xz" \
        "SKIP" \
        "sed -i '/#include <libxml\\/SAX.h>/i #include <libxml/parser.h>' rsvg-private.h && sed -i 's/rsvg_xml_noerror (void \\*data, xmlErrorPtr error)/rsvg_xml_noerror (void *data, const xmlError *error)/' rsvg-css.c && ./configure --prefix=/usr --disable-introspection --disable-gtk-doc && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    # Loader gdk-pixbuf z librsvg potrafi wylądować w zduplikowanej ścieżce
    # (DESTDIR + prefix z .pc rootfs: /mnt/pagan/mnt/pagan/...) – przenosimy go.
    # Uwaga: sprawdzamy NAJPIERW właściwy katalog docelowy (przy resume/buildzie
    # sprzed poprawek loader mógł być już przeniesiony), a dopiero potem szukamy
    # zabłąkanego pliku gdziekolwiek w rootfs (nie tylko w $PAGAN_ROOT/mnt).
    local _stray_svg _gl_dir _have_svg=0
    for _gl_dir in "$PAGAN_ROOT"/usr/lib/gdk-pixbuf-2.0/*/loaders; do
        [[ -f "$_gl_dir/libpixbufloader-svg.so" ]] && _have_svg=1
    done
    if [[ $_have_svg -eq 0 ]]; then
        _stray_svg=$(find "$PAGAN_ROOT" -name 'libpixbufloader-svg.so' 2>/dev/null | head -n1)
        if [[ -n "$_stray_svg" ]]; then
            for _gl_dir in "$PAGAN_ROOT"/usr/lib/gdk-pixbuf-2.0/*/loaders; do
                [[ -d "$_gl_dir" ]] && cp -a "$_stray_svg" "$_gl_dir/libpixbufloader-svg.so"
            done
            _have_svg=1
        fi
    fi
    if [[ $_have_svg -eq 1 ]]; then
        info "  ✓ librsvg: loader SVG w gdk-pixbuf (libpixbufloader-svg.so)"
    else
        warn "  ⚠ librsvg: brak loadera SVG w gdk-pixbuf (SVG/wektory nie będą renderowane)"
    fi
    # UWAGA: NIE kasujemy tu $PAGAN_ROOT/mnt. Wcześniej było tu `rm -rf`
    # „na śmieci po librsvg”, ale w tym momencie w zagnieżdżonym rootfsie leżą
    # już pliki WIELU innych pakietów (m.in. NetworkManager, bluez, pipewire) –
    # kasowanie zabierało je bezpowrotnie i to właśnie dlatego live nie miał
    # sieci. Scalanie (idempotentne) robi teraz _merge_nested_rootfs.
    _merge_nested_rootfs

    # === Ikony pulpitu: komplet loaderów gdk-pixbuf + cache =====================
    # Objaw: xfce4-session ginie z SIGABRT – „Wnck:ERROR: …default_icon_at_size:
    # assertion failed (base)”, a panel/xfwm4 ostrzegają „Could not find the icon
    # '…'. The 'hicolor' theme was not found either”. Sedno problemu: w wariancie
    # XFCE rootfs NIE MA ŻADNEGO motywu ikon – brak hicolor-icon-theme (index.theme
    # dla /usr/share/icons/{hicolor,locolor}) i brak Adwaita (ikony stockowe GTK).
    # GTK bez index.theme nie traktuje katalogów /usr/share/icons jako motywu (pliki
    # ikon XFCE fizycznie tam są, ale są „niewidoczne”), więc ikony panelu/xfwm4 się
    # nie ładują, a libwnck dostaje NULL i robi twardy assert → SIGABRT sesji.

    # hicolor-icon-theme – index.theme + katalogi kontekstów; dopiero z nim GTK
    # widzi hicolor (i locolor) jako motyw zapasowy i znajduje ikony org.xfce.*.
    build_lfs_pkg "hicolor-icon-theme" "0.18" \
        "https://icon-theme.freedesktop.org/releases/hicolor-icon-theme-0.18.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"

    # Adwaita – pełny motyw ikon (stock GTK/panel/xfwm4/libwnck). Uwaga: 47.0
    # buduje się mesonem i instaluje z DESTDIR (do rootfs, NIE na hosta).
    build_lfs_pkg "adwaita-icon-theme" "47.0" \
        "https://download.gnome.org/sources/adwaita-icon-theme/47/adwaita-icon-theme-47.0.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"

    # gdk-pixbuf: loadery png/jpeg są (od 2.42) WBUDOWANE w libgdk_pixbuf
    # („Builtin loaders: png jpeg”) – NIE istnieją jako libpixbufloader-png.so.
    # Heurystyka „brak .so = brak loadera” zawsze przebudowywała gdk-pixbuf bez
    # żadnego efektu; sprawdzamy realną dostępność: moduł .so LUB symbol
    # fill_vtable w bibliotece rootfs.
    local _gp_lib _gp_ok=1 _m
    _gp_lib="$(ls "$PAGAN_ROOT"/usr/lib/libgdk_pixbuf-2.0.so.0 2>/dev/null | head -n1)"
    for _m in png jpeg; do
        if ! ls "$PAGAN_ROOT"/usr/lib/gdk-pixbuf-2.0/*/loaders/libpixbufloader-$_m.so >/dev/null 2>&1; then
            if [[ -z "$_gp_lib" ]] || ! strings "$_gp_lib" 2>/dev/null | grep -q "_gdk_pixbuf__${_m}_fill_vtable"; then
                _gp_ok=0
                break
            fi
        fi
    done
    if [[ $_gp_ok -eq 0 ]]; then
        info "  Przebudowa gdk-pixbuf (brak loadera png/jpeg – ani moduł .so, ani wbudowanie)..."
        sed -i '/^gdk-pixbuf-2\.44\.5$/d' "$RESUME_FILE" 2>/dev/null || true
        build_lfs_pkg "gdk-pixbuf" "2.44.5" \
            "https://download.gnome.org/sources/gdk-pixbuf/2.44/gdk-pixbuf-2.44.5.tar.xz" \
            "SKIP" \
            "meson setup build --prefix=/usr -Dman=false -Dglycin=disabled -Dintrospection=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        info "  ✓ gdk-pixbuf przebudowany (png/jpeg loadery)"
    fi

    # Obcy loader SVG bywa skopiowany pod złą nazwą (libpixbufloader_svg.so), a
    # gdk-pixbuf-query-loaders wykrywa moduły tylko jako libpixbufloader-*.so →
    # poprawiamy nazwę (lub usuwamy duplikat) i generujemy loaders.cache.
    local _gl
    for _gl in "$PAGAN_ROOT"/usr/lib/gdk-pixbuf-2.0/*/loaders; do
        [ -d "$_gl" ] || continue
        if [ -e "$_gl/libpixbufloader_svg.so" ]; then
            if [ ! -e "$_gl/libpixbufloader-svg.so" ]; then
                mv "$_gl/libpixbufloader_svg.so" "$_gl/libpixbufloader-svg.so"
                info "  ✓ loader SVG: poprawiona nazwa modułu (myślnik)"
            else
                rm -f "$_gl/libpixbufloader_svg.so"
            fi
        fi
    done
    chroot "$PAGAN_ROOT" env -u LD_LIBRARY_PATH /usr/bin/gdk-pixbuf-query-loaders --update-cache 2>/dev/null || \
        warn "⚠ gdk-pixbuf-query-loaders --update-cache nie zadziałało (sprawdź loaders.cache)"

    # Pierwszy przebieg cache motywów (hicolor/Adwaita zainstalowane powyżej);
    # drugi przebieg wykonujemy na końcu install_xfce (po instalacji pakietów XFCE),
    # gdy w hicolor są już ikony konkretnych aplikacji.
    _refresh_icon_caches

    # Xfce dependencies (not in BLFS)
    # libwnck: test executables fail to link when host pangoft2 is newer than
    # target pango. Patch meson.build to skip test_progs (no --Dtests option).
    build_lfs_pkg "libwnck" "43.0" \
        "https://download.gnome.org/sources/libwnck/43/libwnck-43.0.tar.xz" \
        "SKIP" \
        "sed -i 's/progs + test_progs/[]/' libwnck/meson.build && meson setup build --prefix=/usr -Dgtk_doc=false -Dintrospection=disabled && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"

    build_lfs_pkg "startup-notification" "0.12" \
        "https://www.freedesktop.org/software/startup-notification/releases/startup-notification-0.12.tar.gz" \
        "SKIP" \
        "./configure --prefix=/usr && make && make install DESTDIR=\$PAGAN_ROOT"

    # libxklavier – wymagane przez xfce4-settings (keyboard-settings) i lightdm.
    # Budujemy PRZED rdzeniem XFCE: xfce4-settings kompiluje się z tym nagłówkiem.
    build_lfs_pkg "libxklavier" "5.4" \
        "https://people.freedesktop.org/~svu/libxklavier-5.4.tar.bz2" \
        "SKIP" \
        "./configure --prefix=/usr --disable-static && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # Katalog nagłówków libxklavier w rootfs bywa libxklavier-1.0 albo inny, a .pc
    # potrafi wskazać nieistniejącą ścieżkę (-I/usr/include/libxklavier-1.0) →
    # „fatal error: libxklavier/xklavier.h”. Faktyczną ścieżkę wykrywamy dynamicznie
    # i wstrzykujemy ROZWINIĘTĄ przez CPPFLAGS (bez $ w stringu receptury – escaping
    # łamie się w ewaluatorze poleceń). Używają jej: xfce4-settings, lightdm, greeter.
    local _xk_inc="$PAGAN_ROOT/usr/include"
    local _xk_h
    _xk_h=$(find "$PAGAN_ROOT/usr/include" -maxdepth 3 \
                -path '*/libxklavier/xklavier.h' 2>/dev/null | head -n1)
    if [[ -n "$_xk_h" ]]; then
        _xk_inc="${_xk_h%/libxklavier/xklavier.h}"
        info "  libxklavier.h w: $_xk_inc"
    else
        warn "  ⚠ nie znaleziono libxklavier/xklavier.h w rootfs (fallback: $_xk_inc)"
    fi

    # Xfce core – seria 4.20 (najnowsza; point-releasy potwierdzone na
    # archive.xfce.org 2026-09). Uwaga: część pakietów 4.20 przeszła na Meson,
    # dlatego receptura sama wybiera: configure → autotools, inaczej meson.
    # libxfce4windowing to nowa zależność 4.20 (xfwm4/xfce4-session) – budowana
    # pierwsza.
    # xfconf/exo/garcon/xfwm4/xfce4-appfinder: 4.20.0 = najnowsze w serii.
    local xfce_packages=(
        "libxfce4windowing:4.20.7:https://archive.xfce.org/src/xfce/libxfce4windowing/4.20/libxfce4windowing-4.20.7.tar.bz2"
        "libxfce4util:4.20.1:https://archive.xfce.org/src/xfce/libxfce4util/4.20/libxfce4util-4.20.1.tar.bz2"
        "xfconf:4.20.0:https://archive.xfce.org/src/xfce/xfconf/4.20/xfconf-4.20.0.tar.bz2"
        "libxfce4ui:4.20.2:https://archive.xfce.org/src/xfce/libxfce4ui/4.20/libxfce4ui-4.20.2.tar.bz2"
        "exo:4.20.0:https://archive.xfce.org/src/xfce/exo/4.20/exo-4.20.0.tar.bz2"
        "garcon:4.20.0:https://archive.xfce.org/src/xfce/garcon/4.20/garcon-4.20.0.tar.bz2"
        "xfce4-panel:4.20.8:https://archive.xfce.org/src/xfce/xfce4-panel/4.20/xfce4-panel-4.20.8.tar.bz2"
        "thunar:4.20.9:https://archive.xfce.org/src/xfce/thunar/4.20/thunar-4.20.9.tar.bz2"
        "xfce4-settings:4.20.5:https://archive.xfce.org/src/xfce/xfce4-settings/4.20/xfce4-settings-4.20.5.tar.bz2"
        "xfce4-session:4.20.4:https://archive.xfce.org/src/xfce/xfce4-session/4.20/xfce4-session-4.20.4.tar.bz2"
        "xfdesktop:4.20.2:https://archive.xfce.org/src/xfce/xfdesktop/4.20/xfdesktop-4.20.2.tar.bz2"
        "xfwm4:4.20.0:https://archive.xfce.org/src/xfce/xfwm4/4.20/xfwm4-4.20.0.tar.bz2"
        "xfce4-appfinder:4.20.0:https://archive.xfce.org/src/xfce/xfce4-appfinder/4.20/xfce4-appfinder-4.20.0.tar.bz2"
        "xfce4-terminal:1.1.5:https://archive.xfce.org/src/apps/xfce4-terminal/1.1/xfce4-terminal-1.1.5.tar.xz"
        "tumbler:4.20.2:https://archive.xfce.org/src/xfce/tumbler/4.20/tumbler-4.20.2.tar.bz2"
        "mousepad:0.6.5:https://archive.xfce.org/src/apps/mousepad/0.6/mousepad-0.6.5.tar.xz"
        "ristretto:0.13.4:https://archive.xfce.org/src/apps/ristretto/0.13/ristretto-0.13.4.tar.xz"
    )

    local xfce_recipe
    for pkg_info in "${xfce_packages[@]}"; do
        IFS=':' read -r name ver url <<< "$pkg_info"
        if [[ "$name" == "xfce4-settings" ]]; then
            # keyboard-settings kompiluje #include <libxklavier/xklavier.h>, a
            # mouse-settings <libinput-properties.h> (SDK Xorga: /usr/include/xorg)
            # – .pc potrafi dać nieistniejące ścieżki, więc dokładamy CPPFLAGS
            # z faktycznymi katalogami rootfs (jak lightdm).
            xfce_recipe="if [ -x ./configure ]; then CPPFLAGS='-I$_xk_inc -I$PAGAN_ROOT/usr/include/xorg' ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-introspection && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT; else meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install; fi"
        else
            xfce_recipe="if [ -x ./configure ]; then ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-introspection && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT; else meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install; fi"
        fi
        build_lfs_pkg "$name" "$ver" "$url" "SKIP" "$xfce_recipe"
    done

    # xfce4-power-manager (potrzebuje upower+libnotify — build best-effort bez nich)
    build_lfs_pkg "xfce4-power-manager" "4.20.1" \
        "https://archive.xfce.org/src/xfce/xfce4-power-manager/4.20/xfce4-power-manager-4.20.1.tar.bz2" \
        "SKIP" \
        "if [ -x ./configure ]; then ./configure --prefix=/usr --sysconfdir=/etc --disable-introspection --enable-polkit --disable-upower --disable-notifications 2>/dev/null && make \$MAKEFLAGS || true; make install DESTDIR=\$PAGAN_ROOT 2>/dev/null || true; else meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install; fi"

    # ── Menedżer schowka w panelu (xfce4-clipman-plugin) ───────────────────────
    # Historia schowka dostępna wprost z panelu – obok tacki systemowej (układ
    # panelu: _xfce_default_panel_layout). 1.7.0 buduje się wyłącznie mesonem;
    # zależności (libxfce4ui/libxfce4panel/gtk3/libX11) są już w rootfs.
    # Wyłączamy opcje opcjonalne: Wayland (live to X11), libXtst (wymaga libXi,
    # którego wariant XFCE nie buduje) i qrencode (kody QR dla wpisu schowka).
    # Best-effort: nieudana budowa wtyczki nie może przerwać całego builda –
    # panel zostanie wtedy bez menedżera schowka (wtyczka to dodatek, nie
    # warunek startu sesji).
    if ( build_lfs_pkg "xfce4-clipman-plugin" "1.7.0" \
            "https://archive.xfce.org/src/panel-plugins/xfce4-clipman-plugin/1.7/xfce4-clipman-plugin-1.7.0.tar.xz" \
            "SKIP" \
            "meson setup build --prefix=/usr -Dx11=enabled -Dwayland=disabled -Dxtst=disabled -Dqrencode=disabled -Dtests=false && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install" ) \
       && [[ -e "$PAGAN_ROOT/usr/lib/xfce4/panel/plugins/libclipman.so" ]]; then
        info "  ✓ clipman: wtyczka panelu gotowa (historia schowka)"
    else
        warn "  ✗ clipman: brak wtyczki w rootfs – panel zostanie bez menedżera schowka"
    fi

    # ── nm-applet (network-manager-applet) – ikona sieci w tacce systemowej ──
    # NetworkManager jest w rootfs od build_blfs; sam applet bierzemy z repo PAG:
    # ze źródeł ciągnąłby libnma → gcr-4 → gtk4 oraz libsecret, a wariant XFCE
    # nie ma gtk4. Best-effort (jak fallback Calamaresa): bez sieci/repo build
    # leci dalej, tylko bez ikony sieci w panelu. Ikona pokazuje się w tacce
    # systemowej (plugin „systray”), która jest w domyślnym układzie panelu.
    if [[ -x "$PAGAN_ROOT/usr/bin/nm-applet" ]]; then
        info "  ✓ nm-applet już w rootfs (ikona sieci w tacce systemowej)"
    elif _pag_install network-manager-applet && [[ -x "$PAGAN_ROOT/usr/bin/nm-applet" ]]; then
        info "  ✓ nm-applet z repo PAG (ikona sieci w tacce systemowej)"
    else
        warn "  ✗ nm-applet: brak w repo PAG – ikona sieci w panelu się nie pojawi (doinstaluj: pag install network-manager-applet)"
    fi

    # LightDM – jawne CPPFLAGS do nagłówków libxklavier (ten sam mechanizm co dla
    # xfce4-settings; _xk_inc wykryte po budowie libxklavier, przed rdzeniem XFCE).
    build_lfs_pkg "lightdm" "1.32.0" \
        "https://github.com/canonical/lightdm/releases/download/1.32.0/lightdm-1.32.0.tar.xz" \
        "SKIP" \
        "CPPFLAGS='-I$_xk_inc' ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-gtk-doc && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # xfce4-dev-tools: make greetera (i innych pakietów Xfce budowanych z tagu git)
    # woła xdt-csource (generowanie nagłówków z .ui/.css). Narzędzie buildowe –
    # instalujemy do $TOOLS_DIR (host, pierwszy w PATH), nie do rootfs.
    build_lfs_pkg "xfce4-dev-tools" "4.20.0" \
        "https://archive.xfce.org/src/xfce/xfce4-dev-tools/4.20/xfce4-dev-tools-4.20.0.tar.bz2" \
        "SKIP" \
        "if [ -x ./configure ]; then ./configure --prefix=$TOOLS_DIR && make \$MAKEFLAGS && make install; else meson setup build --prefix=$TOOLS_DIR && ninja -C build && ninja -C build install; fi"

    build_lfs_pkg "lightdm-gtk-greeter" "2.0.9" \
        "https://github.com/Xubuntu/lightdm-gtk-greeter/archive/refs/tags/lightdm-gtk-greeter-2.0.9.tar.gz" \
        "SKIP" \
        "autoreconf -fiv && CPPFLAGS='-I$_xk_inc' ./configure --prefix=/usr --sysconfdir=/etc && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # === Motyw graficzny (ładny wygląd XFCE) =================================
    # Papirus (ikony) + Nordic (motyw GTK + xfwm4). Nordic bierzemy jako GOTOWE,
    # PREBUILT archiwum z GitHub releases – zero kompilacji SCSS. Poprzednia droga
    # (sassc + arc-theme) była zawodna: release sassc 3.6.2 nie ma żadnych assetów.
    # Fallback: Adwaita-dark (tworzony niżej), a _configure_xfce wybiera motyw dostępny.
    if [[ "${PAGAN_XFCE_THEME:-1}" != "0" ]]; then
        (
            set +e
            # Ikony Papirus: archiwum z master zawiera gotowe Papirus/ i Papirus-Dark/.
            build_lfs_pkg "papirus-icon-theme" "master" \
                "https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/archive/refs/heads/master.tar.gz" \
                "SKIP" \
                "mkdir -p \$PAGAN_ROOT/usr/share/icons && cp -a Papirus Papirus-Dark \$PAGAN_ROOT/usr/share/icons/ && for _d in Papirus Papirus-Dark; do chroot \$PAGAN_ROOT env -u LD_LIBRARY_PATH /usr/bin/gtk-update-icon-cache -f -q /usr/share/icons/\$_d 2>/dev/null || true; done"
            # Nordic-darker: gotowy tarball (gtk-2/3/4 + xfwm4 + index.theme).
            build_lfs_pkg "nordic-darker-theme" "2.2.0" \
                "https://github.com/EliverLara/Nordic/releases/download/v2.2.0/Nordic-darker.tar.xz" \
                "SKIP" \
                "mkdir -p \$PAGAN_ROOT/usr/share/themes/Nordic-darker && cp -a . \$PAGAN_ROOT/usr/share/themes/Nordic-darker/"
            # Nordic: wariant jaśniejszy (alternatywa w ustawieniach XFCE).
            build_lfs_pkg "nordic-theme" "2.2.0" \
                "https://github.com/EliverLara/Nordic/releases/download/v2.2.0/Nordic.tar.xz" \
                "SKIP" \
                "mkdir -p \$PAGAN_ROOT/usr/share/themes/Nordic && cp -a . \$PAGAN_ROOT/usr/share/themes/Nordic/"
        ) || warn "Motyw: część pobrań nie powiodła się – zostaje fallback (Papirus-Dark/Adwaita-dark)"
    fi

    # Firefox ESR (prebuilt, ~90 MB). URL mozilli NIE MA nazwy pliku (redirect
    # ?product=…), więc pobieramy ręcznie do stałej nazwy. Ucięty/niekompletny
    # plik (np. po przerwaniu pobierania) kasujemy i pobieramy ponownie.
    if _is_built "firefox-esr"; then
        ui_skip "firefox-esr"
        ((PKG_SKIPPED++)) || true
    else
        ((UI_PKG_N++)) || true
        local _ff_arc="$SRC_DIR/firefox-esr.tar.xz"
        # Usuń niekompletne archiwum zanim zdecydujemy o pobraniu.
        if [[ -s "$_ff_arc" ]] && ! tar -tJf "$_ff_arc" >/dev/null 2>&1; then
            warn "Firefox ESR: niekompletne archiwum – usuwam i pobieram ponownie"
            rm -f "$_ff_arc"
        fi
        if [[ ! -s "$_ff_arc" ]]; then
            info "  Pobieranie Firefox ESR (prebuilt, ~90 MB)..."
            curl -fL --connect-timeout 20 --max-time 900 --retry 3 --retry-delay 3 \
                -o "$_ff_arc" "https://download.mozilla.org/?product=firefox-esr-latest-ssl&os=linux64&lang=pl" \
                || { rm -f "$_ff_arc"; error "Nie udało się pobrać Firefox ESR (download.mozilla.org)"; }
        fi
        if ! tar -tJf "$_ff_arc" >/dev/null 2>&1; then
            rm -f "$_ff_arc"
            error "Firefox ESR: uszkodzone archiwum $_ff_arc"
        fi
        info "  Rozpakowywanie Firefox ESR do rootfs..."
        rm -rf "$PAGAN_ROOT/usr/lib/firefox"
        tar -xf "$_ff_arc" -C "$PAGAN_ROOT/usr/lib" || error "Firefox ESR: rozpakowanie nieudane"
        local _ff_dir
        _ff_dir=$(find "$PAGAN_ROOT/usr/lib" -maxdepth 1 -type d -name 'firefox*' | head -n1)
        if [[ -n "$_ff_dir" && "$_ff_dir" != "$PAGAN_ROOT/usr/lib/firefox" ]]; then
            mv "$_ff_dir" "$PAGAN_ROOT/usr/lib/firefox"
        fi
        if [[ ! -x "$PAGAN_ROOT/usr/lib/firefox/firefox" ]]; then
            error "Firefox ESR: brak binarki firefox po rozpakowaniu"
        fi
        ln -sf /usr/lib/firefox/firefox "$PAGAN_ROOT/usr/bin/"
        _mark_built "firefox-esr"
        ok "Firefox ESR zainstalowany"
    fi

    export LD_LIBRARY_PATH="$_old_ld"

    # Stary sddm (relikt wcześniejszych buildów w rootfs po resume) nie może
    # startować w live XFCE – wyłączamy (czysty build XFCE go nie ma).
    chroot "$PAGAN_ROOT" env -u LD_LIBRARY_PATH systemctl disable sddm 2>/dev/null || true

    # Odśwież cache motywów PONOWNIE – dopiero teraz wszystkie pakiety XFCE
    # zainstalowały swoje ikony do hicolor (panel, xfwm4, xfdesktop, thunar…).
    _refresh_icon_caches

    _configure_xfce
    ok "Xfce zainstalowane!"
}

_configure_xfce() {
    # LightDM
    mkdir -p "$PAGAN_ROOT/etc/lightdm"
    cat > "$PAGAN_ROOT/etc/lightdm/lightdm.conf" <<'EOF'
[LightDM]
greeter-session=lightdm-gtk-greeter
user-session=xfce
minimum-vt=7
xserver-command=X -background none -verbose

[Seat:*]
greeter-session=lightdm-gtk-greeter
user-session=xfce
EOF

    # .xinitrc
    mkdir -p "$PAGAN_ROOT/etc/skel"
    cat > "$PAGAN_ROOT/etc/skel/.xinitrc" <<'EOF'
#!/bin/sh
exec startxfce4
EOF
    chmod +x "$PAGAN_ROOT/etc/skel/.xinitrc"

    # ── Motyw: spójny ciemny wygląd (Adwaita-dark + Papirus-Dark) ──
    # GTK3 z LFS/BLFS instaluje Adwaitę, ale BEZ katalogu /usr/share/themes/
    # Adwaita-dark. Tworzymy wariant ciemny importujący zasób GTK – dzięki
    # temu nazwa "Adwaita-dark" działa w xfsettingsdialog i w settings.ini.
    mkdir -p "$PAGAN_ROOT/usr/share/themes/Adwaita-dark/gtk-3.0"
    cat > "$PAGAN_ROOT/usr/share/themes/Adwaita-dark/index.theme" <<'EOF'
[Desktop Entry]
Type=X-GNOME-Metatheme
Name=Adwaita-dark
Comment=Adwaita dark variant
GtkTheme=Adwaita-dark
MetacityTheme=Adwaita-dark
EOF
    cat > "$PAGAN_ROOT/usr/share/themes/Adwaita-dark/gtk-3.0/gtk.css" <<'EOF'
@import url("resource:///org/gtk/libgtk/theme/Adwaita/gtk-dark.css");
EOF

    # Motyw: preferuj Nordic-darker (pełny motyw GTK + xfwm4), potem Nordic,
    # potem (legacy) Arc-Dark, w ostateczności Adwaita-dark.
    local _gtk_theme=Adwaita-dark _wm_theme=Default _icon_theme=Papirus-Dark
    if [[ -d "$PAGAN_ROOT/usr/share/themes/Nordic-darker" ]]; then
        _gtk_theme="Nordic-darker"; _wm_theme="Nordic-darker"
    elif [[ -d "$PAGAN_ROOT/usr/share/themes/Nordic" ]]; then
        _gtk_theme="Nordic"; _wm_theme="Nordic"
    elif [[ -d "$PAGAN_ROOT/usr/share/themes/Arc-Dark" ]]; then
        _gtk_theme="Arc-Dark"; _wm_theme="Arc-Dark"
    fi
    [[ -d "$PAGAN_ROOT/usr/share/icons/Papirus-Dark" ]] || _icon_theme="Adwaita"

    # Systemowe domyślne xfconf (/etc/xdg) – działają też dla kont bez
    # własnego ~/.config/xfce4 (nowi użytkownicy z Calamares).
    mkdir -p "$PAGAN_ROOT/etc/xdg/xfce4/xfconf/xfce-perchannel-xml"
    cat > "$PAGAN_ROOT/etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xsettings" version="1.0">
  <property name="Net" type="empty">
    <property name="ThemeName" type="string" value="${_gtk_theme}"/>
    <property name="IconThemeName" type="string" value="${_icon_theme}"/>
  </property>
  <property name="Xft" type="empty">
    <property name="Antialias" type="int" value="1"/>
    <property name="Hinting" type="int" value="1"/>
    <property name="RGBA" type="string" value="rgb"/>
  </property>
</channel>
EOF
    cat > "$PAGAN_ROOT/etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfwm4.xml" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xfwm4" version="1.0">
  <property name="general" type="empty">
    <property name="theme" type="string" value="${_wm_theme}"/>
    <property name="title_font" type="string" value="Sans Bold 10"/>
    <property name="title_alignment" type="string" value="center"/>
  </property>
</channel>
EOF
    # Domyślny układ panelu: tacka systemowa (ikona nm-applet = NetworkManager)
    # + menedżer schowka (clipman) – patrz _xfce_default_panel_layout.
    _xfce_default_panel_layout

    # GTK3 – ten sam motyw dla aplikacji spoza xfconf. finalize_build
    # dopisuje ten plik tylko gdy nie istnieje, więc nasz wybór się utrzyma.
    mkdir -p "$PAGAN_ROOT/etc/gtk-3.0"
    if [[ ! -f "$PAGAN_ROOT/etc/gtk-3.0/settings.ini" ]]; then
        cat > "$PAGAN_ROOT/etc/gtk-3.0/settings.ini" <<EOF
[Settings]
gtk-theme-name=${_gtk_theme}
gtk-icon-theme-name=${_icon_theme}
gtk-font-name=Sans 10
EOF
    fi
    info "  ✓ Motyw XFCE: GTK=${_gtk_theme}, ikony=${_icon_theme}, okna=${_wm_theme}"

    # Użytkownik – domyślna powłoka: fish (gdy zbudowany), inaczej bash.
    local _user_shell=/bin/bash
    [[ -x "$PAGAN_ROOT/usr/bin/fish" ]] && _user_shell=/usr/bin/fish
    chroot "$PAGAN_ROOT" useradd -m -G wheel,audio,video,cdrom,plugdev -s "$_user_shell" pagan 2>/dev/null || true
    echo "pagan:changeme" | chroot "$PAGAN_ROOT" chpasswd 2>/dev/null || true; chroot "$PAGAN_ROOT" passwd -e pagan 2>/dev/null || true
}

# ── Domyślny układ panelu XFCE ────────────────────────────────────────
# /etc/xdg/xfce4/panel/default.xml to szablon, który xfconf kopiuje do
# ~/.config/xfce4/panel/xfce4-panel.xml przy pierwszym uruchomieniu panelu na
# koncie – dotyczy to LIVE oraz każdego nowego użytkownika (Calamares/skel).
#
# Układ = domyślny układ Xfce 4.20 + dwie zmiany:
#   • plugin-6  systray – tacka systemowa, w której pokazuje się ikona nm-applet
#                         (NetworkManager) oraz inne statusnotifiery,
#   • plugin-19 xfce4-clipman-plugin – menedżer schowka (klik → wybór wpisu),
#                         wstawiony do panelu-1 zaraz za tacką.
#
# configver czytamy z pliku instalowanego przez xfce4-panel (fallback 2 = seria
# 4.20): dzięki temu zmiana serii Xfce nie wymusza migracji naszego układu.
_xfce_default_panel_layout() {
    local _f="$PAGAN_ROOT/etc/xdg/xfce4/panel/default.xml" _ver
    if [[ ! -x "$PAGAN_ROOT/usr/bin/xfce4-panel" ]]; then
        info "  Pomijam układ panelu (brak xfce4-panel w rootfs)"
        return 0
    fi
    _ver=$(sed -n 's/.*name="configver" type="int" value="\([0-9]\{1,\}\)".*/\1/p' "$_f" 2>/dev/null | head -n1 || true)
    [[ -n "$_ver" ]] || _ver=2
    mkdir -p "$PAGAN_ROOT/etc/xdg/xfce4/panel"
    cat > "$_f" <<EOF
<?xml version="1.0" encoding="UTF-8"?>

<channel name="xfce4-panel" version="1.0">
  <property name="configver" type="int" value="${_ver}"/>
  <property name="panels" type="array">
    <value type="int" value="1"/>
    <value type="int" value="2"/>
    <property name="dark-mode" type="bool" value="true"/>
    <property name="panel-1" type="empty">
      <property name="position" type="string" value="p=6;x=0;y=0"/>
      <property name="length" type="uint" value="100"/>
      <property name="position-locked" type="bool" value="true"/>
      <property name="icon-size" type="uint" value="16"/>
      <property name="size" type="uint" value="26"/>
      <property name="plugin-ids" type="array">
        <value type="int" value="1"/>
        <value type="int" value="2"/>
        <value type="int" value="3"/>
        <value type="int" value="4"/>
        <value type="int" value="5"/>
        <value type="int" value="6"/>
        <value type="int" value="19"/>
        <value type="int" value="7"/>
        <value type="int" value="8"/>
        <value type="int" value="9"/>
        <value type="int" value="10"/>
      </property>
    </property>
    <property name="panel-2" type="empty">
      <property name="autohide-behavior" type="uint" value="1"/>
      <property name="position" type="string" value="p=10;x=0;y=0"/>
      <property name="length" type="uint" value="1"/>
      <property name="position-locked" type="bool" value="true"/>
      <property name="size" type="uint" value="48"/>
      <property name="plugin-ids" type="array">
        <value type="int" value="11"/>
        <value type="int" value="12"/>
        <value type="int" value="13"/>
        <value type="int" value="14"/>
        <value type="int" value="15"/>
        <value type="int" value="16"/>
        <value type="int" value="17"/>
        <value type="int" value="18"/>
      </property>
    </property>
  </property>
  <property name="plugins" type="empty">
    <property name="plugin-1" type="string" value="applicationsmenu"/>
    <property name="plugin-2" type="string" value="tasklist">
      <property name="grouping" type="uint" value="1"/>
    </property>
    <property name="plugin-3" type="string" value="separator">
      <property name="expand" type="bool" value="true"/>
      <property name="style" type="uint" value="0"/>
    </property>
    <property name="plugin-4" type="string" value="pager"/>
    <property name="plugin-5" type="string" value="separator">
      <property name="style" type="uint" value="0"/>
    </property>
    <property name="plugin-6" type="string" value="systray">
      <property name="square-icons" type="bool" value="true"/>
    </property>
    <property name="plugin-19" type="string" value="xfce4-clipman-plugin"/>
    <property name="plugin-7" type="string" value="separator">
      <property name="style" type="uint" value="0"/>
    </property>
    <property name="plugin-8" type="string" value="clock"/>
    <property name="plugin-9" type="string" value="separator">
      <property name="style" type="uint" value="0"/>
    </property>
    <property name="plugin-10" type="string" value="actions"/>
    <property name="plugin-11" type="string" value="showdesktop"/>
    <property name="plugin-12" type="string" value="separator"/>
    <property name="plugin-13" type="string" value="launcher">
      <property name="items" type="array">
        <value type="string" value="xfce4-terminal-emulator.desktop"/>
      </property>
    </property>
    <property name="plugin-14" type="string" value="launcher">
      <property name="items" type="array">
        <value type="string" value="xfce4-file-manager.desktop"/>
      </property>
    </property>
    <property name="plugin-15" type="string" value="launcher">
      <property name="items" type="array">
        <value type="string" value="xfce4-web-browser.desktop"/>
      </property>
    </property>
    <property name="plugin-16" type="string" value="launcher">
      <property name="items" type="array">
        <value type="string" value="xfce4-appfinder.desktop"/>
      </property>
    </property>
    <property name="plugin-17" type="string" value="separator"/>
    <property name="plugin-18" type="string" value="directorymenu"/>
  </property>
</channel>
EOF
    info "  ✓ panel: tacka systemowa (nm-applet/NetworkManager) + menedżer schowka (clipman)"
}

# Best-effort: instalacja pakietu z repo przez pag w chrocie targeta.
# PAG_INSECURE=1: w środowisku builda pomijamy weryfikację GPG repo (własne repo,
# fail-closed TOFU blokowałby instalację bez trusted.json). Zwraca 0 gdy OK.
_pag_install() {
    local _pkg="$1"
    shift || true   # dodatkowe flagi pag, np. -f (wymuś reinstalację z repo)
    [[ -x "$PAGAN_ROOT/usr/bin/pag" ]] || return 1
    local _had_r=0
    [[ -e "$PAGAN_ROOT/etc/resolv.conf" ]] && _had_r=1
    cp -L /etc/resolv.conf "$PAGAN_ROOT/etc/resolv.conf" 2>/dev/null || true
    # "$@" to flagi pag (np. -f/--force → cmd_install(upgrade=True)), dzięki którym
    # można wymusić reinstalację pakietu już obecnego w rootfsie – patrz
    # install_desktop_extras (naprawa bibliotek z rozjechanym ABI).
    chroot "$PAGAN_ROOT" env PAG_INSECURE=1 /usr/bin/pag install -y "$@" "$_pkg" >/dev/null 2>&1
    local _rc=$?
    [[ $_had_r -eq 0 ]] && rm -f "$PAGAN_ROOT/etc/resolv.conf"
    return $_rc
}

# true, gdy w bibliotece $1 (wzorzec glob, ścieżka względem $PAGAN_ROOT) jest
# NIEROZWIĄZANE odwołanie do symbolu $2.
#
# Tani detektor rozjazdu ABI: symbol jest wymagany przez kod, ale nie eksportuje
# go żadna biblioteka w obrazie → dlopen() pada i np. plugin tumblera się nie
# ładuje („undefined symbol”). Takie biblioteki w rootfsie pochodzą z wcześniejszych
# przebiegów (resume) i są zbudowane w innym środowisku niż reszta obrazu –
# wersja z repo PAG bywa spójna (weryfikowaliśmy to `nm -D`).
_lib_missing_symbol() {
    local _glob="$1" _sym="$2" _lib
    command -v nm >/dev/null 2>&1 || return 1
    _lib=$(ls -1 $PAGAN_ROOT$_glob 2>/dev/null | head -n1)
    [[ -n "$_lib" ]] || return 1
    nm -D --undefined-only "$_lib" 2>/dev/null | awk '{print $NF}' | grep -qx "$_sym"
}

# Instalacja pakietów z repo PAG wprost do rootfs (--repo-pkgs). Najpierw `pag
# update` (raz), potem `pag install -y` dla każdego pakietu. Raportuje każdy
# pakiet osobno; błędne nazwy nie przerywają reszty.
_pag_install_repo() {
    [[ -n "${REPO_PKGS:-}" ]] || return 0
    [[ -x "$PAGAN_ROOT/usr/bin/pag" ]] || { warn "pag nieobecny w rootfs – pomijam instalację z repo"; return 1; }
    [[ -n "${PAGAN_REPO_URL:-}" ]] || { warn "Brak PAGAN_REPO_URL – pomijam instalację z repo"; return 1; }

    step "INSTALACJA PAKIETÓW Z REPO PAG (--repo-pkgs): ${REPO_PKGS}"
    local _had_r=0 _synced=0 _rc=0 _pkg
    [[ -e "$PAGAN_ROOT/etc/resolv.conf" ]] && _had_r=1
    cp -L /etc/resolv.conf "$PAGAN_ROOT/etc/resolv.conf" 2>/dev/null || true

    if chroot "$PAGAN_ROOT" /usr/bin/env PAG_INSECURE=1 /usr/bin/pag update >/dev/null 2>&1; then
        _synced=1
    else
        warn "  ✗ pag update nieudane (repo: ${PAGAN_REPO_URL}) – sprawdź sieć/nazwę repo"
    fi

    for _pkg in $REPO_PKGS; do
        if [[ $_synced -eq 0 ]]; then
            warn "  ✗ pomijam: $_pkg (brak zsynchronizowanego repo)"
            _rc=1
            continue
        fi
        if chroot "$PAGAN_ROOT" /usr/bin/env PAG_INSECURE=1 /usr/bin/pag install -y "$_pkg" >/dev/null 2>&1; then
            info "  ✓ z repo: $_pkg"
        else
            warn "  ✗ pag install $_pkg nieudany (repo: ${PAGAN_REPO_URL}; spróbuj: pag search $_pkg)"
            _rc=1
        fi
    done

    [[ $_had_r -eq 0 ]] && rm -f "$PAGAN_ROOT/etc/resolv.conf"
    return $_rc
}

# ============================ INSTALACJA PAGANDE (FORK LABWC + STACK) ============================
# PaganDE: wlroots-pagan (fork wlroots, fundament; źródło: paganDE/compositor/wlroots) +
# labwc-pagan (nasz fork kompozytora; źródło: paganDE/compositor) +
# lekki stack Catppuccin Mocha (waybar, rofi, foot, mako, kanshi, swaybg,
# swaylock, thunar). Meta-pakiet `pagande` (recipes/de/pagande) niesie sesję
# wayland i domyślne konfiguracje; zależności budowane z receptur przez
# pagbuild i instalowane w chroot przez `pag install`.
install_pagande() {
    step "INSTALACJA PAGANDE (labwc-pagan + Catppuccin Mocha)"

    [[ -x "$PAGAN_ROOT/usr/bin/pag" ]] || { warn "pag nie zainstalowany – pomijam PaganDE"; return 0; }
    command -v pagbuild >/dev/null 2>&1 || { warn "pagbuild niedostępny na hoście – pomijam PaganDE"; return 0; }

    local de_src="$SCRIPT_DIR/recipes"
    [[ -d "$de_src" ]] || { warn "brak recipes/ (katalog: $de_src) – pomijam PaganDE"; return 0; }

    local out_dir="$PAGAN_ROOT/tmp/pagande-pkgs"
    rm -rf "$out_dir"
    mkdir -p "$out_dir"

    # Kolejność zależności: baza (wlroots-pagan itd.) → nasz fork labwc → meta-pakiet
    local -a pkgs=(libdisplay-info libliftoff hwdata seatd wlroots-pagan labwc-pagan pagande)
    local p recipe f sub
    for p in "${pkgs[@]}"; do
        recipe=""
        # pagande mieszka w de/, pozostałe w gui/ (część bazowa w utils/)
        for sub in de gui utils; do
            recipe="$de_src/$sub/$p/PAGBUILD.yaml"
            [[ -f "$recipe" ]] && break
            recipe=""
        done
        [[ -n "$recipe" ]] || { warn "brak receptury: $p – pomijam"; continue; }
        info "  pagbuild $p ..."
        if ! pagbuild "$recipe" --rootfs "$PAGAN_ROOT" --out "$out_dir"; then
            warn "  ✗ pagbuild $p nie powiódł się"
        fi
    done

    # Instalacja pakietów .pag w chroot (kolejność alfabetyczna = zależności najpierw)
    for f in "$out_dir"/*.pag; do
        [[ -e "$f" ]] || continue
        info "  pag install $(basename "$f")"
        chroot "$PAGAN_ROOT" env PAG_YES=1 /usr/bin/pag install "/tmp/pagande-pkgs/$(basename "$f")" \
            || warn "  ✗ pag install $(basename "$f") nie powiódł się"
    done

    ok "PaganDE zainstalowane! (labwc-pagan + stack Catppuccin Mocha)"
}

# ========================= INSTALACJA DEEPIN =========================
install_deepin() {
    step "INSTALACJA DEEPIN"

    export PATH="$TOOLS_DIR/bin:$PATH"
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"

    # Xorg
    build_lfs_pkg "xorg-server" "21.1.13" \
        "https://www.x.org/releases/individual/xserver/xorg-server-21.1.13.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr --enable-glamor --enable-dri3 --enable-xcsecurity && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # Qt6
    build_lfs_pkg "qtbase" "6.7.2" \
        "https://download.qt.io/official_releases/qt/6.7/6.7.2/submodules/qtbase-everywhere-src-6.7.2.tar.xz" \
        "SKIP" \
        "./configure -prefix /usr -opensource -confirm-license -release -shared -nomake tests -nomake examples"

    # Deepin
    local deepin_packages=(
        "dde-qt-dbus-factory:https://github.com/linuxdeepin/dde-qt-dbus-factory/releases/latest/download/dde-qt-dbus-factory.tar.xz"
        "dtkcore:https://github.com/linuxdeepin/dtkcore/releases/latest/download/dtkcore.tar.xz"
        "dtkgui:https://github.com/linuxdeepin/dtkgui/releases/latest/download/dtkgui.tar.xz"
        "dtkwidget:https://github.com/linuxdeepin/dtkwidget/releases/latest/download/dtkwidget.tar.xz"
        "dde-dock:https://github.com/linuxdeepin/dde-dock/releases/latest/download/dde-dock.tar.xz"
        "dde-launcher:https://github.com/linuxdeepin/dde-launcher/releases/latest/download/dde-launcher.tar.xz"
        "dde-control-center:https://github.com/linuxdeepin/dde-control-center/releases/latest/download/dde-control-center.tar.xz"
        "dde-session-shell:https://github.com/linuxdeepin/dde-session-shell/releases/latest/download/dde-session-shell.tar.xz"
        "deepin-terminal:https://github.com/linuxdeepin/deepin-terminal/releases/latest/download/deepin-terminal.tar.xz"
        "deepin-file-manager:https://github.com/linuxdeepin/deepin-file-manager/releases/latest/download/deepin-file-manager.tar.xz"
    )

    for pkg_info in "${deepin_packages[@]}"; do
        IFS=':' read -r name url <<< "$pkg_info"
        build_lfs_pkg "$name" "latest" "$url" "SKIP" \
            "cmake -B build -DCMAKE_INSTALL_PREFIX=/usr && cmake --build build && cmake --install build"
    done

    _configure_deepin
    ok "Deepin zainstalowane!"
}

_configure_deepin() {
    mkdir -p "$PAGAN_ROOT/etc/lightdm"
    cat > "$PAGAN_ROOT/etc/lightdm/lightdm.conf" <<'EOF'
[LightDM]
greeter-session=lightdm-deepin-greeter
user-session=deepin
minimum-vt=7

[Seat:*]
greeter-session=lightdm-deepin-greeter
user-session=deepin
EOF

    chroot "$PAGAN_ROOT" useradd -m -G wheel,audio,video,cdrom,plugdev -s /bin/bash pagan 2>/dev/null || true
    echo "pagan:changeme" | chroot "$PAGAN_ROOT" chpasswd 2>/dev/null || true; chroot "$PAGAN_ROOT" passwd -e pagan 2>/dev/null || true
}

# ========================= INSTALACJA BUDGIE =========================
install_budgie() {
    step "INSTALACJA BUDGIE DESKTOP"

    export PATH="$TOOLS_DIR/bin:$PATH"
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"

    # Xorg base
    build_lfs_pkg "xorg-server" "21.1.13" \
        "https://www.x.org/releases/individual/xserver/xorg-server-21.1.13.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr --enable-glamor --enable-dri3 --enable-xcsecurity && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    build_lfs_pkg "xf86-input-libinput" "1.4.0" \
        "https://www.x.org/releases/individual/driver/xf86-input-libinput-1.4.0.tar.xz" \
        "SKIP" "./configure --prefix=/usr --with-sdkdir=/usr/include/xorg && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    # GTK+3
    build_lfs_pkg "gtk3" "3.24.43" \
        "https://download.gnome.org/sources/gtk+/3.24/gtk+-3.24.43.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr --enable-x11-backend --enable-wayland-backend"

    # GNOME stack dependencies for Budgie
    build_lfs_pkg "gnome-menus" "3.36.0" \
        "https://download.gnome.org/sources/gnome-menus/3.36/gnome-menus-3.36.0.tar.xz" \
        "SKIP" "./configure --prefix=/usr"

    build_lfs_pkg "gnome-settings-daemon" "47.2" \
        "https://download.gnome.org/sources/gnome-settings-daemon/47/gnome-settings-daemon-47.2.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr -Dsystemd=false && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"

    build_lfs_pkg "gnome-control-center" "47.3" \
        "https://download.gnome.org/sources/gnome-control-center/47/gnome-control-center-47.3.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"

    build_lfs_pkg "gnome-session" "47.1" \
        "https://download.gnome.org/sources/gnome-session/47/gnome-session-47.1.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr -Dsystemd=false && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"

    build_lfs_pkg "mutter" "47.5" \
        "https://download.gnome.org/sources/mutter/47/mutter-47.5.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr -Dtests=false && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"

    # Budgie desktop
    build_lfs_pkg "budgie-desktop" "10.9.2" \
        "https://github.com/BuddiesOfBudgie/budgie-desktop/releases/download/v10.9.2/budgie-desktop-10.9.2.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr -Dwith-polkit=disabled && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"

    # LightDM + slick-greeter
    build_lfs_pkg "lightdm" "1.32.0" \
        "https://github.com/canonical/lightdm/releases/download/1.32.0/lightdm-1.32.0.tar.xz" \
        "SKIP" \
        "./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"

    build_lfs_pkg "slick-greeter" "2.0.8" \
        "https://github.com/linuxmint/slick-greeter/archive/refs/tags/2.0.8.tar.gz" \
        "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"

    # Aplikacje
    _install_firefox_binary
    _install_terminal_apps

    _configure_budgie
    ok "Budgie zainstalowane!"
}

_configure_budgie() {
    mkdir -p "$PAGAN_ROOT/etc/lightdm"
    cat > "$PAGAN_ROOT/etc/lightdm/lightdm.conf" <<'EOF'
[LightDM]
greeter-session=slick-greeter
user-session=budgie-desktop
minimum-vt=7

[Seat:*]
greeter-session=slick-greeter
user-session=budgie-desktop
EOF

    mkdir -p "$PAGAN_ROOT/etc/skel"
    cat > "$PAGAN_ROOT/etc/skel/.xinitrc" <<'EOF'
#!/bin/sh
exec budgie-desktop
EOF
    chmod +x "$PAGAN_ROOT/etc/skel/.xinitrc"

    chroot "$PAGAN_ROOT" useradd -m -G wheel,audio,video,cdrom,plugdev -s /bin/bash pagan 2>/dev/null || true
    echo "pagan:changeme" | chroot "$PAGAN_ROOT" chpasswd 2>/dev/null || true; chroot "$PAGAN_ROOT" passwd -e pagan 2>/dev/null || true
}

# ========================= WSPÓLNE APLIKACJE =========================
_install_firefox_binary() {
    local ff_ver="${FIREFOX_VERSION:-140.0.2}"
    local ff_url="https://ftp.mozilla.org/pub/firefox/releases/${ff_ver}/linux-$(uname -m)/en-US/firefox-${ff_ver}.tar.xz"
    local ff_arch="$SRC_DIR/firefox-${ff_ver}.tar.xz"
    info "  Instalowanie Firefox ${ff_ver}..."
    wget -q --timeout=120 -O "$ff_arch" "$ff_url" 2>/dev/null \
        || { warn "Firefox ${ff_ver}: pobieranie nieudane – pomijam"; return 0; }
    mkdir -p "$PAGAN_ROOT/usr/lib/firefox"
    tar -xf "$ff_arch" -C "$PAGAN_ROOT/usr/lib/firefox" --strip-components=1 2>/dev/null \
        || { warn "Firefox: rozpakowanie nieudane – pomijam"; return 0; }
    ln -sf /usr/lib/firefox/firefox "$PAGAN_ROOT/usr/bin/firefox" 2>/dev/null || true
    ok "Firefox ${ff_ver} zainstalowany"
}

_install_terminal_apps() {
    build_lfs_pkg "xfce4-terminal" "1.1.5" \
        "https://archive.xfce.org/src/apps/xfce4-terminal/1.1/xfce4-terminal-1.1.5.tar.xz" \
        "SKIP" \
        "if [ -x ./configure ]; then ./configure --prefix=/usr && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT; else meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install; fi" \
        2>/dev/null || true
    build_lfs_pkg "nano" "8.1" \
        "https://www.nano-editor.org/dist/v8/nano-8.1.tar.xz" \
        "SKIP" "./configure --prefix=/usr --enable-utf8" 2>/dev/null || true
}

# ========================= OSINFO-DB (PAGAN ROLLING) =========================
install_osinfo() {
    step "INSTALACJA OSINFO-DB (Pagan Linux OS database)"

    build_lfs_pkg "osinfo-db-tools" "1.12.0" \
        "https://releases.pagure.org/libosinfo/osinfo-db-tools-1.12.0.tar.xz" \
        "SKIP" \
        "meson setup build --prefix=/usr && DESTDIR=$PAGAN_ROOT ninja -C build install"

    build_lfs_pkg "osinfo-db" "20250606" \
        "https://releases.pagure.org/libosinfo/osinfo-db-20250606.tar.xz" \
        "SKIP" \
        "osinfo-db-import --root $PAGAN_ROOT --system $SRC_DIR/osinfo-db-20250606.tar.xz 2>/dev/null || true"

    # Pagan Linux Rolling XML
    mkdir -p "$PAGAN_ROOT/usr/share/osinfo/os/pagan"
    cat > "$PAGAN_ROOT/usr/share/osinfo/os/pagan/pagan-rolling.xml" <<'OSINFO'
<?xml version="1.0" encoding="UTF-8"?>
<libosinfo version="0.0.1">
  <os id="http://paganlinux.eu/pagan/rolling">
    <short-id>pagan-rolling</short-id>
    <name>Pagan Linux</name>
    <version>Rolling</version>
    <vendor>Pagan Linux Community</vendor>
    <family>linux</family>
    <distributor>paganlinux.eu</distributor>
    <release-status>rolling</release-status>
    <resources arch="x86_64">
      <minimum>
        <cpu>200000000</cpu>
        <ram>1073741824</ram>
        <storage>8589934592</storage>
      </minimum>
      <recommended>
        <cpu>1000000000</cpu>
        <ram>2147483648</ram>
        <storage>21474836480</storage>
      </recommended>
    </resources>
    <media arch="x86_64">
      <iso>
        <volume-id>PaganLinux</volume-id>
        <kernel>boot/vmlinuz-linux</kernel>
        <initrd>boot/initramfs.img</initrd>
      </iso>
    </media>
  </os>
</libosinfo>
OSINFO

    ok "osinfo-db + Pagan Rolling XML ✓"
}

# ========================= ZALEŻNOŚCI CALAMARES (Qt6 + KF6) =========================
# Calamares 3.3 + KPMcore wymagają Qt6 i KF6. Przy DE=XFCE tego stosu nie ma,
# więc budujemy go tutaj. Receptury/wersje sprawdzone (ta sama warstwa Qt6/KF6),
# bez Plasma/SDDM i z mniejszym podzbiorem modułów Qt6.
# Wywoływane w podpowłoce przez install_calamares – awaria nie ubija builda.
install_calamares_deps() {
    step "ZALEŻNOŚCI CALAMARES (Qt6 + KF6)"

    export PATH="$TOOLS_DIR/bin:$PATH"
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig:$PAGAN_ROOT/usr/lib/x86_64-linux-gnu/pkgconfig:$PAGAN_ROOT/usr/lib64/pkgconfig"
    export CMAKE_PREFIX_PATH="$PAGAN_ROOT/usr:/usr"

    local _old_ld="${LD_LIBRARY_PATH:-}"
    export LD_LIBRARY_PATH="/usr/lib:$PAGAN_ROOT/usr/lib${_old_ld:+:$_old_ld}"
    local _old_xdg="${XDG_DATA_DIRS:-}"
    export XDG_DATA_DIRS="$PAGAN_ROOT/usr/share${_old_xdg:+:$_old_xdg}"

    # ── Warstwa X11/XCB wymagana przez platformę xcb Qt6 (XFCE jej nie buduje –
    #    Xorg w install_xfce linkuje hostowe libki). Wersje sprawdzone;
    #    pomijamy Xorg/narzędzia, które XFCE już ma (inna wersja = clobber). ──
    local xorg_url="https://xorg.freedesktop.org/archive/individual"
    local x_autotools="./configure --prefix=/usr --disable-static && make \$MAKEFLAGS && make install DESTDIR=\$PAGAN_ROOT"
    build_lfs_pkg "xorgproto" "2025.1" \
        "$xorg_url/proto/xorgproto-2025.1.tar.gz" "SKIP" \
        "meson setup build --prefix=/usr && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
    build_lfs_pkg "libXau" "1.0.12" "$xorg_url/lib/libXau-1.0.12.tar.gz" "SKIP" "$x_autotools"
    build_lfs_pkg "libXdmcp" "1.1.5" "$xorg_url/lib/libXdmcp-1.1.5.tar.gz" "SKIP" "$x_autotools"
    build_lfs_pkg "libxcb" "1.17.0" "$xorg_url/lib/libxcb-1.17.0.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xcb-util" "0.4.1" "$xorg_url/xcb/xcb-util-0.4.1.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xcb-util-image" "0.4.1" "$xorg_url/xcb/xcb-util-image-0.4.1.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xcb-util-keysyms" "0.4.1" "$xorg_url/xcb/xcb-util-keysyms-0.4.1.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xcb-util-renderutil" "0.3.10" "$xorg_url/xcb/xcb-util-renderutil-0.3.10.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xcb-util-wm" "0.4.2" "$xorg_url/xcb/xcb-util-wm-0.4.2.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "xcb-util-cursor" "0.1.6" "$xorg_url/xcb/xcb-util-cursor-0.1.6.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "libX11" "1.8.13" "$xorg_url/lib/libX11-1.8.13.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "libXext" "1.3.6" "$xorg_url/lib/libXext-1.3.6.tar.xz" "SKIP" "$x_autotools"
    build_lfs_pkg "libxkbfile" "1.1.3" "$xorg_url/lib/libxkbfile-1.1.3.tar.xz" "SKIP" "$x_autotools"
    # libxkbcommon-x11: build_blfs zbudował libxkbcommon z -Denable-x11=false,
    # a platforma xcb Qt6 wymaga xkbcommon-x11. Wersja "-x11" wymusza przebudowę.
    build_lfs_pkg "libxkbcommon" "1.13.1-x11" \
        "https://github.com/lfs-book/libxkbcommon/archive/v1.13.1/libxkbcommon-1.13.1.tar.gz" \
        "SKIP" \
        "meson setup build --prefix=/usr -Denable-x11=true && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"

    # ── Perl target dla kdoctools (meinproc6/URI::Escape) – jak przy budowie KF6. ──
    local _old_perl5lib="${PERL5LIB:-}"
    local _pver="" _parch="" _d
    for _d in "$PAGAN_ROOT"/usr/lib/perl5/5.*; do [[ -d "$_d" ]] && _pver="$(basename "$_d")"; done
    if [[ -n "$_pver" && -x "$PAGAN_ROOT/usr/bin/perl" ]]; then
        for _d in "$PAGAN_ROOT/usr/lib/perl5/$_pver"/*/; do [[ -d "$_d/CORE" ]] && _parch="$(basename "$_d")" && break; done
        export LD_LIBRARY_PATH="$PAGAN_ROOT/usr/lib/perl5/$_pver/$_parch/CORE:$LD_LIBRARY_PATH"
        export PERL5LIB="$PAGAN_ROOT/usr/lib/perl5/$_pver:$PAGAN_ROOT/usr/lib/perl5/site_perl/$_pver:$PAGAN_ROOT/usr/lib/perl5/$_pver/$_parch:$PAGAN_ROOT/usr/lib/perl5/site_perl/$_pver/$_parch"
        local _site="$PAGAN_ROOT/usr/lib/perl5/site_perl/$_pver"
        if [[ -f /usr/share/perl5/vendor_perl/URI.pm && ! -e "$_site/URI.pm" ]]; then
            install -Dm644 /usr/share/perl5/vendor_perl/URI.pm "$_site/URI.pm"
            cp -a /usr/share/perl5/vendor_perl/URI "$_site/"
        fi
    fi
    find "$PAGAN_ROOT/usr/bin" -maxdepth 1 -type f -exec sed -i 's|^#!/usr/bin/perl|#!'"$PAGAN_ROOT"'/usr/bin/perl|' {} + 2>/dev/null || true

    # XML::Parser (XS) dla intltool-merge (kdoctools/KF6).
    # NIE budujemy w chrocie: target nie ma natywnego as/ld (binutils powstaje
    # tylko jako cross do $TOOLS_DIR), więc kompilacja XS pada. Targetowy perl
    # 5.44 uruchamia się jednak na hoście (ta sama glibc 2.44), a hostowy gcc ma
    # as/ld. Trzeba tylko przepisać ścieżki w Config.pm rootfsu
    # (/usr/lib/perl5 → $PAGAN_ROOT/usr/lib/perl5), bo inaczej MakeMaker nie
    # znajdzie CORE („Failed to opendir '/usr/lib/perl5/.../CORE'”). Config w 5.44
    # jest read-only (nie da się nadpisać %Config w locie), dlatego cieniujemy
    # Config.pm/Config_heavy.pl kopią z przepisanymi ścieżkami (katalog o nazwie
    # = archname, żeby MakeMaker nie ostrzegał o niezgodności arch).
    local _xp_perl_lib="$PAGAN_ROOT/usr/lib/perl5/$_pver"
    if [[ -n "$_pver" && -n "$_parch" && -x "$PAGAN_ROOT/usr/bin/perl" ]] \
            && ! LD_LIBRARY_PATH="$_xp_perl_lib/$_parch/CORE:/usr/lib:$PAGAN_ROOT/usr/lib" \
                 PERL5LIB="$_xp_perl_lib/$_parch:$_xp_perl_lib:$_xp_perl_lib/site_perl/$_parch:$PAGAN_ROOT/usr/lib/perl5/site_perl/$_pver" \
                 "$PAGAN_ROOT/usr/bin/perl" -MXML::Parser -e 'exit 0' >/dev/null 2>&1; then
        local _xp_ver="2.44"
        local _xp_arch="$SRC_DIR/XML-Parser-${_xp_ver}.tar.gz"
        if download_pkg "https://cpan.metacpan.org/authors/id/T/TO/TODDR/XML-Parser-${_xp_ver}.tar.gz" "$_xp_arch" "SKIP"; then
            local _xp_build="$SRC_DIR/build-xml-parser"
            local _xp_cfg="$SRC_DIR/xml-parser-config/$_parch"
            local _xp_log="/var/log/pagan-pkgs/XML-Parser-${_xp_ver}.log"
            rm -rf "$_xp_build" "$SRC_DIR/xml-parser-config"
            mkdir -p "$_xp_build" "$_xp_cfg" "$(dirname "$_xp_log")"
            tar -xzf "$_xp_arch" -C "$_xp_build" --strip-components=1
            cp "$_xp_perl_lib/$_parch/Config.pm" "$_xp_perl_lib/$_parch/Config_heavy.pl" "$_xp_cfg/" 2>/dev/null || true
            sed -i -e "s|'/usr/|'$PAGAN_ROOT/usr/|g" "$_xp_cfg/Config.pm" "$_xp_cfg/Config_heavy.pl" 2>/dev/null || true
            (
                cd "$_xp_build" || exit 1
                export LD_LIBRARY_PATH="$_xp_perl_lib/$_parch/CORE:/usr/lib:$PAGAN_ROOT/usr/lib"
                export PERL5LIB="$_xp_cfg:$_xp_perl_lib/$_parch:$_xp_perl_lib:$_xp_perl_lib/site_perl/$_parch:$PAGAN_ROOT/usr/lib/perl5/site_perl/$_pver"
                PERL_MM_USE_DEFAULT=1 "$PAGAN_ROOT/usr/bin/perl" Makefile.PL && make && make install
            ) >"$_xp_log" 2>&1 || {
                warn "  ⚠ XML::Parser build nieudany (log: $_xp_log) – kdoctools (KF6) może paść"
                tail -n 15 "$_xp_log" 2>/dev/null | sed 's/^/     /' >&2 || true
            }
            rm -rf "$_xp_build" "$SRC_DIR/xml-parser-config"
        fi
    fi

    # ── Qt6 (TYLKO to, co wymaga Calamares/KPMcore) ──
    # Calamares 3.3: Qt6 Core/Concurrent/DBus/Gui/LinguistTools/Network/Svg/Widgets.
    # KPMcore 24.12: Qt6 Core/DBus/Gui/Widgets.
    # QML/Qt Quick (qtdeclarative + qtshadertools) NIE jest potrzebne: Calamares
    # budujemy z -DWITH_QML=OFF, a KF6 bez modułów QML. To wycina największy
    # i najbardziej pamięciożerny moduł Qt (kilkaset MB źródeł, ~3700 zadań).
    local qt_ver="6.11.2"
    local qt_url="https://download.qt.io/official_releases/qt/${qt_ver%.*}/$qt_ver/submodules"
    # Build/target-first LD_LIBRARY_PATH dla narzędzi z drzewa build (moc/rcc/qmlcachegen):
    # host może mieć INNĄ wersję Qt niż budowana, a przy host-first ld.so brał
    # hostowe libQt6* → mieszane ABI i qmlcachegen dostawał SIGSEGV. LD_LIBRARY_PATH
    # ma priorytet nad RUNPATH, więc prependujemy:
    #   $PWD/build/lib      – libki właśnie budowanego modułu (np. libQt6Qml)
    #   $PAGAN_ROOT/usr/lib – zainstalowany Qt6 (np. libQt6Core)
    # Wersje glibc/libstdc++ w targetcie = host (gcc 16.2.x), więc bezpiecznie.
    local _qt_env="export LD_LIBRARY_PATH=\"\$PWD/build/lib:$PAGAN_ROOT/usr/lib:\${LD_LIBRARY_PATH:-}\";"
    local qt_cfg="$_qt_env cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DQT_BUILD_TESTS=OFF -DQT_BUILD_EXAMPLES=OFF && ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"
    local qt_modules=(qtbase qtsvg qttools)
    # Sprzątanie zbędnych libek Qt z poprzednich prób (QML/Quick/shadertools oraz
    # stara wersja), żeby nie trafiły do ISO. NIE ruszamy bieżącej qtbase/qtsvg/
    # qttools (mogą być już w resume i nie zostaną reinstalowane).
    rm -f "$PAGAN_ROOT"/usr/lib/libQt6*.so.6.11.1 \
          "$PAGAN_ROOT"/usr/lib/libQt6Qml*.so* \
          "$PAGAN_ROOT"/usr/lib/libQt6Quick*.so* \
          "$PAGAN_ROOT"/usr/lib/libQt6ShaderTools*.so* 2>/dev/null || true
    local qm _cfg
    for qm in "${qt_modules[@]}"; do
        _cfg="$qt_cfg"
        if [[ "$qm" == "qtbase" ]]; then
            # OpenSSL 4: ASN1_STRING jest nieprzezroczysty, a plugin TLS nie linkuje
            # -lcrypto (Qt ładuje OpenSSL przez dlopen i wrappers q_*).
            # UWAGA: Qt 6.11.2 ma to już naprawione upstream (QT_OPENSSL4_CONST,
            # q_ASN1_STRING_*), więc ten sed jest zwykle no-opem – zostawiony jako
            # zabezpieczenie dla starszych 6.11.x. Ważne: zamiana musi używać q_*
            # (bezpośrednie ASN1_STRING_* daje „undefined reference” przy linkowaniu).
            # Rzuty obok muszą być const (get0_data zwraca const unsigned char*).
            _cfg="sed -i -e 's|auth_key->keyid->data|q_ASN1_STRING_get0_data(auth_key->keyid)|' -e 's|auth_key->keyid->length|q_ASN1_STRING_length(auth_key->keyid)|' -e 's|genName->d\\.iPAddress->data|q_ASN1_STRING_get0_data(genName->d.iPAddress)|' -e 's|serialNumber->length|q_ASN1_STRING_length(serialNumber)|g' -e 's|serialNumber->data\\[a\\]|q_ASN1_STRING_get0_data(serialNumber)[a]|' -e 's|reinterpret_cast<quint32|reinterpret_cast<const quint32|g' -e 's|reinterpret_cast<quint8|reinterpret_cast<const quint8|g' src/plugins/tls/openssl/qx509_openssl.cpp && $qt_cfg"
        fi
        build_lfs_pkg "qt6-$qm" "$qt_ver" "$qt_url/$qm-everywhere-src-$qt_ver.tar.xz" "SKIP" "$_cfg"
    done

    # ── KDE Frameworks 6 (TYLKO minimum wymagane przez Calamares/KPMcore) ──
    # KPMcore: KF6 CoreAddons/I18n/WidgetsAddons. Calamares: CoreAddons.
    # Reszta (KIO/XMLGUI/KParts/KDeclarative/Kirigami/…) jest zbędna dla instalatora.
    # Uwaga: bez tych flag KCoreAddons i KI18n ciągną Qt6Qml (i tym samym
    # qtdeclarative): KCoreAddons → KCOREADDONS_USE_QML=OFF, KI18n → BUILD_WITH_QML=OFF.
    local kf_ver="6.28.0"
    local kf_url="https://download.kde.org/stable/frameworks/$kf_ver"
    local kf_base="$_qt_env cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_TESTING=OFF -DKDE_INSTALL_USE_QT_SYS_PATHS=ON -DKDE_INSTALL_QTMETATYPESDIR=metatypes -DBUILD_PYTHON_BINDINGS=OFF"
    local kf_install="&& ninja -C build && DESTDIR=$PAGAN_ROOT ninja -C build install"
    build_lfs_pkg "extra-cmake-modules" "$kf_ver" "$kf_url/extra-cmake-modules-$kf_ver.tar.xz" "SKIP" \
        "$kf_base $kf_install"
    build_lfs_pkg "kcoreaddons" "$kf_ver" "$kf_url/kcoreaddons-$kf_ver.tar.xz" "SKIP" \
        "$kf_base -DKCOREADDONS_USE_QML=OFF $kf_install"
    # KConfig: wymagane przez Calamares (KPMcoreHelper: find_package(KF6Config)).
    # Bez KCONFIG_USE_QML=OFF ciągnie Qt6Qml/Quick. GUI/DBus zostają (są w qtbase).
    build_lfs_pkg "kconfig" "$kf_ver" "$kf_url/kconfig-$kf_ver.tar.xz" "SKIP" \
        "$kf_base -DKCONFIG_USE_QML=OFF $kf_install"
    build_lfs_pkg "ki18n" "$kf_ver" "$kf_url/ki18n-$kf_ver.tar.xz" "SKIP" \
        "$kf_base -DBUILD_WITH_QML=OFF $kf_install"
    build_lfs_pkg "kwidgetsaddons" "$kf_ver" "$kf_url/kwidgetsaddons-$kf_ver.tar.xz" "SKIP" \
        "$kf_base -DBUILD_DESIGNERPLUGIN=OFF $kf_install"

    # ── Dodatkowe zależności Calamares/KPMcore ──
    # yaml-cpp 0.8.0: (1) cmake_minimum_required < 3.5 – CMake 4 wymaga zgody
    # (CMAKE_POLICY_VERSION_MINIMUM); (2) emitterutils.cpp używa uint16_t bez
    # #include <cstdint>, a GCC 16 już go nie dołącza transitively → dodajemy
    # globalnie -include cstdint.
    build_lfs_pkg "yaml-cpp" "0.8.0" \
        "https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.tar.gz" "SKIP" \
        "cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_CXX_FLAGS=\"-include cstdint\" -DYAML_CPP_BUILD_TESTS=OFF -DYAML_BUILD_SHARED_LIBS=ON && ninja -C build && DESTDIR=\$PAGAN_ROOT ninja -C build install"
    # libpwquality POMIJAMY: wymaga cracklib (+ słownika), a Calamares ma je jako
    # zależność OPCJONALNĄ (moduł „users”: find_package(LibPWQuality) bez REQUIRED).
    # Bez niego instalator działa – tracimy tylko dodatkową ocenę siły hasła.

    # polkit-qt-1 (Qt6): WYMAGANE przez KPMcore (PolkitQt6-1) i Calamares.
    # -DQT_MAJOR_VERSION=6 jest konieczne (domyślnie projekt buduje Qt5!).
    # Korzysta z polkit-gobject-1/polkit-agent-1 (zbudowane w build_blfs).
    build_lfs_pkg "polkit-qt-1" "0.201.1" \
        "https://download.kde.org/stable/polkit-qt-1/polkit-qt-1-0.201.1.tar.xz" "SKIP" \
        "$_qt_env cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DQT_MAJOR_VERSION=6 -DBUILD_EXAMPLES=OFF -DBUILD_TEST=OFF && DESTDIR=\$PAGAN_ROOT ninja -C build install"

    export LD_LIBRARY_PATH="$_old_ld"
    export XDG_DATA_DIRS="$_old_xdg"
    export PERL5LIB="$_old_perl5lib"
}

# ========================= CZYSZCZENIE STALEGO Qt6 QML =========================
# W rootfsie zostawały libki Qt6 QML/Quick z wcześniejszych prób (wersja 6.11.1)
# oraz stare KF6 z QML. Mieszanie ich z Qt6Core 6.11.2 dawało przy starcie:
#   "symbol lookup error: /usr/lib/libQt6Qml.so.6: undefined symbol
#    ...Qt_6_11_1, version Qt_6_Private_API"
# QML nie jest nam potrzebny (Calamares budujemy z WITH_QML=OFF), więc usuwamy cały
# ten stos (libki + .prl/.a + cmake + dane) i wymuszamy przebudowę calamaresa,
# jeśli zainstalowany libcalamaresui linkuje QML.
_purge_stale_qt() {
    local r="$PAGAN_ROOT/usr"
    if [[ -e "$r/lib/libQt6Qml.so.6" || -e "$r/lib/libQt6Core.so.6.11.1" ]]; then
        info "  Usuwam stary stos Qt6 QML/Quick (6.11.1) z targetu – nie budujemy QML"
    fi
    rm -f "$r"/lib/libQt6Qml* "$r"/lib/libQt6Quick* "$r"/lib/libQt6Labs* \
          "$r"/lib/libQt6ShaderTools* "$r"/lib/libQt6*.so.6.11.1 \
          "$r"/lib/libKF6*Qml* 2>/dev/null || true
    rm -rf "$r"/lib/cmake/Qt6Qml* "$r"/lib/cmake/Qt6Quick* "$r"/lib/cmake/Qt6Labs* \
           "$r"/lib/cmake/Qt6ShaderTools* 2>/dev/null || true
    rm -rf "$r"/lib/qt6/qml "$r"/lib/qml "$r"/qml 2>/dev/null || true
    rm -rf "$r"/include/QtQml* "$r"/include/QtQuick* "$r"/include/QtLabs* \
           "$r"/include/QtShaderTools* 2>/dev/null || true
    rm -f "$r"/bin/qml* "$r"/libexec/qml* 2>/dev/null || true
    # dangling symlinki (libQt6Qml.so.6 → usunięta .6.11.1 itp.)
    find "$r"/lib -maxdepth 1 -xtype l \( -name 'libQt6*' -o -name 'libKF6*' \) -delete 2>/dev/null || true
    # Stary calamares linkujący QML MUSI się przebudować, inaczej po usunięciu
    # libQt6Qml dostanie "cannot open shared object file".
    if objdump -p "$r/lib/libcalamaresui.so.3.3.14" 2>/dev/null | grep -q 'NEEDED.*libQt6Qml'; then
        warn "  Calamares: zainstalowany libcalamaresui linkuje QML – wymuszam przebudowę (WITH_QML=OFF)"
        sed -i '/^calamares-3\.3\.14$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
}

# Naprawa stalych configów Qt6 (typowy efekt rollbacku `paga`): libki są już
# nowej wersji (np. 6.11.2), a configi CMake wciąż wskazują starą (6.11.1) →
# configure pada: „Qt6CoreTargets.cmake ... references the file
# /usr/lib/libQt6Core.so.6.11.1 but this file does not exist”. Przepisujemy
# wersję w configach na tę, której libki faktycznie istnieją.
_fix_stale_qt_cmake() {
    local libdir="$PAGAN_ROOT/usr/lib" cfgdir="$PAGAN_ROOT/usr/lib/cmake"
    [[ -d "$cfgdir" ]] || return 0
    local cur
    cur=$(ls "$libdir"/libQt6Core.so.6.* 2>/dev/null \
          | sed -n 's/.*\.so\.\(6\.[0-9][0-9.]*\)$/\1/p' | sort -V | tail -1)
    [[ -n "$cur" ]] || return 0
    local v vre
    for v in $(grep -rhoE 'libQt6[A-Za-z0-9]*\.so\.6\.[0-9]+(\.[0-9]+)*' "$cfgdir" 2>/dev/null \
                 | sed -E 's/.*\.so\.(6\.[0-9]+(\.[0-9]+)*)/\1/' | sort -u); do
        [[ "$v" == "$cur" ]] && continue
        [[ -e "$libdir/libQt6Core.so.$v" ]] && continue
        vre=$(printf '%s' "$v" | sed 's/\./\\./g')
        warn "Qt6: configi CMake wskazują $v bez libek – przepisuję na $cur"
        find "$cfgdir" -type f -exec sed -i "s/$vre/$cur/g" {} + 2>/dev/null || true
    done
}

# Pisze branding Calamares (branding.desc + obrazy). Wydzielone, żeby korzystał
# z tego zarówno pełny build, jak i fix-calamares.sh.
# UWAGA: `slideshow` jest WYMAGANY; przy WITH_QML=OFF musi być LISTĄ obrazków
# (scalar = QML → błąd), a wskazane pliki muszą istnieć.
_write_calamares_branding() {
    local _cdir="$PAGAN_ROOT/usr/share/calamares/branding/paganlinux"
    mkdir -p "$_cdir"
    cat > "$_cdir/branding.desc" <<'CALEOF'
---
componentName:  paganlinux

# Okno i panele (widget = klasyczne widgety; bez QML)
windowExpanding:  normal
windowPlacement:  center
sidebar:          widget,left
navigation:       widget,top
welcomeStyleCalamares:  true

strings:
    productName:         "PaganLinux"
    shortProductName:    "PaganLinux"
    version:             "1.0"
    shortVersion:        "1.0"
    versionedName:       "PaganLinux"
    shortVersionedName:  "PaganLinux"
    bootloaderEntryName: "PaganLinux"
    productUrl:          "https://paganlinux.eu"
    supportUrl:          "https://paganlinux.eu/support"

images:
    productLogo:         "logo.png"
    productIcon:         "icon.png"
    productWelcome:      "welcome.png"

# Slideshow bez QML: lista obrazków wyświetlanych w trakcie instalacji.
slideshow:
    - "welcome.png"

# Klucze stylu są WALIDOWANE case-sensitively (Branding::validateStyleEntries);
# nieznane lądują w logu jako warning i są ignorowane. Dlatego dokładnie:
# SidebarBackground / SidebarBackgroundCurrent / SidebarText / SidebarTextCurrent.
style:
    SidebarBackground:        "#2e3440"
    SidebarBackgroundCurrent: "#434c5e"
    SidebarText:              "#d8dee9"
    SidebarTextCurrent:       "#88c0d0"
CALEOF
    # Motyw Calamares – Calamares ładuje ten plik automatycznie
    # (Branding::stylesheet() czyta <branding>/stylesheet.qss). Paleta Nord,
    # spójna z motywem XFCE Nordic.
    cat > "$_cdir/stylesheet.qss" <<'QSSEOF'
/* Pagan Linux – motyw Calamares (Nord) */
QWidget { font-size: 10pt; color: #d8dee9; }

#mainApp  { background: #2e3440; }
#sidebarApp { background: #2e3440; }

QPushButton {
    background: #5e81ac; color: #eceff4; border: 0; border-radius: 6px;
    padding: 8px 20px; min-height: 26px; font-weight: 600;
}
QPushButton:hover    { background: #81a1c1; }
QPushButton:pressed  { background: #4c566a; }
QPushButton:disabled { background: #434c5e; color: #7b8394; }
QPushButton#quitButton { background: transparent; color: #d8dee9; font-weight: 500; }
QPushButton#quitButton:hover { background: #434c5e; }

QProgressBar {
    border: 1px solid #4c566a; border-radius: 6px;
    background: #3b4252; text-align: center; color: #eceff4;
}
QProgressBar::chunk { background: #88c0d0; border-radius: 5px; }

QLineEdit, QComboBox, QSpinBox, QTextEdit {
    background: #3b4252; color: #eceff4;
    border: 1px solid #4c566a; border-radius: 6px; padding: 5px;
}
QLineEdit:focus, QComboBox:focus, QSpinBox:focus { border: 1px solid #88c0d0; }
QListView, QTreeView, QTableView {
    background: #3b4252; color: #eceff4;
    border: 1px solid #4c566a; border-radius: 6px;
}
QCheckBox, QRadioButton { color: #d8dee9; }
QScrollArea { border: 0; }
QSSEOF
    # Obrazy brandingu z zasobów branding/ – bez nich Calamares zgłasza brak
    # logo/ikony/welcome (a slideshow musi wskazywać istniejący plik).
    local _bdir="$SCRIPT_DIR/branding"
    local _wallpaper="$_bdir/$PAGAN_WALLPAPER"
    if [[ -f "$_bdir/pagan-loki-pingwin.png" ]]; then
        install -m644 "$_bdir/pagan-loki-pingwin.png" "$_cdir/logo.png"
        install -m644 "$_bdir/pagan-loki-pingwin.png" "$_cdir/icon.png"
    fi
    if [[ -f "$_wallpaper" ]]; then
        install -m644 "$_wallpaper" "$_cdir/welcome.png"
    fi
    # Fallback: slideshow/welcome musi istnieć.
    if [[ ! -f "$_cdir/welcome.png" && -f "$_cdir/logo.png" ]]; then
        install -m644 "$_cdir/logo.png" "$_cdir/welcome.png"
    fi
}

# Moduły Calamares wymagane przez naszą sekwencję z /etc/calamares/settings.conf.
# Moduły Pythonowe (mount, unpackfs, fstab, bootloader, services-systemd, …)
# powstają TYLKO przy -DWITH_PYTHON=ON + -DWITH_PYBIND11=ON. Bez któregokolwiek
# Calamares kończy się „Initialization Failed” i nie zainstaluje systemu –
# a system MUSI wstać po instalacji, więc brak modułu = twardy błąd builda.
_CALAMARES_REQUIRED_MODULES=(
    welcome summary partition mount unpackfs machineid fstab
    locale keyboard localecfg users displaymanager networkcfg
    hwclock services-systemd grubcfg bootloader umount finished
    # netinstall (wybór pakietów) i packages (backend `pag`) – netinstall sam nic
    # nie instaluje, więc brak któregokolwiek z nich cicho psuje instalację
    # dodatkowego oprogramowania.
    netinstall packages
    # packagechooser (wybór dodatków po instalacji, m.in. aktualizacja z repo)
    # + contextualprocess, który tę komendę wykonuje w systemie docelowym.
    packagechooser contextualprocess
)

# 0 = komplet wymaganych modułów jest zainstalowany w rootfsie.
_calamares_modules_ok() {
    local _mdir="$PAGAN_ROOT/usr/lib/calamares/modules" _m
    [[ -d "$_mdir" ]] || return 1
    for _m in "${_CALAMARES_REQUIRED_MODULES[@]}"; do
        [[ -e "$_mdir/$_m" ]] || return 1
    done
    return 0
}

# Wypisuje brakujące moduły (diagnostyka).
_calamares_missing_modules() {
    local _mdir="$PAGAN_ROOT/usr/lib/calamares/modules" _m
    for _m in "${_CALAMARES_REQUIRED_MODULES[@]}"; do
        [[ -e "$_mdir/$_m" ]] || printf '%s ' "$_m"
    done
}

# ── KPMcore / wtyczki Qt ─────────────────────────────────────────────
# Targetowy Qt6 ma QT_INSTALL_PLUGINS=<prefix>/plugins (tu: /usr/plugins), ale
# część pakietów (KF6, kpmcore) instaluje wtyczki do <libdir>/qt6/plugins.
# QCoreApplication::libraryPaths() NIE zawiera tego drugiego katalogu, więc takie
# wtyczki nie ładują się (KPMcore → qFatal „Failed to initialize KPMcore backend”).

# Bezwzględna ścieżka katalogu wtyczek Qt w BUDOWANYM rootfsie.
_qt_runtime_plugin_dir() {
    local p=""
    if [[ -x "$PAGAN_ROOT/usr/bin/qmake6" ]]; then
        p=$(env LD_LIBRARY_PATH="$PAGAN_ROOT/usr/lib:/usr/lib" \
            "$PAGAN_ROOT/usr/bin/qmake6" -query QT_INSTALL_PLUGINS 2>/dev/null) || p=""
    fi
    [[ -n "$p" ]] || p="$PAGAN_ROOT/usr/plugins"
    # qmake może zwrócić ścieżkę w przestrzeni docelowej (/usr/...) – przenieś ją
    # do budowanego rootfsu, jeśli nie zaczyna się od PAGAN_ROOT.
    if [[ "$p" != "$PAGAN_ROOT"* ]]; then
        p="$PAGAN_ROOT$p"
    fi
    printf '%s\n' "$p"
}

# Udostępnia wtyczki z <libdir>/qt6/plugins w katalogu runtime Qt.
# KOPIUJE pliki (nie symlinkuje): bezwzględne symlinki wskazywałyby na ścieżki
# z build-prefixem ($PAGAN_ROOT/...) i byłyby zepsute po instalacji na /.
_sync_qt_plugin_dirs() {
    local qtp src top name dst f
    qtp="$(_qt_runtime_plugin_dir)"
    [[ -d "$qtp" ]] || return 0
    # Kolejność ma znaczenie: najpierw katalog używany przez nasz Qt6 (właściwa
    # wersja wtyczek), potem legacy <libdir>/plugins (np. stara kpmcore).
    for src in "$PAGAN_ROOT/usr/lib/qt6/plugins" "$PAGAN_ROOT/usr/lib/plugins"; do
        [[ -d "$src" ]] || continue
        for top in "$src"/*; do
            [[ -d "$top" ]] || continue
            name=$(basename "$top")
            dst="$qtp/$name"
            # Zepsuty symlink z wcześniejszych wersji – usuń.
            if [[ -L "$dst" ]]; then
                rm -f "$dst"
            fi
            if [[ ! -e "$dst" ]]; then
                cp -a "$top" "$dst"
            elif [[ -d "$dst" ]]; then
                for f in "$top"/*; do
                    [[ -e "$f" ]] || continue
                    [[ -e "$dst/$(basename "$f")" ]] || cp -a "$f" "$dst/"
                done
            fi
        done
    done
}

# Czy backend KPMcore jest widoczny dla runtime Qt?
_kpmcore_backend_ok() {
    local qtp
    qtp="$(_qt_runtime_plugin_dir)"
    [[ -e "$qtp/kpmcore/pmsfdiskbackendplugin.so" ]]
}

# KPMcore w dwóch wersjach (różne soname) = Calamares linkuje jedną, a wtyczkę
# backendu bierze z drugiej → backend się nie inicjalizuje. Zostaw jedną wersję.
_purge_duplicate_kpmcore() {
    local n
    n=$(ls "$PAGAN_ROOT"/usr/lib/libkpmcore.so.[0-9]* 2>/dev/null | wc -l)
    (( n > 1 )) || return 0
    warn "  KPMcore: wykryto $n plików biblioteki (więcej niż jedna wersja) – czyszczę"
    rm -f  "$PAGAN_ROOT"/usr/lib/libkpmcore.so*
    rm -rf "$PAGAN_ROOT"/usr/lib/plugins/kpmcore \
           "$PAGAN_ROOT"/usr/lib/qt6/plugins/kpmcore \
           "$PAGAN_ROOT"/usr/plugins/kpmcore \
           "$PAGAN_ROOT"/usr/lib/cmake/KPMcore \
           "$PAGAN_ROOT"/usr/include/kpmcore
    rm -f  "$PAGAN_ROOT"/usr/lib/libexec/kpmcore_externalcommand
    sed -i -E '/^kpmcore-/d' "$RESUME_FILE" 2>/dev/null || true
    sed -i '/^calamares-3\.3\.14$/d' "$RESUME_FILE" 2>/dev/null || true
}

# libpwquality w tym systemie jest MARTWE: linkuje libcrack.so.2 (cracklib),
# którego w targecie nie ma. Calamares wykrywa je jednak przez find_package
# i podlinkowuje moduł „users” → przy starcie „Module users loading FAILED”
# (QPluginLoader nie może załadować .so). PAM go nie używa, więc usuwamy.
_purge_broken_pwquality() {
    rm -f "$PAGAN_ROOT"/usr/lib/libpwquality.so* \
          "$PAGAN_ROOT"/usr/lib/libpwquality.a \
          "$PAGAN_ROOT"/usr/lib/security/pam_pwquality.so \
          "$PAGAN_ROOT"/usr/lib/security/pam_pwquality.a \
          "$PAGAN_ROOT"/usr/lib/pkgconfig/pwquality.pc \
          "$PAGAN_ROOT"/usr/lib/pkgconfig/libpwquality.pc \
          "$PAGAN_ROOT"/usr/include/pwquality.h
}

# os-prober: moduł `partition` Calamaresa woła `os-prober` BEZWARUNKOWO
# (PartUtils::runOsprober), a bez binarki loguje „os-prober cannot start.”.
# Wchodzi do rootfsu live; unpackfs skopiuje go też do zainstalowanego systemu,
# gdzie użyje go grub-mkconfig (wykrywanie innych systemów w menu GRUB).
install_os_prober() {
    build_lfs_pkg "os-prober" "1.85" \
        "https://deb.debian.org/debian/pool/main/o/os-prober/os-prober_1.85.tar.xz" \
        "f06094db841a214f975e79d66e2a79ea8c2b39b0f762e9d384b2feffd4ae7447" \
        "sed -i -e 's|/lib/ld*\.so*|/lib*/ld*.so*|g' os-probes/mounted/common/90linux-distro && rm -f Makefile && make newns && install -Dm755 linux-boot-prober \$PAGAN_ROOT/usr/bin/linux-boot-prober && install -Dm755 os-prober \$PAGAN_ROOT/usr/bin/os-prober && install -Dm755 newns \$PAGAN_ROOT/usr/lib/os-prober/newns && install -Dm755 common.sh \$PAGAN_ROOT/usr/share/os-prober/common.sh && for dir in os-probes os-probes/mounted os-probes/init linux-boot-probes linux-boot-probes/mounted; do install -dm755 \$PAGAN_ROOT/usr/lib/\$dir; install -m755 -t \$PAGAN_ROOT/usr/lib/\$dir \$dir/common/*; if [ -d \"\$dir/x86\" ]; then cp -r \"\$dir/x86/.\" \$PAGAN_ROOT/usr/lib/\$dir/; fi; done && install -Dm755 os-probes/mounted/powerpc/20macosx \$PAGAN_ROOT/usr/lib/os-probes/mounted/20macosx && install -dm755 \$PAGAN_ROOT/var/lib/os-prober"
}

# /etc/group: (1) usuwa Wiodące białe znaki (psuły nazwy grup: zapisane jako
# "\twheel" nie są widoczne jako `wheel`, więc `useradd -G wheel` zawodzi),
# (2) dopisuje grupy wymagane przez Calamaresa (`users.defaultGroups`: optical,
# storage, scanner, power) oraz `autologin` (autologinGroup) i `users`.
# Idempotentne – bezpieczne do wołania przy każdym respinie.
_ensure_system_groups() {
    local gf="$PAGAN_ROOT/etc/group"
    [[ -f "$gf" ]] || return 0

    sed -i 's/^[[:space:]]\+//' "$gf"

    local item gid g
    for item in optical:21 scanner:22 storage:23 power:24 users:25 autologin:26; do
        g="${item%%:*}"; gid="${item##*:}"
        if ! awk -F: -v G="$g" '{n=$1; gsub(/^[ \t]+|[ \t]+$/,"",n); if(n==G) f=1} END{exit !f}' "$gf"; then
            printf '%s:x:%s:\n' "$g" "$gid" >> "$gf"
        fi
    done
}

# Awaryjne dopełnienie plików polkita. Polkit instaluje część plików pod
# ścieżkami ABSOLUTNYMI, przez co meson dokłada i sysroot, i DESTDIR → lądują
# w zagnieżdżonym rootfsie ($PAGAN_ROOT/mnt/pagan/...). Gdy zginą, polkitd
# nie wystartuje, a wtedy KPMcore (ExternalCommand → D-Bus helper → polkit)
# nie wykona `lsblk` i Calamares zgłasza „There are no partitions to install on”.
# Idempotentne: pisze tylko brakujące pliki.
_ensure_polkit_units() {
    [[ -x "$PAGAN_ROOT/usr/lib/polkit-1/polkitd" ]] || return 0

    local _dbusdir="$PAGAN_ROOT/usr/share/dbus-1/system-services"
    local _unitdir="$PAGAN_ROOT/usr/lib/systemd/system"
    mkdir -p "$_dbusdir" "$_unitdir" "$PAGAN_ROOT/usr/lib/sysusers.d" \
             "$PAGAN_ROOT/usr/lib/tmpfiles.d" "$PAGAN_ROOT/var/lib/polkit-1"

    # Zawsze zapisuj kanoniczne wersje. Sam warunek „jeśli brak” pozostawiał
    # stary, niekompatybilny unit po wcześniejszym buildzie/resume.
    cat > "$_dbusdir/org.freedesktop.PolicyKit1.service" <<'PKEOF'
[D-BUS Service]
Name=org.freedesktop.PolicyKit1
Exec=/usr/lib/polkit-1/polkitd --no-debug
User=root
SystemdService=polkit.service
PKEOF

    cat > "$_unitdir/polkit.service" <<'PKEOF'
[Unit]
Description=Authorization Manager
Documentation=man:polkit(8)
After=dbus.service

[Service]
Type=simple
BusName=org.freedesktop.PolicyKit1
ExecStart=/usr/lib/polkit-1/polkitd --no-debug --log-level=notice
User=polkitd
Restart=on-failure

[Install]
WantedBy=multi-user.target
PKEOF

    # Nie polegaj na `systemctl enable` w chroot (może nie działać bez PID 1).
    # Symlink trafia bezpośrednio do gotowego systemu i uruchamia polkit przy boot.
    mkdir -p "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants"
    ln -sfn /usr/lib/systemd/system/polkit.service \
        "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/polkit.service"

    if [[ ! -f "$_unitdir/polkit-agent-helper@.service" ]]; then
        cat > "$_unitdir/polkit-agent-helper@.service" <<'PKEOF'
[Unit]
Description=Authorization Manager Agent Helper
Documentation=man:polkit(8)

[Service]
Type=oneshot
DeviceAllow=/dev/null rw
DevicePolicy=strict
ExecStart=/usr/lib/polkit-1/polkit-agent-helper-1 --socket-activated
SuccessExitStatus=2
StandardInput=socket
StandardOutput=socket
LimitMEMLOCK=0
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateDevices=yes
PrivateNetwork=yes
PrivateTmp=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectKernelTunables=yes
ProtectSystem=strict
ProtectClock=yes
ProtectHostname=yes
RemoveIPC=yes
RestrictAddressFamilies=AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
UMask=0077
PKEOF
    fi

    if [[ ! -f "$_unitdir/polkit-agent-helper.socket" ]]; then
        cat > "$_unitdir/polkit-agent-helper.socket" <<'PKEOF'
[Unit]
Description=Authorization Manager Agent Helper
Documentation=man:polkit(8)

[Socket]
Accept=yes
RemoveOnStop=yes
ListenStream=/run/polkit/agent-helper.socket

[Install]
WantedBy=sockets.target
PKEOF
    fi

    [[ -f "$PAGAN_ROOT/usr/lib/sysusers.d/polkit.conf" ]] || \
        printf 'u polkitd 110 "User for polkitd"\n' > "$PAGAN_ROOT/usr/lib/sysusers.d/polkit.conf"
    [[ -f "$PAGAN_ROOT/usr/lib/tmpfiles.d/polkit-tmpfiles.conf" ]] || \
        printf 'd /etc/polkit-1/rules.d 0750 root polkitd - -\n' \
            > "$PAGAN_ROOT/usr/lib/tmpfiles.d/polkit-tmpfiles.conf"

    # D-Bus odrzuca żądanie polkita błędem Spawn.PermissionsInvalid, jeśli
    # setuid helper nie ma dokładnie właściciela root:root i trybu 4755.
    # Nie polegamy na uprawnieniach zachowanych przez instalator/DESTDIR —
    # ustawiamy je jawnie przed utworzeniem squashfs.
    local _polkit_helper="$PAGAN_ROOT/usr/lib/polkit-1/polkit-agent-helper-1"
    if [[ -e "$_polkit_helper" ]]; then
        chown root:root "$_polkit_helper"
        chmod 4755 "$_polkit_helper"
    else
        warn "  Brak polkit-agent-helper-1 – KPMcore nie będzie mógł autoryzować operacji dyskowych"
    fi

    # KPMcore uruchamia własny uprzywilejowany helper przez systemowy D-Bus.
    # Bez setuid D-Bus odrzuca start komunikatem Spawn.PermissionsInvalid,
    # a Calamares widzi wtedy 0 urządzeń i pokazuje „There are no partitions”.
    local _kpm_helper="$PAGAN_ROOT/usr/lib/libexec/kpmcore_externalcommand"
    if [[ -e "$_kpm_helper" ]]; then
        chown root:root "$_kpm_helper"
        chmod 4755 "$_kpm_helper"
    else
        warn "  Brak kpmcore_externalcommand – moduł partycjonowania nie zadziała"
    fi

    # To jest helper samego D-Busa, używany do aktywacji usług systemowych.
    # Jeśli ma 0755 zamiast 4755, dbus-daemon zwraca:
    # Spawn.PermissionsInvalid: The permission of the setuid helper is not correct.
    local _dbus_helper="$PAGAN_ROOT/usr/libexec/dbus-daemon-launch-helper"
    if [[ -e "$_dbus_helper" ]]; then
        chown root:root "$_dbus_helper"
        chmod 4755 "$_dbus_helper"
    else
        warn "  Brak dbus-daemon-launch-helper – aktywacja usług D-Bus może nie działać"
    fi
}

# NetworkManager: jednostki systemd + polityka/aktywacja D-Bus + config + WŁĄCZENIE w live.
#
# DLACZEGO TO ISTNIEJE: NetworkManager instaluje jednostki i politykę D-Bus do
# katalogów wyliczonych przez pkg-config (systemdsystemunitdir, dbus_conf_dir,
# dbussystemservicedir). Przy budowie na hoście z PKG_CONFIG_SYSROOT_DIR te
# ścieżki są JUŻ z sysrootem, a meson dokłada jeszcze DESTDIR → pliki lądują
# w $PAGAN_ROOT/mnt/pagan/... i w systemie ich NIE MA (patrz _merge_nested_rootfs).
#
# Objawy (dokładnie to widzieliśmy w live i w QEMU):
#   * /usr/sbin/NetworkManager istnieje, ale nie ma NetworkManager.service →
#     w live NIKT nie podnosi interfejsu: brak DHCP (`ip a` ma tylko lo,
#     `nmcli device status` nie działa), mimo że QEMU sieć przekazuje poprawnie
#     (`-netdev user` + kernel z CONFIG_VIRTIO_NET=y dają gościowi 10.0.2.15),
#   * brak org.freedesktop.NetworkManager.conf → <deny own="*"/> z
#     /usr/share/dbus-1/system.conf blokuje NM nazwę na magistrali,
#   * brak /etc/NetworkManager/NetworkManager.conf (receptura zapisuje go dopiero
#     na etapie package, którego ten build nie uruchamia).
#
# Idempotentna: pisze TYLKO brakujące pliki, więc nie nadpisuje nowszych
# z przebudowy NM; wołana z finalize_build – naprawia też rootfs zbudowany przed
# tą poprawką, bez pełnego przebudowania ISO.
_ensure_network_manager() {
    [[ -x "$PAGAN_ROOT/usr/sbin/NetworkManager" ]] || return 0

    local _us="$PAGAN_ROOT/usr/lib/systemd/system"
    local _pol="$PAGAN_ROOT/usr/share/dbus-1/system.d"
    local _svc="$PAGAN_ROOT/usr/share/dbus-1/system-services"
    local _etc="$PAGAN_ROOT/etc/NetworkManager"
    mkdir -p "$_us" "$_pol" "$_svc" "$_etc/conf.d" "$_etc/system-connections" "$_etc/VPN"

    local _fixed=0

    # 1. Jednostka główna. Treść wierna NetworkManager-1.56.0/data/NetworkManager.service.in,
    #    z podstawionymi ścieżkami tego buildu (sbindir=/usr/sbin).
    #    `Before=network.target` bez @DISTRO_NETWORK_SERVICE@ – ifcfg-rh jest wyłączony.
    if [[ ! -f "$_us/NetworkManager.service" ]]; then
        cat > "$_us/NetworkManager.service" <<'NMEOF'
[Unit]
Description=Network Manager
Documentation=man:NetworkManager(8)
Wants=network.target
After=network-pre.target dbus.service
Before=network.target
BindsTo=dbus.service

[Service]
Type=dbus
BusName=org.freedesktop.NetworkManager
ExecReload=/usr/bin/busctl call org.freedesktop.NetworkManager /org/freedesktop/NetworkManager org.freedesktop.NetworkManager Reload u 0
ExecStart=/usr/sbin/NetworkManager --no-daemon
Restart=on-failure
# NM nie chce, żeby systemd zabijał jego procesy potomne
KillMode=process

# Przy dużej liczbie interfejsów start potrafi się wydłużyć.
TimeoutStartSec=600

CapabilityBoundingSet=CAP_NET_ADMIN CAP_DAC_OVERRIDE CAP_NET_RAW CAP_BPF CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_MODULE CAP_AUDIT_WRITE CAP_KILL CAP_SYS_CHROOT

ProtectSystem=true
ProtectHome=read-only

LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
Also=NetworkManager-dispatcher.service
NMEOF
        _fixed=$((_fixed + 1))
    fi

    # 2. Dispatcher (NM startuje go po D-Bus, stąd Type=dbus + alias dbus-org.*).
    if [[ ! -f "$_us/NetworkManager-dispatcher.service" ]]; then
        cat > "$_us/NetworkManager-dispatcher.service" <<'NMEOF'
[Unit]
Description=Network Manager Script Dispatcher Service
Documentation=man:NetworkManager-dispatcher.service(8)

[Service]
Type=dbus
BusName=org.freedesktop.nm_dispatcher
ExecStart=/usr/libexec/nm-dispatcher
NotifyAccess=main
KillMode=process

[Install]
Alias=dbus-org.freedesktop.nm-dispatcher.service
NMEOF
        _fixed=$((_fixed + 1))
    fi

    # 3. Aktywacja D-Bus dispatchera (NM nie ma własnego pliku aktywacji –
    #    korzysta z Type=dbus i BindsTo=dbus.service).
    if [[ ! -f "$_svc/org.freedesktop.nm_dispatcher.service" ]]; then
        cat > "$_svc/org.freedesktop.nm_dispatcher.service" <<'NMEOF'
[D-BUS Service]
Name=org.freedesktop.nm_dispatcher
Exec=/usr/libexec/nm-dispatcher
User=root
SystemdService=dbus-org.freedesktop.nm-dispatcher.service
NMEOF
        _fixed=$((_fixed + 1))
    fi

    # 4. Polityka D-Bus – BEZ NIEJ NM nie zajmie nazwy org.freedesktop.NetworkManager
    #    (system.conf ma <deny own="*"/> dla kontekstu domyślnego).
    #    Treść 1:1 z NetworkManager-1.56.0/src/core/org.freedesktop.NetworkManager.conf.
    if [[ ! -f "$_pol/org.freedesktop.NetworkManager.conf" ]]; then
        cat > "$_pol/org.freedesktop.NetworkManager.conf" <<'NMEOF'
<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
    <policy user="root">
        <allow own="org.freedesktop.NetworkManager"/>
        <allow send_destination="org.freedesktop.NetworkManager"/>

        <allow send_destination="org.freedesktop.NetworkManager"
               send_interface="org.freedesktop.NetworkManager.PPP"/>

        <allow send_interface="org.freedesktop.NetworkManager.SecretAgent"/>

        <!-- Nadpisuje <deny send_interface="..."/> z „połamanych” polityk
             (patrz dbus-daemon(8)) dla znanych pluginów VPN. -->
        <allow send_destination="org.freedesktop.NetworkManager.openconnect"/>
        <allow send_destination="org.freedesktop.NetworkManager.openswan"/>
        <allow send_destination="org.freedesktop.NetworkManager.openvpn"/>
        <allow send_destination="org.freedesktop.NetworkManager.pptp"/>
        <allow send_destination="org.freedesktop.NetworkManager.vpnc"/>
        <allow send_destination="org.freedesktop.NetworkManager.ssh"/>
        <allow send_destination="org.freedesktop.NetworkManager.iodine"/>
        <allow send_destination="org.freedesktop.NetworkManager.l2tp"/>
        <allow send_destination="org.freedesktop.NetworkManager.libreswan"/>
        <allow send_destination="org.freedesktop.NetworkManager.fortisslvpn"/>
        <allow send_destination="org.freedesktop.NetworkManager.strongswan"/>
        <allow send_interface="org.freedesktop.NetworkManager.VPN.Plugin"/>

        <allow send_destination="org.fedoraproject.FirewallD1"/>

        <allow own="org.freedesktop.NetworkManager.dnsmasq"/>
        <allow send_destination="org.freedesktop.NetworkManager.dnsmasq"/>
    </policy>
    <policy context="default">
        <deny own="org.freedesktop.NetworkManager"/>
        <deny send_destination="org.freedesktop.NetworkManager"/>

        <!-- Podstawy API D-Bus -->
        <allow send_destination="org.freedesktop.NetworkManager"
               send_interface="org.freedesktop.DBus.Introspectable"/>
        <allow send_destination="org.freedesktop.NetworkManager"
               send_interface="org.freedesktop.DBus.Properties"/>
        <allow send_destination="org.freedesktop.NetworkManager"
               send_interface="org.freedesktop.DBus.ObjectManager"/>

        <!-- Urządzenia (właściwości tylko-do-odczytu, bez metod) -->
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.AccessPoint"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Adsl"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Bluetooth"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Bond"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Bridge"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Generic"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Gre"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Infiniband"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Macvlan"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Modem"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.OlpcMesh"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Team"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Tun"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Veth"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Vlan"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Wired"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.WiMax.Nsp"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.WifiP2PPeer"/>

        <!-- Urządzenia (tylko-do-odczytu, bez wymagań bezpieczeństwa) -->
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.WiMax"/>

        <!-- Urządzenia (zapis, zabezpieczone PolicyKitem) -->
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Wireless"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.WifiP2P"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device"/>

        <!-- Rdzeń (właściwości tylko-do-odczytu, bez metod) -->
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Connection.Active"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.DHCP4Config"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.DHCP6Config"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.IP4Config"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.IP6Config"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.VPN.Connection"/>

        <!-- Rdzeń (zapis, zabezpieczone PolicyKitem) -->
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings"/>
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings.Connection"/>

        <!-- Agenci: każde połączenie może gadać z AgentManager, ale tylko
             NetworkManager może gadać z samymi agentami. -->
        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.AgentManager"/>

        <!-- Funkcje tylko dla roota -->
        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager"          send_member="SetLogging"/>
        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager"          send_member="Sleep"/>
        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings" send_member="LoadConnections"/>
        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings" send_member="ReloadConnections"/>

        <deny own="org.freedesktop.NetworkManager.dnsmasq"/>
        <deny send_destination="org.freedesktop.NetworkManager.dnsmasq"/>
    </policy>

    <limit name="max_replies_per_connection">1024</limit>
    <limit name="max_match_rules_per_connection">2048</limit>
</busconfig>
NMEOF
        _fixed=$((_fixed + 1))
    fi

    # 5. Polityka D-Bus dispatchera (NetworkManager-1.56.0/src/nm-dispatcher/nm-dispatcher.conf).
    if [[ ! -f "$_pol/nm-dispatcher.conf" ]]; then
        cat > "$_pol/nm-dispatcher.conf" <<'NMEOF'
<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
    <policy user="root">
        <allow own="org.freedesktop.nm_dispatcher"/>
        <allow send_destination="org.freedesktop.nm_dispatcher"/>
    </policy>
    <policy context="default">
        <deny own="org.freedesktop.nm_dispatcher"/>
        <deny send_destination="org.freedesktop.nm_dispatcher"/>
    </policy>
</busconfig>
NMEOF
        _fixed=$((_fixed + 1))
    fi

    # 6. Minimalna konfiguracja. Bez tego pliku NM działa na wbudowanych
    #    domyślnych – wolimy mieć jawnie to, co deklaruje receptura.
    if [[ ! -f "$_etc/NetworkManager.conf" ]]; then
        cat > "$_etc/NetworkManager.conf" <<'NMEOF'
[main]
plugins=keyfile
NMEOF
        _fixed=$((_fixed + 1))
    fi

    # 7. DNS: wymuś plugin `default` (NM sam zapisuje /etc/resolv.conf).
    #    Powód: w obrazie JEST plik aktywacji D-Bus systemd-resolved
    #    (/usr/share/dbus-1/system-services/org.freedesktop.resolve1.service),
    #    więc NM może przełączyć się na plugin systemd-resolved, którego
    #    w tym systemie nikt nie uruchamia – wtedy DNS nie działa.
    if [[ ! -f "$_etc/conf.d/dns.conf" ]]; then
        cat > "$_etc/conf.d/dns.conf" <<'NMEOF'
[main]
dns=default
NMEOF
        _fixed=$((_fixed + 1))
    fi

    # 8. WŁĄCZENIE W LIVE. Calamares włącza NM dopiero na instalowanym systemie
    #    (services-systemd.conf), więc bez tego dowiązania live nie ma jak
    #    podnieść sieci. Nie polegamy na `systemctl enable` (w chroot może nie
    #    działać bez PID 1) – piszemy symlinki wprost.
    mkdir -p "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants"
    ln -sfn /usr/lib/systemd/system/NetworkManager.service \
        "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/NetworkManager.service"
    # Alias dispatchera (jego jednostka nie ma WantedBy, tylko Alias=)
    ln -sfn /usr/lib/systemd/system/NetworkManager-dispatcher.service \
        "$PAGAN_ROOT/etc/systemd/system/dbus-org.freedesktop.nm-dispatcher.service"

    # NetworkManager-wait-online.service celowo nieaktywny (tak jak w recepturze:
    # post-install robi `systemctl disable NetworkManager-wait-online`) –
    # blokowałby start na czekaniu na sieć online.
    rm -f "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/NetworkManager-wait-online.service" 2>/dev/null || true

    if [[ $_fixed -gt 0 ]]; then
        ok "  NetworkManager: dopisano $_fixed brakujących plików (jednostki/D-Bus/config)"
    fi
    ok "  NetworkManager: włączony w live + polityka D-Bus (network.target)"
}

# Kompiluje bazę stref czasowych (tzdata) za pomocą zic.
#
# DLACZEGO: stara receptura tylko KOPIOWAŁA pliki źródłowe do
# /usr/share/zoneinfo, więc w systemie nie było ŻADNEJ skompilowanej strefy
# (np. Europe/Warsaw), tylko surowe pliki „europe”, „asia”, „africa”…
# Moduł locale Calamaresa (SetTimezoneJob) sprawdza
# <root>/usr/share/zoneinfo/<Region>/<Zone> i przerywa instalację błędem:
#   „Cannot access selected timezone path. Bad path: /tmp/calamares-root/usr/share/zoneinfo/Europe/Warsaw”.
#
# Idempotentna: gdy strefy są już skompilowane (i nie ma surowych plików),
# kończy natychmiast.
# Katalog tymczasowy `mktemp -d` ma uprawnienia 0700 – po `mv` korzeń
# /usr/share/zoneinfo dziedziczył je i TAK TRAFIAŁ DO ISO (drwx------).
# Procesy nie-root nie mogły wtedy czytać stref (glibc przy TZ=…, panel XFCE,
# systemd-timedated), a Calamares raportował „Bad path …/zoneinfo/<R>/<Z>”.
_fix_zoneinfo_perms() {
    local _zi="${1:-$PAGAN_ROOT/usr/share/zoneinfo}"
    [[ -d "$_zi" ]] || return 0
    chmod 0755 "$_zi" 2>/dev/null || true
    # a+rX: czytanie dla wszystkich, wykonanie tylko tam, gdzie już było
    # (katalogi) – czyli bez rozdawania +x plikom danych.
    chmod -R a+rX "$_zi" 2>/dev/null || true
}

_install_tzdata() {
    local _zi="$PAGAN_ROOT/usr/share/zoneinfo"

    # Kanoniczny test: skompilowana strefa istnieje, a surowe pliki zniknęły.
    if [[ -f "$_zi/Europe/Warsaw" && ! -e "$_zi/europe" ]]; then
        # Strefy już są – ale napraw uprawnienia (stare ISO miały 0700).
        _fix_zoneinfo_perms "$_zi"
        return 0
    fi

    # zic: najpierw z targetu (właściwa wersja), potem z hosta (pliki TZif są
    # niezależne od architektury).
    local _zic="" _c
    for _c in "$PAGAN_ROOT/usr/bin/zic" "$PAGAN_ROOT/usr/sbin/zic" "$PAGAN_ROOT/bin/zic"; do
        [[ -x "$_c" ]] && { _zic="$_c"; break; }
    done
    if [[ -z "$_zic" ]]; then
        _zic="$(command -v zic 2>/dev/null || true)"
    fi
    if [[ -z "$_zic" ]]; then
        warn "tzdata: brak zic – strefy czasowe pozostaną nieskompilowane (Calamares padnie na wyborze strefy)"
        return 0
    fi

    # Źródło danych: rozpakowany tarball albo surowe pliki skopiowane przez
    # wcześniejszą, wadliwą receptę.
    local _src="" _d
    for _d in "$SRC_DIR"/tzdata-*; do
        [[ -f "$_d/europe" ]] && { _src="$_d"; break; }
    done
    local _tmp_src=""
    if [[ -z "$_src" && -f "$_zi/europe" ]]; then
        _tmp_src="$(mktemp -d)"
        local _f
        for _f in etcetera southamerica northamerica europe africa antarctica \
                  asia australasia backward leapseconds leap-seconds.list \
                  zone.tab zone1970.tab iso3166.tab; do
            [[ -f "$_zi/$_f" ]] && cp -f "$_zi/$_f" "$_tmp_src/"
        done
        _src="$_tmp_src"
    fi
    if [[ -z "$_src" ]]; then
        warn "tzdata: brak danych źródłowych (SRC_DIR/tzdata-* ani surowe pliki w zoneinfo) – pomijam"
        return 0
    fi

    info "  tzdata: kompilacja stref czasowych ($_zic)"
    local _out
    _out="$(mktemp -d)"
    mkdir -p "$_out/posix" "$_out/right"

    # -L /dev/null = strefy bez sekund przestępnych (domyślne),
    # /right = z sekundami przestępnymi (TZ=…right), 
    # ( cd … && … ) bo zic czyta pliki wejściowe z katalogu bieżącego.
    local _tz _ok=1
    for _tz in etcetera southamerica northamerica europe africa antarctica asia australasia backward; do
        ( cd "$_src" && "$_zic" -L /dev/null   -d "$_out"       "$_tz" ) || _ok=0
        ( cd "$_src" && "$_zic" -L /dev/null   -d "$_out/posix" "$_tz" ) || _ok=0
        ( cd "$_src" && "$_zic" -L leapseconds -d "$_out/right" "$_tz" ) || _ok=0
    done

    if [[ "$_ok" != 1 || ! -f "$_out/Europe/Warsaw" ]]; then
        warn "tzdata: kompilacja stref nie powiodła się – zostawiam poprzednie dane"
        rm -rf "$_out" "$_tmp_src"
        return 0
    fi

    # Pliki pomocnicze i posixrules (TZ=EST5EDT itp.).
    local _t
    for _t in zone.tab zone1970.tab iso3166.tab leapseconds leap-seconds.list; do
        [[ -f "$_src/$_t" ]] && cp -f "$_src/$_t" "$_out/"
    done
    ( cd "$_src" && "$_zic" -d "$_out" -p Europe/Warsaw ) || true

    # Podmiana atomowa: najpierw znikają surowe pliki źródłowe.
    rm -rf "$_zi"
    mv "$_out" "$_zi"
    # $_out pochodzi z mktemp -d (0700!) – bez tego poprawka nie ma sensu.
    _fix_zoneinfo_perms "$_zi"
    rm -rf "$_tmp_src"

    # Domyślna strefa systemu live (Calamares i tak ustawia ją w docelowym
    # systemie według wyboru użytkownika).
    if [[ ! -e "$PAGAN_ROOT/etc/localtime" ]]; then
        ln -sf /usr/share/zoneinfo/Europe/Warsaw "$PAGAN_ROOT/etc/localtime"
    fi

    ok "  tzdata: strefy skompilowane ($(find "$_zi" -type f | wc -l) plików, Europe/Warsaw ✓)"
}

# Zapisuje konfigurację Calamaresa: settings.conf + pliki modułów. Wydzielone,
# żeby korzystał z tego zarówno pełny build, jak i fix-calamares.sh (respin ISO
# nie może zostawić starego settings.conf).
#
# UWAGA: `modules-search: [ local ]` jest OBOWIĄZKOWE – Calamares buduje z niego
# listę ścieżek (Settings::interpretModulesSearch); bez tego klucza lista jest
# pusta i KAŻDY moduł kończy się „not found in module search paths”.
_write_calamares_config() {
    mkdir -p "$PAGAN_ROOT/etc/calamares/modules"

    cat > "$PAGAN_ROOT/etc/calamares/settings.conf" <<'CALEOF'
---
# Pagan Linux – Calamares: instalacja z live (squashfs przez moduł unpackfs).
modules-search: [ local ]

sequence:
  - show: [welcome, locale, keyboard, partition, users, netinstall, packagechooser, summary]
  - exec: [partition, mount, unpackfs, machineid, fstab, locale, keyboard, localecfg, users, networkcfg, packages, contextualprocess, displaymanager, hwclock, services-systemd, grubcfg, bootloader, umount]
  - show: [finished]

branding: paganlinux
CALEOF

    # Źródło systemu dla Calamaresa: drzewo squashfs wystawione przez initramfs
    # pod /live/root. NIE używamy /run/live/root – systemd montuje tmpfs na /run
    # i zasłoniłby ten katalog. sourcefs "file" = kopiowanie drzewa 1:1.
    # exclude: ikona instalatora ma być TYLKO na pulpicie live (root), nie w
    # zainstalowanym systemie.
    cat > "$PAGAN_ROOT/etc/calamares/modules/unpackfs.conf" <<'CALEOF'
---
unpack:
    -   source: "/live/root"
        sourcefs: "file"
        destination: ""
        exclude:
            - "/root/Desktop/pagan-calamares.desktop"
            - "/root/Desktop/pagan-wifi.desktop"
CALEOF

    cat > "$PAGAN_ROOT/etc/calamares/modules/partition.conf" <<'CALEOF'
---
# Przy automatycznym "Wymaż dysk" używamy GPT na BIOS i UEFI.
# Na BIOS+GPT Calamares wymaga ręcznego 8 MiB bios_grub z flagą BiosGrub;
# nie dodajemy go do layoutu, bo 3.3.14 nie ustawia tej flagi z partitionLayout.
defaultPartitionTableType: gpt
defaultFileSystemType: "ext4"
# f2fs USUNIĘTE: kernel nie ma CONFIG_F2FS_FS → instalacja na f2fs nie wstałaby.
availableFileSystemTypes: ["ext4","xfs","btrfs"]
# Jawny układ automatycznej instalacji. Na UEFI Calamares doda ESP
# automatycznie przed tym wpisem; BIOS+GPT wymaga ręcznego bios_grub.
partitionLayout:
    - name: "root"
      size: 100%
      filesystem: "ext4"
      mountPoint: "/"
# WYMAGANE przez schemat modułu (partition.schema.yaml: required: [userSwapChoices]).
userSwapChoices: [ none, file ]
# `enableLuks` NIE istnieje w Calamares 3.3 (był ignorowany). Szyfrowanie w UI
# włącza dopiero to:
enableLuksAutomatedPartitioning: true
luksGeneration: luks2
requiredStorage: 8.0
CALEOF

    # users.conf – zgodnie ze schematem wymagane są defaultGroups, autologinGroup
    # i sudoersGroup. Bez sudoersGroup nowy użytkownik NIE dostaje sudo.
    # Grupy (optical/storage/scanner/power/autologin) tworzy builder w /etc/group.
    cat > "$PAGAN_ROOT/etc/calamares/modules/users.conf" <<'CALEOF'
---
defaultGroups: [lp, video, audio, optical, storage, scanner, power, wheel]
autologinGroup: autologin
sudoersGroup: wheel
setRootPassword: true
doAutologin: false
user:
    shell: /usr/bin/fish
CALEOF

    # Moduł włącza menedżera logowania na ZAINSTALOWANYM systemie (lightdm) –
    # bez tego system wstaje do trybu tekstowego (brak GUI po instalacji).
    cat > "$PAGAN_ROOT/etc/calamares/modules/displaymanager.conf" <<'CALEOF'
---
displaymanagers: [lightdm]
basicSetup: false
CALEOF

    # UWAGA: nazwa pliku = nazwa modułu, a KLUCZ to `units` (nie `services`!).
    # services-systemd/main.py czyta cfg.get("units", []) – ze starą nazwą klucza
    # lightdm/NetworkManager NIE zostałyby włączone na zainstalowanym systemie
    # (brak GUI po instalacji).
    cat > "$PAGAN_ROOT/etc/calamares/modules/services-systemd.conf" <<'CALEOF'
---
units:
    - name: NetworkManager
      action: enable
    - name: lightdm
      action: enable
    - name: pipewire
      action: enable
    - name: sshd
      action: disable
CALEOF
    # ── Netinstall (dobór pakietów z repo PAG) + moduł packages ──
    #
    # `netinstall` sam NIE instaluje – zapisuje wybór do globalstorage, a moduł
    # `packages` (backend `pag`, dodany przez _install_pag_package_backend) woła
    # `pag install -y <pakiety>` w systemie docelowym. Dlatego:
    #  • netinstall jest w `show`, packages w `exec` PO unpackfs,
    #  • networkcfg idzie PRZED packages (system docelowy ma konfigurację sieci),
    #  • grupy mają critical: false → błąd pojedynczego pakietu nie przerywa
    #    instalacji (moduł packages dostaje je jako „try_install”).
    cat > "$PAGAN_ROOT/etc/calamares/modules/packages.conf" <<'CALEOF'
---
backend: pag
# Bez sieci pomiń krok pakietów zamiast przerywać instalację.
skip_if_no_internet: true
# Odśwież indeks repo w systemie docelowym ("pag sync").
update_db: true
# Pełny upgrade systemu podczas instalacji byłby zbyt wolny – pakiety i tak
# pochodzą z tego samego repozytorium co obraz.
update_system: false
CALEOF

    cat > "$PAGAN_ROOT/etc/calamares/modules/netinstall.conf" <<'CALEOF'
---
# Grupy pakietów trzymamy w tym samym pliku (groupsUrl: local).
groupsUrl: local
required: false
label:
    sidebar: "Pakiet dodatkowe"
    sidebar[pl]: "Pakiety dodatkowe"
    title: "Wybierz dodatkowe oprogramowanie"
    title[pl]: "Wybierz dodatkowe oprogramowanie"

groups:
    - name: "Przeglądarka"
      description: "Firefox ESR z repozytorium PaganOS"
      selected: false
      critical: false
      packages:
        - firefox

    - name: "Biuro"
      description: "LibreOffice z czytnikiem PDF (polskie tłumaczenie jest w pakiecie)"
      selected: false
      critical: false
      packages:
        - libreoffice

    - name: "Multimedia"
      description: "Kodeki, odtwarzacz i edycja grafiki"
      selected: false
      critical: false
      packages:
        - vlc
        - gimp
        - ffmpeg

    - name: "Narzędzia"
      description: "Podstawowe narzędzia użytkownika"
      selected: false
      critical: false
      packages:
        - htop
        - nano
        - git

    - name: "Polskie czcionki"
      description: "Dodatkowe kroje z pełną polską diakrytyką"
      selected: false
      critical: false
      packages:
        - dejavu-ttf
        - noto-fonts
        - noto-fonts-emoji

    # ── Grafika ──────────────────────────────────────────────────────────
    # mesa w obrazie ISO jest zbudowana pod instalator (softpipe, bez GLX/X11)
    # – repo ma pełne mesa (gallium all + Vulkan). Bez tej grupy desktop
    # zostaje na renderowaniu programowym.
    - name: "Akceleracja 3D (Mesa + Vulkan)"
      description: "Sprzętowe OpenGL/GLX i Vulkan – zalecane na każdym desktopie (mesa z repo ma pełne sterowniki)"
      selected: false
      critical: false
      packages:
        - mesa
        - libglvnd
        - vulkan-icd-loader

    - name: "Sterowniki AMD (amdgpu)"
      description: "Dla kart AMD Radeon: xf86-video-amdgpu + firmware amdgpu"
      selected: false
      critical: false
      packages:
        - xorg-xf86-video-amdgpu
        - amdgpu-firmware

    - name: "Sterowniki Intel"
      description: "Dla zintegrowanych kart Intel: xf86-video-intel + intel-media-driver (VA-API)"
      selected: false
      critical: false
      packages:
        - xorg-xf86-video-intel
        - intel-media-driver

    - name: "Sterowniki NVIDIA (DKMS)"
      description: "Dla kart NVIDIA: gotowe moduły nvidia-kernel (do jądra z repo) + nvidia-dkms. Instaluje pakiet kernel i kompiluje moduły – trwa dłużej"
      selected: false
      critical: false
      packages:
        - nvidia-kernel
        - nvidia-dkms

    # ── Sprzęt dodatkowy ─────────────────────────────────────────────────
    - name: "Drukarki i skanery"
      description: "CUPS, sterowniki drukarek i skanerów oraz narzędzie konfiguracji"
      selected: false
      critical: false
      packages:
        - cups
        - cups-filters
        - ghostscript
        - gutenprint
        - sane
        - system-config-printer

    - name: "Wirtualizacja"
      description: "QEMU i libvirt – maszyny wirtualne"
      selected: false
      critical: false
      packages:
        - qemu
        - libvirt
CALEOF

    # ── Dodatki po instalacji: packagechooser + contextualprocess ──
    #
    # `packagechooser` to JEDEN ekran wyboru (0 lub 1 pozycja). Zapisuje wybór
    # do globalstorage pod kluczem packagechooser_<instancja> (Config.cpp:
    # "packagechooser_" + key.id(); bez własnego instances = packagechooser_packagechooser),
    # a `contextualprocess` (moduł C++, bez Pythona) wykonuje komendę przypisaną
    # do tej wartości – już w systemie docelowym (dontChroot: false).
    #
    # Dlaczego nie `update_system: true` w packages.conf: moduł packages robi
    # upgrade PRZED instalacją wybranych pakietów, a my chcemy PO nich – i z
    # własnym DNS w chrocie. Dodatkowo `pag upgrade` (inaczej niż `pag update`)
    # przebudowuje initramfs i woła grub-update, a bootloaderem zajmuje się
    # późniejszy moduł Calamaresa (grubcfg/bootloader) – dlatego w tym kroku
    # aktualizujemy tylko PAKIETY.
    cat > "$PAGAN_ROOT/etc/calamares/modules/packagechooser.conf" <<'CALEOF'
---
# 0 lub 1 pozycja: "" albo id wybranego elementu.
mode: optional
# "legacy" = moduł tylko zapisuje wybór do globalstorage; wykonanie → contextualprocess.
method: legacy

labels:
    step: "Dodatki"
    step[pl]: "Dodatki"

items:
    - id: ""
      name: "Nic dodatkowego (najszybciej)"
      name[pl]: "Nic dodatkowego (najszybciej)"
      description: "System zainstaluje się dokładnie w wersji z obrazu. Pakiety można zaktualizować później: sudo pag upgrade"
      description[pl]: "System zainstaluje się dokładnie w wersji z obrazu. Pakiety można zaktualizować później: sudo pag upgrade"
      screenshot: ":/images/no-selection.png"

    - id: "upgrade"
      name: "Zaktualizuj pakiety z repozytorium"
      name[pl]: "Zaktualizuj pakiety z repozytorium"
      description: "Po instalacji pobiera indeks repo i aktualizuje pakiety z repozytorium PAG (m.in. jądro z obrazu). Wymaga internetu i trwa dłużej"
      description[pl]: "Po instalacji pobiera indeks repo i aktualizuje pakiety z repozytorium PAG (m.in. jądro z obrazu). Wymaga internetu i trwa dłużej"
      screenshot: "logo.png"
CALEOF

    cat > "$PAGAN_ROOT/etc/calamares/modules/contextualprocess.conf" <<'CALEOF'
---
# Uruchamiane w chrocie systemu docelowego.
dontChroot: false
# Limit: pełna aktualizacja na wolnym łączu potrafi potrwać.
timeout: 5400

# Klucz = wartość wybrana w packagechooser ("" = nic nie wybrano).
packagechooser_packagechooser:
    # Lista = osobne polecenia. Prefiks "-" ignoruje kod wyjścia – brak sieci
    # albo brak aktualizacji nie może wywalić całej instalacji.
    "upgrade":
        - "echo '[pagan] Aktualizacja pakietów z repo (pag update)...'"
        - test -s /etc/resolv.conf || printf 'nameserver 1.1.1.1\nnameserver 8.8.8.8\n' > /etc/resolv.conf
        - "-pag sync"
        - "-pag update -y"
    "": /bin/true
    "*": /bin/true
CALEOF

    # Sprzątanie: dawna nazwa pliku (moduł `services` nie istnieje w 3.3.14).
    rm -f "$PAGAN_ROOT/etc/calamares/modules/services.conf"

    # grubcfg: preferGrubEfi NIE jest kluczem tego modułu (nie ma go w źródłach
    # Calamares 3.3.14). Poprawne klucze: overwrite/kernel_params/defaults.
    # Konsola szeregowa (PAGAN_SERIAL_CONSOLE=1): przy testach w QEMU/KVM
    # `-serial file:log` daje pełny log startu ZAINSTALOWANEGO systemu jako tekst
    # (bez tego stage-2 w VM nie zapisuje nic – nie ma gdzie zobaczyć, dlaczego
    # system nie wstaje). console=tty0 PRZED ttyS0 zostawia jądro na ekranie;
    # ttyS0 jako ostatnia przejmuje /dev/console, żeby logi systemd trafiły na serial.
    local _kernel_params='[ "quiet", "splash" ]'
    if _is_true "$PAGAN_SERIAL_CONSOLE"; then
        _kernel_params='[ "quiet", "splash", "console=tty0", "console=ttyS0", "systemd.journald.forward_to_console=yes" ]'
    fi

    cat > "$PAGAN_ROOT/etc/calamares/modules/grubcfg.conf" <<CALEOF
---
overwrite: true
kernel_params: ${_kernel_params}
defaults:
    GRUB_TIMEOUT: 5
    GRUB_DEFAULT: "saved"
    GRUB_DISABLE_SUBMENU: true
    GRUB_TERMINAL_OUTPUT: "console"
    GRUB_DISABLE_RECOVERY: true
always_use_defaults: false
CALEOF

    # Kompletny odpowiednik domyślnego bootloader.conf z Calamares.
    #
    # UWAGA: Calamares NIE scala konfiguracji – Module::loadConfigurationFile()
    # bierze PIERWSZY istniejący plik z listy
    # (/etc/calamares/modules → /usr/share/calamares/modules). Nasz plik w /etc
    # zasłania domyślny, więc MUSI zawierać wszystkie klucze czytane przez
    # main.py jako configuration["…"] (bez .get/„in”). Brak np. grubInstall
    # dawał KeyError, który Calamares raportuje jako:
    #   „Bad main script file … for python job bootloader raised an exception”.
    cat > "$PAGAN_ROOT/etc/calamares/modules/bootloader.conf" <<'CALEOF'
---
# Bootloader: GRUB (BIOS i UEFI).
efiBootLoader: "grub"

# Wykrywanie jąder (systemd-boot/refind; dla GRUB-a nieszkodliwe).
kernelSearchPath: "/usr/lib/modules"
kernelPattern: "^vmlinuz.*"
loaderEntries:
  - "timeout 5"
  - "console-mode keep"
kernelParams: [ "quiet" ]

# Nazwy programów GRUB-a użytych przez moduł.
grubInstall: "grub-install"
grubMkconfig: "grub-mkconfig"
grubCfg: "/boot/grub/grub.cfg"
grubProbe: "grub-probe"

# Wymagane: modyfikowanie wpisów firmware (efi_boot_next).
efiBootMgr: "efibootmgr"

# Kopiuj GRUB-a na ścieżkę fallback (EFI/BOOT/BOOTX64.EFI).
installEFIFallback: true
CALEOF

    # networkcfg nie ma opcji konfiguracji – kopiuje konfigurację sieci z live
    # do docelowego systemu (writeNetworkManager nie istnieje w 3.3.14).
    cat > "$PAGAN_ROOT/etc/calamares/modules/networkcfg.conf" <<'CALEOF'
---
CALEOF

    # Pusty URL = przycisk ukryty (bool `false` byłby potraktowany jako adres).
    #
    # requirements: BEZ tej sekcji Calamares loguje „no valid requirements map
    # found in welcome module configuration” i nie sprawdza NIC (domyślnie
    # requiredStorage/requiredRam = -1). Dajemy konkretne progi pod desktop XFCE.
    #
    # „internet” jest w `check`, ale NIE w `required`: sprawdzenie ustawia
    # globalstorage["hasInternet"], którego używa moduł packages/netinstall
    # (skip_if_no_internet, pobranie indeksu repo), a brak sieci NIE blokuje
    # instalacji – użytkownik może ją zrobić offline.
    # Świadomie NIE sprawdzamy „power” (VM bez baterii).
    cat > "$PAGAN_ROOT/etc/calamares/modules/welcome.conf" <<'CALEOF'
---
showDonateUrl: ""
showSupportUrl: true
showKnownIssuesUrl: false
showReleaseNotesUrl: false

requirements:
    requiredStorage: 8.0
    requiredRam: 1.0
    check:
        - storage
        - ram
        - root
        - internet
    required:
        - ram
CALEOF

    # restartNowEnabled jest deprecated – używamy restartNowMode.
    cat > "$PAGAN_ROOT/etc/calamares/modules/finished.conf" <<'CALEOF'
---
restartNowMode: user-checked
restartNowCommand: "systemctl reboot"
notifyOnFinished: false
CALEOF
}

# ========================= CALAMARES INSTALLER =========================
install_calamares() {
    step "INSTALACJA CALAMARES (Instalator systemu)"

    export PATH="$TOOLS_DIR/bin:$PATH"
    export PKG_CONFIG_PATH="$PAGAN_ROOT/usr/lib/pkgconfig:$PAGAN_ROOT/usr/share/pkgconfig"
    # Pozbądź się zaległego Qt6 QML/Quick (6.11.1) i starych KF6-QML z targetu
    # (mieszanie z Qt6Core 6.11.2 = "symbol lookup error: libQt6Qml.so.6").
    _purge_stale_qt
    _fix_stale_qt_cmake
    # KPMcore w dwóch wersjach = backend KPMcore się nie załaduje.
    _purge_duplicate_kpmcore
    # Binarka jest, ale brakuje modułów z settings.conf (np. stary build
    # z -DWITH_PYTHON=OFF) → wymuś przebudowę, inaczej „Initialization Failed”.
    if [[ -x "$PAGAN_ROOT/usr/bin/calamares" ]] && ! _calamares_modules_ok; then
        warn "  Calamares: brak modułów ($(_calamares_missing_modules)) – wymuszam przebudowę"
        sed -i '/^calamares-3\.3\.14$/d' "$RESUME_FILE" 2>/dev/null || true
    fi
    # KPMcore/Calamares szukają ECM (share/ECM/cmake) i przez FindKF6.cmake
    # komponentów KF6 – wszystko w ROOTFSIE. Bez CMAKE_PREFIX_PATH cmake widzi
    # tylko hosta (który nie ma ECM) → „Could not find ECM”. Target-first.
    export CMAKE_PREFIX_PATH="$PAGAN_ROOT/usr:/usr"
    # Jawne wskazanie ECM – kpmcore/calamares używają go do FindKF6.cmake.
    [[ -f "$PAGAN_ROOT/usr/share/ECM/cmake/ECMConfig.cmake" ]] && \
        export ECM_DIR="$PAGAN_ROOT/usr/share/ECM/cmake"
    export LD_LIBRARY_PATH="$PAGAN_ROOT/usr/lib:/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
    export XDG_DATA_DIRS="$PAGAN_ROOT/usr/share${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}"

    # Calamares 3.3 wymaga Qt6 + KF6 oraz kpmcore (moduł partycjonowania).
    # Przy DE=XFCE stosu Qt6/KF6 nie ma – budujemy go (install_calamares_deps).
    # Gdy się nie powiedzie, fallback: repo PAG (`pag install calamares`).
    local _have_qt6=0 _have_kf6=0 _have_extra=1 _calamares_from_repo=0
    [[ -e "$PAGAN_ROOT/usr/lib/libQt6Core.so.6" ]] && _have_qt6=1
    [[ -d "$PAGAN_ROOT/usr/lib/kf6" || -e "$PAGAN_ROOT/usr/lib/libKF6CoreAddons.so.6" ]] && _have_kf6=1
    # Sprawdzamy też yaml-cpp i polkit-qt-1: bez nich kpmcore/calamares się nie
    # skonfigurują, a sam warunek Qt6/KF6 po restarcie pominąłby ich budowę.
    [[ -e "$PAGAN_ROOT/usr/lib/libyaml-cpp.so.0.8" || -e "$PAGAN_ROOT/usr/lib/libyaml-cpp.so" ]] || _have_extra=0
    [[ -e "$PAGAN_ROOT/usr/lib/cmake/PolkitQt6-1/PolkitQt6-1Config.cmake" ]] || _have_extra=0
    # KF6 CoreAddons metatypes (bez tego kpmcore nie skonfiguruje się – patrz
    # KDE_INSTALL_QTMETATYPESDIR w install_calamares_deps).
    [[ -e "$PAGAN_ROOT/usr/metatypes/qt6kf6coreaddons_metatypes.json" ]] || _have_extra=0
    # Stare configi KF6 eksportujące QML bez libki → trzeba przebudować KF6 (bez QML),
    # inaczej find_package(KF6Config/KF6I18n) rzuca twardy błąd.
    if grep -q "Qml" "$PAGAN_ROOT/usr/lib/cmake/KF6Config/KF6ConfigTargets.cmake" 2>/dev/null; then _have_extra=0; fi
    if grep -q "Qml" "$PAGAN_ROOT/usr/lib/cmake/KF6I18n/KF6I18nTargets.cmake" 2>/dev/null; then _have_extra=0; fi

    if [[ $_have_qt6 -ne 1 || $_have_kf6 -ne 1 || $_have_extra -ne 1 ]]; then
        info "  Brak Qt6/KF6 w rootfs – buduję stos zależności Calamares (Qt6 + KF6)..."
        if ( set +e; install_calamares_deps ); then
            ok "  ✓ stos Qt6 + KF6 zbudowany"
        else
            warn "  ✗ Budowa Qt6/KF6 nie powiodła się – próbuję repo PAG..."
            if _pag_install calamares; then
                ok "  ✓ calamares z repo PAG (zależności Qt6/KF6/kpmcore)"
                _calamares_from_repo=1
            elif [[ -x "$PAGAN_ROOT/usr/bin/calamares" ]]; then
                info "  ✓ calamares już obecny w rootfs"
                _calamares_from_repo=1
            else
                error "Brak Calamares: Qt6/KF6 nie zbudowane i brak w repo PAG (pomiń świadomie: --no-calamares)"
            fi
        fi
    fi

    if [[ "$_calamares_from_repo" != "1" ]]; then
        # os-prober – moduł partition woła go bezwarunkowo.
        if ! ( set +e; install_os_prober ); then
            warn "  ✗ os-prober: budowa nieudana (Calamares zaloguje 'os-prober cannot start.')"
        fi
        # Martwe pwquality (brak libcrack) – usuń, żeby moduł „users” się go nie
        # podlinkował i nie failował przy starcie Calamaresa.
        _purge_broken_pwquality
        # KPMcore + Calamares ze źródeł. Best-effort: awaria → repo PAG.
        if ! (
            set +e
            # KPMcore – biblioteka partycjonowania
            build_lfs_pkg "kpmcore" "26.04.3" \
                "https://download.kde.org/stable/release-service/26.04.3/src/kpmcore-26.04.3.tar.xz" \
                "SKIP" \
                "cmake -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -G Ninja && DESTDIR=\$PAGAN_ROOT ninja -C build install"
            # Calamares – MUSI mieć moduły Pythonowe (mount/unpackfs/fstab/
            # bootloader/services-systemd/…), inaczej brakuje modułów z settings.conf
            # („Initialization Failed”). Wymuszamy targetowy Python 3.14.
            # Bez QML (nie budujemy qtdeclarative); bez schemat-testów.
            build_lfs_pkg "calamares" "3.3.14" \
                "https://github.com/calamares/calamares/releases/download/v3.3.14/calamares-3.3.14.tar.gz" \
                "SKIP" \
                "cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DWITH_QT6=ON -DWITH_QML=OFF -DWITH_PYTHON=ON -DWITH_PYBIND11=ON -DCMAKE_DISABLE_FIND_PACKAGE_LibPWQuality=TRUE -DPython_EXECUTABLE=\$PAGAN_ROOT/usr/bin/python3 -DPython_ROOT_DIR=\$PAGAN_ROOT/usr -DBUILD_SCHEMA_TESTING=OFF -DBUILD_TESTING=OFF -DBUILD_CRASH_REPORTING=OFF -DINSTALL_CONFIG=ON && DESTDIR=\$PAGAN_ROOT ninja -C build install"
        ); then
            warn "  ✗ Budowa kpmcore/calamares nie powiodła się – próbuję repo PAG..."
            _pag_install calamares || warn "  ✗ Nie udało się także z repo PAG – brak instalatora"
        fi
    fi

    # Konfiguracja
    mkdir -p "$PAGAN_ROOT/etc/calamares"
    mkdir -p "$PAGAN_ROOT/usr/share/calamares/branding/paganlinux"

    # Wtyczki z <libdir>/qt6/plugins (kpmcore, kf6) muszą być widoczne w katalogu
    # runtime Qt (<prefix>/plugins) – inaczej backend KPMcore nie wstanie.
    _sync_qt_plugin_dirs

    _write_calamares_config

    _write_calamares_branding

    # Twarda kontrola: instalator MUSI istnieć. Gdy Calamares jest włączony
    # (domyślnie), brak instalatora = przerwanie builda (nie robimy ISO bez niego).
    # Świadome pominięcie: --no-calamares (wtedy ta funkcja w ogóle nie jest wołana).
    if [[ ! -x "$PAGAN_ROOT/usr/bin/calamares" ]]; then
        error "Calamares nie powstał – przerywam build (świadomie pominiesz instalator: --no-calamares)"
    fi
    # Twarda kontrola: instalator MUSI mieć komplet modułów używanych przez
    # settings.conf. Bez modułów Pythonowych Calamares odpali się, pokaże
    # „Initialization Failed” i NIE zainstaluje systemu.
    if ! _calamares_modules_ok; then
        error "Calamares: brakujące moduły [$(_calamares_missing_modules)] – zbuduj z -DWITH_PYTHON=ON -DWITH_PYBIND11=ON (WITH_QML=OFF) i bez -DWITH_PYTHON=OFF"
    fi
    # settings.conf MUSI mieć modules-search – inaczej zero modułów w search paths.
    if ! grep -q '^modules-search:' "$PAGAN_ROOT/etc/calamares/settings.conf" 2>/dev/null; then
        error "settings.conf bez 'modules-search' – Calamares nie znajdzie żadnego modułu"
    fi
    # Backend KPMcore musi być widoczny dla runtime Qt (inaczej qFatal przy partycjonowaniu).
    if ! _kpmcore_backend_ok; then
        error "Backend KPMcore niewidoczny w '$(_qt_runtime_plugin_dir)' – sprawdź _sync_qt_plugin_dirs"
    fi

    ok "Calamares Installer + Pagan branding ✓"
}

# ========================= PAG / PAGSYNC / PAGBUILD =========================
install_pag() {
    step "INSTALACJA PAG + PAGSYNC + PAGBUILD"

    # --- Python: pag używa wyłącznie stdlib; pagsync/pagbuild potrzebują PyYAML.
    #     pip/setuptools/wheel/flit-core/packaging/markupsafe/jinja2 + lxml –
    #     instalowane pipem (zgodnie z LFS 13.1). ---
    local _pmods=(pyyaml setuptools wheel flit-core packaging markupsafe jinja2 lxml)
    # Sprawdź WSZYSTKIE moduły – sam yaml nie wystarczy (resume-build ze starym
    # pyyaml pomijałby instalację nowych modułów).
    local _need_pip=0 _m _imp
    for _m in "${_pmods[@]}"; do
        _imp="${_m//-/_}"
        [[ "$_m" == "pyyaml" ]] && _imp="yaml"
        if ! chroot "$PAGAN_ROOT" /usr/bin/python3 -c "import $_imp" >/dev/null 2>&1; then
            _need_pip=1
            break
        fi
    done
    if [[ $_need_pip -eq 0 ]]; then
        info "  ✓ Moduły Pythona dostępne (${_pmods[*]})"
    else
        info "  ⬇ Instalowanie modułów Pythona (pip) w systemie docelowym..."
        # DNS dla chroot (hostowy resolv.conf; usuwany po instalacji – NM/systemd
        # wygenerują własny na starcie, nie chcemy wpisywać IP hosta w obraz).
        local _had_resolv=0
        [[ -e "$PAGAN_ROOT/etc/resolv.conf" ]] && _had_resolv=1
        cp -L /etc/resolv.conf "$PAGAN_ROOT/etc/resolv.conf" 2>/dev/null || true
        # Bootstrap pip: make install Pythona NIE instaluje pip3 (LFS robi to przez
        # `python3 -m ensurepip`). Bez tego pip3 nie istnieje i instalacja pada.
        # Fallback: gdy Python zbudowany bez ensurepip – pobierz oficjalny get-pip.py.
        if ! chroot "$PAGAN_ROOT" /usr/bin/python3 -m ensurepip --upgrade >/dev/null 2>&1; then
            warn "  ensurepip niedostępny – próbuję get-pip.py"
            if curl -fsSL https://bootstrap.pypa.io/get-pip.py -o "$PAGAN_ROOT/tmp/get-pip.py" 2>/dev/null; then
                chroot "$PAGAN_ROOT" /usr/bin/python3 /tmp/get-pip.py --break-system-packages >/dev/null 2>&1 || \
                    warn "  get-pip.py nie powiódł się – pip3 może być niedostępny"
            fi
        fi
        chroot "$PAGAN_ROOT" /usr/bin/pip3 install "${_pmods[@]}" --no-cache-dir --break-system-packages >/dev/null 2>&1 || \
        chroot "$PAGAN_ROOT" /usr/bin/pip3 install "${_pmods[@]}" --no-cache-dir >/dev/null 2>&1 || \
            warn "  ⚠ Nie udało się zainstalować modułów Pythona (${_pmods[*]}) – pag działa (stdlib), pagsync/pagbuild mogą wymagać pyyaml"
        if [[ $_had_resolv -eq 0 ]]; then
            rm -f "$PAGAN_ROOT/etc/resolv.conf"
        fi
    fi

    # ---- pag - natywny menedżer pakietów (pełna wersja v3, stdlib-only) ----
    # Źródło: $SCRIPT_DIR/pag (dev z repo) albo pełna wersja z hosta
    # (/usr/local/bin/pag – instalowana np. przez install-pag-fix.sh).
    local _pag_src="" _p
    if [[ -f "$SCRIPT_DIR/pag" ]]; then
        _pag_src="$SCRIPT_DIR/pag"
    elif command -v pag >/dev/null 2>&1 \
         && grep -q "PAG - Pagan Linux Package Manager v3" "$(command -v pag)" 2>/dev/null; then
        _pag_src="$(command -v pag)"
    else
        for _p in /usr/local/bin/pag /usr/bin/pag; do
            if [[ -f "$_p" ]] && grep -q "PAG - Pagan Linux Package Manager v3" "$_p" 2>/dev/null; then
                _pag_src="$_p"; break
            fi
        done
    fi
    if [[ -n "$_pag_src" ]]; then
        install -Dm755 "$_pag_src" "$PAGAN_ROOT/usr/bin/pag"
        info "  ✓ pag → /usr/bin/pag (ze: $_pag_src)"
    else
        warn "Brak źródła pag (szukałem: $SCRIPT_DIR/pag, pag w PATH, /usr/local/bin/pag) – menedżer pakietów nie zostanie zainstalowany"
    fi

    # Tłumaczenia paga (pag czyta PAG_LANG_DIR → /etc/pag/lang → /usr/share/pag/lang)
    if [[ -d "$SCRIPT_DIR/pag-lang" ]]; then
        mkdir -p "$PAGAN_ROOT/usr/share/pag/lang"
        cp -a "$SCRIPT_DIR/pag-lang/." "$PAGAN_ROOT/usr/share/pag/lang/"
        info "  ✓ pag: tłumaczenia → /usr/share/pag/lang"
    fi

    # Uwaga: pagsync i pagbuild to narzędzia BUILDERA (repo + budowa pakietów) –
    # celowo NIE trafiają do systemu live; są w repozytorium projektu.

    # --- Konfiguracja PAG: repozytorium + katalogi stanu + keyring ---
    mkdir -p "$PAGAN_ROOT/etc/pag/gpg"
    mkdir -p "$PAGAN_ROOT/var/lib/pag" "$PAGAN_ROOT/var/cache/pag"
    mkdir -p "$PAGAN_ROOT/var/cache/pagbuild/"{sources,output}
    mkdir -p "$PAGAN_ROOT/.pag_staging" "$PAGAN_ROOT/.deployments"

    # repos.conf – jedno repozytorium na linię (format PAG)
    cat > "$PAGAN_ROOT/etc/pag/repos.conf" <<EOF
# Pagan Linux – repozytoria PAG (jedno URL na linię)
# Ustawiane automatycznie przez buildera (PAGAN_REPO_URL / --repo).
${PAGAN_REPO_URL%/}
EOF
    chmod 644 "$PAGAN_ROOT/etc/pag/repos.conf"
    chmod 700 "$PAGAN_ROOT/etc/pag/gpg"

    ok "PAG + PAGSYNC + PAGBUILD zainstalowane i skonfigurowane!"
}

# ========================= KONFIGURACJA FISH =========================
# fish jako domyślna powłoka użytkowników oraz roota (zamiast bash). Ponieważ
# fish nie czyta /etc/profile.d/*.sh, autostart sesji live dla roota odtwarzamy
# w globalnym /etc/fish/config.fish.
configure_fish() {
    _is_true "${PAGAN_FISH:-1}" || { info "fish: pominięto (--no-fish), zostaje bash"; return 0; }
    [[ -x "$PAGAN_ROOT/usr/bin/fish" ]] || { warn "fish nieobecny w rootfs – zostaje bash"; return 0; }
    step "KONFIGURACJA FISH (domyślna powłoka użytkowników i roota)"

    # fish musi być zarejestrowany w /etc/shells (inaczej chsh/login odrzuci).
    grep -qx '/usr/bin/fish' "$PAGAN_ROOT/etc/shells" 2>/dev/null || \
        echo '/usr/bin/fish' >> "$PAGAN_ROOT/etc/shells"

    # Domyślna powłoka dla NOWYCH kont (useradd / Calamares), użytkownika live i roota.
    chroot "$PAGAN_ROOT" useradd -D -s /usr/bin/fish 2>/dev/null || true
    if chroot "$PAGAN_ROOT" id pagan >/dev/null 2>&1; then
        chroot "$PAGAN_ROOT" usermod -s /usr/bin/fish pagan 2>/dev/null || true
    fi
    chroot "$PAGAN_ROOT" usermod -s /usr/bin/fish root 2>/dev/null || true

    # Globalna konfiguracja fish (/etc/fish/config.fish). fish NIE czyta
    # /etc/profile.d, więc autostart sesji live na tty1 (root) jest tutaj.
    mkdir -p "$PAGAN_ROOT/etc/fish"
    cat > "$PAGAN_ROOT/etc/fish/config.fish" <<'FISHEOF'
# Pagan Linux – globalna konfiguracja fish (/etc/fish/config.fish)
fish_add_path /usr/local/sbin /usr/local/bin /usr/sbin /sbin

# Autostart sesji live na tty1 – odpowiednik /etc/profile.d/pagan-live.sh dla
# basha (fish go nie czyta). Tylko powłoka loginowa, tylko tty1, raz.
if status is-login; and status is-interactive
    if test (command tty 2>/dev/null) = /dev/tty1
        if command -q startx
            if not set -q DISPLAY; and not set -q WAYLAND_DISPLAY
                clear
                echo "PaganLinux – uruchamiam sesję live (startx)..."
                startx
                set -l xrc $status
                clear
                if test $xrc -ne 0
                    echo "Sesja X zakończyła się z kodem $xrc – logi (ostatnie linie):"
                    set -l xlog (ls -t /root/.local/share/xorg/Xorg.0.log /var/log/Xorg.0.log 2>/dev/null | head -n1)
                    if test -n "$xlog"
                        tail -n 20 "$xlog"
                    end
                end
                printf "\nUruchomić ponownie? [y/N] "
                read -l -n 1 _ans
                if string match -qi y -- "$_ans"
                    reboot
                end
            end
        end
    end
end
FISHEOF

    # Konfiguracja dla nowych użytkowników (/etc/skel) – bez Oh My Fish.
    mkdir -p "$PAGAN_ROOT/etc/skel/.config/fish"
    cat > "$PAGAN_ROOT/etc/skel/.config/fish/config.fish" <<'FISHEOF'
# Pagan Linux – domyślna konfiguracja fish
if status is-interactive
    set -g fish_greeting "Witaj w Pagan Linux"
    fish_add_path /usr/local/bin /usr/local/sbin
    abbr -a ll 'ls -lah'
    abbr -a update 'sudo pag update && sudo pag upgrade'
    abbr -a install 'sudo pag install'
    abbr -a search 'pag search'
end
FISHEOF
    info "  ✓ fish: /etc/shells + powłoka roota i użytkowników + /etc/fish/config.fish"
}

# ========================= INITRAMFS (ROLLING) =========================
# pagan-initramfs – przebudowa initramfs na żywym systemie po aktualizacji jądra.
# Priorytet: mkinitramfs (NuTyX) → dracut → mkinitcpio → własny busybox+cpio.
install_initramfs_tool() {
    step "INSTALACJA NARZĘDZIA INITRAMFS (ROLLING)"

    cat > "$PAGAN_ROOT/usr/bin/pagan-initramfs" <<'EOF'
#!/bin/bash
# pagan-initramfs – przebudowa initramfs Pagan Linux (rolling: po aktualizacji jądra)
# Kolejność: mkinitramfs (NuTyX) → dracut → mkinitcpio → własny busybox+cpio.

[ "$(id -u)" = "0" ] || { echo "Uruchom jako root"; exit 1; }

kver() { ls -1 /usr/lib/modules 2>/dev/null | head -n1; }

if command -v mkinitramfs >/dev/null 2>&1 && [ -n "$(kver)" ]; then
    echo "mkinitramfs $(kver)..."
    mkinitramfs "$(kver)" && { echo "✅ Initramfs zbudowany (mkinitramfs)"; exit 0; }
fi
if command -v dracut >/dev/null 2>&1; then
    echo "dracut..."
    dracut --force /boot/initramfs.img && { echo "✅ Initramfs zbudowany (dracut)"; exit 0; }
fi
if command -v mkinitcpio >/dev/null 2>&1; then
    echo "mkinitcpio..."
    mkinitcpio -g /boot/initramfs.img && { echo "✅ Initramfs zbudowany (mkinitcpio)"; exit 0; }
fi

# --- Fallback: własny initramfs (busybox + cpio) ---
KV="$(kver)"
[ -n "$KV" ] || { echo "❌ Brak modułów w /usr/lib/modules"; exit 1; }
echo "Fallback: busybox+cpio (initramfs dla $KV)..."

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
mkdir -p "$TMP"/{bin,dev,proc,sys,run,new_root,etc,lib,lib64,usr,sbin}

cat > "$TMP/init" <<'INITEOF'
#!/bin/sh
# Pagan Linux initramfs (minimal, dysk + deploymenty) – fallback pagan-initramfs
mount -t proc proc /proc 2>/dev/null
mount -t sysfs sysfs /sys 2>/dev/null
mount -t devtmpfs dev /dev 2>/dev/null || mount -t tmpfs dev /dev 2>/dev/null
mkdir -p /run /new_root /etc /bin /sbin /usr/bin /usr/sbin

ROOT_DEV=""
CRYPT_DEV_ARG=""
for arg in $(cat /proc/cmdline 2>/dev/null); do
    case "$arg" in
        root=*) ROOT_DEV="${arg#root=}" ;;
        cryptdevice=*) CRYPT_DEV_ARG="${arg#cryptdevice=}" ;;
    esac
done
[ -n "$ROOT_DEV" ] || ROOT_DEV="/dev/sda1"

# ---- LUKS: odblokowanie roota (cryptdevice= lub wprost partycja LUKS) ----
unlock_luks() {
    local dev="$1" name="$2" pass
    [ -n "$name" ] || name="cryptroot"
    mkdir -p /dev/mapper
    mknod /dev/mapper/control c 10 236 2>/dev/null
    modprobe dm-crypt 2>/dev/null || true
    if ! command -v cryptsetup >/dev/null 2>&1; then
        echo "[pagan-init] BŁĄD: initramfs nie ma cryptsetup – nie mogę odblokować $dev"
        return 1
    fi
    stty -echo 2>/dev/null
    printf "Hasło (LUKS %s): " "$dev"
    read -r pass
    stty echo 2>/dev/null
    echo
    if ! printf '%s\n' "$pass" | cryptsetup open "$dev" "$name" 2>/dev/null; then
        pass=""
        echo "[pagan-init] BŁĄD: nie udało się odblokować $dev (złe hasło?)"
        return 1
    fi
    pass=""
    echo "[pagan-init] Odblokowano: /dev/mapper/$name"
    return 0
}

if [ -n "$CRYPT_DEV_ARG" ]; then
    crypt_src="${CRYPT_DEV_ARG%%:*}"; crypt_name="${CRYPT_DEV_ARG#*:}"; crypt_name="${crypt_name%%:*}"
    crypt_dev=""
    case "$crypt_src" in
        UUID=*)
            want="${crypt_src#UUID=}"
            for d in /dev/sd*[0-9] /dev/hd*[0-9] /dev/vd*[0-9] /dev/nvme*[0-9]n[0-9]p[0-9]* /dev/mmcblk*p[0-9]*; do
                [ -b "$d" ] || continue
                [ "$(blkid -s UUID -o value "$d" 2>/dev/null)" = "$want" ] && { crypt_dev="$d"; break; }
            done ;;
        /dev/*) crypt_dev="$crypt_src" ;;
    esac
    if [ -n "$crypt_dev" ] && [ -b "$crypt_dev" ]; then
        if unlock_luks "$crypt_dev" "$crypt_name"; then
            ROOT_DEV="/dev/mapper/$crypt_name"
        else
            echo "[pagan-init] Powłoka ratunkowa:"; exec /bin/sh
        fi
    else
        echo "[pagan-init] BŁĄD: cryptdevice nie znaleziono ($crypt_src)"; exec /bin/sh
    fi
fi

i=0
while [ ! -e "$ROOT_DEV" ] && [ "$i" -lt 10 ]; do sleep 1; i=$((i+1)); done

# Fallback: root= wskazuje wprost na partycję LUKS
if blkid -t TYPE=crypto_LUKS "$ROOT_DEV" >/dev/null 2>&1; then
    echo "[pagan-init] Wykryto partycję LUKS na root ($ROOT_DEV)"
    if unlock_luks "$ROOT_DEV" cryptroot; then
        ROOT_DEV="/dev/mapper/cryptroot"
    else
        echo "[pagan-init] Powłoka ratunkowa:"; exec /bin/sh
    fi
fi

mount -t auto -o rw "$ROOT_DEV" /new_root 2>/dev/null || {
    echo "[pagan-init] BŁĄD: nie można zamontować roota ($ROOT_DEV)"; exec /bin/sh; }

# Deploymenty PAG (immutable OS)
ACTIVE="/new_root/.deployments/active"
DEP=""
if [ -L "$ACTIVE" ]; then
    DEP=$(readlink "$ACTIVE")
    [ -n "$DEP" ] && DEP="/new_root/.deployments/$DEP"
elif [ -d "/new_root/.deployments" ]; then
    DEP=$(ls -1 /new_root/.deployments/ 2>/dev/null | grep -v active | sort -r | head -1)
    [ -n "$DEP" ] && DEP="/new_root/.deployments/$DEP"
fi
[ -n "$DEP" ] && [ -d "$DEP" ] && NEWROOT="$DEP" || NEWROOT="/new_root"

mount --move /proc "$NEWROOT/proc" 2>/dev/null
mount --move /sys "$NEWROOT/sys" 2>/dev/null
mount --move /dev "$NEWROOT/dev" 2>/dev/null
exec switch_root "$NEWROOT" /sbin/init
INITEOF
chmod +x "$TMP/init"

BB=""
for p in /usr/bin/busybox /bin/busybox; do [ -x "$p" ] && BB="$p" && break; done
if [ -n "$BB" ]; then
    cp "$BB" "$TMP/bin/busybox"
    chmod 755 "$TMP/bin/busybox"
    for c in sh mount umount ls cat echo sleep readlink mkdir switch_root cp rm ln grep poweroff reboot \
             blkid mknod stty vi; do
        ln -sf busybox "$TMP/bin/$c"
    done
else
    for t in bash mount umount ls cat echo sleep readlink mkdir switch_root cp rm ln grep blkid; do
        s=$(command -v "$t" 2>/dev/null) && [ -n "$s" ] && [ -f "$s" ] && cp "$s" "$TMP/bin/"
    done
fi

# cryptsetup + biblioteki (obsługa LUKS) – jeśli system go ma
CS=""
for p in /usr/sbin/cryptsetup /usr/bin/cryptsetup /sbin/cryptsetup; do [ -x "$p" ] && CS="$p" && break; done
if [ -n "$CS" ]; then
    mkdir -p "$TMP/usr/sbin"
    cp "$CS" "$TMP/usr/sbin/cryptsetup"
    chmod 755 "$TMP/usr/sbin/cryptsetup"
    for lib in $(ldd "$CS" 2>/dev/null | awk '/=>/ {print $3} /^[[:space:]]*\// {print $1}'); do
        [ -f "$lib" ] || continue
        mkdir -p "$TMP/${lib%/*}"
        cp "$lib" "$TMP$lib"
    done
    loader=$(ldd "$CS" 2>/dev/null | awk '/ld-linux/ {print $1}')
    if [ -n "$loader" ] && [ -f "$loader" ]; then
        mkdir -p "$TMP/${loader%/*}"
        cp "$loader" "$TMP$loader"
    fi
    echo "cryptsetup → initramfs (LUKS)"
else
    echo "Brak cryptsetup – initramfs nie obsłuży LUKS (pag install cryptsetup)"
fi

[ -f /etc/pagan-release ] && cp /etc/pagan-release "$TMP/etc/"
cd "$TMP"
find . | cpio -oH newc 2>/dev/null | gzip > /boot/initramfs.img
echo "✅ Initramfs zbudowany (fallback busybox+cpio)"
exit 0
EOF
    chmod +x "$PAGAN_ROOT/usr/bin/pagan-initramfs"
    ok "Narzędzie initramfs (rolling) zainstalowane!"
}

# ========================= MENEDŻER STEROWNIKÓW (PAG – ROLLING) =========================
# Wszystkie sterowniki i jądro to pakiety z repo → aktualizacja bez nowego ISO
# (model Arch, nie point-release jak Debian).
install_driver_manager() {
    step "INSTALACJA MENEDŻERA STEROWNIKÓW (PAG/ROLLING)"

    # pciutils (lspci) – pagan-drivers rozpoznaje GPU właśnie przez `lspci -nn`.
    # Bez tego `pagan-drivers auto/detect` nie ma czym wykryć karty (a grupy
    # „Sterowniki …” w instalatorze opierają się na tej samej wiedzy użytkownika).
    build_lfs_pkg "pciutils" "3.15.0" \
        "https://www.kernel.org/pub/software/utils/pciutils/pciutils-3.15.0.tar.xz" \
        "SKIP" \
        "make PREFIX=/usr SBINDIR=/usr/bin SHAREDIR=/usr/share/misc MANDIR=/usr/share/man SHARED=yes ZLIB=no LIBKMOD=no && make PREFIX=\$PAGAN_ROOT/usr SBINDIR=\$PAGAN_ROOT/usr/bin SHAREDIR=\$PAGAN_ROOT/usr/share/misc MANDIR=\$PAGAN_ROOT/usr/share/man SHARED=yes ZLIB=no LIBKMOD=no install install-lib && chmod 755 \$PAGAN_ROOT/usr/lib/libpci.so.3.15.0 2>/dev/null || true"

    cat > "$PAGAN_ROOT/usr/bin/pagan-drivers" <<'EOF'
#!/bin/bash
# pagan-drivers – menedżer sterowników i jądra Pagan Linux (rolling przez PAG).
# Sterowniki/jądro to pakiety z repo → pagan-drivers update / kernel aktualizują
# system BEZ czekania na nowe ISO.
#
# Użycie:
#   pagan-drivers detect     – wykryj sprzęt (lspci)
#   pagan-drivers status     – wersja jądra + zainstalowane sterowniki
#   pagan-drivers auto       – wykryj GPU i zainstaluj właściwy sterownik
#   pagan-drivers nvidia     – NVIDIA (userland + moduły jądra)  [pag install nvidia-dkms nvidia-kernel]
#   pagan-drivers amd        – AMDGPU (xf86-video-amdgpu + firmware)
#   pagan-drivers intel      – Intel (xf86-video-intel + media driver)
#   pagan-drivers kernel     – zaktualizuj jądro z repo (kernel) + initramfs + GRUB
#   pagan-drivers initramfs  – przebudowa initramfs
#   pagan-drivers update     – pełna aktualizacja systemu (pag upgrade)

GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
PAG="pag"

[ "$(id -u)" = "0" ] || { echo -e "${RED}Uruchom jako root (sudo)${NC}"; exit 1; }

have() { command -v "$1" >/dev/null 2>&1; }

gpu_vendor() {
    have lspci || return 0
    lspci -nn 2>/dev/null | grep -iE "vga|3d|display" | grep -oiE "nvidia|amd|ati|intel" | head -n1 | tr 'A-Z' 'a-z'
}

install_pkgs() {
    echo -e "${CYAN}  $PAG update && $PAG install $*${NC}"
    PAG_YES=1 "$PAG" update >/dev/null 2>&1 || true
    PAG_YES=1 "$PAG" install "$@" || { echo -e "${RED}Instalacja nieudana (sprawdź repo: $PAG repo-list)${NC}"; return 1; }
}

detect() {
    if ! have lspci; then
        echo -e "${YELLOW}Brak narzędzia lspci (pciutils) – doinstaluj: sudo pag install pciutils${NC}"
        return 1
    fi
    echo "Wykrywanie sprzętu..."
    echo -e "\n${YELLOW}GPU:${NC}"
    lspci | grep -E "VGA|3D|Display" || echo "  Brak"
    echo -e "\n${YELLOW}Sieć:${NC}"
    lspci | grep -E "Ethernet|Network|Wi-Fi" || echo "  Brak"
    echo -e "\n${YELLOW}Audio:${NC}"
    lspci | grep -E "Audio|Sound" || echo "  Brak"
}

status() {
    echo -e "${YELLOW}Jądro:${NC} $(uname -r 2>/dev/null)"
    echo -e "${YELLOW}GPU:${NC}   $(lspci -nn 2>/dev/null | grep -iE 'vga|3d|display' | head -n1 || echo 'brak')"
    echo -e "${YELLOW}Pakiety:${NC}"
    "$PAG" list 2>/dev/null | grep -iE "^(kernel|linux|nvidia|mesa|xorg-xf86-video|xorg-server)" || true
}

nvidia() {
    echo "⬇ NVIDIA (userland + moduły jądra) z repo..."
    # Pakiet z receptury nazywa się nvidia-dkms (dawne „nvidia” nie istnieje w repo).
    # nvidia-kernel ciągnie pakiet `kernel` (jądro rolling z repo) jako zależność.
    install_pkgs nvidia-dkms nvidia-kernel || return 1
    local kv kver
    kv=$(uname -r 2>/dev/null)
    if have modinfo; then
        kver=$(modinfo -F vermagic nvidia 2>/dev/null | awk '{print $1}')
        if [ -n "$kver" ] && [ "$kver" != "$kv" ]; then
            echo -e "${YELLOW}⚠ Moduły nvidia zbudowane pod $kver, działasz na $kv.${NC}"
            echo -e "${YELLOW}  Poczekaj na nvidia-kernel dla $kv lub zaktualizuj jądro: pagan-drivers kernel${NC}"
        else
            echo -e "${GREEN}✅ NVIDIA gotowe (moduły pasują do jądra)${NC}"
        fi
    fi
}

amd() {
    echo "⬇ AMDGPU (xf86-video-amdgpu + firmware) z repo..."
    install_pkgs xorg-xf86-video-amdgpu amdgpu-firmware || return 1
    echo -e "${GREEN}✅ AMD gotowe${NC}"
}

intel() {
    echo "⬇ Intel (xf86-video-intel + media driver) z repo..."
    install_pkgs xorg-xf86-video-intel intel-media-driver || return 1
    echo -e "${GREEN}✅ Intel gotowe${NC}"
}

kernel() {
    echo "Aktualizacja jądra (kernel z repo) + initramfs + GRUB..."
    # W repo jądro to pakiet `kernel` (kernel-lts / kernel-mainline = alternatywy);
    # nagłówki/źródła DKMS są w tym samym pakiecie (/usr/src/kernel).
    install_pkgs kernel || return 1
    echo -e "${GREEN}✅ Jądro zaktualizowane (initramfs + GRUB przebudowane hookiem)${NC}"
}

initramfs() {
    echo "Przebudowa initramfs..."
    if have pagan-initramfs; then pagan-initramfs; else "$PAG" initramfs-update; fi
}

update() {
    echo "Pełna aktualizacja systemu (rolling)..."
    PAG_YES=1 "$PAG" update || true
    PAG_YES=1 "$PAG" upgrade || return 1
    echo -e "${GREEN}✅ System zaktualizowany${NC}"
}

auto() {
    detect
    local v
    v=$(gpu_vendor)
    echo -e "\n${CYAN}Wykryto GPU: ${v:-nieznany}${NC}"
    case "$v" in
        nvidia) nvidia ;;
        amd|ati) amd ;;
        intel) intel ;;
        *) echo -e "${YELLOW}Brak rozpoznanego GPU – domyślne (mesa) już działa.${NC}" ;;
    esac
}

case "${1:-help}" in
    detect) detect ;;
    status) status ;;
    nvidia) nvidia ;;
    amd) amd ;;
    intel) intel ;;
    kernel) kernel ;;
    initramfs) initramfs ;;
    update) update ;;
    auto) auto ;;
    *) echo "Użycie: pagan-drivers {detect|status|nvidia|amd|intel|kernel|initramfs|update|auto}" ;;
esac
EOF

    chmod +x "$PAGAN_ROOT/usr/bin/pagan-drivers"
    ok "Menedżer sterowników (PAG/rolling) zainstalowany!"
}

# ========================= PAKIET KERNELA JAKO ROLLING (.pag) =========================
# Po build_kernel pakujemy jądro jako pakiet `kernel` (metadata.json + data.tar.xz
# + hooks/post-install) i instalujemy przez PAG. Dzięki temu pag "widzi" jądro
# w bazie: `pag upgrade` / `pagan-drivers kernel` aktualizują je z repo bez nowego ISO.
#
# NAZWA: `kernel` – DOKŁADNIE tak, jak jądro w repozytorium PAG
# (recipes/utils/kernel). Wcześniej pakiet nazywał się `linux`, przez co
# `pag upgrade` NIGDY nie ruszył jądra (repo nie ma pakietu `linux`), a
# `nvidia-kernel` (depends: kernel) nie widział zainstalowanego jądra.
# Dla kompatybilności starych skryptów i przyzwyczajeń pakiet deklaruje
# `provides: ["linux"]` – `pag install linux` nadal działa (rozwiąże się na kernel).
install_rolling_kernel() {
    step "PAKIET KERNELA JAKO ROLLING (kernel-${KERNEL_VERSION})"

    if ! _is_true "${ROLLING_KERNEL:-1}"; then
        info "Pomijam pakiet rolling kernela (ROLLING_KERNEL=0)"
        return 0
    fi
    [[ -x "$PAGAN_ROOT/usr/bin/pag" ]] || { warn "pag nie zainstalowany – pomijam rolling kernel"; return 0; }

    local kver="${KERNEL_VERSION}-pagan"
    local pkgdir="$PAGAN_ROOT/tmp/kernel-pkg"
    local tmp="$PAGAN_ROOT/tmp/kernel-pag"
    local pag_file="$PAGAN_ROOT/tmp/kernel-${KERNEL_VERSION}-1.pag"

    rm -rf "$pkgdir" "$tmp"
    mkdir -p "$pkgdir/boot" "$pkgdir/usr/lib/modules" "$tmp/hooks"

    # boot (rozwiń symlinki)
    cp -L "$PAGAN_ROOT/boot/vmlinuz-${kver}" "$pkgdir/boot/vmlinuz-${kver}" 2>/dev/null \
        || { warn "brak vmlinuz-${kver} – pomijam rolling kernel"; return 0; }
    ln -sf "vmlinuz-${kver}" "$pkgdir/boot/vmlinuz-linux"
    cp -L "$PAGAN_ROOT/boot/System.map-${kver}" "$pkgdir/boot/System.map-${kver}" 2>/dev/null || true
    ln -sf "System.map-${kver}" "$pkgdir/boot/System.map"
    cp -L "$PAGAN_ROOT/boot/config-${kver}" "$pkgdir/boot/config-${kver}" 2>/dev/null || true

    # moduły (usrmerge: /lib → usr/lib)
    local modsrc=""
    if [[ -d "$PAGAN_ROOT/usr/lib/modules/${kver}" ]]; then modsrc="$PAGAN_ROOT/usr/lib/modules/${kver}"
    elif [[ -d "$PAGAN_ROOT/lib/modules/${kver}" ]]; then modsrc="$PAGAN_ROOT/lib/modules/${kver}"; fi
    if [[ -n "$modsrc" ]]; then
        mkdir -p "$pkgdir/usr/lib/modules"
        # cp -a (bez -L): build/source to absolutne symlinki – ich NIE wolno
        # rozwijać, bo cp -aL skopiowałby całe drzewo źródłowe kernela do pakietu
        cp -a "$modsrc" "$pkgdir/usr/lib/modules/"
        # absolutne symlinki build/source są blokowane przez pag (security);
        # rm -rf, bo po błędnym cp -aL mogły zostać jako katalogi
        rm -rf "$pkgdir/usr/lib/modules/${kver}"/{build,source}
    fi

    # hook post-install: przebudowa initramfs + GRUB po instalacji jądra
    cat > "$tmp/hooks/post-install" <<'HOOK'
#!/bin/bash
# Czysty locale – unikaj "cannot change locale (C.UTF-8)" przy hookach
export LC_ALL=C LANG=C
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KV="$(uname -r 2>/dev/null)"
if [ -d "/usr/src/linux-headers-${KV}" ] && [ -d "/usr/lib/modules/${KV}" ]; then
    ln -sfn "/usr/src/linux-headers-${KV}" "/usr/lib/modules/${KV}/build"
    ln -sfn "/usr/src/linux-headers-${KV}" "/usr/lib/modules/${KV}/source"
fi
if command -v pagan-initramfs >/dev/null 2>&1; then pagan-initramfs; fi
# pag grub-update NIE może działać podczas pag install (lock jest trzymany)
if command -v pag >/dev/null 2>&1 && [ ! -f /var/lib/pag/pag.lock ]; then pag grub-update; fi
HOOK
    chmod +x "$tmp/hooks/post-install"

    # data.tar.xz (ścieżki względne – bezpieczne dla pag)
    tar -cJf "$tmp/data.tar.xz" -C "$pkgdir" .

    # metadata.json (format PAG)
    local size
    size=$(du -sb "$pkgdir" 2>/dev/null | cut -f1)
    cat > "$tmp/metadata.json" <<EOF
{"name":"kernel","version":"${KERNEL_VERSION}","description":"Pagan Linux kernel (rolling)","dependencies":[],"provides":["linux"],"size":${size:-0},"repo":"local","filename":"kernel-${KERNEL_VERSION}-1.pag"}
EOF

    ( cd "$tmp" && tar -cJf "$pag_file" metadata.json data.tar.xz hooks )

    info "Rejestracja kernela w PAG (pag install kernel)..."
    # pag po instalacji odświeża cache ld.so, a to wymaga /dev/null WEWNĄTRZ
    # chroota. Gdy /dev nie jest zamontowane, pag tylko ostrzega („No such file
    # or directory: '/dev/null'”) – stwórzmy więc minimalne węzły urządzeń.
    if [[ ! -e "$PAGAN_ROOT/dev/null" ]]; then
        mkdir -p "$PAGAN_ROOT/dev"
        mknod -m 666 "$PAGAN_ROOT/dev/null" c 1 3 2>/dev/null || true
        mknod -m 666 "$PAGAN_ROOT/dev/zero" c 1 5 2>/dev/null || true
        mknod -m 666 "$PAGAN_ROOT/dev/tty" c 5 0 2>/dev/null || true
        mknod -m 644 "$PAGAN_ROOT/dev/random" c 1 8 2>/dev/null || true
        mknod -m 644 "$PAGAN_ROOT/dev/urandom" c 1 9 2>/dev/null || true
    fi
    if chroot "$PAGAN_ROOT" /usr/bin/env PAG_YES=1 /usr/bin/pag install "/tmp/kernel-${KERNEL_VERSION}-1.pag"; then
        ok "Kernel spakowany i zarejestrowany w PAG jako kernel (rolling: pagan-drivers kernel / pag upgrade)"
    else
        warn "pag install kernela nieudany – przywracam kernel z build_kernel"
        cp -a "$pkgdir/boot/." "$PAGAN_ROOT/boot/" 2>/dev/null || true
        [[ -d "$pkgdir/usr/lib/modules/${kver}" ]] \
            && cp -a "$pkgdir/usr/lib/modules/${kver}" "$PAGAN_ROOT/usr/lib/modules/" 2>/dev/null || true
    fi

    rm -rf "$pkgdir" "$tmp" "$pag_file"
}

# GRUB a nazwy initramfs – KLUCZOWE dla rozruchu ZAINSTALOWANEGO systemu.
#
# Calamares (moduł `bootloader`) regeneruje /boot/grub/grub.cfg poleceniem
# `grub-mkconfig`, które uruchamia /etc/grub.d/10_linux. Ten skrypt wylicza
# wersję jądra z NAZWY pliku i szuka initrd WYŁĄCZNIE pod nazwami z wersją:
#   initrd.img-<ver>, initrd-<ver>.img, initramfs-<ver>.img, …
# gdzie <ver> = `basename vmlinuz-<ver> | sed 's,^[^0-9]*-,,g'`,
# czyli dla /boot/vmlinuz-7.2.5-pagan → 7.2.5-pagan.
#
# Nasz initramfs nazywa się /boot/initramfs.img, więc NIE pasuje do żadnego
# wzorca → grub-mkconfig generuje menuentry BEZ linii `initrd`. Skutek zależy
# tylko od tego, co jest wkompilowane na sztywno w jądro: przy sterownikach
# modułowych kończy się to „VFS: Cannot open root device” / paniką jądra.
#
# Dodatkowo /boot/vmlinuz-linux jest symlinkiem, a 10_linux go NIE odrzuca
# (grub_file_is_not_garbage sprawdza tylko *.dpkg-*, *.rpmnew, README*, *.sig),
# więc powstaje DRUGI wpis menu z wersją `linux`. On też musi mieć initrd, bo
# właśnie on bywa wpisem domyślnym (kolejność z sortowania wersji).
#
# Rozwiązanie: dla każdego /boot/vmlinuz* tworzymy alias
# /boot/initramfs-<ver>.img → initramfs.img. Symlink, a nie kopia, żeby
# przebudowa initramfs (`pagan-initramfs`) automatycznie go odświeżała;
# unpackfs kopiuje drzewo rsynciem -aHAXSr, więc symlinki przechodzą do
# zainstalowanego systemu bez zmian.
#
# Idempotentne; wołane z build_initramfs, finalize_build i fix-calamares.sh.
_ensure_grub_boot_artifacts() {
    local _bd="$PAGAN_ROOT/boot" _k _b _v _n=0

    # /etc/default/grub: bez tego pliku grub-mkconfig działa „na domyślnych”
    # i nie da się ustawić GRUB_DISTRIBUTOR ani parametrów jądra poza
    # Calamaresem (np. przy `pag grub-update` po aktualizacji jądra).
    # Moduł grubcfg Calamaresa i tak go potem scala z grubcfg.conf.
    if [[ -x "$PAGAN_ROOT/usr/bin/grub-mkconfig" && ! -f "$PAGAN_ROOT/etc/default/grub" ]]; then
        mkdir -p "$PAGAN_ROOT/etc/default"
        cat > "$PAGAN_ROOT/etc/default/grub" <<'GRUBDEF'
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="PaganLinux"
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""
GRUB_TERMINAL_OUTPUT="console"
GRUB_DISABLE_SUBMENU=true
GRUB_DISABLE_RECOVERY=true
GRUB_DISABLE_LINUX_UUID=false
GRUBDEF
        ok "  grub: /etc/default/grub utworzony (wcześniej go brakowało)"
    fi

    [[ -f "$_bd/initramfs.img" ]] || return 0

    for _k in "$_bd"/vmlinuz*; do
        [[ -e "$_k" ]] || continue
        _b="$(basename "$_k")"
        # Dokładnie jak /etc/grub.d/10_linux: s,^[^0-9]*-,,g
        _v="$(printf '%s' "$_b" | sed -e 's,^[^0-9]*-,,g')"
        [[ -n "$_v" && "$_v" != "$_b" ]] || continue
        if [[ ! -e "$_bd/initramfs-$_v.img" ]]; then
            ln -sfn initramfs.img "$_bd/initramfs-$_v.img"
            _n=$((_n + 1))
        fi
        # „initrd.img-<ver>” – 10_linux sprawdza ten wzorzec jako pierwszy,
        # a używają go też inne narzędzia.
        if [[ ! -e "$_bd/initrd.img-$_v" ]]; then
            ln -sfn initramfs.img "$_bd/initrd.img-$_v"
            _n=$((_n + 1))
        fi
    done

    if ((_n > 0)); then
        ok "  grub: aliasy initramfs-<wersja>.img ($_n) – grub-mkconfig doda linię initrd"
    fi
}

# ========================= KONFIGURACJA GRUB =========================
configure_grub() {
    step "KONFIGURACJA GRUB"

    mkdir -p "$PAGAN_ROOT/boot/grub"

    cat > "$PAGAN_ROOT/boot/grub/grub.cfg" <<'EOF'
# GRUB dla Pagan Linux
set default=0
set timeout=5
set root=(hd0,gpt1)

# Splash PaganLinux (jeśli zainstalowany)
insmod png
if background_image /boot/grub/splash.png; then
    set color_normal=white/black
    set color_highlight=green/black
fi

menuentry "Pagan Linux" {
    linux /boot/vmlinuz-linux root=/dev/sda1 rw quiet splash
    initrd /boot/initramfs.img
}

menuentry "Pagan Linux (single)" {
    linux /boot/vmlinuz-linux root=/dev/sda1 rw single
    initrd /boot/initramfs.img
}

menuentry "Pagan Linux (fallback)" {
    linux /boot/vmlinuz-linux root=/dev/sda1 rw noacpi
    initrd /boot/initramfs.img
}
EOF

    ok "GRUB skonfigurowany"
}

# ========================= BRANDING (tapeta, neofetch, GRUB splash) =========================
install_branding() {
    step "INSTALACJA BRANDINGU (tapeta, neofetch, GRUB splash)"

    local bdir="$SCRIPT_DIR/branding"
    [[ -d "$bdir" ]] || { warn "brak katalogu branding/ – pomijam branding"; return 0; }

    # --- Tapeta (XFCE i pozostałe motywy) ---
    if [[ -f "$bdir/$PAGAN_WALLPAPER" ]]; then
        mkdir -p "$PAGAN_ROOT/usr/share/wallpapers/paganlinux"
        install -m644 "$bdir/$PAGAN_WALLPAPER" "$PAGAN_ROOT/usr/share/wallpapers/paganlinux/$PAGAN_WALLPAPER"
        cat > "$PAGAN_ROOT/usr/share/wallpapers/paganlinux/metadata.json" <<EOF
{
    "KPlugin": {
        "Id": "paganlinux",
        "Name": "PaganLinux Mokosz",
        "Description": "PaganLinux Mokosz autumn wallpaper"
    }
}
EOF
        info "  ✓ Tapeta → /usr/share/wallpapers/paganlinux/"
    fi

    # --- neofetch logo ---
    if [[ -f "$bdir/neofetch/pagan" ]]; then
        mkdir -p "$PAGAN_ROOT/usr/share/neofetch/ascii/distro"
        install -m644 "$bdir/neofetch/pagan" "$PAGAN_ROOT/usr/share/neofetch/ascii/distro/pagan"
        info "  ✓ neofetch logo → /usr/share/neofetch/ascii/distro/pagan"
    fi

    # --- GRUB splash ---
    if [[ -f "$bdir/grub/paganlinux-grub.png" ]]; then
        mkdir -p "$PAGAN_ROOT/boot/grub"
        install -m644 "$bdir/grub/paganlinux-grub.png" "$PAGAN_ROOT/boot/grub/splash.png"
        info "  ✓ GRUB splash → /boot/grub/splash.png"
    fi

    ok "Branding zainstalowany!"
}

# ========================= BUDOWA INITRAMFS =========================
build_initramfs() {
    step "BUDOWA INITRAMFS"

    local initramfs_dir="$PAGAN_ROOT/tmp/initramfs"
    rm -rf "$initramfs_dir"
    mkdir -p "$initramfs_dir"/{bin,dev,proc,sys,run,new_root,etc,lib,lib64,usr,sbin}

    # Skrypt init – obsługuje:
    #   • tryb LIVE (pagan.live|live w cmdline): montuje squashfs z nośnika ISO/USB,
    #     nadpisywalny overlay i przełącza do systemu live
    #   • tryb DYSK: montuje root (root= z cmdline), wspiera /.deployments (pag)
    cat > "$initramfs_dir/init" <<'EOF'
#!/bin/sh
# Pagan Linux initramfs init
echo "[pagan-init] Uruchamianie initramfs..."

mount -t proc proc /proc 2>/dev/null
mount -t sysfs sysfs /sys 2>/dev/null
mount -t devtmpfs dev /dev 2>/dev/null || mount -t tmpfs dev /dev 2>/dev/null

mkdir -p /run /new_root /etc /bin /sbin /usr/bin /usr/sbin /dev/disk/by-label

# --- parsowanie cmdline ---
LIVE=0
ROOT_DEV=""
CRYPT_DEV_ARG=""
FINDISO=""   # Ventoy / grub loopback: findiso=ścieżka.iso (lub iso-scan/filename=)
IMG_DEV=""    # jawnie wskazany nośnik live (img_dev=/dev/sdX)
for arg in $(cat /proc/cmdline 2>/dev/null); do
    case "$arg" in
        pagan.live|live) LIVE=1 ;;
        root=*) ROOT_DEV="${arg#root=}" ;;
        cryptdevice=*) CRYPT_DEV_ARG="${arg#cryptdevice=}" ;;
        findiso=*) FINDISO="${arg#findiso=}" ;;
        iso-scan/filename=*) FINDISO="${arg#iso-scan/filename=}" ;;
        img_dev=*) IMG_DEV="${arg#img_dev=}" ;;
    esac
done

# ================= TRYB LIVE =================
if [ "$LIVE" = "1" ]; then
    echo "[pagan-init] Tryb LIVE (pagan.live)"

    LIVE_DEV=""            # nośnik do zamontowania (partycja / CD / loop)
    LIVE_ISO=""            # ścieżka ISO na nośniku (Ventoy / grub loopback: findiso=…)
    SFS="live/filesystem.squashfs"

    # Montowanie nośnika z próbą kilku systemów plików. `mount -t auto` bywa
    # zawodne – zwłaszcza na exFAT (partycja danych Ventoy), dlatego próbujemy
    # też jawnie. Bez tego boot z Ventoy kończy się „nie można zamontować”.
    live_mount_ro() {
        # $1 = urządzenie, $2 = punkt montowania
        # Nie używaj mount -t auto dla napędu CD: kernel próbuje wtedy kolejno
        # ext4/FAT/UDF itd. i zaśmieca log błędami superbloku. Nie skanujemy też
        # loop* jako nośników — są to pętle utworzone dla squashfs/ISO.
        case "$1" in
            /dev/sr*|/dev/cdrom)
                for _t in iso9660 udf; do
                    mount -t "$_t" -o ro "$1" "$2" 2>/dev/null && return 0
                done
                ;;
            *)
                mount -t auto -o ro "$1" "$2" 2>/dev/null && return 0
                for _t in exfat vfat ntfs3 ntfs; do
                    mount -t "$_t" -o ro "$1" "$2" 2>/dev/null && return 0
                done
                ;;
        esac
        return 1
    }

    # Próba montowania kandydata; po sukcesie ustawia LIVE_DEV (i LIVE_ISO).
    try_medium() {
        # $1 = urządzenie blokowe
        [ -b "$1" ] || return 1
        live_mount_ro "$1" /run/mnt || return 1
        if [ -f "/run/mnt/$SFS" ]; then
            LIVE_DEV="$1"; LIVE_ISO=""
            umount /run/mnt 2>/dev/null
            return 0
        fi
        if [ -n "$FINDISO" ] && [ -f "/run/mnt/$FINDISO" ]; then
            LIVE_DEV="$1"; LIVE_ISO="$FINDISO"
            umount /run/mnt 2>/dev/null
            return 0
        fi
        umount /run/mnt 2>/dev/null
        return 1
    }

    # Szukanie ISO w TOP-LEVEL katalogu zamontowanego nośnika (Ventoy bez
    # parametru findiso): weryfikuje przez losetup, czy ISO zawiera squashfs.
    find_iso_on_dev() {
        [ -b "$1" ] || return 1
        _dev="$1"
        live_mount_ro "$_dev" /run/mnt || return 1
        # ISO w katalogu głównym lub podkatalogach (do 4 poziomów) – Ventoy lubi
        # /ISO/…; każdy kandydat jest weryfikowany przez losetup (czy zawiera
        # live/filesystem.squashfs), więc obce ISO są pomijane.
        _iso_candidates=""
        if command -v find >/dev/null 2>&1; then
            _iso_candidates=$(find /run/mnt -maxdepth 4 -type f -name '*.iso' 2>/dev/null)
        fi
        [ -n "$_iso_candidates" ] || _iso_candidates=$(printf '%s\n' /run/mnt/*.iso /run/mnt/*/*.iso /run/mnt/*/*/*.iso)
        for f in $_iso_candidates; do
            [ -f "$f" ] || continue
            LOK=""
            for L in /dev/loop0 /dev/loop1 /dev/loop2 /dev/loop3 /dev/loop4; do
                [ -e "$L" ] || mknod "$L" b 7 "${L#/dev/loop}" 2>/dev/null
                if losetup "$L" "$f" 2>/dev/null; then LOK="$L"; break; fi
            done
            [ -n "$LOK" ] || continue
            mkdir -p /run/mnt2
            if (mount -t iso9660 -o ro "$LOK" /run/mnt2 2>/dev/null || \
                mount -t auto -o ro "$LOK" /run/mnt2 2>/dev/null) && \
               [ -f "/run/mnt2/$SFS" ]; then
                LIVE_DEV="$_dev"; LIVE_ISO="${f#/run/mnt/}"
                umount /run/mnt2 2>/dev/null
                losetup -d "$LOK" 2>/dev/null
                umount /run/mnt 2>/dev/null
                return 0
            fi
            umount /run/mnt2 2>/dev/null
            losetup -d "$LOK" 2>/dev/null
        done
        umount /run/mnt 2>/dev/null
        return 1
    }

    mkdir -p /run/mnt
    # Odśwież /dev (mdev/udev, gdyby były w initramfs) – USB potrafi pojawić się
    # z opóźnieniem; pętla skanuje przez ~30 s (5 s dłużej niż stary limit).
    command -v mdev >/dev/null 2>&1 && mdev -s 2>/dev/null
    command -v udevadm >/dev/null 2>&1 && udevadm trigger 2>/dev/null
    i=0
    while [ -z "$LIVE_DEV" ] && [ "$i" -lt 30 ]; do
        # dysk USB może się pojawić chwilę po starcie – odświeżaj listing /dev
        command -v mdev >/dev/null 2>&1 && mdev -s 2>/dev/null
        # 1) po etykiecie woluminu
        for l in PAGANLIVE PAGAN_LIVE PAGANLIVE_EFI; do
            if [ -e "/dev/disk/by-label/$l" ]; then
                LIVE_DEV="/dev/disk/by-label/$l"; LIVE_ISO=""; break
            fi
        done
        # 2) jawnie wskazany nośnik (img_dev=)
        if [ -z "$LIVE_DEV" ] && [ -n "$IMG_DEV" ]; then
            try_medium "$IMG_DEV"
        fi
        # 3) skan: partycje I całe dyski (USB bez tablicy), loop (Ventoy/grub
        #    loopback), napędy CD i mapowania device-mapper.
        if [ -z "$LIVE_DEV" ]; then
            for d in /dev/sd* /dev/hd* /dev/vd* /dev/xvd* /dev/nvme*n* \
                     /dev/mmcblk* /dev/sr* /dev/mapper/*; do
                try_medium "$d" && break
            done
        fi
        # 4) Ventoy/grub loopback bez findiso: szukaj pliku *.iso w katalogu
        #    głównym partycji danych (zwykle exFAT) i podepnij go przez loop.
        if [ -z "$LIVE_DEV" ]; then
            for d in /dev/sd* /dev/hd* /dev/vd* /dev/xvd* /dev/nvme*n* /dev/mmcblk*; do
                find_iso_on_dev "$d" && break
            done
        fi
        [ -z "$LIVE_DEV" ] && { sleep 1; i=$((i+1)); }
    done

    if [ -z "$LIVE_DEV" ]; then
        echo "[pagan-init] BŁĄD: nie znaleziono nośnika LIVE ($SFS)"
        echo "[pagan-init] cmdline: $(cat /proc/cmdline 2>/dev/null)"
        echo "[pagan-init] /dev: $(ls /dev 2>/dev/null | tr '\n' ' ')"
        echo "[pagan-init] /proc/filesystems: $(cat /proc/filesystems 2>/dev/null | tr '\n' ' ')"
        echo "[pagan-init] blkid: $(blkid 2>/dev/null | tr '\n' ' ')"
        echo "[pagan-init] Wskazówka: Ventoy używa exFAT (kernel musi mieć CONFIG_EXFAT_FS);"
        echo "[pagan-init]            ISO powinno być w katalogu głównym partycji danych."
        echo "[pagan-init] Wchodzę do powłoki ratunkowej:"
        exec /bin/sh
    fi
    echo "[pagan-init] Nośnik live: $LIVE_DEV${LIVE_ISO:+ (ISO: $LIVE_ISO)}"

    mkdir -p /run/live/media /run/live/iso /run/live/root /run/live/overlay
    mount -t auto -o ro "$LIVE_DEV" /run/live/media 2>/dev/null || {
        echo "[pagan-init] BŁĄD: nie można zamontować nośnika $LIVE_DEV"; exec /bin/sh; }

    SFS_BASE="/run/live/media"
    if [ -n "$LIVE_ISO" ]; then
        # ISO leży na partycji (Ventoy / grub loopback) – podepnij przez loop.
        LDEV=""
        for L in /dev/loop0 /dev/loop1 /dev/loop2 /dev/loop3 /dev/loop4 /dev/loop5; do
            [ -e "$L" ] || mknod "$L" b 7 "${L#/dev/loop}" 2>/dev/null
            if losetup "$L" "/run/live/media/$LIVE_ISO" 2>/dev/null; then
                LDEV="$L"; break
            fi
        done
        [ -n "$LDEV" ] || { echo "[pagan-init] BŁĄD: losetup ISO nieudany ($LIVE_ISO)"; exec /bin/sh; }
        if mount -t iso9660 -o ro "$LDEV" /run/live/iso 2>/dev/null || \
           mount -t auto   -o ro "$LDEV" /run/live/iso 2>/dev/null; then
            SFS_BASE="/run/live/iso"
            echo "[pagan-init] ISO zamontowane: $LIVE_ISO (loop $LDEV)"
        else
            echo "[pagan-init] BŁĄD: nie można zamontować ISO ($LIVE_ISO)"; exec /bin/sh
        fi
    fi

    mount -t squashfs -o ro,loop "$SFS_BASE/$SFS" /run/live/root 2>/dev/null || {
        echo "[pagan-init] BŁĄD: nie można zamontować $SFS_BASE/$SFS"; exec /bin/sh; }

    # Nadpisywalny root przez overlay (tmpfs + squashfs)
    mount -t tmpfs tmpfs /run/live/overlay 2>/dev/null || true
    mkdir -p /run/live/overlay/upper /run/live/overlay/work
    if ! mount -t overlay overlay \
        -o "lowerdir=/run/live/root,upperdir=/run/live/overlay/upper,workdir=/run/live/overlay/work" \
        /new_root 2>/dev/null; then
        echo "[pagan-init] Overlay niedostępny – montuję root readonly"
        mount --move /run/live/root /new_root 2>/dev/null
    fi

    # Zachowaj źródło squashfs dla Calamaresa (moduł unpackfs).
    #
    # UWAGA: NIE wystarczy /run/live/root. Przy overlay ten katalog jest
    # używany jako lowerdir, więc `mount --move` nie zadziała (błąd był
    # ukrywany przez 2>/dev/null i Calamares zgłaszał dopiero
    # „source filesystem /run/live/root does not exist”). Dodatkowo systemd
    # montuje przy starcie świeży tmpfs na /run i zasłoniłby zamontowane tam
    # katalogi. Dlatego źródło wystawiamy pod /live/root (poza /run).
    mkdir -p /new_root/live/root /new_root/live/source
    # Bind działa także dla mountpointu użytego jako lowerdir overlay. Sprawdzamy
    # jednak ZAWARTOŚĆ: przy awarii overlay /run/live/root jest już przeniesione,
    # więc bind "się udaje", ale daje pusty katalog.
    mount --bind /run/live/root /new_root/live/root 2>/dev/null || true
    if [ ! -d /new_root/live/root/usr ] && [ ! -d /new_root/live/root/etc ]; then
        umount /new_root/live/root 2>/dev/null || true
        # Overlay niedostępny (root = squashfs przeniesiony na /new_root) –
        # montujemy squashfs jeszcze raz, bezpośrednio pod /live/root.
        mount -t squashfs -o ro,loop "$SFS_BASE/$SFS" /new_root/live/root 2>/dev/null || true
    fi
    # Katalog zawierający live/filesystem.squashfs (medium lub podpięte ISO)
    # – przydatny, gdy unpackfs używa sourcefs: "squashfs".
    mount --bind "$SFS_BASE" /new_root/live/source 2>/dev/null || true

    # /run/live/* zachowujemy dla zgodności z układem live-boot. Najpierw
    # montujemy tam własny tmpfs, żeby systemd nie położył drugiego i nie
    # zasłonił bindów.
    mkdir -p /new_root/run
    mount -t tmpfs tmpfs /new_root/run 2>/dev/null || true
    mkdir -p /new_root/run/live/root /new_root/run/live/media /new_root/run/live/overlay
    mount --bind /run/live/root /new_root/run/live/root 2>/dev/null || true
    mount --move /run/live/media /new_root/run/live/media 2>/dev/null || true
    mount --move /run/live/overlay /new_root/run/live/overlay 2>/dev/null || true

    # Diagnostyka: bez tego Calamares pokazuje tylko „does not exist”,
    # nie mówiąc, że źródło nie zostało w ogóle podmontowane.
    if [ ! -d /new_root/live/root/usr ] && [ ! -d /new_root/live/root/etc ]; then
        echo "[pagan-init] UWAGA: /live/root nie zawiera drzewa systemu –"
        echo "[pagan-init]         unpackfs nie skopiuje systemu na dysk"
    else
        echo "[pagan-init] Źródło dla instalatora: /live/root (squashfs)"
    fi

    mount --move /proc /new_root/proc 2>/dev/null
    mount --move /sys /new_root/sys 2>/dev/null
    mount --move /dev /new_root/dev 2>/dev/null

    exec switch_root /new_root /sbin/init
fi

# ================= TRYB DYSK =================
echo "[pagan-init] Tryb dyskowy"

# ---- LUKS: odblokowanie roota (cryptdevice= z GRUB/systemd-boot) ----
# Format: cryptdevice=UUID=<luks-uuid>:<nazwa-mappera>[:opcje]
# (taki sam jak w mkinitcpio/Arch – systemd-boot już go zapisuje w loader.conf).
unlock_luks() {
    # $1 = urządzenie (partycja LUKS), $2 = nazwa mappera
    local dev="$1" name="$2" pass
    [ -n "$name" ] || name="cryptroot"
    mkdir -p /dev/mapper
    mknod /dev/mapper/control c 10 236 2>/dev/null
    modprobe dm-crypt 2>/dev/null || true
    if ! command -v cryptsetup >/dev/null 2>&1; then
        echo "[pagan-init] BŁĄD: initramfs nie zawiera cryptsetup – nie mogę odblokować $dev"
        return 1
    fi
    stty -echo 2>/dev/null
    printf "Hasło (LUKS %s): " "$dev"
    read -r pass
    stty echo 2>/dev/null
    echo
    if ! printf '%s\n' "$pass" | cryptsetup open "$dev" "$name" 2>/dev/null; then
        pass=""
        echo "[pagan-init] BŁĄD: nie udało się odblokować $dev (złe hasło?)"
        return 1
    fi
    pass=""
    echo "[pagan-init] Odblokowano: /dev/mapper/$name"
    return 0
}

if [ -n "$CRYPT_DEV_ARG" ]; then
    crypt_src="${CRYPT_DEV_ARG%%:*}"
    crypt_name="${CRYPT_DEV_ARG#*:}"; crypt_name="${crypt_name%%:*}"
    crypt_dev=""
    case "$crypt_src" in
        UUID=*)
            want="${crypt_src#UUID=}"
            for d in /dev/sd*[0-9] /dev/hd*[0-9] /dev/vd*[0-9] /dev/xvd*[0-9] \
                     /dev/nvme*[0-9]n[0-9]p[0-9]* /dev/mmcblk*p[0-9]*; do
                [ -b "$d" ] || continue
                [ "$(blkid -s UUID -o value "$d" 2>/dev/null)" = "$want" ] && { crypt_dev="$d"; break; }
            done
            ;;
        /dev/*) crypt_dev="$crypt_src" ;;
    esac
    if [ -n "$crypt_dev" ] && [ -b "$crypt_dev" ]; then
        if unlock_luks "$crypt_dev" "$crypt_name"; then
            ROOT_DEV="/dev/mapper/$crypt_name"
        else
            echo "[pagan-init] Powłoka ratunkowa:"
            exec /bin/sh
        fi
    else
        echo "[pagan-init] BŁĄD: cryptdevice nie znaleziono ($crypt_src)"
        echo "[pagan-init] Powłoka ratunkowa:"
        exec /bin/sh
    fi
fi

# root= z GRUB-a może być: UUID=... / PARTUUID=... / LABEL=... / PARTLABEL=...
# albo /dev/... . Initramfs NIE ma udev (brak /dev/disk/by-*), więc bez tej
# konwersji `mount UUID=...` padał i wyskakiwała powłoka ratunkowa (bash).
# Rozwiązujemy przez blkid (busybox) z fallbackiem probe-mount.
resolve_root() {
    case "$ROOT_DEV" in
        /dev/*) return 0 ;;
        UUID=*|PARTUUID=*|LABEL=*|PARTLABEL=*)
            local want="${ROOT_DEV#*=}" d tag
            case "$ROOT_DEV" in UUID=*) tag=UUID;; PARTUUID=*) tag=PARTUUID;; LABEL=*) tag=LABEL;; *) tag=PARTLABEL;; esac
            for d in /dev/sd*[0-9] /dev/hd*[0-9] /dev/vd*[0-9] /dev/xvd*[0-9] \
                     /dev/nvme*[0-9]n[0-9]p[0-9]* /dev/mmcblk*p[0-9]*; do
                [ -b "$d" ] || continue
                [ "$(blkid -s "$tag" -o value "$d" 2>/dev/null)" = "$want" ] && { ROOT_DEV="$d"; return 0; }
            done
            ;;
    esac
    # Fallback (brak blkid w busybox / nieczytelny FS): probe-mount każdej
    # partycji i szukaj markera systemu (/etc/pagan-release lub /bin/bash).
    local d
    for d in /dev/sd*[0-9] /dev/hd*[0-9] /dev/vd*[0-9] /dev/xvd*[0-9] \
             /dev/nvme*[0-9]n[0-9]p[0-9]* /dev/mmcblk*p[0-9]*; do
        [ -b "$d" ] || continue
        mkdir -p /run/probe
        if mount -t auto -o ro "$d" /run/probe 2>/dev/null; then
            if [ -f /run/probe/etc/pagan-release ] || [ -x /run/probe/bin/bash ]; then
                ROOT_DEV="$d"; umount /run/probe 2>/dev/null; return 0
            fi
            umount /run/probe 2>/dev/null
        fi
    done
    return 1
}

if ! resolve_root; then
    echo "[pagan-init] BŁĄD: nie znaleziono urządzenia root ($ROOT_DEV)"
    echo "[pagan-init] Powłoka ratunkowa:"
    exec /bin/sh
fi

[ -n "$ROOT_DEV" ] || ROOT_DEV="/dev/sda1"
i=0
while [ ! -b "$ROOT_DEV" ] && [ "$i" -lt 10 ]; do sleep 1; i=$((i+1)); done

# Fallback: root= wskazuje wprost na partycję LUKS (bez cryptdevice=).
# blkid czyta typ z superbloku LUKS, więc rozpoznamy ją bez montowania.
if blkid -t TYPE=crypto_LUKS "$ROOT_DEV" >/dev/null 2>&1; then
    echo "[pagan-init] Wykryto partycję LUKS na root ($ROOT_DEV)"
    if unlock_luks "$ROOT_DEV" cryptroot; then
        ROOT_DEV="/dev/mapper/cryptroot"
    else
        echo "[pagan-init] Powłoka ratunkowa:"
        exec /bin/sh
    fi
fi

mount -t auto -o rw "$ROOT_DEV" /new_root 2>/dev/null || {
    echo "[pagan-init] BŁĄD: nie można zamontować roota ($ROOT_DEV)"; exec /bin/sh; }

# Obsługa deploymentów PAG (immutable OS)
ACTIVE_LINK="/new_root/.deployments/active"
DEPLOYMENT=""

if [ -L "$ACTIVE_LINK" ]; then
    DEPLOYMENT=$(readlink "$ACTIVE_LINK")
    [ -n "$DEPLOYMENT" ] && DEPLOYMENT="/new_root/.deployments/$DEPLOYMENT"
elif [ -d "/new_root/.deployments" ]; then
    DEPLOYMENT=$(ls -1 /new_root/.deployments/ 2>/dev/null | grep -v active | sort -r | head -1)
    [ -n "$DEPLOYMENT" ] && DEPLOYMENT="/new_root/.deployments/$DEPLOYMENT"
fi

if [ -n "$DEPLOYMENT" ] && [ -d "$DEPLOYMENT" ]; then
    echo "[pagan-init] Boot z deploymentu: $DEPLOYMENT"
    mount --move /proc "$DEPLOYMENT/proc" 2>/dev/null
    mount --move /sys "$DEPLOYMENT/sys" 2>/dev/null
    mount --move /dev "$DEPLOYMENT/dev" 2>/dev/null
    exec switch_root "$DEPLOYMENT" /sbin/init
else
    echo "[pagan-init] Brak deploymentu, boot z /new_root"
    mount --move /proc /new_root/proc 2>/dev/null
    mount --move /sys /new_root/sys 2>/dev/null
    mount --move /dev /new_root/dev 2>/dev/null
    exec switch_root /new_root /sbin/init
fi
EOF

    chmod +x "$initramfs_dir/init"

    # Busybox lub bash
    local busybox=""
    for p in "$PAGAN_ROOT/usr/bin/busybox" "$PAGAN_ROOT/bin/busybox" "/usr/bin/busybox"; do
        [ -f "$p" ] && busybox="$p" && break
    done

    if [ -n "$busybox" ]; then
        cp "$busybox" "$initramfs_dir/bin/busybox"
        chmod 755 "$initramfs_dir/bin/busybox"
        for cmd in sh mount umount ls cat echo sleep readlink mkdir switch_root \
                   cp rm ln blkid losetup modprobe grep dmesg lsblk poweroff reboot \
                   mknod stty find sync vi; do
            ln -sf busybox "$initramfs_dir/bin/$cmd"
        done
    else
        # Kopiuj bash i ldd
        for tool in bash mount umount ls cat echo sleep readlink mkdir switch_root cp rm ln; do
            src=$(which "$tool" 2>/dev/null)
            [ -n "$src" ] && cp "$src" "$initramfs_dir/bin/"
        done
    fi

    # ── cryptsetup + biblioteki (obsługa LUKS w initramfs) ──
    # Bez tego initramfs nie odblokuje zaszyfrowanego roota. Kopiujemy binarkę
    # i wszystkie zależności dynamiczne (ldd) + interpreter, żeby zadziałało
    # na czystym initramfs bez udev.
    local _cs=""
    for _p in "$PAGAN_ROOT/usr/sbin/cryptsetup" "$PAGAN_ROOT/usr/bin/cryptsetup" "$PAGAN_ROOT/sbin/cryptsetup"; do
        [[ -x "$_p" ]] && _cs="$_p" && break
    done
    if [[ -n "$_cs" ]]; then
        mkdir -p "$initramfs_dir/usr/sbin"
        cp "$_cs" "$initramfs_dir/usr/sbin/cryptsetup"
        chmod 755 "$initramfs_dir/usr/sbin/cryptsetup"
        local _lib
        for _lib in $(ldd "$_cs" 2>/dev/null | awk '/=>/ {print $3} /^[[:space:]]*\// {print $1}'); do
            [[ -f "$_lib" ]] || continue
            mkdir -p "$initramfs_dir/${_lib%/*}"
            cp "$_lib" "$initramfs_dir${_lib}"
        done
        local _loader
        _loader=$(ldd "$_cs" 2>/dev/null | awk '/ld-linux/ {print $1}')
        if [[ -n "$_loader" && -f "$_loader" ]]; then
            mkdir -p "$initramfs_dir/${_loader%/*}"
            cp "$_loader" "$initramfs_dir${_loader}"
        fi
        info "  ✓ cryptsetup + biblioteki → initramfs (LUKS)"
    else
        warn "Brak cryptsetup w rootfs – initramfs bez obsługi LUKS!"
    fi

    # Kopiuj /etc/pagan-release
    [ -f "$PAGAN_ROOT/etc/pagan-release" ] && cp "$PAGAN_ROOT/etc/pagan-release" "$initramfs_dir/etc/"

    # Pakowanie
    pushd "$initramfs_dir" > /dev/null
    find . | cpio -oH newc | gzip > "$PAGAN_ROOT/boot/initramfs.img"
    popd > /dev/null

    rm -rf "$initramfs_dir"

    # Świeży obraz = trzeba odświeżyć aliasy initramfs-<wersja>.img, po które
    # grub-mkconfig (10_linux) szuka initrd. Bez nich wpis w menu GRUB jest bez
    # linii `initrd` i zainstalowany system nie ma initramfs.
    _ensure_grub_boot_artifacts

    ok "Initramfs zbudowany!"
}

# ========================= TWORZENIE IMG =========================
# Sprząta zasoby IMG (bind-mounty + loop device) także przy błędzie/przerwaniu.
# Wołane z trapa EXIT (_show_summary), więc korzysta z globalnych rejestrów.
_cleanup_image_mounts() {
    local mnt="${_PAGAN_IMG_MNT:-}"
    if [[ -n "$mnt" && -d "$mnt" ]]; then
        umount "$mnt/dev" 2>/dev/null || true
        umount "$mnt/proc" 2>/dev/null || true
        umount "$mnt/sys" 2>/dev/null || true
        umount "$mnt/boot/efi" 2>/dev/null || true
        umount "$mnt" 2>/dev/null || true
    fi
    if [[ -n "${_PAGAN_IMG_LOOPDEV:-}" ]]; then
        losetup -d "$_PAGAN_IMG_LOOPDEV" 2>/dev/null || true
    fi
    _PAGAN_IMG_LOOPDEV=""
    _PAGAN_IMG_MNT=""
}

create_image() {
    step "TWORZENIE OBRAZU IMG (BIOS + UEFI)"

    mkdir -p "$OUTPUT_DIR"
    local img_file="$OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.img"
    local img_esp_size=512   # MB – partycja EFI (FAT32)

    info "Tworzenie IMG (${IMAGE_SIZE}MB)..."

    dd if=/dev/zero of="$img_file" bs=1M count="$IMAGE_SIZE" status=progress 2>/dev/null
    parted -s "$img_file" mklabel gpt
    # Układ: [ESP fat32] [root ext4]
    parted -s "$img_file" mkpart "EFI" fat32 1MiB "${img_esp_size}MiB"
    parted -s "$img_file" set 1 esp on
    parted -s "$img_file" mkpart "root" ext4 "${img_esp_size}MiB" 100%
    parted -s "$img_file" set 2 boot on

    local loopdev
    loopdev=$(losetup -f)
    losetup -P "$loopdev" "$img_file"
    # Zarejestruj loop device – przy błędzie zdejmie go trap EXIT.
    _PAGAN_IMG_LOOPDEV="$loopdev"

    mkfs.vfat -F 32 -n PAGAN_ESP "${loopdev}p1" >/dev/null 2>&1
    mkfs.ext4 -F -L PAGAN_ROOT "${loopdev}p2" >/dev/null 2>&1

    local mount_point="/mnt/pagan-img"
    mkdir -p "$mount_point"
    _PAGAN_IMG_MNT="$mount_point"
    mount "${loopdev}p2" "$mount_point"
    mkdir -p "$mount_point/boot/efi"
    mount "${loopdev}p1" "$mount_point/boot/efi"

    info "Kopiowanie systemu..."
    rsync -aAX --delete \
        --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found","*.img","*.iso","/sources/*","/tools/*","/var/cache/*"} \
        "$PAGAN_ROOT/" "$mount_point/" 2>/dev/null

    # Wirtualne FS dla chroot
    mount --bind /dev "$mount_point/dev"
    mount --bind /proc "$mount_point/proc"
    mount --bind /sys "$mount_point/sys"

    # GRUB – BIOS (MBR) i UEFI
    chroot "$mount_point" grub-install --target=i386-pc "$loopdev" 2>/dev/null || \
        warn "grub-install i386-pc nieudany – IMG bez boot BIOS"
    chroot "$mount_point" grub-install --target=x86_64-efi \
        --efi-directory=/boot/efi --bootloader-id=PAGAN 2>/dev/null || \
        warn "grub-install x86_64-efi nieudany – IMG bez boot UEFI"

    # Root na partycji 2 w tym układzie (p2 = ext4). Aktualizacja grub.cfg i fstab.
    sed -i 's|root=/dev/sda1|root=/dev/sda2|g' "$mount_point/boot/grub/grub.cfg" 2>/dev/null || true
    sed -i 's|set root=(hd0,gpt1)|set root=(hd0,gpt2)|' "$mount_point/boot/grub/grub.cfg" 2>/dev/null || true
    cat > "$mount_point/etc/fstab" <<'EOF'
# /etc/fstab (obraz IMG)
/dev/sda2       /               ext4    defaults        0       1
/dev/sda1       /boot/efi       vfat    umask=0077      0       1
proc            /proc           proc    nosuid,noexec,nodev 0       0
sysfs           /sys            sysfs   nosuid,noexec,nodev 0       0
devtmpfs        /dev            devtmpfs mode=0755,nosuid 0       0
tmpfs           /tmp            tmpfs   defaults        0       0
devpts          /dev/pts        devpts  gid=5,mode=620  0       0
shm             /dev/shm        tmpfs   defaults        0       0
EOF

    _cleanup_image_mounts

    ok "IMG: $img_file ($(du -h "$img_file" 2>/dev/null | cut -f1))"
}

# ========================= TWORZENIE ISO =========================

# Sprawdza, czy obraz ISO jest hybrydowy (isohybrid): sektor 0 musi zawierać MBR
# z sygnaturą 0x55AA (offset 510) i aktywny wpis tablicy partycji (offset 446).
# Tylko takie ISO da się zapisać na pendrive (balenaEtcher/Rufus/dd) w sposób
# bootowalny – czysty ISO9660 (sektor 0 = zera) kończy się błędem
# „Missing partition table" w balenaEtcher.
_iso_is_hybrid() {
    local file="$1" boot_flag sig
    [[ -s "$file" ]] || return 1
    sig=$(dd if="$file" bs=1 skip=510 count=2 2>/dev/null | od -An -tx1 | tr -d ' \n')
    [[ "$sig" == "55aa" ]] || return 1
    boot_flag=$(dd if="$file" bs=1 skip=446 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')
    [[ "$boot_flag" != "00" && -n "$boot_flag" ]]
}

create_iso() {
    step "TWORZENIE OBRAZU ISO (LIVE + INSTALATOR)"

    mkdir -p "$OUTPUT_DIR"
    local iso_file="$OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.iso"
    local iso_root="/tmp/pagan-iso-$$"

    rm -rf "$iso_root"
    mkdir -p "$iso_root"/{boot,isolinux,live,tools}

    # --- Binaria ISOLINUX z hosta (boot BIOS) + MBR dla isohybrid ---
    # check_dependencies zadbał o instalację syslinux na hoście – tutaj tylko
    # lokalizujemy pliki przez wspólny helper i kopiujemy do drzewa ISO.
    # (Twardy błąd na wypadek, gdyby pliki zniknęły między fazami builda.)
    local isolinux_bin ldlinux mbr
    isolinux_bin=$(_find_syslinux_file isolinux.bin) || true
    ldlinux=$(_find_syslinux_file ldlinux.c32) || true
    mbr=$(_find_syslinux_file isohdpfx.bin) || true
    if [[ -n "$isolinux_bin" && ! -f "$iso_root/isolinux/isolinux.bin" ]]; then
        cp "$isolinux_bin" "$iso_root/isolinux/"
    fi
    if [[ -n "$ldlinux" && ! -f "$iso_root/isolinux/ldlinux.c32" ]]; then
        cp "$ldlinux" "$iso_root/isolinux/"
    fi
    if [[ -z "$isolinux_bin" || -z "$ldlinux" || -z "$mbr" ]]; then
        error "Brak plików syslinux na hoście mimo auto-instalacji (potrzebne: isolinux.bin, ldlinux.c32, isohdpfx.bin). Zainstaluj ręcznie: Arch/EndeavourOS 'sudo pacman -S syslinux' | Debian/Ubuntu 'sudo apt install isolinux syslinux-common syslinux-utils' | Fedora 'sudo dnf install syslinux'"
    fi

    # --- Kernel + initramfs (podążaj za symlinkami vmlinuz-linux) ---
    cp -L "$PAGAN_ROOT/boot/vmlinuz-linux" "$iso_root/boot/vmlinuz-linux" 2>/dev/null || \
        cp -L "$PAGAN_ROOT"/boot/vmlinuz-* "$iso_root/boot/vmlinuz-linux" 2>/dev/null || true
    cp -L "$PAGAN_ROOT/boot/initramfs.img" "$iso_root/boot/initramfs.img" 2>/dev/null || true

    # --- System live (squashfs) – bez toolchaina/sources/cache (zbędna waga) ---
    info "Tworzenie squashfs (to potrwa)..."
    # UWAGA: mksquashfs 4.7 NIE akceptuje wzorców z wiodącym "/" razem
    # z -wildcards (FATAL ERROR). Wzorce są względne do źródła → bez "/".
    # -no-progress MUSI być przed -e (wszystko po -e to wzorce wykluczeń).
    # -no-xattrs: xatrry (file capabilities itp.) i tak nie działają w live
    # na starych jądrach bez CONFIG_SQUASHFS_XATTR („Xattrs in filesystem,
    # these will be ignored") – pomijamy je, żeby obraz był neutralny.
    # Stderr zostaje widoczne – dzięki temu realne błędy widać w logu.
    # UWAGA: NIE wykluczamy "root/*"! Wzorzec bez kropki łapie /root/Desktop
    # (ikonę instalatora na pulpicie live) i wycinał ją z ISO. W /root poza tym
    # są tylko wpisy z kropką (.config, .xinitrc), których ten wzorzec i tak nie
    # łapie – więc usunięcie "root/*" niczego nie zaśmieca, a przywraca ikonę.
    # .pag_staging/.deployments to katalogi robocze buildera (same kopie
    # zapasowe paga – u nas 475 MB!), które nie mają po co trafiać do live ani
    # do systemu instalowanego przez unpackfs.
    mksquashfs "$PAGAN_ROOT" "$iso_root/live/filesystem.squashfs" \
        -comp xz -b 1M -noappend -no-progress -no-xattrs -processors "$PAGAN_JOBS" \
        -wildcards -e "*.img" "*.iso" "*.log" \
        "dev/*" "proc/*" "sys/*" "tmp/*" "run/*" "mnt/*" "media/*" \
        "sources/*" "tools/*" "var/cache/*" "usr/src/*" \
        ".pag_staging/*" ".deployments/*"
    if [[ ! -s "$iso_root/live/filesystem.squashfs" ]]; then
        error "mksquashfs nie wygenerował filesystem.squashfs"
    fi

    # --- Menu BIOS (isolinux) – tryb LIVE ---
    cat > "$iso_root/isolinux/isolinux.cfg" <<'EOF'
DEFAULT pagan
TIMEOUT 100
PROMPT 0

LABEL pagan
    MENU LABEL ^Pagan Linux Live
    KERNEL /boot/vmlinuz-linux
    APPEND initrd=/boot/initramfs.img pagan.live rw quiet splash

LABEL pagan-fallback
    MENU LABEL Pagan Linux (tryb awaryjny)
    KERNEL /boot/vmlinuz-linux
    APPEND initrd=/boot/initramfs.img pagan.live rw noacpi nomodeset

LABEL pagan-debug
    MENU LABEL Pagan Linux (debug – powłoka)
    KERNEL /boot/vmlinuz-linux
    APPEND initrd=/boot/initramfs.img pagan.live break
EOF

    # --- Menu EFI (GRUB) + obraz efi dla eltorito ---
    local efi_ok=0
    if [[ -x "$PAGAN_ROOT/usr/bin/grub-mkstandalone" ]]; then
        mkdir -p "$iso_root/boot/grub" "$iso_root/EFI/BOOT"
        # Splash GRUB (branding) – tło menu startowego ISO
        if [[ -f "$PAGAN_ROOT/boot/grub/splash.png" ]]; then
            cp "$PAGAN_ROOT/boot/grub/splash.png" "$iso_root/boot/grub/splash.png"
        fi
        cat > "$iso_root/boot/grub/grub.cfg" <<'GRUBEOF'
set timeout=10
set default=0
insmod all_video
insmod gfxterm
insmod png
insmod part_gpt
insmod iso9660

if background_image /boot/grub/splash.png; then
    set color_normal=light-gray/black
    set color_highlight=white/black
else
    set color_normal=cyan/blue
    set color_highlight=white/blue
fi

menuentry "Pagan Linux (LIVE)" {
    linux /boot/vmlinuz-linux pagan.live rw quiet splash
    initrd /boot/initramfs.img
}
menuentry "Pagan Linux (verbose)" {
    linux /boot/vmlinuz-linux pagan.live rw
    initrd /boot/initramfs.img
}
menuentry "Pagan Linux (tryb awaryjny)" {
    linux /boot/vmlinuz-linux pagan.live rw noacpi nomodeset
    initrd /boot/initramfs.img
}
menuentry "Pagan Linux (debug – konsola szeregowa)" {
    linux /boot/vmlinuz-linux pagan.live rw console=ttyS0 systemd.journald.forward_to_console=yes
    initrd /boot/initramfs.img
}
GRUBEOF
        # Osadzony config = PEŁNE menu (self-contained). WAŻNE: bez `configfile`
        # i bez zmiany $prefix – GRUB leniwie ładuje moduły (configfile.mod itd.)
        # z $prefix, a w ISO /boot/grub NIE MA modułów → "file
        # '/boot/grub/x86_64-efi/configfile.mod' not found" → grub>.
        # Moduły ładujemy jawnie z memdisk (startowy prefix=(memdisk)/boot/grub).
        cat > "$iso_root/EFI/BOOT/grub-embed.cfg" <<'EMBED'
set timeout=10
set default=0
insmod all_video
insmod gfxterm
insmod png
insmod search
insmod iso9660
insmod part_gpt
insmod linux
search --no-floppy --set=root --label PAGANLIVE
if background_image ($root)/boot/grub/splash.png; then
    set color_normal=light-gray/black
    set color_highlight=white/black
else
    set color_normal=cyan/blue
    set color_highlight=white/blue
fi
menuentry "Pagan Linux (LIVE)" {
    linux ($root)/boot/vmlinuz-linux pagan.live rw quiet splash
    initrd ($root)/boot/initramfs.img
}
menuentry "Pagan Linux (verbose)" {
    linux ($root)/boot/vmlinuz-linux pagan.live rw
    initrd ($root)/boot/initramfs.img
}
menuentry "Pagan Linux (tryb awaryjny)" {
    linux ($root)/boot/vmlinuz-linux pagan.live rw noacpi nomodeset
    initrd ($root)/boot/initramfs.img
}
menuentry "Pagan Linux (debug – konsola szeregowa)" {
    linux ($root)/boot/vmlinuz-linux pagan.live rw console=ttyS0 systemd.journald.forward_to_console=yes
    initrd ($root)/boot/initramfs.img
}
EMBED
        # grub-mkstandalone to binarka docelowa → uruchamiamy przez chroot
        # (targetowe biblioteki), z tymczasowo podpiętymi /proc /sys /dev.
        cp "$iso_root/EFI/BOOT/grub-embed.cfg" "$PAGAN_ROOT/tmp/grub-embed.cfg" 2>/dev/null || true
        mount_virtual_fs
        # grub-mkstandalone/mkimage (binarki targetowe) wymagają libdevmapper
        # (lvm2), którego nie ma w rootfs → loader w chrocie pada natychmiast.
        # Podepnij tymczasowo z hosta (host i target: glibc 2.44); usuwamy po kroku.
        local _dl_tmp=""
        if [[ ! -e "$PAGAN_ROOT/usr/lib/libdevmapper.so.1.02" && -e "/usr/lib/libdevmapper.so.1.02" ]]; then
            cp -L /usr/lib/libdevmapper.so.1.02 "$PAGAN_ROOT/usr/lib/libdevmapper.so.1.02" && _dl_tmp=1
        fi
        if chroot "$PAGAN_ROOT" /usr/bin/grub-mkstandalone \
                --format=x86_64-efi --output=/tmp/grubx64.efi \
                --locales="" --themes="" --fonts="" \
                "boot/grub/grub.cfg=/tmp/grub-embed.cfg" \
           && [[ -s "$PAGAN_ROOT/tmp/grubx64.efi" ]]; then
            cp "$PAGAN_ROOT/tmp/grubx64.efi" "$iso_root/EFI/BOOT/BOOTX64.EFI"
            efi_ok=1
            info "  ✓ BOOTX64.EFI wygenerowany (GRUB EFI)"
        else
            warn "grub-mkstandalone nie udał się – ISO bez boot EFI"
        fi
        [[ -n "$_dl_tmp" ]] && rm -f "$PAGAN_ROOT/usr/lib/libdevmapper.so.1.02"
        umount_virtual_fs
        rm -f "$PAGAN_ROOT/tmp/grub-embed.cfg" "$PAGAN_ROOT/tmp/grubx64.efi"

        if [[ $efi_ok -eq 1 ]]; then
            # Zapakuj EFI/BOOT do obrazu FAT dla eltorito
            rm -rf "/tmp/pagan-efi-$$"
            mkdir -p "/tmp/pagan-efi-$$/EFI"
            cp -a "$iso_root/EFI/BOOT" "/tmp/pagan-efi-$$/EFI/"
            # Label ≤ 11 znaków (limit FAT). PAGANLIVE_EFI (13) wywalało mkfs.vfat;
            # odrębna etykieta niż ISO (PAGANLIVE) – grub szuka --label PAGANLIVE
            truncate -s 8M "$iso_root/boot/efiboot.img" || true
            if ! mkfs.vfat -n PAGANEFI "$iso_root/boot/efiboot.img" >/dev/null 2>&1; then
                warn "mkfs.vfat nie sformatował efiboot.img – ISO bez boot EFI"
                efi_ok=0
            elif ! mcopy -s -i "$iso_root/boot/efiboot.img" \
                    "/tmp/pagan-efi-$$/EFI" ::/ >/dev/null 2>&1; then
                mcopy -s -i "$iso_root/boot/efiboot.img" \
                    "/tmp/pagan-efi-$$/EFI" :: >/dev/null 2>&1 || {
                        warn "Nie udało się zapisać efiboot.img (brak mtools?) – ISO tylko BIOS"
                        efi_ok=0
                    }
            fi
            rm -rf "/tmp/pagan-efi-$$"
        fi
    else
        warn "Brak grub-mkstandalone w systemie docelowym – ISO bez boot EFI"
    fi

    # --- Skrót do instalatora (Calamares) + narzędzia PAG na nośniku ---
    cat > "$iso_root/install-pagan.sh" <<'EOF'
#!/bin/bash
# Uruchomienie instalatora systemu (Calamares) z sesji graficznej live.
# Calamares jest też dostępny z ikony „Zainstaluj Pagan Linux” na pulpicie.
if [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && command -v calamares >/dev/null 2>&1; then
    exec calamares "$@"
fi
echo "Instalator Calamares wymaga sesji graficznej (uruchom go z pulpitu live)."
echo "Ręcznie: sudo calamares"
EOF
    chmod +x "$iso_root/install-pagan.sh"
    # Dodatkowe narzędzia (wygodne do pobrania z nośnika) – tylko to, co użyteczne
    # w systemie live; pagsync/pagbuild to narzędzia buildera (są w repo projektu)
    for t in pag; do
        [[ -f "$SCRIPT_DIR/$t" ]] && cp "$SCRIPT_DIR/$t" "$iso_root/tools/$t" 2>/dev/null || true
    done
    chmod +x "$iso_root"/tools/* 2>/dev/null || true
    cat > "$iso_root/tools/README.txt" <<'EOF'
Pagan Linux – narzędzia na nośniku live
=======================================
  install-pagan.sh  – uruchamia instalator systemu (Calamares, GUI)
                       dostępny też z ikony „Zainstaluj Pagan Linux” na pulpicie
  pagan             – menedżer pakietów PAG (pełna wersja)

W samym systemie live narzędzia są już zainstalowane w /usr/bin.
EOF

    # --- Generuj ISO (BIOS isolinux + EFI eltorito + hybrid MBR) ---
    if ! command -v xorriso &>/dev/null; then
        error "Brak xorriso (zainstaluj pakiet xorriso)"
    fi

    local xorriso_args=(
        xorriso -as mkisofs -o "$iso_file"
        -V PAGANLIVE
    )
    # Boot BIOS tylko gdy isolinux.bin faktycznie jest – inaczej xorriso
    # kończy się FAILURE ("Cannot find in ISO image: bin_path=...")
    if [[ -f "$iso_root/isolinux/isolinux.bin" ]]; then
        xorriso_args+=(
            -b isolinux/isolinux.bin -c isolinux/boot.cat
            -no-emul-boot -boot-load-size 4 -boot-info-table
        )
    fi
    if [[ $efi_ok -eq 1 ]]; then
        xorriso_args+=(
            -eltorito-alt-boot -e boot/efiboot.img -no-emul-boot -isohybrid-gpt-basdat
        )
    fi
    [[ -n "$mbr" ]] && xorriso_args+=( -isohybrid-mbr "$mbr" )
    xorriso_args+=( "$iso_root" )

    "${xorriso_args[@]}" >/dev/null 2>&1 || {
        # Ponów z widocznym błędem (bez -no-progress na xorriso)
        "${xorriso_args[@]}" || error "xorriso nie wygenerował ISO"
    }

    # Weryfikacja hybrydy: sektor 0 musi mieć MBR z sygnaturą 0x55AA i tablicą
    # partycji – inaczej balenaEtcher/Rufus odrzucą ISO („Missing partition table").
    # (Sprawdzenie bez fdisk: sygnatura MBR na offset 510 + niezerowy bajt flagi
    # rozruchu w pierwszym wpisie tablicy partycji na offset 446.)
    if ! _iso_is_hybrid "$iso_file"; then
        error "Wygenerowane ISO NIE jest hybrydowe (brak MBR/tablicy partycji) – balenaEtcher odrzuci je jako 'Missing partition table'. Sprawdź, czy xorriso dostał poprawny isohdpfx.bin."
    fi

    rm -rf "$iso_root"
    ok "ISO: $iso_file ($(du -h "$iso_file" 2>/dev/null | cut -f1))"
}

# ========================= FINALIZACJA =========================
# ===================== IMPORT BRAKUJĄCYCH LIB Z HOSTA =====================
# Buildery linkowały pakiety przeciw zależnościom HOSTA (krb5, KF6 Frameworks,
# gpg-stack, ICU 78…), których nie ma w targetcie → setki brakujących libek
# (np. libgssapi_krb5.so.2 dla Qt6Network, libKF6I18n.so.6, libaudit.so.1...).
# Host i target mają tę samą glibc (2.44) → libki z hosta są ABI-kompatybilne.
# Iteracyjnie uzupełniamy braki (kopiujemy realny plik + symlink soname).
# Uwaga: to pragmatyczny krok (libki "niezarządzane"); docelowo pakiety powinny
# być zbudowane w targetcie (IMPORT_HOST_LIBS=0 wyłącza).
_import_host_libs() {
    step "IMPORT BRAKUJĄCYCH BIBLIOTEK Z HOSTA (krb5/KF6/gpg/icu…)"
    _is_true "${IMPORT_HOST_LIBS:-1}" || { info "Import pominięty (IMPORT_HOST_LIBS=0)"; return 0; }

    # Całość w PODPOWŁOCE z set +e: wewnętrzne błędy skanu/pętli (set -e/pipefail
    # potrafiły zabić cały build w rundzie 2) nie mogą przerwać budowy.
    (
    set +e
    local round=0 imported=0 lib src real
    # indeks wszystkich plików *.so* w targetcie (szybkie wyszukiwanie)
    local all_libs
    all_libs=$(cd "$PAGAN_ROOT" && find . -name "*.so*" -printf "%f\n" 2>/dev/null | sort -u)

    while (( round < 6 )); do
        imported=0
        # skan: wszystkie NEEDED w binarkach/libkach targeta
        while IFS= read -r lib; do
            [[ -n "$lib" ]] || continue
            # już jest w /usr/lib? albo gdziekolwiek w targetcie?
            [[ -e "$PAGAN_ROOT/usr/lib/$lib" ]] && continue
            echo "$all_libs" | grep -qxF "$lib" && continue
            # szukaj na hoście
            src=""
            for d in /usr/lib /usr/lib64 /usr/lib/x86_64-linux-gnu /lib64 /usr/local/lib; do
                [[ -e "$d/$lib" ]] && { src="$d/$lib"; break; }
            done
            [[ -n "$src" ]] || continue
            real=$(readlink -f "$src")
            [[ -f "$real" ]] || continue
            cp -a "$real" "$PAGAN_ROOT/usr/lib/$(basename "$real")" 2>/dev/null || continue
            # symlink soname (jeśli nazwa się różni od realnego pliku)
            if [[ "$(basename "$real")" != "$lib" ]]; then
                ln -sf "$(basename "$real")" "$PAGAN_ROOT/usr/lib/$lib" 2>/dev/null || true
            fi
            imported=1
            info "  ✓ $lib"
        done < <( cd "$PAGAN_ROOT" && { find usr/bin usr/sbin usr/libexec -type f 2>/dev/null; find usr/lib usr/lib64 -name "*.so*" -type f 2>/dev/null; } \
            | while read -r f; do readelf -d "$f" 2>/dev/null | grep NEEDED | sed 's/.*\[\(.*\)\]/\1/' || true; done \
            | sort -u )
        (( imported == 0 )) && break
        (( round++ ))
    done
    ) || true
    # Gwarancja obecności kluczowych narzędzi X11/SDDM (xkbcomp, xauth, setxkbmap, xinit)
    for _xbin in xkbcomp xauth setxkbmap xinit; do
        if [[ ! -x "$PAGAN_ROOT/usr/bin/$_xbin" && -x "/usr/bin/$_xbin" ]]; then
            cp -a "/usr/bin/$_xbin" "$PAGAN_ROOT/usr/bin/$_xbin" 2>/dev/null || true
            chmod +x "$PAGAN_ROOT/usr/bin/$_xbin" 2>/dev/null || true
            info "  ✓ $PAGAN_ROOT/usr/bin/$_xbin (z hosta)"
        fi
    done

    ok "Import libek z hosta zakończony"
}

configure_pam() {
    step "KONFIGURACJA LINUX-PAM"
    mkdir -p "$PAGAN_ROOT/etc/pam.d"

    cat > "$PAGAN_ROOT/etc/pam.d/other" <<'EOF'
#%PAM-1.0
auth        required    pam_deny.so
auth        required    pam_warn.so
account     required    pam_deny.so
account     required    pam_warn.so
password    required    pam_deny.so
password    required    pam_warn.so
session     required    pam_deny.so
session     required    pam_warn.so
EOF

    cat > "$PAGAN_ROOT/etc/pam.d/system-auth" <<'EOF'
#%PAM-1.0
# pam_faillock: blokada po 4 nieudanych logowaniach (patrz /etc/security/faillock.conf)
auth      required    pam_env.so
auth      required    pam_faillock.so preauth
auth      sufficient  pam_unix.so nullok try_first_pass
auth      [default=die] pam_faillock.so authfail
auth      sufficient  pam_faillock.so authsucc
auth      required    pam_deny.so
EOF

    cat > "$PAGAN_ROOT/etc/pam.d/system-account" <<'EOF'
#%PAM-1.0
account   required    pam_unix.so
account   required    pam_faillock.so
EOF

    mkdir -p "$PAGAN_ROOT/etc/security"
    cat > "$PAGAN_ROOT/etc/security/faillock.conf" <<'EOF'
# Blokada konta po nieudanych logowaniach (pam_faillock)
deny = 4
unlock_time = 600
EOF

    # pam_faillock wymaga katalogu licznika w tmpfs (/var/run/faillock = /run/faillock).
    # Bez niego faza "account" odrzuca KAŻDĄ weryfikację konta → sudo/login:
    # "account validation failure, is your account locked?". /run jest tmpfs,
    # więc sam mkdir nie wystarczy – dostarczamy wpis tmpfiles.d, który odtworzy
    # katalog przy każdym starcie (systemd-tmpfiles-setup).
    mkdir -p "$PAGAN_ROOT/run/faillock"
    chmod 0700 "$PAGAN_ROOT/run/faillock"
    mkdir -p "$PAGAN_ROOT/usr/lib/tmpfiles.d"
    cat > "$PAGAN_ROOT/usr/lib/tmpfiles.d/pam.conf" <<'EOF'
# Katalog licznika pam_faillock – musi istnieć po starcie (/run to tmpfs).
d /run/faillock 0700 root root -
EOF

    cat > "$PAGAN_ROOT/etc/pam.d/system-password" <<'EOF'
#%PAM-1.0
password  required    pam_unix.so sha512 shadow nullok try_first_pass
EOF

    cat > "$PAGAN_ROOT/etc/pam.d/system-session" <<'EOF'
#%PAM-1.0
session   required    pam_limits.so
session   required    pam_unix.so
session   optional    pam_systemd.so
EOF

    cat > "$PAGAN_ROOT/etc/pam.d/system-login" <<'EOF'
#%PAM-1.0
auth      include     system-auth
account   include     system-account
password  include     system-password
session   include     system-session
EOF

    cat > "$PAGAN_ROOT/etc/pam.d/system-local-login" <<'EOF'
#%PAM-1.0
auth      include     system-login
account   include     system-login
password  include     system-login
session   include     system-login
EOF

    # Nadpisanie domyślnego niespójnego /etc/pam.d/login (np. z shadow), które odwołuje się do SELinux/console
    cat > "$PAGAN_ROOT/etc/pam.d/login" <<'EOF'
#%PAM-1.0
auth       required     pam_securetty.so
auth       requisite    pam_nologin.so
auth       include      system-local-login
account    include      system-local-login
password   include      system-local-login
session    include      system-local-login
session    optional     pam_loginuid.so
EOF

    # /etc/pam.d/sudo – MUSI zawierać system-account/system-session. Starszy
    # wariant z "account include system-auth" daje PUSTY stos account (system-auth
    # ma tylko sekcję auth) → PAM zwraca błąd i sudo pada komunikatem
    # "account validation failure, is your account locked?". Wymuszamy poprawny plik.
    cat > "$PAGAN_ROOT/etc/pam.d/sudo" <<'EOF'
#%PAM-1.0
auth      include     system-auth
account   include     system-account
session   required    pam_env.so
session   include     system-session
EOF
    chmod 644 "$PAGAN_ROOT/etc/pam.d/sudo"

    # Upewnij sie, ze uzytkownik pagan nie ma wygaszonego hasla (wazne dla autologin)
    chroot "$PAGAN_ROOT" chage -E -1 -m 0 -M 99999 pagan 2>/dev/null || true
    echo "pagan:changeme" | chroot "$PAGAN_ROOT" chpasswd 2>/dev/null || true

    ok "Konfiguracja PAM utworzona"
}

# ========================= LIVE XFCE: PULPIT + IKONA INSTALATORA + TAPETA =========================
# Gdy build ma pełne DE (domyślnie XFCE), live wita pełny pulpit XFCE (root) z:
#   • ikoną „Zainstaluj Pagan Linux” na pulpicie (uruchamia Calamares),
#   • tapetą PaganLinux (pliki z install_branding), ustawioną w xfconf dla każdego
#     użytkownika (root / skel / pagan), ze stylem zoom.
_setup_xfce_live_desktop() {
    # ── ikona (wpis menu + pixmap) ──
    local _logo="" _cand
    for _cand in "$SCRIPT_DIR/branding/pagan-loki-pingwin.png" \
                 "$SCRIPT_DIR/paganlinux-loki-logo.svg"; do
        [[ -f "$_cand" ]] && { _logo="$_cand"; break; }
    done
    if [[ -n "$_logo" ]]; then
        install -Dm644 "$_logo" "$PAGAN_ROOT/usr/share/pixmaps/pagan-calamares.png"
    fi

    mkdir -p "$PAGAN_ROOT/usr/share/applications"
    cat > "$PAGAN_ROOT/usr/share/applications/pagan-calamares.desktop" <<'EOF'
[Desktop Entry]
Type=Application
Version=1.0
Name=Install Pagan Linux
Name[pl]=Zainstaluj Pagan Linux
Name[de]=Pagan Linux installieren
Comment=Install PaganLinux to the disk
Comment[pl]=Zainstaluj PaganLinux na dysku
Comment[de]=PaganLinux auf der Festplatte installieren
Exec=/usr/bin/calamares
Icon=/usr/share/pixmaps/pagan-calamares.png
Terminal=false
Categories=System;Settings;Utility;
Keywords=installer;system;pagan;paganlinux;
StartupNotify=true
EOF

    # ── Ikona instalatora TYLKO na pulpicie live (root). NIE kopiujemy jej do
    #    /etc/skel ani /home/pagan – na zainstalowanym systemie jest zbędna.
    if [[ -d "$PAGAN_ROOT/root" ]]; then
        mkdir -p "$PAGAN_ROOT/root/Desktop"
        cp -a "$PAGAN_ROOT/usr/share/applications/pagan-calamares.desktop" \
              "$PAGAN_ROOT/root/Desktop/pagan-calamares.desktop"
        chmod 755 "$PAGAN_ROOT/root/Desktop/pagan-calamares.desktop"
    fi

    # ── Wybór sieci Wi-Fi w sesji live (PRZED instalatorem) ──
    #
    # Calamares 3.3 NIE ma już strony konfiguracji sieci: opcję
    # showNetworkConfiguration usunięto w 3.3 (sprawdzone w źródle 3.3.14 – brak
    # takiego klucza i brak wywołań nm-connection-editor), a moduł
    # interactiveterminal wymaga KDE Konsole/KParts, którego nie mamy.
    # Standardowe rozwiązanie dystrybucji: połączyć się z siecią w live przed
    # uruchomieniem instalatora. Stąd pomocnik pagan-wifi (nmcli + xfce4-terminal).
    if [[ -f "$SCRIPT_DIR/branding/pagan-wifi" ]]; then
        install -Dm755 "$SCRIPT_DIR/branding/pagan-wifi" "$PAGAN_ROOT/usr/bin/pagan-wifi"

        cat > "$PAGAN_ROOT/usr/share/applications/pagan-wifi.desktop" <<'EOF'
[Desktop Entry]
Type=Application
Version=1.0
Name=Connect to Wi-Fi
Name[pl]=Połącz z siecią Wi-Fi
Comment=Choose a wireless network
Comment[pl]=Wybierz sieć bezprzewodową
Exec=/usr/bin/pagan-wifi
Icon=network-wireless
Terminal=true
Categories=Network;Settings;Utility;
Keywords=wifi;wireless;network;siec;pagan;
StartupNotify=true
EOF

        if [[ -d "$PAGAN_ROOT/root/Desktop" ]]; then
            cp -a "$PAGAN_ROOT/usr/share/applications/pagan-wifi.desktop" \
                  "$PAGAN_ROOT/root/Desktop/pagan-wifi.desktop"
            chmod 755 "$PAGAN_ROOT/root/Desktop/pagan-wifi.desktop"
        fi
        ok "  live: pomocnik Wi-Fi (pagan-wifi) + ikona na pulpicie"
    else
        warn "  live: brak branding/pagan-wifi – nie ma wyboru sieci przed instalatorem"
    fi

    # ── tapeta (root / skel / pagan), workspaces 0-3 ──
    # Ustawiamy też jawnie /desktop-icons (style=2 → ikony plików/launcherów),
    # żeby ikona instalatora na pulpicie była widoczna niezależnie od domyślnych
    # xfdesktop. Domyślny ICON_STYLE_DEFAULT to FILES, ale wolimy nie polegać na
    # kompilacyjnych domyślach (ENABLE_DESKTOP_ICONS/ENABLE_FILE_ICONS).
    local _home _cfg _w
    for _home in /root /etc/skel /home/pagan; do
        [[ -d "$PAGAN_ROOT$_home" ]] || continue
        _cfg="$PAGAN_ROOT$_home/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-desktop.xml"
        mkdir -p "$(dirname "$_cfg")"
        {
            echo '<?xml version="1.0" encoding="UTF-8"?>'
            echo '<channel name="xfce4-desktop" version="1.0">'
            echo '  <property name="backdrop" type="empty">'
            echo '    <property name="screen0" type="empty">'
            echo '      <property name="monitor0" type="empty">'
            for _w in 0 1 2 3; do
                echo "        <property name=\"workspace$_w\" type=\"empty\">"
                echo '          <property name="color-style" type="int" value="0"/>'
                echo '          <property name="image-style" type="int" value="5"/>'
                echo '          <property name="last-image" type="string" value="/usr/share/wallpapers/paganlinux/paganlinux-wallpaper.png"/>'
                echo '        </property>'
            done
            echo '      </property>'
            echo '    </property>'
            echo '  </property>'
            echo '  <property name="desktop-icons" type="empty">'
            echo '    <property name="style" type="int" value="2"/>'
            echo '    <property name="icon-size" type="uint" value="48"/>'
            echo '    <property name="single-click" type="bool" value="false"/>'
            echo '    <property name="show-thumbnails" type="bool" value="true"/>'
            echo '    <property name="file-icons" type="empty">'
            echo '      <property name="show-filesystem" type="bool" value="true"/>'
            echo '      <property name="show-home" type="bool" value="true"/>'
            echo '      <property name="show-trash" type="bool" value="true"/>'
            echo '      <property name="show-removable" type="bool" value="true"/>'
            echo '    </property>'
            echo '  </property>'
            echo '</channel>'
        } > "$_cfg"
    done

    info "  ✓ XFCE live: ikona Calamares na pulpicie (root) + ikony plików + tapeta PaganLinux"
}

# Xorg budowany NA HOŚCIE potrafi linkować biblioteki HOSTA (np. libtirpc),
# których w rootfs nie ma -> live: „/usr/bin/X: error while loading shared
# libraries”. Samouzdrawianie: ldd w chroot -> „not found” -> kopia z hosta
# (host == target: glibc 2.44, ABI zgodne – ten sam wzorzec co import libek).
import_xorg_runtime_libs() {
    # Binarki X (jeśli są w rootfs) + narzędzia gruba (są zawsze) – ldd w chroot
    # pokaże brakujące libki (libtirpc, libdevmapper, ...) i importujemy je z hosta.
    local _xbin=()
    local _x
    # Xorg + menedżer logowania + rdzeń XFCE + Calamares. Gdy tym binarkom
    # brakuje bibliotek w rootfs, „zainstalowany” system wstaje do TTY bez GUI
    # (np. lightdm: „lib...: cannot open shared object file”).
    for _x in /usr/bin/X /usr/bin/Xorg /usr/bin/xinit /usr/bin/Xvfb \
              /usr/sbin/lightdm /usr/bin/lightdm /usr/bin/lightdm-gtk-greeter \
              /usr/bin/startxfce4 /usr/bin/xfce4-session /usr/bin/xfwm4 \
              /usr/bin/xfce4-panel /usr/bin/xfdesktop /usr/bin/thunar \
              /usr/bin/calamares; do
        [[ -e "$PAGAN_ROOT$_x" ]] && _xbin+=("$_x")
    done
    # grub-install/mkimage (repo-grub linkuje libdevmapper) – rootfs go nie ma
    # (source-lvm2 nie położył .so), a bez niego grub-install pada przy instalacji.
    local _g
    for _g in /usr/bin/grub-install /usr/bin/grub-mkimage /usr/bin/grub-bios-setup \
              /usr/bin/grub-mkconfig /usr/bin/grub-mkstandalone; do
        [[ -e "$PAGAN_ROOT$_g" ]] && _xbin+=("$_g")
    done
    [[ ${#_xbin[@]} -eq 0 ]] && return 0
    local _miss _name _src _real _cand _i
    for _i in 1 2 3; do
        # || true: ldd zwraca niezerowo, gdy czegoś brakuje; przy set -e + pipefail
        # niezerowy pipeline w $(...) ubiłby CAŁY build (tuż przed ISO).
        _miss=$(chroot "$PAGAN_ROOT" /bin/sh -c "ldd ${_xbin[*]} 2>/dev/null" 2>/dev/null \
                | awk '/not found/{print $1}' | sort -u || true)
        [[ -z "$_miss" ]] && break
        for _name in $_miss; do
            _src=""
            for _cand in "/usr/lib/$_name" "/usr/lib64/$_name" "/lib/$_name"; do
                [[ -e "$_cand" ]] && { _src="$_cand"; break; }
            done
            if [[ -z "$_src" ]]; then
                _src=$(find /usr/lib /usr/lib64 /lib -maxdepth 3 -name "$_name" 2>/dev/null | head -1 || true)
            fi
            if [[ -z "$_src" || ! -e "$_src" ]]; then
                warn "  Xorg: brak '$_name' także na hoście (raportuj)"
                continue
            fi
            _real=$(readlink -f "$_src")
            install -Dm755 "$_real" "$PAGAN_ROOT/usr/lib/$(basename "$_real")"
            ln -sf "$(basename "$_real")" "$PAGAN_ROOT/usr/lib/$_name"
            info "  Xorg: import libki z hosta: $_name <- $(basename "$_real")"
        done
        # skopiowana libka też może mieć braki – pętla to domknie
    done
    _miss=$(chroot "$PAGAN_ROOT" /bin/sh -c "ldd ${_xbin[*]} 2>/dev/null" 2>/dev/null \
            | awk '/not found/{print $1}' | sort -u || true)
    if [[ -n "$_miss" ]]; then
        warn "  Xorg: nadal brak w rootfs: $_miss"
    else
        info "  ✓ Xorg: komplet zależności (ldd OK)"
    fi
}

# Stopgap: gdy build gruba daje zepsuty kernel.img i386-pc („miscompiled: start
# != 0x9000”), importujemy SPÓJNY zestaw gruba z HOSTA (Arch: moduły i386-pc +
# x86_64-efi + narzędzia + potrzebne libki). Host == target (glibc 2.44, x86_64)
# – ten sam wzorzec co import libek Xorga.
# Uwaga: główną przyczyną był brak -Os w TARGET_CFLAGS (GRUB dodaje -Os tylko
# gdy TARGET_CFLAGS jest puste) – receptura przekazuje teraz jawnie -Os.
import_host_grub() {
    step "IMPORT GRUB-A Z HOSTA (kernel.img i386-pc z builda zepsuty)"

    # UWAGA: importujemy moduły/narzędzia gruba z HOSTA. Zadziała, gdy wersja
    # gruba na hoście zgadza się z targetem (2.14, ten sam builder). Przy innej
    # wersji moduły mogą nie pasować do grub-install/mkimage w targetcie.
    [[ -d /usr/lib/grub/i386-pc ]] || { warn "Brak /usr/lib/grub/i386-pc na hoście – nie importuję"; return 1; }

    # Kontrola zgodności wersji: import mieszający moduły/narzędzia z RÓŻNYCH
    # wersji gruba daje niespójny bootloader. Host i target to zwykle ten sam
    # builder (2.14) – jeśli nie, ostrzegamy zamiast cicho psuć obraz.
    # Porównujemy tylko wersję upstream (x.y[.z]) – ignorujemy prefiks ścieżki
    # wywołania, epoch (2:) i pkgrel (-1), które różnią się między dystrybucjami
    # (np. host "… (GRUB) 2:2.14-1" vs target "… (GRUB) 2.14").
    local _tver _hver
    _hver=$(grub-install --version 2>/dev/null | head -n1 \
            | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 || true)
    _tver=$(chroot "$PAGAN_ROOT" /usr/bin/grub-install --version 2>/dev/null | head -n1 \
            | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 || true)
    [[ -n "$_tver" ]] || _tver=$(chroot "$PAGAN_ROOT" /usr/sbin/grub-install --version 2>/dev/null | head -n1 \
            | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 || true)
    if [[ -n "$_tver" && -n "$_hver" && "$_tver" != "$_hver" ]]; then
        warn "  grub: wersja hosta (GRUB $_hver) != targetu (GRUB $_tver) – import może dać niespójny bootloader"
    else
        info "  ✓ grub: wersja host/target zgodna (GRUB ${_tver:-?})"
    fi

    mkdir -p "$PAGAN_ROOT/usr/lib/grub"
    cp -a /usr/lib/grub/. "$PAGAN_ROOT/usr/lib/grub/"
    local _f _d
    for _f in /usr/bin/grub-* /usr/sbin/grub-*; do
        [[ -e "$_f" ]] || continue
        _d="$PAGAN_ROOT$(dirname "$_f")"
        mkdir -p "$_d"
        cp -a "$_f" "$PAGAN_ROOT$_f"
    done
    info "  ✓ moduły + narzędzia gruba skopiowane z hosta"

    # grub-install/mkimage z hosta może potrzebować libek, których nie ma w
    # rootfs (np. libdevmapper) – import jak przy Xorgu (pętla domyka zależności).
    local _gbins=(/usr/bin/grub-install /usr/bin/grub-mkimage \
                  /usr/bin/grub-bios-setup /usr/bin/grub-mkconfig \
                  /usr/bin/grub-mkstandalone)
    local _miss _name _src _real _cand _i
    for _i in 1 2 3; do
        # || true: ldd niezerowo przy brakach; set -e + pipefail w $(...) ubiłby build.
        _miss=$(chroot "$PAGAN_ROOT" /bin/sh -c "ldd ${_gbins[*]} 2>/dev/null" 2>/dev/null \
                | awk '/not found/{print $1}' | sort -u || true)
        [[ -z "$_miss" ]] && break
        for _name in $_miss; do
            _src=""
            for _cand in "/usr/lib/$_name" "/usr/lib64/$_name" "/lib/$_name"; do
                [[ -e "$_cand" ]] && { _src="$_cand"; break; }
            done
            [[ -z "$_src" ]] && _src=$(find /usr/lib /lib /usr/lib64 -maxdepth 3 -name "$_name" 2>/dev/null | head -1 || true)
            if [[ -z "$_src" || ! -e "$_src" ]]; then
                warn "  grub: brak libki '$_name' także na hoście"
                continue
            fi
            _real=$(readlink -f "$_src")
            install -Dm755 "$_real" "$PAGAN_ROOT/usr/lib/$(basename "$_real")"
            ln -sf "$(basename "$_real")" "$PAGAN_ROOT/usr/lib/$_name"
            info "  grub: import libki z hosta: $_name"
        done
    done
    info "  ✓ GRUB z hosta gotowy (wersja hosta, spójna: moduły + narzędzia)"
}

# Release notes: pagan-news / pagan-update + pliki changelogu.
# System jest ROLLING (repo PAG): nowa wersja ISO nie wymaga reinstalacji
# (aktualizacja to `pag update && pag upgrade`); wpisy changelogu to pliki
# /usr/share/pagan/news/<wersja>.md. Wydzielone, żeby odświeżał to także
# fix-calamares.sh (respin ISO bez pełnego rebuildu).
_write_pagan_news() {
    mkdir -p "$PAGAN_ROOT/usr/share/pagan/news" "$PAGAN_ROOT/var/lib/pag"

    cat > "$PAGAN_ROOT/usr/bin/pagan-news" <<'NEWSEOF'
#!/bin/bash
# pagan-news – pokazuje wpisy zmian (release notes) nowsze niż ostatnio pokazane.
# Nowy wpis: dodaj plik /usr/share/pagan/news/<wersja>.md (np. 0.2.0.md).
[ "$(id -u)" = "0" ] || { echo "Uruchom jako root: sudo pagan-news"; exit 1; }
NEWS_DIR="/usr/share/pagan/news"
SEEN_FILE="/var/lib/pag/news.seen"
[ -d "$NEWS_DIR" ] || { echo "Brak katalogu z newsami: $NEWS_DIR"; exit 0; }
seen=""
[ -f "$SEEN_FILE" ] && seen="$(cat "$SEEN_FILE" 2>/dev/null)"
state=0
[ -z "$seen" ] && state=1
newest="$seen"
while read -r v; do
    [ -n "$v" ] || continue
    if [ "$state" = "0" ]; then
        if [ "$v" = "$seen" ]; then state=1; fi
        continue
    fi
    newest="$v"
    echo ""
    echo "=================================================="
    cat "$NEWS_DIR/$v.md" 2>/dev/null || true
    echo ""
done < <(for f in "$NEWS_DIR"/*.md; do [ -f "$f" ] || continue; basename "$f" .md; done | sort -V)
if [ -n "$newest" ]; then
    echo "$newest" > "$SEEN_FILE" 2>/dev/null || true
fi
exit 0
NEWSEOF
    chmod 755 "$PAGAN_ROOT/usr/bin/pagan-news"

    cat > "$PAGAN_ROOT/usr/bin/pagan-update" <<'UPDEOF'
#!/bin/bash
# pagan-update – pełna aktualizacja systemu z repo PAG + pokazanie nowości.
# Użycie: pagan-update [--check]   (--check = tylko pokaż newsy, bez aktualizacji)
set -u
[ "$(id -u)" = "0" ] || { echo "Uruchom jako root: sudo pagan-update"; exit 1; }
if [ "${1:-}" = "--check" ]; then
    exec /usr/bin/pagan-news
fi
echo "==> pag update (synchronizacja repo)..."
/usr/bin/pag update || { echo "pag update nieudane (sieć/repo?)"; exit 1; }
echo "==> pag upgrade (aktualizacja pakietów)..."
/usr/bin/pag upgrade
rc=$?
/usr/bin/pagan-news
exit "$rc"
UPDEOF
    chmod 755 "$PAGAN_ROOT/usr/bin/pagan-update"

    # Wpis startowy 0.1.0.
    # Jak dodać wpis dla kolejnej wersji: utwórz /usr/share/pagan/news/<wersja>.md,
    # opisz zmiany – użytkownicy zobaczą go po `sudo pagan-update` (albo `sudo pagan-news`).
    # (Ta instrukcja jest CELOWO komentarzem w builderze – nie trafia do treści newsa.)
    cat > "$PAGAN_ROOT/usr/share/pagan/news/0.1.0.md" <<'NEWSMD'
# PaganLinux 0.1.0

Pierwsze wydanie systemu:

* środowisko **XFCE 4.20** (budowane ze źródeł, tapeta Pagan + motyw Nordic-darker + ikony Papirus-Dark)
* instalator systemu **Calamares** (GUI)
* menedżer pakietów **pag** + repo https://repo.paganlinux.eu/stable
* powłoka **fish** dla użytkowników (root na live pozostaje przy bashu)
* live ISO (BIOS + UEFI), squashfs + overlay, kernel z obsługą WiFi USB
NEWSMD

    ok "Release notes: pagan-news + pagan-update zainstalowane"
}

finalize_build() {
    step "FINALIZACJA BUDOWY"
    configure_pam

    # Zagnieżdżony rootfs z błędnego DESTDIR+sysroot (np. gdy sdkdir z
    # xorg-server.pc wskazywał już $PAGAN_ROOT/usr → /mnt/pagan/mnt/pagan).
    # NIE kasujemy – SCALAMY z rootfsem, bo kasowanie gubi legalne pliki
    # (NetworkManager.service, polkit.service, org.freedesktop.PolicyKit1.service
    # itd.). Bez polkitd helper KPMcore nie autoryzuje `lsblk` → Calamares
    # pokazuje „There are no partitions to install on”.
    # Normalnie jest to już puste (scalanie po każdym pakiecie) – zostaje jako
    # bezpiecznik dla przebiegów sprzed tej poprawki.
    _merge_nested_rootfs
    # Dopełnij pliki polkita (gdyby scalanie czegoś nie objęło).
    _ensure_system_groups
    _ensure_polkit_units

    # dbus: jednostki bez cyklu zależności (inaczej D-Bus nie wstaje → brak
    # polkitd, NetworkManager i lightdm). Naprawia też stary rootfs.
    _ensure_dbus_units

    # D-Bus: polityka systemd (/usr/share/dbus-1/system.d/org.freedesktop.*.conf).
    # Bez niej obowiązuje <deny own="*"/> z /usr/share/dbus-1/system.conf i PID1
    # NIE MOŻE zająć nazwy org.freedesktop.systemd1 (zweryfikowane eksperymentalnie
    # na dbus-daemon 1.16.2). Skutki: „systemctl” nie działa, aktywacja kończy się
    # „Failed to activate service 'org.freedesktop.systemd1': timed out”, logind nie
    # tworzy sesji (ENOTCONN) i nie startuje lightdm.
    _ensure_systemd_dbus_policy

    # WiFi: wpa_supplicant w /usr/sbin + aktywacja D-Bus (inaczej NM nie ma
    # czym zarządzać kartą WLAN – dotyczy też kart na USB).
    _ensure_wifi_supplicant

    # NetworkManager: jednostki + polityka D-Bus + włączenie w live. Bez tego
    # live nie podnosi interfejsu (brak DHCP – także w QEMU), mimo że sprzęt
    # i sterowniki są w porządku.
    _ensure_network_manager

    # Usługi pulpitu (upower/udisks2) – włączenie jest tanie i idempotentne,
    # a musi być w rootfsie, żeby trafiło też do systemu instalowanego (Calamares
    # kopiuje rootfs). Wołane także z install_desktop_extras.
    _ensure_desktop_units

    # Netinstall: backend `pag` dla modułu packages Calamaresa.
    _install_pag_package_backend

    # Udev/hwdb: bez /etc/udev/hwdb.bin generator imds loguje błąd przy starcie.
    _ensure_hwdb

    # Strefy czasowe: bez skompilowanych danych Calamares przerywa instalację
    # na module locale (SetTimezoneJob → „Bad path: …/zoneinfo/Europe/Warsaw”).
    _install_tzdata

    # GRUB: aliasy initramfs-<wersja>.img + /etc/default/grub. Bez nich
    # grub-mkconfig (10_linux) nie znajdzie initrd i wpis w menu GRUB nie ma
    # linii `initrd` – zainstalowany system startuje bez initramfs.
    _ensure_grub_boot_artifacts

    # CZYSTY OBRAZ: w ISO ŻADEN display manager nie może być włączony (relikty
    # po wcześniejszych buildach w rootfs po resume: sddm, gdm itd. potrafią
    # kraść vt w live). Live startuje przez getty tty1 → startx. DM na
    # zainstalowanym systemie włącza DOPIERO instalator – moduł `displaymanager`
    # Calamaresa (lightdm). Binarki i konfiguracje zostają – usuwamy tylko stan
    # „enabled”.
    local _dm_unit
    for _dm_unit in sddm gdm lightdm lxdm slim; do
        chroot "$PAGAN_ROOT" env -u LD_LIBRARY_PATH \
            systemctl disable "$_dm_unit" 2>/dev/null || true
    done
    rm -f "$PAGAN_ROOT/etc/systemd/system/display-manager.service" 2>/dev/null || true

    # --- Konfiguracja live: autologin root na tty1 + sesja XFCE (Calamares) ---
    # Autologin na tty1 (root) dla automatycznego startu sesji live
    mkdir -p "$PAGAN_ROOT/etc/systemd/system/getty@tty1.service.d"
    cat > "$PAGAN_ROOT/etc/systemd/system/getty@tty1.service.d/autologin.conf" <<'EOF'
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM
EOF

    # Fallback .xinitrc (gdy brak startxfce4): startx → Calamares.
    # Polski układ klawiszy – DISPLAY jest już ustawione.
    mkdir -p "$PAGAN_ROOT/root"
    cat > "$PAGAN_ROOT/root/.xinitrc" <<'EOF'
#!/bin/sh
# Minimalna sesja X live: instalator Calamares (bez DE).
setxkbmap -layout pl 2>/dev/null || true
/usr/bin/calamares > /tmp/calamares.log 2>&1
_rc=$?
if [ $_rc -ne 0 ] && [ -s /tmp/calamares.log ]; then
    echo "--- calamares (stdout+stderr, kod $_rc): ---" >&2
    tail -n 60 /tmp/calamares.log >&2
fi
exit $_rc
EOF
    chmod +x "$PAGAN_ROOT/root/.xinitrc"

    # DE z pełnym pulpitem (domyślnie XFCE): live wita XFCE z ikoną instalatora
    # i tapetą zamiast „gołego” okna instalatora (patrz wyżej – tryb minimalny).
    local _live_de="${PAGAN_DE,,}"
    if [[ "$_live_de" == "xfce" && -x "$PAGAN_ROOT/usr/bin/startxfce4" ]]; then
        cat > "$PAGAN_ROOT/root/.xinitrc" <<'EOF'
#!/bin/sh
# Live XFCE (root). Wyjście sesji + diagnostyka trafiają do /tmp/xfce-session.log.
# Dodatkowo w MASZYNIE WIRTUALNEJ (QEMU/KVM – systemd-detect-virt) lustrujemy je
# na /dev/ttyS0 → qemu_boot.log. Na prawdziwym PC sink = /dev/null (żaden port
# szeregowy nie jest potrzebny ani nie blokuje startu).
setxkbmap -layout pl 2>/dev/null || true
SINK=/dev/null
if [ -c /dev/ttyS0 ] && command -v systemd-detect-virt >/dev/null 2>&1; then
    case "$(systemd-detect-virt 2>/dev/null)" in
        ""|none) ;;                 # fizyczny PC – bez lustra na serial
        *) SINK=/dev/ttyS0 ;;       # VM (kvm/qemu/…) – log na serial
    esac
fi
{
    echo "===== [live-xfce] start $(date) ====="
    # efivarfs: bez zamontowanych zmiennych firmware efibootmgr (wołany przez
    # moduł bootloader) kończy się błędem. W maszynie UEFI montujemy jawnie.
    if [ -d /sys/firmware/efi ] && [ ! -d /sys/firmware/efi/efivars ]; then
        mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true
    fi
    # D-Bus wymaga setuid helpera polkita jako root:root 4755. Squashfs/overlay
    # może zachować plik, ale zgubić bit setuid, więc odtwarzamy go w live.
    if [ -e /usr/lib/polkit-1/polkit-agent-helper-1 ]; then
        chown root:root /usr/lib/polkit-1/polkit-agent-helper-1 2>/dev/null || true
        chmod 4755 /usr/lib/polkit-1/polkit-agent-helper-1 2>/dev/null || true
    fi
    if [ -e /usr/lib/libexec/kpmcore_externalcommand ]; then
        chown root:root /usr/lib/libexec/kpmcore_externalcommand 2>/dev/null || true
        chmod 4755 /usr/lib/libexec/kpmcore_externalcommand 2>/dev/null || true
    fi
    if [ -e /usr/libexec/dbus-daemon-launch-helper ]; then
        chown root:root /usr/libexec/dbus-daemon-launch-helper 2>/dev/null || true
        chmod 4755 /usr/libexec/dbus-daemon-launch-helper 2>/dev/null || true
    fi
    stat -c '[live-xfce] polkit-helper: %A %a %U:%G %n' \
        /usr/lib/polkit-1/polkit-agent-helper-1 2>/dev/null || true
    stat -c '[live-xfce] kpmcore-helper: %A %a %U:%G %n' \
        /usr/lib/libexec/kpmcore_externalcommand 2>/dev/null || true
    stat -c '[live-xfce] dbus-helper: %A %a %U:%G %n' \
        /usr/libexec/dbus-daemon-launch-helper 2>/dev/null || true
    # KPMcore (moduł partition Calamaresa) wykonuje `lsblk` przez swój D-Bus helper,
    # a ten autoryzuje się przez polkit → w live MUSZĄ działać systemowy D-Bus
    # i polkitd. `systemctl` roota działa także bez magistrali (private socket),
    # więc wymuszamy ich start zanim ruszy pulpit/instalator.
    systemctl start dbus.socket dbus.service polkit.service 2>/dev/null || true
    sleep 1
    echo "[live-xfce] dbus-daemon: $(pgrep -c dbus-daemon 2>/dev/null || echo 0) proc; polkitd: $(pgrep -c polkitd 2>/dev/null || echo 0) proc"
    # Sieć: ważne przy instalacji z netinstall (moduł packages pobiera pakiety)
    # oraz dla WiFi – użytkownik może je ustawić w live przed instalatorem.
    echo "[live-xfce] sieć: $(nmcli -t -f DEVICE,TYPE,STATE device 2>/dev/null | tr '\n' ' ')"
    if ! pgrep -x polkitd >/dev/null 2>&1; then
        echo "----- polkit.service nie wstał – diagnostyka -----"
        systemctl status polkit.service --no-pager -l 2>&1 | head -20
        systemctl status dbus.socket dbus.service --no-pager -l 2>&1 | head -20
        journalctl -b -u polkit.service -u dbus.service --no-pager 2>/dev/null | tail -30
    fi
    if command -v dbus-run-session >/dev/null 2>&1; then
        dbus-run-session -- startxfce4
    else
        startxfce4
    fi
    _rc=$?
    echo "===== [live-xfce] sesja zakonczona rc=$_rc ====="
    echo "----- Xorg.0.log (ogon) -----"
    tail -n 40 /root/.local/share/xorg/Xorg.0.log 2>/dev/null || true
    tail -n 40 /var/log/Xorg.0.log 2>/dev/null || true
    echo "----- xsession-errors (ogon) -----"
    tail -n 60 /root/.xsession-errors 2>/dev/null || true
    echo "----- journalctl (xfce/err/dbus) -----"
    journalctl -b --no-pager 2>/dev/null | grep -iE 'xfce|xfdesktop|xfwm|xfsettings|panel|dbus|error|fail|fatal' | tail -n 60 || true
    echo "===== [live-xfce] koniec ====="
    exit "$_rc"
} 2>&1 | tee /tmp/xfce-session.log > "$SINK"
EOF
        chmod +x "$PAGAN_ROOT/root/.xinitrc"
        _setup_xfce_live_desktop
    fi

    mkdir -p "$PAGAN_ROOT/etc/profile.d"
    cat > "$PAGAN_ROOT/etc/profile.d/pagan-live.sh" <<'EOF'
# Automatyczny start sesji live na tty1 (autologin root).
# Live wita pulpit XFCE (instalator Calamares uruchamiasz ikoną na pulpicie).
# Gdy XFCE niedostępny, .xinitrc sam odpala Calamares.
if [ "$(tty 2>/dev/null)" = "/dev/tty1" ] && command -v startx >/dev/null 2>&1; then
    if [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]; then
        return 0 2>/dev/null || true
    fi
    clear
    echo "PaganLinux – uruchamiam sesję live (startx)..."
    startx
    _xrc=$?
    clear
    if [ "$_xrc" -ne 0 ]; then
        echo "Sesja X zakończyła się z kodem $_xrc – logi (ostatnie linie):"
        _xlog="$(ls -t /root/.local/share/xorg/Xorg.0.log /var/log/Xorg.0.log 2>/dev/null | head -n1)"
        [ -n "$_xlog" ] && tail -n 20 "$_xlog"
        [ -s /tmp/calamares.log ] && { echo "--- calamares ---"; tail -n 30 /tmp/calamares.log; }
    fi
    echo ""
    echo "Uruchomić ponownie? [y/N] "
    read -r _ans
    case "${_ans:-n}" in y|Y) reboot ;; esac
fi
EOF
    chmod +x "$PAGAN_ROOT/etc/profile.d/pagan-live.sh"

    # /etc/profile – bash login shell ładuje TYLKO /etc/profile; bez niego
    # skrypty /etc/profile.d/*.sh (m.in. pagan-live.sh) NIGDY się nie
    # wykonują i live wita gołym bashem zamiast pulpitem live (GUI).
    # (Standardowy wzorzec LFS/BLFS "Configuring the Bash Shell Startup Files")
    if [[ ! -s "$PAGAN_ROOT/etc/profile" ]]; then
        cat > "$PAGAN_ROOT/etc/profile" <<'EOF'
# /etc/profile – systemowe zmienne środowiskowe i programy startowe
# (wczytywane przez każdy bash login shell).
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
export HISTCONTROL=ignoredups
export HISTSIZE=1000
export HISTFILESIZE=2000
if [ "$PS1" ]; then # interaktywny shell
    umask 022
fi
# Ładuj skrypty z /etc/profile.d (kolejność alfabetyczna)
for script in /etc/profile.d/*.sh ; do
    if [ -r "$script" ]; then
        . "$script"
    fi
done
unset script
EOF
        info "  ✓ /etc/profile (ładuje /etc/profile.d/*.sh)"
    fi
    # /etc/bashrc – dla interaktywnych shelli NIE-loginowych
    if [[ ! -s "$PAGAN_ROOT/etc/bashrc" ]]; then
        cat > "$PAGAN_ROOT/etc/bashrc" <<'EOF'
# /etc/bashrc – interaktywne shelle nie-loginowe (aliasy/funkcje)
if [ "$PS1" ]; then
    umask 022
fi
EOF
    fi

    # DNS dla live: bez /etc/resolv.conf curl nie rozwiąże nazw i netinstall
    # pada („repo unreachable”) nawet z działającą siecią.
    #
    # PISZEMY ZAWSZE, nie tylko gdy pliku brak: _pag_install/install_calamares
    # kopiują HOSTOWY /etc/resolv.conf do rootfsu na czas instalacji z repo i nie
    # zawsze go potem czyszczą, więc do ISO trafiał adres DNS z sieci budującej
    # (np. 192.168.x.x) – bezużyteczny poza tą siecią i wyciek prywatnego adresu
    # do wydania. NetworkManager nadpisze ten plik dzierżawą DHCP po połączeniu,
    # a poniższe wpisy to tylko sensowny awaryjny fallback.
    cat > "$PAGAN_ROOT/etc/resolv.conf" <<'EOF'
# Nadpisywany przez NetworkManager po uzyskaniu adresu (DHCP).
# Poniżej tylko awaryjny fallback – gdyby NM nie wystartował.
nameserver 1.1.1.1
nameserver 8.8.8.8
EOF
    info "  ✓ /etc/resolv.conf: neutralny fallback (DNS hosta nie trafia do ISO)"
    # Szyfrowany DNS (DoT) – systemd-resolved w zainstalowanym systemie użyje
    # tego pliku (instalator włącza resolved + stub resolv.conf). DoT = 853/tls,
    # wiec nawet na otwartej sieci nikt nie podgląda zapytań DNS.
    if [[ -n "$PAGAN_DNS" ]]; then
        cat > "$PAGAN_ROOT/etc/systemd/resolved.conf" <<EOF
[Resolve]
DNS=${PAGAN_DNS}
DNSOverTLS=${PAGAN_DNS_TLS}
DNSSEC=allow-downgrade
Cache=yes
EOF
        info "  ✓ /etc/systemd/resolved.conf (DoT: DNS=$PAGAN_DNS, TLS=$PAGAN_DNS_TLS)"
    fi

    # ── Utwardzanie sysctl (aplikowane przez systemd-sysctl przy starcie) ──
    mkdir -p "$PAGAN_ROOT/etc/sysctl.d"
    cat > "$PAGAN_ROOT/etc/sysctl.d/99-pagan-hardening.conf" <<'EOF'
# Pagan Linux – utwardzanie jądra (sysctl)
# Ukrywanie informacji
kernel.kptr_restrict=2
kernel.dmesg_restrict=1
kernel.unprivileged_bpf_disabled=1
kernel.yama.ptrace_scope=1
# Sieć: antyspoofing, brak przekierowań, syn-cookies
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.tcp_syncookies=1
# Ochrona plików (hardlink/symlink)
fs.protected_hardlinks=1
fs.protected_symlinks=1
EOF
    info "  ✓ /etc/sysctl.d/99-pagan-hardening.conf"

    # ── SSH: domyślnie WYŁĄCZONY + utwardzony ──
    chroot "$PAGAN_ROOT" systemctl disable sshd 2>/dev/null || true
    mkdir -p "$PAGAN_ROOT/etc/ssh/sshd_config.d"
    cat > "$PAGAN_ROOT/etc/ssh/sshd_config.d/99-pagan.conf" <<'EOF'
# Pagan Linux – utwardzanie SSH (usługa domyślnie wyłączona)
PermitRootLogin no
MaxAuthTries 4
LoginGraceTime 30
X11Forwarding no
AllowTcpForwarding no
EOF
    chmod 600 "$PAGAN_ROOT/etc/ssh/sshd_config.d/99-pagan.conf" 2>/dev/null || true
    info "  ✓ sshd utwardzony + wyłączony (włącz: systemctl enable sshd)"

    # Domyślny start do trybu tekstowego (multi-user.target) zamiast odpalania menedżera logowania
    chroot "$PAGAN_ROOT" systemctl set-default multi-user.target 2>/dev/null || true
    chroot "$PAGAN_ROOT" systemctl disable sddm 2>/dev/null || true

    # Uprawnienia sudo dla grupy wheel
    mkdir -p "$PAGAN_ROOT/etc/sudoers.d"
    echo "%wheel ALL=(ALL:ALL) ALL" > "$PAGAN_ROOT/etc/sudoers.d/wheel"
    chmod 440 "$PAGAN_ROOT/etc/sudoers.d/wheel" 2>/dev/null || true

    # Przywróć shebang perl do #!/usr/bin/perl (docelowy system w chrocie ma tam swój perl)
    find "$PAGAN_ROOT/usr/bin" -maxdepth 1 -type f -exec sed -i 's|^#!'"$PAGAN_ROOT"'/usr/bin/perl|#!/usr/bin/perl|' {} + 2>/dev/null || true

    # /etc/pagan-release
    cat > "$PAGAN_ROOT/etc/pagan-release" <<EOF
Pagan Linux ${PAGAN_VERSION}
Środowisko: ${PAGAN_DE^^}
Kernel: ${KERNEL_VERSION}
Data: $(date)
Architektura: $(uname -m)
LFS: ${LFS_VERSION}
EOF

    # /etc/issue
    cat > "$PAGAN_ROOT/etc/issue" <<EOF
Pagan Linux ${PAGAN_VERSION} \n \l

EOF

    # /etc/os-release
    cat > "$PAGAN_ROOT/etc/os-release" <<EOF
NAME="Pagan Linux"
VERSION="${PAGAN_VERSION}"
ID=pagan
PRETTY_NAME="Pagan Linux ${PAGAN_VERSION}"
ANSI_COLOR="1;32"
LOGO=paganlinux
HOME_URL="https://paganlinux.eu"
EOF

    # Uzupełnij brakujące libki z hosta (krb5/KF6/gpg/icu…) – musi być PRZED
    # squashfs/ISO, żeby trafiły do obrazu
    _import_host_libs

    # Świeży ld.so.cache – stary (sprzed instalacji pakietów) nie zawiera
    # większości libek; ld.so i tak szuka w /lib /usr/lib, ale cache przyspiesza
    # i obejmuje też katalogi po scaleniu /usr/lib64 → /usr/lib.
    ldconfig -r "$PAGAN_ROOT" 2>/dev/null || warn "ldconfig -r nie powiodło się (niekrytyczne)"

    # XKB: dane xkeyboard-config leżą w /usr/share/xkeyboard-config-2, a Xorg czyta
    # /usr/share/X11/xkb (symlink). Pre-cleanup przy buildzie potrafi go usunąć,
    # a przy skip-resume nikt go nie odtwarza → domykamy ZAWSZE na końcu buildu.
    if [[ -d "$PAGAN_ROOT/usr/share/xkeyboard-config-2" && ! -L "$PAGAN_ROOT/usr/share/X11/xkb" ]]; then
        rm -rf "$PAGAN_ROOT/usr/share/X11/xkb"
        mkdir -p "$PAGAN_ROOT/usr/share/X11"
        ln -s ../xkeyboard-config-2 "$PAGAN_ROOT/usr/share/X11/xkb"
        info "  ✓ XKB: /usr/share/X11/xkb → xkeyboard-config-2"
    fi

    _write_pagan_news

    # GSettings: bez gschemas.compiled każde g_settings… pada (at-spi robi TRAP:
    # „Cannot get the default GSettingsSchemaSource”). Kompilujemy schematy.
    if [[ -d "$PAGAN_ROOT/usr/share/glib-2.0/schemas" && -x "$PAGAN_ROOT/usr/bin/glib-compile-schemas" ]]; then
        chroot "$PAGAN_ROOT" env -u LD_LIBRARY_PATH /usr/bin/glib-compile-schemas \
            /usr/share/glib-2.0/schemas >/dev/null 2>&1 || true
        info "  ✓ GSettings: gschemas.compiled wygenerowane"
    fi
    # at-spi (a11y) startuje z autostartu XFCE, a at-spi2-core w tym buildzie nie
    # dostarcza schemy org.a11y.Bus → launcher wpada w TRAP przy starcie sesji.
    # Na 0.1 wyłączamy autostart (usługa pozostaje aktywowalna na żądanie).
    if [[ -f "$PAGAN_ROOT/etc/xdg/autostart/at-spi-dbus-bus.desktop" ]]; then
        mv -f "$PAGAN_ROOT/etc/xdg/autostart/at-spi-dbus-bus.desktop" \
              "$PAGAN_ROOT/etc/xdg/autostart/at-spi-dbus-bus.desktop.disabled" 2>/dev/null || true
        info "  ✓ at-spi autostart wyłączony (brak org.a11y.Bus schema)"
    fi

    # GTK: bez gsettings-desktop-schemas GTK potrafi nie ustawić motywu ikon i
    # spaść do (pustego) hicolor → „Could not load a pixbuf from icon theme” +
    # twardy assert libwnck w xfce4-session. Wymuszamy Adwaitę w /etc/gtk-3.0
    # i dajemy fallback PNG w hicolor dla ikony okien (niezależny od loadera SVG).
    mkdir -p "$PAGAN_ROOT/etc/gtk-3.0"
    if [[ ! -f "$PAGAN_ROOT/etc/gtk-3.0/settings.ini" ]]; then
        cat > "$PAGAN_ROOT/etc/gtk-3.0/settings.ini" <<'GTKINI'
[Settings]
gtk-icon-theme-name=Adwaita
gtk-theme-name=Adwaita
gtk-font-name=Sans 10
GTKINI
    fi
    mkdir -p "$PAGAN_ROOT/usr/share/icons/hicolor/16x16/apps" \
             "$PAGAN_ROOT/usr/share/icons/hicolor/scalable/apps"
    _ai16="$PAGAN_ROOT/usr/share/icons/Adwaita/16x16/mimetypes/application-x-executable.png"
    _aisvg="$PAGAN_ROOT/usr/share/icons/Adwaita/scalable/mimetypes/application-x-executable.svg"
    [[ -f "$_ai16" ]] && cp -a "$_ai16" "$PAGAN_ROOT/usr/share/icons/hicolor/16x16/apps/application-x-executable.png"
    [[ -f "$_aisvg" ]] && cp -a "$_aisvg" "$PAGAN_ROOT/usr/share/icons/hicolor/scalable/apps/application-x-executable.svg"
    chroot "$PAGAN_ROOT" env -u LD_LIBRARY_PATH /usr/bin/gtk-update-icon-cache -f -q \
        /usr/share/icons/hicolor 2>/dev/null || true
    chroot "$PAGAN_ROOT" env -u LD_LIBRARY_PATH /usr/bin/gtk-update-icon-cache -f -q \
        /usr/share/icons/Adwaita 2>/dev/null || true
    info "  ✓ GTK: motyw ikon Adwaita wymuszony + fallback PNG w hicolor"

    # X + narzędzia gruba muszą mieć komplet libek w rootfs – inaczej GUI live
    # nie wstanie („libtirpc.so.3: …”), a grub-install pada:
    # „libdevmapper.so.1.02: cannot open shared object file”. Import z hosta
    # (host == target glibc 2.44); funkcja pomija nieobecne binarki.
    import_xorg_runtime_libs || warn "  Xorg: import libek nieudany – kontynuuję"

    # Manifest bazy z obrazu (/usr/share/pag/base.json) – dla `pag base`:
    # informuje, które pakiety wbudował builder (nazwa + wersja), żeby
    # `pag base adopt` mógł dograć je z repo i włączyć je do rollingu.
    generate_base_manifest

    ok "Finalizacja zakończona!"
}

# ========================= WERYFIKACJA SYSTEMU =========================
verify_rootfs() {
    step "WERYFIKACJA SYSTEMU – sanity check po buildzie"
    local fails=0
    VERIFY_CRITICAL=0   # globalny licznik braków KRYTYCZNYCH (patrz `crit`)

    # ── BOOTOWALNOŚĆ (krytyczne) ────────────────────────────────────────────
    # To, bez czego system NIE WSTANIE. Sprawdzane jako pierwsze i twardo, bo
    # właśnie tu obraz potrafił wyjść niekompletny (brak NVIDIA, niepełny
    # initramfs) i mimo to został wydany.
    local _kimg _init _mdep _grubimg _gtext
    _kimg=$(ls -1 "$PAGAN_ROOT/boot/"vmlinuz* 2>/dev/null | grep -v 'vmlinuz-linux$' | head -n1 || true)
    if [[ -n "$_kimg" ]]; then
        ok "Boot: jądro $(basename "$_kimg")"
    else
        crit "Boot: brak /boot/vmlinuz* – GRUB (10_linux szuka 'vmlinuz*') nie znajdzie jądra"
    fi

    # initramfs: pusty/uszkodzony = rootfs się nie zamontuje (brak squashfs/ext4/nvme).
    _init=$(ls -1 "$PAGAN_ROOT/boot/"initramfs* "$PAGAN_ROOT/boot/"initrd* 2>/dev/null | head -n1 || true)
    if [[ -s "$_init" ]]; then
        ok "Boot: initramfs $(basename "$_init") ($(du -hL "$_init" 2>/dev/null | cut -f1 || echo '?'))"
    else
        crit "Boot: brak lub pusty /boot/initramfs* – system nie wystartuje (rootfs się nie zamontuje)"
    fi

    # modules.dep: bez depmod żaden moduł nie zostanie znaleziony (m.in. squashfs,
    # sterowniki dysku i nvidia-drm).
    _mdep=$(ls -1 "$PAGAN_ROOT/usr/lib/modules/"*/modules.dep 2>/dev/null | head -n1 || true)
    if [[ -s "$_mdep" ]]; then
        ok "Boot: $(basename "$(dirname "$_mdep")")/modules.dep obecne (depmod wykonany)"
    else
        crit "Boot: brak /usr/lib/modules/*/modules.dep – moduły się nie załadują (brak depmod)"
    fi

    # GRUB: instalator (Calamares) i `pag grub-update` wołają grub-install/mkconfig.
    if [[ -x "$PAGAN_ROOT/usr/bin/grub-install" || -x "$PAGAN_ROOT/usr/sbin/grub-install" ]]; then
        ok "Boot: grub-install obecny"
    else
        crit "Boot: brak grub-install – instalacja bootloadera padnie"
    fi

    # GRUB i386-pc: kernel.img musi mieć .text na 0x9000, inaczej grub-mkimage kończy
    # „kernel.img is miscompiled: its start address is not 0x9000” i instalacja na
    # BIOS (MBR) NIE działa. UWAGA: w GRUB 2.14 kernel.img JEST plikiem ELF (dlatego
    # czyta go readelf), a `kernel.exec` NIE jest używany przez grub-mkimage – jego
    # adres 0x9074 (typowy po imporcie z dystrybucji) niczego nie psuje, więc go nie
    # sprawdzamy. Test funkcjonalny: grub-mkimage -d <rootfs>/usr/lib/grub/i386-pc.
    #
    # PORÓWNANIE LICZBOWE (_addr_is_0x9000), nie tekstowe: readelf zeruje pole
    # Addr do szerokości ELF-a, więc dla i386-pc wychodzi „00009000”. Wcześniejsze
    # `== "9000"` dawało fałszywy „miscompiled” i wstrzymywało wydanie ISO mimo
    # sprawnego GRUB-a (a wcześniejsza kontrola w fazie builda, glob `*9000`,
    # raportowała wtedy OK – stąd sprzeczność w logach).
    _grubimg="$PAGAN_ROOT/usr/lib/grub/i386-pc/kernel.img"
    if [[ -f "$_grubimg" ]] && command -v readelf >/dev/null 2>&1; then
        _gtext=$(readelf -SW "$_grubimg" 2>/dev/null \
                 | sed -n 's/^ *\[ *[0-9]*\] *\.text  *[A-Z]* *\([0-9a-fA-F]*\).*/\1/p' | head -n1 || true)
        if _addr_is_0x9000 "$_gtext"; then
            ok "Boot: GRUB i386-pc kernel.img .text=0x9000 (instalacja na BIOS OK)"
        else
            crit "Boot: GRUB i386-pc kernel.img .text=0x${_gtext:-?} (ma być 0x9000) – instalacja na BIOS padnie (grub-mkimage: miscompiled)"
        fi
    fi

    # Strefy czasowe: Calamares (SetTimezoneJob) wymaga SKOMPILOWANYCH danych
    # w <root>/usr/share/zoneinfo/<Region>/<Zone>, inaczej instalacja kończy się
    # błędem „Bad path” jeszcze przed zapisem systemu.
    if [[ -f "$PAGAN_ROOT/usr/share/zoneinfo/Europe/Warsaw" ]]; then
        ok "Strefy czasowe: dane zoneinfo skompilowane"
    else
        warn "Brak /usr/share/zoneinfo/Europe/Warsaw – instalator padnie na wyborze strefy czasowej"
        ((fails++)) || true
    fi

    # Moduły Pythona Calamaresa czytają klucze konfiguracji TWARDO
    # (configuration["…"]), a Calamares nie scala plików conf — bierze pierwszy
    # istniejący. Moduł bootloader bez klucza grubInstall/efiBootMgr rzuca
    # KeyError, co Calamares raportuje jako „Bad main script file … raised an
    # exception”. Sprawdzamy więc kompletność tego pliku.
    local _bl_conf="$PAGAN_ROOT/etc/calamares/modules/bootloader.conf"
    if [[ -f "$_bl_conf" ]]; then
        local _missing="" _key
        for _key in efiBootLoader grubInstall grubMkconfig grubCfg grubProbe efiBootMgr; do
            grep -qE "^[[:space:]]*${_key}:" "$_bl_conf" || _missing+=" $_key"
        done
        if [[ -n "$_missing" ]]; then
            warn "bootloader.conf: brak kluczy:$_missing – moduł bootloader padnie (KeyError)"
            ((fails++)) || true
        else
            ok "Calamares bootloader.conf: wszystkie wymagane klucze obecne"
        fi
    else
        warn "Brak /etc/calamares/modules/bootloader.conf – moduł bootloader padnie"
        ((fails++)) || true
    fi

    # efibootmgr jest potrzebny przy instalacji na UEFI (efi_boot_next).
    if [[ -x "$PAGAN_ROOT/usr/bin/efibootmgr" ]]; then
        ok "efibootmgr obecny (instalacja UEFI)"
    else
        warn "Brak efibootmgr – instalacja na UEFI padnie na module bootloader"
        ((fails++)) || true
    fi

    # WiFi: bez wpa_supplicant w /usr/sbin + aktywacji D-Bus NetworkManager nie
    # obsłuży żadnej karty WLAN (także USB).
    if [[ -x "$PAGAN_ROOT/usr/sbin/wpa_supplicant" ]]; then
        if [[ -f "$PAGAN_ROOT/usr/share/dbus-1/system-services/fi.w1.wpa_supplicant1.service" ]]; then
            ok "WiFi: wpa_supplicant + aktywacja D-Bus obecne"
        else
            warn "WiFi: brak aktywacji D-Bus dla wpa_supplicant – NetworkManager nie połączy WLAN"
            ((fails++)) || true
        fi
        if [[ -x "$PAGAN_ROOT/usr/local/sbin/wpa_supplicant" ]]; then
            warn "WiFi: kopia wpa_supplicant w /usr/local/sbin – NM szuka w /usr/sbin"
        fi
    else
        warn "WiFi: brak /usr/sbin/wpa_supplicant – sieć bezprzewodowa nie zadziała"
        ((fails++)) || true
    fi

    # Netinstall: moduł packages musi znać backend `pag`.
    if [[ -f "$PAGAN_ROOT/usr/lib/calamares/modules/packages/main.py" ]]; then
        if grep -q 'PAGAN: backend pag' "$PAGAN_ROOT/usr/lib/calamares/modules/packages/main.py"; then
            ok "Calamares: backend pag dla netinstall obecny"
        else
            warn "Calamares: brak backendu pag – netinstall nie zainstaluje pakietów"
            ((fails++)) || true
        fi
    fi

    # D-Bus: wykryj stary, wadliwy układ (usługa w sockets.target.wants), który
    # tworzy cykl zależności i TWARDO blokuje start systemu.
    if [[ -e "$PAGAN_ROOT/etc/systemd/system/sockets.target.wants/dbus.service" ]]; then
        crit "dbus: dbus.service w sockets.target.wants → cykl zależności (system nie wstanie)"
    else
        if [[ -e "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/dbus.service" ]] || \
           [[ -e "$PAGAN_ROOT/etc/systemd/system/sockets.target.wants/dbus.socket" ]]; then
            ok "dbus: układ jednostek bez cyklu zależności"
        else
            crit "dbus: brak obu dowiązań (multi-user.target.wants/dbus.service i sockets.target.wants/dbus.socket) – bez D-Busa nie wstanie polkitd ani sieć"
        fi
    fi

    # NetworkManager: bez jednostki systemd w live NIKT nie podniesie interfejsu
    # (brak DHCP), a bez polityki D-Bus NM nie zajmie nazwy na magistrali.
    # To najczęstsza przyczyna „QEMU nie przekazuje sieci” – a QEMU przekazuje.
    if [[ -x "$PAGAN_ROOT/usr/sbin/NetworkManager" ]]; then
        local _nm_ok=1
        if [[ ! -f "$PAGAN_ROOT/usr/lib/systemd/system/NetworkManager.service" ]]; then
            warn "NetworkManager: brak jednostki /usr/lib/systemd/system/NetworkManager.service – live bez sieci (brak DHCP)"
            _nm_ok=0
        fi
        if [[ ! -f "$PAGAN_ROOT/usr/share/dbus-1/system.d/org.freedesktop.NetworkManager.conf" ]]; then
            warn "NetworkManager: brak polityki D-Bus – NM nie zajmie nazwy org.freedesktop.NetworkManager"
            _nm_ok=0
        fi
        if [[ ! -e "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/NetworkManager.service" ]]; then
            warn "NetworkManager: nie włączony w multi-user.target.wants – live wystartuje bez sieci"
            _nm_ok=0
        fi
        if [[ $_nm_ok -eq 1 ]]; then
            ok "NetworkManager: jednostka + polityka D-Bus + włączony w live"
        else
            crit "NetworkManager: niekompletny (jednostka/polityka/włączenie) – live bez sieci. QEMU sieć PRZEKAZUJE (-netdev user), więc winny jest obraz, a nie maszyna wirtualna"
        fi
    else
        crit "Brak /usr/sbin/NetworkManager – system bez menedżera sieci (tylko DHCP na piechotę)"
    fi

    # Dodatki pulpitu – każdy wpis naprawia KONKRETNY błąd z sesji live
    # (log serial qemu_install.log). Brak = sesja działa, ale okrojona.
    local _dt_ok=1
    if [[ ! -x "$PAGAN_ROOT/usr/bin/xrdb" ]]; then
        warn "xrdb: brak /usr/bin/xrdb – xinitrc XFCE zgłosi „xrdb: command not found” (Xresources nie wczytane)"
        _dt_ok=0
    fi
    if [[ ! -x "$PAGAN_ROOT/usr/bin/upower" ]] || \
       [[ ! -e "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/upower.service" ]]; then
        warn "upower: brak binarki lub usługa niewłączona – xfce4-power-manager nie pokaże stanu baterii (i będzie spam w logu)"
        _dt_ok=0
    fi
    if [[ ! -x "$PAGAN_ROOT/usr/bin/xfce4-notifyd" && \
          ! -x "$PAGAN_ROOT/usr/lib/xfce4/notifyd/xfce4-notifyd" ]] || \
       [[ ! -x "$PAGAN_ROOT/usr/bin/notify-send" ]]; then
        warn "powiadomienia: brak demona xfce4-notifyd (bin/ lub lib/xfce4/notifyd/) albo notify-send – libnotify zgłosi „Failed to connect to proxy”"
        _dt_ok=0
    fi
    if [[ ! -x "$PAGAN_ROOT/usr/bin/udisksctl" ]] || \
       [[ ! -e "$PAGAN_ROOT/etc/systemd/system/multi-user.target.wants/udisks2.service" ]]; then
        warn "udisks2: brak binarki lub usługa niewłączona – montowanie nośników nie zadziała"
        _dt_ok=0
    fi
    if [[ $_dt_ok -eq 1 ]]; then
        ok "Dodatki pulpitu: xrdb + upower + powiadomienia + udisks2 obecne i włączone"
    else
        ((fails++)) || true
    fi

    # Tumbler: pluginy nie mogą mieć nierozwiązanych symboli (rozjazd ABI
    # z wcześniejszych przebiegów/resume). Repo PAG ma spójne wersje libgsf/ffmpeg.
    local _tb_bad=""
    if _lib_missing_symbol "/usr/lib/libgsf-1.so.114*" "xmlNanoHTTPClose"; then
        _tb_bad+=" odf(libgsf)"
    fi
    if _lib_missing_symbol "/usr/lib/libavcodec.so.63*" "rsvg_handle_render_document"; then
        _tb_bad+=" ffmpeg(libavcodec)"
    fi
    if [[ -n "$_tb_bad" ]]; then
        warn "tumbler: rozjechane ABI – te pluginy się nie wczytają:$_tb_bad (napraw: pag install -f libgsf ffmpeg)"
        ((fails++)) || true
    else
        ok "tumbler: biblioteki pluginów bez rozjechanego ABI"
    fi

    # NVIDIA: moduły muszą być W OBRAZIE – kernel nie ma nouveau, więc bez nich
    # karta NVIDIA w live nie ma żadnego sterownika. Wcześniej build je gubił
    # (DESTDIR zamiast INSTALL_MOD_PATH → lądowały na HOŚCIE, nie w rootfsie).
    if _is_true "${SKIP_NVIDIA:-0}"; then
        info "NVIDIA: pominięte w tym buildzie (SKIP_NVIDIA=1/--no-nvidia)"
    else
        local _nvko
        _nvko=$(find "$PAGAN_ROOT/usr/lib/modules" -name 'nvidia.ko*' 2>/dev/null | head -n1)
        if [[ -n "$_nvko" ]]; then
            ok "NVIDIA: moduły w obrazie (${_nvko#"$PAGAN_ROOT"})"
        else
            crit "NVIDIA: brak nvidia.ko w obrazie – karta NVIDIA w live nie dostanie żadnego sterownika (kernel bez nouveau). Zbuduj moduły i wydaj ISO ponownie"
        fi
    fi

    # Toolchain docelowy: bez `ld` (binutils) nie da się skompilować NICZEGO na
    # zainstalowanym systemie – czyli DKMS/nvidia-dkms też nie zadziała.
    if [[ -x "$PAGAN_ROOT/usr/bin/ld" ]]; then
        ok "Toolchain docelowy: binutils obecne (ld/as/objcopy) – DKMS zadziała"
    else
        crit "Toolchain docelowy: brak /usr/bin/ld (binutils) – na zainstalowanym systemie nie zbudujesz NICZEGO (DKMS/nvidia-dkms też nie)"
    fi

    # Udev: baza hwdb.
    if [[ -s "$PAGAN_ROOT/etc/udev/hwdb.bin" ]]; then
        ok "udev: /etc/udev/hwdb.bin obecne"
    else
        warn "udev: brak /etc/udev/hwdb.bin – generator systemd-imds zgłosi błąd przy starcie"
    fi

    # Edytor tekstu w systemie live – bez niego nie da się poprawić żadnego
    # pliku .conf z powłoki ratunkowej/analitycznej. nano jest domyślny,
    # vim/vi jako alternatywa.
    if [[ -x "$PAGAN_ROOT/usr/bin/nano" || -x "$PAGAN_ROOT/bin/nano" ]]; then
        ok "Edytor tekstu: nano obecny"
    elif [[ -x "$PAGAN_ROOT/usr/bin/vi" || -x "$PAGAN_ROOT/bin/vi" ]]; then
        ok "Edytor tekstu: vi/vim obecny (brak nano)"
    else
        warn "Brak edytora tekstu (nano/vi) – nie poprawisz konfiguracji z live"
        ((fails++)) || true
    fi

    # Podstawowe binarki
    local check
    for check in bash sh ls cat grep sed awk make gcc python3 perl; do
        if [[ ! -x "$PAGAN_ROOT/usr/bin/$check" ]] && [[ ! -x "$PAGAN_ROOT/bin/$check" ]]; then
            warn "Brak binarki: $check"
            ((fails++)) || true
        fi
    done

    # Python + sqlite3 (wymagane przez pag)
    if [[ -x "$PAGAN_ROOT/usr/bin/python3" ]]; then
        if chroot "$PAGAN_ROOT" /usr/bin/python3 -c "import sqlite3; import zlib; import tarfile" >/dev/null 2>&1; then
            ok "python3 + sqlite3 działa"
        else
            warn "python3 nie ma sqlite3/zlib/tarfile – pag nie będzie działać!"
            ((fails++)) || true
        fi
    else
        warn "Brak python3 – pag nie będzie działać!"
        ((fails++)) || true
    fi

    # pag – pełna wersja v3 (nie stub)
    if [[ -f "$PAGAN_ROOT/usr/bin/pag" ]]; then
        if grep -q "PAG - Pagan Linux Package Manager v3" "$PAGAN_ROOT/usr/bin/pag" 2>/dev/null; then
            ok "pag – pełna wersja v3"
        else
            warn "pag to stub (niepełna wersja) – sprawdź install_pag"
            ((fails++)) || true
        fi
    else
        warn "Brak /usr/bin/pag"
        ((fails++)) || true
    fi

    # Manifest bazy z obrazu (/usr/share/pag/base.json) – bez niego `pag base`
    # nie wie, które pakiety obrazu da się zaadoptować z repo (rolling).
    if [[ -s "$PAGAN_ROOT/usr/share/pag/base.json" ]]; then
        local _bn
        _bn=$(python3 -c "import json,sys; print(len(json.load(open(sys.argv[1])).get('packages', [])))" \
                "$PAGAN_ROOT/usr/share/pag/base.json" 2>/dev/null || echo 0)
        if [[ "${_bn:-0}" -gt 0 ]]; then
            ok "Manifest bazy (pag base): ${_bn} pakietów"
        else
            warn "Manifest bazy jest pusty – 'pag base adopt' nie zaadoptuje niczego"
            ((fails++)) || true
        fi
    else
        warn "Brak /usr/share/pag/base.json – 'pag base adopt' (rolling) nie zadziała"
        ((fails++)) || true
    fi

    # Calamares – instalator systemu (domyślnie; dla XFCE z repo PAG)
    if [[ -x "$PAGAN_ROOT/usr/bin/calamares" ]]; then
        ok "calamares – instalator obecny"
    else
        warn "Brak /usr/bin/calamares – z live nie zainstalujesz systemu (sprawdź install_calamares)"
        ((fails++)) || true
    fi

    # fish – domyślna powłoka użytkowników (wyłącza ją --no-fish)
    if [[ "${PAGAN_FISH:-1}" == "1" ]]; then
        if [[ -x "$PAGAN_ROOT/usr/bin/fish" ]]; then
            ok "fish – powłoka obecna (domyślna dla użytkowników)"
        else
            warn "Brak /usr/bin/fish – zostaje bash (sprawdź build_final_system)"
            ((fails++)) || true
        fi
    fi

    # pagsync – tylko po stronie buildera (repo/receptury); w live system nie trafia
    # (usunięty z install_pag – narzędzie deweloperskie)

    # Konfiguracja PAG: repozytorium + keyring
    if [[ -s "$PAGAN_ROOT/etc/pag/repos.conf" ]]; then
        local _repo_url
        _repo_url=$(grep -v '^#' "$PAGAN_ROOT/etc/pag/repos.conf" | grep -v '^$' | head -1)
        ok "PAG repos.conf: ${_repo_url:-?}"
    else
        warn "Brak /etc/pag/repos.conf"
        ((fails++)) || true
    fi
    if [[ -d "$PAGAN_ROOT/etc/pag/gpg" ]]; then
        ok "PAG keyring: /etc/pag/gpg"
    else
        warn "Brak /etc/pag/gpg"
        ((fails++)) || true
    fi

    # Kernel: wsparcie dla live ISO (squashfs + overlay + iso9660 wbudowane)
    local kconf="$PAGAN_ROOT/boot/config-${KERNEL_VERSION}-pagan"
    if [[ -f "$kconf" ]]; then
        local missing_k=()
        for opt in CONFIG_SQUASHFS CONFIG_SQUASHFS_XATTR CONFIG_OVERLAY_FS \
                   CONFIG_ISO9660_FS CONFIG_BLK_DEV_LOOP CONFIG_TMPFS_POSIX_ACL; do
            grep -q "^${opt}=y" "$kconf" || missing_k+=("$opt")
        done
        if [[ ${#missing_k[@]} -eq 0 ]]; then
            ok "Kernel: live-boot (squashfs/overlay/iso9660) wbudowane"
        else
            warn "Kernel: brak wbudowanych: ${missing_k[*]} – live ISO może nie wstać"
            ((fails++)) || true
        fi
    else
        warn "Brak configu kernela: $kconf"
    fi

    # --- X11/Xorg: bez tych elementów Xorg pada (brak ekranu logowania) ---
    # W trybie none/base/cli Xorg nie jest instalowany – checki X11 pomijamy.
    if [[ "${PAGAN_DE,,}" != "none" && "${PAGAN_DE,,}" != "base" && "${PAGAN_DE,,}" != "cli" ]]; then
    # xkbcomp – bez niego: "XKB: Failed to compile keymap" → fatal server error.
    if [[ -x "$PAGAN_ROOT/usr/bin/xkbcomp" ]]; then
        ok "xkbcomp obecny"
    else
        warn "Brak /usr/bin/xkbcomp – Xorg nie wystartuje (Failed to compile keymap)"
        ((fails++)) || true
    fi
    # xkeyboard-config (dane XKB) – bez nich XKB nie ma czym kompilować mapy
    if [[ -d "$PAGAN_ROOT/usr/share/X11/xkb/rules" && -d "$PAGAN_ROOT/usr/share/X11/xkb/symbols" ]]; then
        ok "xkeyboard-config (dane XKB) obecne"
    else
        warn "Brak danych xkeyboard-config (/usr/share/X11/xkb) – XKB nie zadziała"
        ((fails++)) || true
    fi
    # Pozostałe narzędzia do startu sesji graficznej (live: startx, system: lightdm)
    for _xbin in Xorg xauth setxkbmap xinit; do
        if [[ -x "$PAGAN_ROOT/usr/bin/$_xbin" ]]; then
            ok "$_xbin obecny"
        else
            warn "Brak /usr/bin/$_xbin – sesja graficzna (startx/lightdm) nie wystartuje poprawnie"
            ((fails++)) || true
        fi
    done
    # Sterowniki Xorg: libinput (input) + modesetting (ekran przez DRM/KMS)
    if [[ -f "$PAGAN_ROOT/usr/lib/xorg/modules/drivers/modesetting_drv.so" ]]; then
        ok "Xorg modesetting driver obecny"
    else
        warn "Brak modesetting_drv.so – Xorg nie ma sterownika ekranu (tylko fbdev/vesa)"
        ((fails++)) || true
    fi
    if [[ -f "$PAGAN_ROOT/usr/lib/xorg/modules/input/libinput_drv.so" ]]; then
        ok "Xorg libinput driver obecny"
    else
        warn "Brak libinput_drv.so – klawiatura/mysz nie będą działać w Xorg"
        ((fails++)) || true
    fi
    # Menedżer logowania + sesja XFCE – bez nich „zainstalowany” system wstaje
    # do trybu tekstowego zamiast do okna logowania (objaw: brak GUI po instalacji).
    for _sesbin in lightdm startxfce4 xfce4-session; do
        if [[ -x "$PAGAN_ROOT/usr/bin/$_sesbin" ]]; then
            ok "$_sesbin obecny"
        else
            warn "Brak /usr/bin/$_sesbin – logowanie/sesja XFCE nie wystartuje"
            ((fails++)) || true
        fi
    done
    fi # X11 checks (pomijane dla --de none/base/cli)

    # regulatory.db – bez niego cfg80211 loguje błąd i WiFi nie ma kraju domyślnego
    if [[ -s "$PAGAN_ROOT/usr/lib/firmware/regulatory.db" ]]; then
        ok "regulatory.db (WiFi) obecny"
    else
        warn "Brak regulatory.db – WiFi: cfg80211 failed to load regulatory.db"
        ((fails++)) || true
    fi

    # /etc/profile – bez niego profile.d (m.in. pagan-live.sh) nigdy się
    # nie wykona i live kończy w gołym bashu zamiast pulpitem live (GUI).
    if [[ -s "$PAGAN_ROOT/etc/profile" ]] && \
       grep -q 'profile.d/\*.sh' "$PAGAN_ROOT/etc/profile" 2>/dev/null; then
        ok "/etc/profile ładuje /etc/profile.d/*.sh"
    else
        warn "Brak /etc/profile (lub bez pętli profile.d) – live wita gołym bashem, sesja live nie startuje"
        ((fails++)) || true
    fi
    if [[ -f "$PAGAN_ROOT/etc/profile.d/pagan-live.sh" ]]; then
        ok "pagan-live.sh w profile.d"
    else
        warn "Brak /etc/profile.d/pagan-live.sh – sesja live nie wystartuje"
        ((fails++)) || true
    fi

    # dbus.socket – aktywacja socketowa; cmake z dbus 1.16.2 generuje jednostki
    # z pustymi @EXPANDED_BINDIR@/@DBUS_SYSTEM_SOCKET@ (bug) → sprawdzamy poprawność
    if [[ -f "$PAGAN_ROOT/usr/lib/systemd/system/dbus.socket" ]] && \
       grep -q '^ListenStream=/run/dbus/system_bus_socket$' "$PAGAN_ROOT/usr/lib/systemd/system/dbus.socket" && \
       grep -q '^ExecStart=/usr/bin/dbus-daemon' "$PAGAN_ROOT/usr/lib/systemd/system/dbus.service"; then
        ok "dbus.socket (aktywacja socketowa) poprawny"
    else
        warn "dbus: brak poprawnych jednostek dbus.socket/dbus.service – dbus nie wystartuje (logind padnie)"
        ((fails++)) || true
    fi

    # Polityka D-Bus systemd. /usr/share/dbus-1/system.conf ma w polityce
    # domyślnej <deny own="*"/>, więc bez tych plików PID1 nie zajmie nazwy
    # org.freedesktop.systemd1, a logind org.freedesktop.login1.
    if [[ -f "$PAGAN_ROOT/usr/share/dbus-1/system.d/org.freedesktop.systemd1.conf" ]]; then
        ok "dbus: polityka systemd (org.freedesktop.systemd1.conf) obecna"
    elif [[ -f "$PAGAN_ROOT/usr/lib/systemd/systemd" ]]; then
        warn "dbus: BRAK org.freedesktop.systemd1.conf – PID1 nie zajmie nazwy (systemctl/logind padną)"
        ((fails++)) || true
    fi
    if [[ -f "$PAGAN_ROOT/usr/share/dbus-1/system-services/org.freedesktop.login1.service" ]]; then
        ok "dbus: aktywacja org.freedesktop.login1 dostępna"
    fi

    # Strefy czasowe: katalog musi być czytelny dla nie-roota. mktemp -d dawał
    # 0700 i taki korzeń trafiał do ISO (objaw: „Bad path …/zoneinfo/Europe/Warsaw").
    if [[ -f "$PAGAN_ROOT/usr/share/zoneinfo/Europe/Warsaw" ]]; then
        _zi_mode=$(stat -c '%a' "$PAGAN_ROOT/usr/share/zoneinfo" 2>/dev/null || echo '')
        # 10# wymusza podstawę 10 (bash inaczej czytałby np. 0755 jako ósemkowe).
        if [[ -n "$_zi_mode" && $(( 10#$_zi_mode % 10 )) -ge 4 ]]; then
            ok "zoneinfo: /usr/share/zoneinfo czytelne dla wszystkich (mode $_zi_mode)"
        else
            warn "zoneinfo: uprawnienia /usr/share/zoneinfo = ${_zi_mode:-brak} (powinno być 755) – nie-root nie odczyta stref"
            ((fails++)) || true
        fi
    fi

    # GRUB + initramfs: grub-mkconfig (/etc/grub.d/10_linux) szuka initrd
    # wyłącznie pod nazwą Z WERSJĄ JĄDRA (`initramfs-<ver>.img`), gdzie
    # <ver> = nazwa kernela bez wiodących znaków nie-cyfrowych. Nasz obraz to
    # /boot/initramfs.img, więc bez aliasu wpis w menu GRUB nie ma linii
    # `initrd` i zainstalowany system startuje bez initramfs.
    if [[ -f "$PAGAN_ROOT/boot/initramfs.img" ]]; then
        local _zk _zb _zv _zbad=""
        for _zk in "$PAGAN_ROOT"/boot/vmlinuz*; do
            [[ -e "$_zk" ]] || continue
            _zb="$(basename "$_zk")"
            _zv="$(printf '%s' "$_zb" | sed -e 's,^[^0-9]*-,,g')"
            [[ -n "$_zv" && "$_zv" != "$_zb" ]] || continue
            [[ -e "$PAGAN_ROOT/boot/initramfs-$_zv.img" ]] || _zbad+=" $_zb"
        done
        if [[ -z "$_zbad" ]]; then
            ok "GRUB: każdy /boot/vmlinuz* ma initramfs-<wersja>.img (menuentry z initrd)"
        else
            warn "GRUB: brak initramfs-<wersja>.img dla:$_zbad – grub-mkconfig pominie linię initrd (system może nie wstać)"
            ((fails++)) || true
        fi
    fi

    # Initramfs zawiera skrypt init (obsługa live + dysk)
    if [[ -f "$PAGAN_ROOT/boot/initramfs.img" ]]; then
        if gzip -dc "$PAGAN_ROOT/boot/initramfs.img" 2>/dev/null | cpio -it 2>/dev/null | grep -qx "init"; then
            ok "Initramfs: zawiera /init"
        else
            warn "Initramfs: brak /init"
            ((fails++)) || true
        fi
    else
        warn "Brak /boot/initramfs.img"
        ((fails++)) || true
    fi

    # glibc
    if [[ -f "$PAGAN_ROOT/usr/lib/libc.so.6" ]]; then
        local glibc_ver
        glibc_ver=$(chroot "$PAGAN_ROOT" /usr/bin/ldd --version 2>/dev/null | head -1 | grep -o '[0-9.]*' || true)
        ok "glibc: ${glibc_ver:-?}"
    else
        warn "Brak libc.so.6!"
        ((fails++)) || true
    fi

    # Chroot test
    if chroot "$PAGAN_ROOT" /bin/bash -c "echo OK" >/dev/null 2>&1; then
        ok "chroot + bash działa"
    else
        local _chroot_err
        _chroot_err=$(chroot "$PAGAN_ROOT" /bin/bash -c "echo OK" 2>&1 >/dev/null || true)
        warn "chroot /bin/bash NIE działa! (${_chroot_err:-brak komunikatu błędu})"
        ((fails++)) || true
    fi

    if [[ $fails -gt 0 ]]; then
        warn "Weryfikacja: $fails problemów znalezionych (patrz wyżej)"
    else
        ok "Weryfikacja: wszystkie sanity checki przeszły ✅"
    fi
    return $fails
}

# ========================= WIRTUALNE SYSTEMY PLIKÓW =========================
mount_virtual_fs() {
    info "Montowanie wirtualnych systemów plików w $PAGAN_ROOT..."
    # --rbind + --make-rslave: kopia /dev (razem z /dev/pts) wewnątrz rootfs
    # jest SLAVE – odmontowanie jej NIE zdejmie devpts z HOSTA. Wcześniejszy
    # `mount --bind /dev/pts` + `umount -l` odmontowywał hostowy /dev/pts
    # (po budowie nowe ptysy padały: sudo, nowe terminale).
    mount --rbind /dev "$PAGAN_ROOT/dev" 2>/dev/null && mount --make-rslave "$PAGAN_ROOT/dev" 2>/dev/null || true
    mount -t proc proc "$PAGAN_ROOT/proc" 2>/dev/null || true
    mount -t sysfs sysfs "$PAGAN_ROOT/sys" 2>/dev/null || true
    mount -t tmpfs tmpfs "$PAGAN_ROOT/run" 2>/dev/null || true
}

umount_virtual_fs() {
    info "Odmontowywanie wirtualnych systemów plików..."
    # -R zdejmuje całe drzewo (w tym /dev/pts wewnątrz rbinda /dev).
    # Bezpieczne: to slave-kopie, host zostaje nietknięty.
    umount -R "$PAGAN_ROOT/dev" 2>/dev/null || true
    umount "$PAGAN_ROOT/proc" 2>/dev/null || true
    umount "$PAGAN_ROOT/sys" 2>/dev/null || true
    umount -R "$PAGAN_ROOT/run" 2>/dev/null || true
}

# ========================= PURGE Qt/KDE (tryb none/base/cli) =========================
# `--de none` na resume-buildzie ZOSTAWIA pakiety z poprzednich prób (Plasma/KDE) –
# przez co „baza” waży tyle co pełne DE. Ta funkcja czyści resztki Plasma/SDDM/appek
# KDE. UWAGA: gdy Calamares jest włączony (domyślnie), Qt6/KF6 są POTRZEBNE
# instalatorowi (KPMcore) – wtedy NIE usuwamy Qt6/KF6 ani markera resume, tylko resztki.
purge_kde() {
    step "PURGE Qt/KDE (tryb none/base/cli)"
    local root="$PAGAN_ROOT"
    local _keep_qt=0
    _is_true "${PAGAN_CALAMARES:-1}" && _keep_qt=1

    # Nic do roboty, gdy brak śladów Qt6/KDE
    if [[ ! -d "$root/usr/lib/qt6" && ! -d "$root/usr/lib/kf6" && \
          ! -e "$root/usr/bin/sddm" && ! -e "$root/usr/lib/libQt6Core.so.6" ]]; then
        info "Brak pakietów Qt/KDE – nic do usunięcia"
        return 0
    fi

    if [[ $_keep_qt -eq 1 ]]; then
        info "Usuwam Plasma/SDDM/appki KDE z $root (Qt6/KF6 zostają – potrzebne Calamares)"
    else
        info "Usuwam Qt6/KF6/Plasma/SDDM/Calamares z $root ..."
    fi

    # ── Biblioteki plasma/phonon (zawsze – Plasma nie jest już wspierana) ──
    find "$root/usr/lib" -maxdepth 1 \( -name 'libplasma*' -o -name 'libphonon*' \) \
         -delete 2>/dev/null || true

    # ── Qt6/KF6: usuwamy TYLKO gdy Calamares wyłączony (inaczej potrzebne) ──
    if [[ $_keep_qt -eq 0 ]]; then
        rm -rf "$root/usr/lib/qt6" "$root/usr/include/qt6" "$root/usr/share/qt6" \
               "$root/usr/lib/kf6" "$root/usr/include/kf6" "$root/usr/share/kf6" \
               "$root/usr/lib/cmake/Qt6" "$root/usr/lib/cmake/KF6" \
               "$root/usr/lib64/qt6" "$root/usr/lib64/kf6" 2>/dev/null || true
        find "$root/usr/lib" -maxdepth 1 \( -name 'libQt6*' -o -name 'libKF6*' \) \
             -delete 2>/dev/null || true
        rm -rf "$root/usr/libexec"/kf6 2>/dev/null || true
    fi

    # ── Dane / konfiguracja Plasma/SDDM/KDE (zawsze) ──
    rm -rf "$root/usr/share/plasma" "$root/usr/share/plasma-workspace" \
           "$root/usr/share/breeze" "$root/usr/share/breeze-icons" \
           "$root/usr/share/sddm" "$root/usr/share/konsole" "$root/usr/share/dolphin" \
           "$root/usr/share/kde" "$root/usr/share/kde-settings" \
           "$root/usr/share/kdevappwizard" 2>/dev/null || true
    rm -rf "$root/etc/sddm.conf.d" "$root/etc/pam.d/sddm" \
           "$root/etc/pam.d/sddm-autologin" "$root/etc/pam.d/sddm-greeter" 2>/dev/null || true
    rm -rf "$root/usr/lib/systemd/system/sddm.service" 2>/dev/null || true

    # ── KF6-owe dane – tylko gdy Qt6/KF6 usuwamy ──
    if [[ $_keep_qt -eq 0 ]]; then
        rm -rf "$root/usr/share/kio" "$root/usr/share/knotifications5" \
               "$root/usr/share/kservices5" "$root/usr/share/kxmlgui5" \
               "$root/usr/share/kauth" "$root/usr/share/kpackage" \
               "$root/usr/share/kfontinst" 2>/dev/null || true
    fi

    # ── Binarki KDE/Plasma ──
    local b
    for b in sddm plasmashell kwin_wayland kwin_x11 konsole dolphin systemsettings \
             kactivitymanagerd kscreenlocker powerdevil ksmserver kstart krunner \
             kglobalaccel6 kaccess klipper kmenuedit khotkeys kpackagetool6 \
             kreadconfig6 kwriteconfig6 kuser kcmshell6; do
        rm -f "$root/usr/bin/$b" 2>/dev/null || true
    done
    rm -f "$root/usr/bin"/plasma-* "$root/usr/bin"/kde-* "$root/usr/bin"/kwin* 2>/dev/null || true

    # ── libexec KDE (bez kf6 – patrz blok Qt6/KF6 wyżej) ──
    rm -rf "$root/usr/libexec"/kwin* plasma* 2>/dev/null || true

    # ── Calamares: usuwamy tylko gdy instalator wyłączony ──
    if [[ $_keep_qt -eq 0 ]]; then
        rm -f "$root/usr/bin/calamares" 2>/dev/null || true
        rm -rf "$root/usr/share/calamares" "$root/etc/calamares" 2>/dev/null || true
    fi

    # ── Markery resume: przy włączonym Calamares zachowujemy Qt6/KF6/kpmcore/calamares ──
    if [[ -f "$RESUME_FILE" ]]; then
        if [[ $_keep_qt -eq 1 ]]; then
            sed -i -E '/^(plasma-|plasma-desktop|plasma-workspace|kwin|konsole|dolphin|sddm|sddm-kcm|phonon|breeze|breeze-icons|kactivitymanagerd|kscreenlocker|kde-cli-tools|powerdevil|polkit-kde-agent|systemsettings|kwayland|layer-shell-qt|kdecoration|libkscreen|libksysguard|libplasma|plasma-activities|plasma-integration|plasma-wayland-protocols)/d' \
                "$RESUME_FILE" 2>/dev/null || true
            info "Resume: usunięto wpisy Plasma/KDE (markery Qt6/KF6 zachowane dla Calamares)"
        else
            sed -i -E '/^(qt6-|plasma-|plasma-desktop|plasma-workspace|kwin|konsole|dolphin|sddm|sddm-kcm|kpmcore|calamares|phonon|breeze|kactivitymanagerd|kscreenlocker|kde-cli-tools|powerdevil|polkit-kde-agent|systemsettings|kwayland|layer-shell-qt|kdecoration|libkscreen|libksysguard|libplasma|plasma-activities|plasma-integration|plasma-wayland-protocols|extra-cmake-modules|kconfig|kcoreaddons|ki18n|kwidgetsaddons|kjobwidgets|kdbusaddons|kitemviews|kitemmodels|kcodecs|karchive|kcrash|kguiaddons|kwindowsystem|kauth|kcompletion|knotifications|kservice|ktextwidgets|kglobalaccel|kxmlgui|kbookmarks|kiconthemes|kdoctools|kunitconversion|kconfigwidgets|kdesu|solid|sonnet|kidletime|kdnssd|syntax-highlighting|kpackage|kdeclarative|kcmutils|kio|kparts|kpty|kfilemetadata|kirigami|kquickcharts|knotifyconfig|breeze-icons)/d' \
                "$RESUME_FILE" 2>/dev/null || true
            info "Resume: usunięto wpisy pakietów Qt/KDE (przyszły build odtworzy je od nowa)"
        fi
    fi

    # Użytkownik pagan (relikt starszych buildów) – w bazie niepotrzebny
    if chroot "$root" /usr/bin/getent passwd pagan >/dev/null 2>&1; then
        chroot "$root" /usr/sbin/userdel -r pagan 2>/dev/null || true
        info "Usunięto konto 'pagan' (relikt starszego builda)"
    fi

    ok "Qt/KDE usunięte – baza bez środowiska (doinstaluj: pag install xfce4 lightdm)"
}

# ========================= TRIM FIRMWARE (PAGAN_FIRMWARE_MIN=1) =========================
# linux-firmware waży ~1.9 GB, a większość to firmware martwe na x86 (SoC
# z telefonów/ARM, egzotyczne karty sieciowe). PAGAN_FIRMWARE_MIN=1 zostawia
# tylko typowy sprzęt desktop/laptop: intel (WiFi/BT/GPU), amdgpu, nvidia,
# iwlwifi, rtl, atheros, i915, bt* ... Oszczędność ~800-900 MB (przed xz).
trim_firmware() {
    [[ "${PAGAN_FIRMWARE_MIN:-0}" == "1" ]] || return 0
    step "TRIM FIRMWARE (PAGAN_FIRMWARE_MIN=1)"
    local fw="$PAGAN_ROOT/usr/lib/firmware"
    [[ -d "$fw" ]] || return 0
    local d tot=0
    # SoC z telefonów/ARM (0% użycia na x86) + egzotyczne NIC/fibre-channel
    local drop=(qcom tegra exynos mvebu imx amlogic meson rockchip allwinner \
                sun s5p rk hisilicon nxp cavium cnm inside-secure ezchip \
                netronome mellanox mrvl liquidio netxen qed ql2500 ql2700 \
                ql3500 qlcnic qla2xxx ti)
    for d in "${drop[@]}"; do
        if [[ -d "$fw/$d" ]]; then
            sz=$(du -sm "$fw/$d" 2>/dev/null | cut -f1)
            rm -rf "$fw/$d" 2>/dev/null
            tot=$((tot + ${sz:-0}))
            info "  ✓ usunięto firmware/$d (~${sz} MB)"
        fi
    done
    ok "Firmware przycięty (usunięto ~$((tot/1024)) GB)"
}

# ========================= WALIDACJA PAGAN_ROOT =========================
# Chroni przed skasowaniem katalogu systemowego przez --rebuild
# (clean_build_state robi rm -rf "$PAGAN_ROOT").
_validate_pagan_root() {
    local r="${PAGAN_ROOT%/}"
    [[ -n "$r" ]] || error "PAGAN_ROOT nie może być pusty"
    [[ "$r" == /* ]] || error "PAGAN_ROOT musi być ścieżką absolutną (jest: '$PAGAN_ROOT')"
    # /run/media/... to standardowy punkt montowania nośników – dozwolony.
    case "$r" in /run/media/*) return 0 ;; esac
    case "$r" in
        /|/bin|/boot|/dev|/etc|/home|/lib|/lib64|/media|/mnt|/opt|/proc|/root|/run|/sbin|/srv|/sys|/tmp|/usr|/var)
            error "Odmawiam użycia PAGAN_ROOT='$PAGAN_ROOT' – to katalog systemowy/montowania. Użyj np. /mnt/pagan" ;;
        /bin/*|/boot/*|/dev/*|/etc/*|/lib/*|/lib64/*|/proc/*|/sbin/*|/sys/*|/usr/*|/var/*|/root/*|/run/*)
            error "Odmawiam użycia PAGAN_ROOT='$PAGAN_ROOT' – leży w katalogu systemowym. Użyj np. /mnt/pagan" ;;
    esac
}

# ========================= GŁÓWNY PROCES =========================
main() {
    cd /
    show_banner
    _validate_pagan_root

    # Argumenty CLI zostały już sparsowane na początku skryptu (parse_args_early).
    # Clean environment from any previous SYSROOT leakage
    unset PKG_CONFIG_SYSROOT_DIR

    if [[ $EUID -ne 0 ]]; then
        error "Wymagane uprawnienia root (sudo)"
    fi

    if _is_true "${REBUILD_FROM_SCRATCH:-0}"; then
        clean_build_state
    fi

    # Wybór środowiska
    if [[ -z "${PAGAN_DE:-}" ]]; then
        echo ""
        echo "Wybierz środowisko graficzne:"
        echo "  1) Xfce   (wersja podstawowa – lekkie, stabilne; + Calamares)"
        echo "  2) Deepin (nowoczesne, eleganckie)"
        echo "  3) Budgie (minimalne, eleganckie)"
        echo "  4) PaganDE (labwc-pagan + lekki stack Wayland, Catppuccin Mocha)"
        echo "  5) none   (BAZA – bez środowiska; DE/aplikacje doinstalujesz przez pag)"
        echo ""
        echo "  (KDE Plasma i GNOME są tymczasowo wyłączone.)"
        echo ""
        read -p "Wybierz [1-5]: " de_choice
        case "$de_choice" in
            1|"") PAGAN_DE="xfce" ;;
            2) PAGAN_DE="deepin" ;;
            3) PAGAN_DE="budgie" ;;
            4) PAGAN_DE="pagande" ;;
            5) PAGAN_DE="none" ;;
            *) error "Nieprawidłowy wybór" ;;
        esac
    fi

    # Wybór formatu obrazu
    BUILD_IMG="${BUILD_IMG:-}"
    BUILD_ISO="${BUILD_ISO:-}"
    if [[ -z "$BUILD_IMG" ]] && [[ -z "$BUILD_ISO" ]]; then
        case "${ARTIFACT_MODE,,}" in
            img|"") BUILD_IMG=1; BUILD_ISO=0 ;;
            iso)      BUILD_IMG=0; BUILD_ISO=1 ;;
            both)     BUILD_IMG=1; BUILD_ISO=1 ;;
            ask)
                echo ""
                echo "Wybierz format obrazu:"
                echo "  1) IMG (obraz dysku – gotowy system)"
                echo "  2) ISO (obraz live/instalacyjny)"
                echo "  3) Oba (IMG + ISO)"
                echo ""
                read -p "Wybierz [1-3]: " img_choice
                case "$img_choice" in
                    1|"") BUILD_IMG=1; BUILD_ISO=0 ;;
                    2) BUILD_IMG=0; BUILD_ISO=1 ;;
                    3) BUILD_IMG=1; BUILD_ISO=1 ;;
                    *) error "Nieprawidłowy wybór" ;;
                esac
                ;;
            *) error "Nieprawidłowe ARTIFACT_MODE: ${ARTIFACT_MODE}. Użyj: img|iso|both|ask" ;;
        esac
    fi

    check_dependencies
    check_lfs_patches
    _detect_stale_resume
    init_build
    build_toolchain          # Ch. 5

    mount_virtual_fs        # Montuj /dev /proc /sys przed budową systemu

    # Tryb BAZA (none/base/cli): relikty Qt6/KF6/Plasma/Calamares z poprzedniego
    # builda (resume) muszą zniknąć NA POCZĄTKU – zanim build_final_system/
    # build_blfs cokolwiek zbudują lub ponownie wykorzystają – a nie na końcu,
    # tuż przed pakowaniem ISO. purge_kde sam sprawdza, czy cokolwiek usunąć.
    if [[ "${PAGAN_DE,,}" == "none" || "${PAGAN_DE,,}" == "base" || "${PAGAN_DE,,}" == "cli" ]]; then
        purge_kde
    fi

    build_final_system       # Ch. 6-8 (LFS + systemd + GRUB)
    build_blfs               # BLFS
    build_kernel             # Kernel (w tym moduły WiFi)
    install_wireless_regdb   # regulatory.db dla cfg80211 (WiFi)
    install_pagan_wifi       # konsolowy asystent Wi-Fi (PL/EN) do rootfs
    if ! _is_true "${SKIP_NVIDIA:-0}"; then
        install_nvidia_drivers   # NVIDIA open-gpu-kernel-modules (best-effort)
    else
        info "Pomijam sterowniki NVIDIA (--no-nvidia)"
    fi
    install_pag
    configure_fish           # fish jako domyślna powłoka użytkowników
    install_driver_manager
    install_initramfs_tool     # pagan-initramfs (przebudowa initramfs po aktualizacji jądra)
    install_rolling_kernel     # kernel jako pakiet rolling (linux) w PAG

    case "${PAGAN_DE,,}" in
        xfce)   install_xfce ;;
        deepin) install_deepin ;;
        budgie) install_budgie ;;
        pagande|pagan) install_pagande ;;
        kde|gnome)
            error "DE '${PAGAN_DE}' jest tymczasowo wyłączone (KDE/GNOME odłożone). Wybierz: xfce|deepin|budgie|pagande|none"
            ;;
        none|base|cli)
            # Tryb BAZA: bez środowiska graficznego – użytkownik wybiera DE/aplikacje
            # w Calamares / przez `pag` (netinstall z repo). Środowisko w live = instalator.
            info "PAGAN_DE=$PAGAN_DE – pomijam instalację środowiska graficznego"
            info "Środowisko/aplikacje można dobrać w instalatorze (netinstall z repo)"
            # Minimalny X (xorg-server + xinit) – instalator GUI musi wystartować na
            # pierwszym rozruchu (profile.d → startx), bo live nie ma środowiska graficznego.
            # (Qt6/KF6/Plasma z resume-builda usunął już purge_kde NA POCZĄTKU main)
            install_x_installer
            ;;
        *)      warn "Nieznane DE: $PAGAN_DE – używam XFCE (podstawowe)"; install_xfce ;;
    esac

    # Dodatki pulpitu: braki, które wyszły dopiero w sesji live (xrdb, upower,
    # powiadomienia, udisks2) + naprawa bibliotek z rozjechanym ABI z wcześniejszych
    # przebiegów (tumbler ODF/ffmpeg). Pakiety pochodzą z repo PAG.
    # Dla obrazu BAZOWEGO (none/base/cli) pomijamy – tam nie ma pulpitu, a sens
    # tego obrazu polega na tym, że środowisko dobiera się w Calamares/netinstall.
    case "${PAGAN_DE,,}" in
        none|base|cli) info "Obraz bazowy – pomijam dodatki pulpitu (repo PAG)" ;;
        *)             install_desktop_extras ;;
    esac

    # Toolchain docelowy (binutils) – niezależnie od DE: bez linkera `ld`
    # nie zadziała DKMS, czyli m.in. `nvidia-dkms` na zainstalowanym systemie.
    install_target_toolchain_extras

    # Opcjonalne odchudzenie linux-firmware (PAGAN_FIRMWARE_MIN=1)
    trim_firmware

    # Calamares – instalator systemu (DOMYŚLNIE WŁĄCZONY). Dla wersji podstawowej
    # (XFCE) stos Qt6/KF6 buduje install_calamares (zależności źródłowe, przy
    # niepowodzeniu fallback do repo PAG). Wyłączenie: --no-calamares.
    # (KDE/GNOME są tymczasowo wyłączone.)
    if _is_true "${PAGAN_CALAMARES:-1}"; then
        install_calamares
    else
        info "Pomijam calamares (--no-calamares) – ISO bez instalatora systemu"
    fi
    install_osinfo

    # Doinstalowanie pakietów z repo PAG do rootfs (przed initramfs/branding,
    # żeby trafiły do obrazu). Błędne nazwy = warning, reszta builda leci dalej.
    # UWAGA: auto --repo-pkgs dla XFCE jest WYŁĄCZONE – repo potrafi mieć nowszą
    # serię XFCE (4.20) niż source-build (4.18); nadpisanie binarek łamie sesję
    # („libxfce4windowing-0.so.0: cannot open shared object file”). Włącz tylko,
    # gdy repo będzie zgodne wersyjnie: PAGAN_REPO_XFCE=1 sudo ./build-pagan.sh …
    if _is_true "${PAGAN_REPO_XFCE:-0}" && [[ "${PAGAN_DE,,}" == "xfce" && -z "${REPO_PKGS:-}" ]]; then
        REPO_PKGS="xfce4 lightdm htop firefox"
        info "DE=xfce: repo-pkgs (PAGAN_REPO_XFCE=1): ${REPO_PKGS}"
    fi
    if [[ -n "${REPO_PKGS:-}" ]]; then
        _pag_install_repo || warn "Niektóre pakiety z repo nie zostały zainstalowane (patrz wyżej)"
    fi

    configure_grub
    install_branding        # tapeta + neofetch logo + GRUB splash
    build_initramfs
    # Pozbądź się zaległego Qt6 QML/Quick z targetu także tutaj (idempotentne) –
    # gdyby poprzednie kroki zostawiły libki mieszające ABI z Qt6Core.
    _purge_stale_qt
    # Best-effort: nieudany krok finalizacji nie może przerwać builda przed ISO
    # (raportujemy i lecimy dalej – inaczej pod trapem EXIT zostajemy bez obrazu).
    finalize_build || warn "  Finalizacja zgłosiła problem (patrz wyżej) – kontynuuję"
    # verify_rootfs kończy się liczbą problemów (return $fails). Pod set -e
    # niezerowy wynik UBIŁBY build tuż przed tworzeniem ISO, więc łapiemy go `||`.
    # DWA POZIOMY (patrz `crit`):
    #   * VERIFY_CRITICAL > 0 – braki, przez które obraz jest bezużyteczny (brak
    #     jądra/initramfs/modules.dep, zepsuty GRUB i386-pc, brak NVIDIA mimo
    #     włączonego budowania, brak `ld`, brak sieci w live, cykl D-Busa).
    #     PRZERYWAMY – dokładnie tak wyszło już ISO bez modułów NVIDIA.
    #     Świadome wydanie mimo tego: PAGAN_ALLOW_BROKEN_ISO=1.
    #   * pozostałe uwagi – raportujemy i tworzymy obraz (kosmetyka).
    verify_rootfs || {
        if [[ ${VERIFY_CRITICAL:-0} -gt 0 ]] && ! _is_true "${PAGAN_ALLOW_BROKEN_ISO:-0}"; then
            umount_virtual_fs >/dev/null 2>&1 || true
            error "Weryfikacja: ${VERIFY_CRITICAL} braków KRYTYCZNYCH – NIE wydaję obrazu (byłby bezużyteczny). Napraw powyższe i uruchom build ponownie, albo wymuś wydanie: PAGAN_ALLOW_BROKEN_ISO=1"
        fi
        warn "  Weryfikacja: są uwagi (patrz wyżej) – kontynuuję tworzenie obrazu"
    }

    umount_virtual_fs       # Odmontuj przed tworzeniem obrazów

    if [[ "${BUILD_IMG:-1}" -eq 1 ]]; then
        create_image
    fi

    if [[ "${BUILD_ISO:-0}" -eq 1 ]]; then
        create_iso
    fi

    report_artifacts

    # Podsumowanie
    step "✅ BUDOWA ZAKOŃCZONA!"

    banner_final
}

# ========================= BANNER KOŃCOWY (dynamiczny) =========================
# Ramka dopasowuje się do najdłuższej linii – długie ścieżki
# (OUTPUT_DIR/ISO/LOG_FILE) nie łamią ramki, a w treści nie ma emoji
# (4-bajtowe znaki potrafiły wywalać 'syntax error' na starszych bashach).
banner_final() {
    # Jeśli plik zmienił się w trakcie builda, wynikom nie można ufać – nawet
    # gdy wszystko „przeszło” (bash mógł wykonać przypadkowe fragmenty linii).
    if _pagan_self_changed; then
        warn "  build-pagan.sh zmienił się w trakcie działania – wyniki mogą być niepewne!"
        warn "  (edytor z auto-zapisem? Zamknij go i powtórz build, zanim zaufasz obrazowi.)"
    fi

    local -a L=(
        ""
        "  PAGAN LINUX ${PAGAN_VERSION} z ${PAGAN_DE^^} ZBUDOWANY!"
        ""
        "  System:      ${PAGAN_ROOT}"
        "  Środowisko:  ${PAGAN_DE^^}"
        "  Kernel:      ${KERNEL_VERSION}"
        "  Menedżer:    PAG + Calamares + pagan-drivers"
        ""
    )
    [[ -f "$OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.img" ]] \
        && L+=("  IMG:  $OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.img")
    [[ -f "$OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.iso" ]] \
        && L+=("  ISO:  $OUTPUT_DIR/Pagan-Linux-${PAGAN_VERSION}-${PAGAN_DE}.iso")
    L+=(
        ""
        "  Log:  ${LOG_FILE}"
        ""
        "  Następne kroki:"
        "    - ISO: wypal na USB (dd/balenaEtcher) i uruchom LIVE"
        "    - W live: uruchom 'calamares' (lub ikonę na pulpicie), aby zainstalować system"
        "    - Pakiety:   pag install <pkg>   (repo: ${PAGAN_REPO_URL})"
        "    - Sterowniki: pagan-drivers auto"
        "    - Rolling:    pagan-drivers update (kernel+sterowniki, bez nowego ISO)"
        ""
    )

    local w=0 i len
    for i in "${L[@]}"; do
        len=${#i}
        (( len > w )) && w=$len
    done

    local bar pad
    bar=$(printf '%*s' "$((w + 2))" "")
    bar=${bar// /═}

    echo -e "\n${GREEN}${BOLD}${bar}${NC}"
    for i in "${L[@]}"; do
        pad=$(printf '%*s' "$((w - ${#i}))" "")
        echo -e "${GREEN}${BOLD}${i}${pad}${NC}"
    done
    echo -e "${GREEN}${BOLD}${bar}${NC}"
    echo ""
}

# ========================= URUCHOMIENIE =========================
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi