🔒 Repository is read-only – file editing is disabled.
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