From a588f8310f4cbbad5f3c27407945c8758b022692 Mon Sep 17 00:00:00 2001 From: macmpi <16296055+macmpi@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:21:40 +0100 Subject: [PATCH] version 1.0 unattended.sh script may disable sshd (#NO_SSH option) gadget mode: enable both USB serial terminal and USB-ethernet networking check new version availability online (notified on login banner & logs) redesigned as OpenRC services many other minor improvements --- LICENSE | 2 +- README.md | 23 +- headless.apkovl.tar.gz | Bin 6156 -> 7196 bytes make.sh | 32 +- overlay/etc/init.d/headless_bootstrap | 12 + overlay/etc/init.d/headless_cleanup | 12 + overlay/etc/init.d/headless_unattended | 12 + overlay/etc/local.d/headless.start | 247 -------------- overlay/etc/modprobe.d/g_ether.conf | 6 - overlay/etc/modprobe.d/headless_gadget.conf | 6 + overlay/etc/modules-load.d/g_ether.conf | 9 - .../etc/runlevels/default/headless_bootstrap | 1 + overlay/etc/runlevels/default/local | 1 - .../ssh => tmp/.trash}/ssh_host_ed25519_key | 0 .../.trash}/ssh_host_ed25519_key.pub | 0 .../{etc/ssh => tmp/.trash}/ssh_host_rsa_key | 0 .../ssh => tmp/.trash}/ssh_host_rsa_key.pub | 0 overlay/usr/local/bin/headless_bootstrap | 303 ++++++++++++++++++ sample_interfaces | 7 +- sample_unattended.sh | 39 ++- sample_wpa_supplicant.conf | 2 +- 21 files changed, 417 insertions(+), 297 deletions(-) create mode 100755 overlay/etc/init.d/headless_bootstrap create mode 100755 overlay/etc/init.d/headless_cleanup create mode 100755 overlay/etc/init.d/headless_unattended delete mode 100755 overlay/etc/local.d/headless.start delete mode 100644 overlay/etc/modprobe.d/g_ether.conf create mode 100644 overlay/etc/modprobe.d/headless_gadget.conf delete mode 100644 overlay/etc/modules-load.d/g_ether.conf create mode 120000 overlay/etc/runlevels/default/headless_bootstrap delete mode 120000 overlay/etc/runlevels/default/local rename overlay/{etc/ssh => tmp/.trash}/ssh_host_ed25519_key (100%) rename overlay/{etc/ssh => tmp/.trash}/ssh_host_ed25519_key.pub (100%) rename overlay/{etc/ssh => tmp/.trash}/ssh_host_rsa_key (100%) rename overlay/{etc/ssh => tmp/.trash}/ssh_host_rsa_key.pub (100%) create mode 100755 overlay/usr/local/bin/headless_bootstrap diff --git a/LICENSE b/LICENSE index 5d18f1d..4f1a4d7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 macmpi +Copyright (c) 2022-2023 macmpi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1fa6aee..c906df8 100644 --- a/README.md +++ b/README.md @@ -4,45 +4,46 @@ However, in many cases one might want to deploy a headless system that is only available through a network connection (ethernet, wifi or as USB ethernet gadget). This repo provides an **overlay file** to initially bootstrap[^1] a headless system (leveraging Alpine distro's `initramfs` feature): it starts a ssh server to log-into from another Computer, so that actual install on fresh system (or rescue on existing disk-based system) can then be performed remotely.\ -An optional script may be launched at startup, to perform automated actions/setup. +An optional script may also be launched during that same initial bootstrap, to perform fully automated setup. ## Setup procedure: Please follow [Alpine Linux Wiki](https://wiki.alpinelinux.org/wiki/Installation#Installation_Overview) to download & create installation media for the target platform.\ Tools provided here can be used on any plaform for any install modes (diskless, data disk, system disk). -Just add [**headless.apkovl.tar.gz**](https://is.gd/apkovl_master)[^2] overlay file at the root of Alpine Linux boot media (or onto any custom side-media) and boot-up the system.\ -With default network interface definitions (and [SSID/pass file](#extra-configuration) if using wifi), system can then be remotely accessed with: `ssh root@`\ +Just add [**headless.apkovl.tar.gz**](https://is.gd/apkovl_master)[^2] overlay file *as-is* at the root of Alpine Linux boot media (or onto any custom side-media) and boot-up the system.\ +With default DCHP-based network interface definitions (and [SSID/pass file](#extra-configuration) if using wifi), system can then be remotely accessed with: `ssh root@`\ (system IP address may be determined with any IP scanning tools such as `nmap`). -As with Alpine Linux initial bring-up, `root` account has no password initially (change that during setup!).\ +As with Alpine Linux initial bring-up, `root` account has no password initially (change that during target setup!).\ From there, actual system install can be performed as usual with `setup-alpine` for instance (check [wiki](https://wiki.alpinelinux.org/wiki/Alpine_setup_scripts#setup-alpine) for details). ## Extra configuration: Extra files may be added next to `headless.apkovl.tar.gz` to customise boostrapping configuration (check sample files): - `wpa_supplicant.conf`[^3] (*mandatory for wifi usecase*): define wifi SSID & password. +- `unattended.sh`[^3] (*optional*): provide a deployment script to automate setup & customizations during initial bootstrap. - `interfaces`[^3] (*optional*): define network interfaces at will, if defaults DCHP-based are not suitable. - `authorized_keys` (*optional*): provide client's public SSH key to secure `root` ssh login. - `ssh_host_*_key*` (*optional*): provide server's custom ssh keys to be injected (may be stored), instead of using bundled ones[^2] (not stored). Providing an empty key file will trigger new keys generation (ssh server may take longer to start). -- `unattended.sh`[^3] (*optional*): create custom automated deployment script to further tune & extend actual setup (backgrounded). -**Goody:** seamless USB-ethernet gadget boostrapping (PiZero for instance):\ -On supporting Pi devices, just add `dtoverlay=dwc2` in `usercfg.txt` (or `config.txt`), and plug USB cable into Computer port.\ -With Computer set-up to share networking with USB interface as 10.42.0.1 gateway, one can log into device from Computer with: `ssh root@10.42.0.2` +**Goody:** seamless USB-serial & USB-ethernet gadget mode (PiZero for instance):\ +On supporting Pi devices, just add `dtoverlay=dwc2,dr_mode=peripheral` in `usercfg.txt` (or `config.txt`), and plug USB cable into host Computer port.\ +Serial terminal can then be connected-to from host Computer (xon/xoff flow control: e.g. on Linux with `cu -l ttyACM0`).\ +Alternatively, with host Computer set-up to share networking with USB interface as 10.42.0.1 gateway, one can log into device from host with: `ssh root@10.42.0.2`. -Main execution steps are logged in `/var/log/messages`. +Main execution steps are logged: `cat /var/log/messages | grep headless`. [^1]: Initial boot fully preserves system's original state (config files & installed packages): a fresh system will therefore come-up as unconfigured. -[^2]: About bundled ssh keys: this overlay is meant to **quickly bootstrap** system in order to then proceed with proper install; therefore it purposely embeds [some ssh keys](https://github.com/macmpi/alpine-linux-headless-bootstrap/tree/main/overlay/etc/ssh) so that bootstrapping is as fast as possible. Those temporary keys are moved in RAM /tmp: they will **not be stored/reused** once actual system install is performed (whether or not ssh server is installed in final setup). +[^2]: About bundled ssh keys: this overlay is meant to **quickly bootstrap** system in order to then proceed with proper install; therefore it purposely embeds [some ssh keys](https://github.com/macmpi/alpine-linux-headless-bootstrap/tree/main/overlay/tmp/.trash) so that bootstrapping is as fast as possible. Those temporary keys are moved in RAM /tmp: they will **not be stored/reused** once actual system install is performed (whether or not ssh server is installed in final setup). [^3]: These files are linux text files: Windows/macOS users need to use text editors supporting linux text line-ending (such as [notepad++](https://notepad-plus-plus.org/), BBEdit or any similar). ## Want to tweak more ? This repository may be forked/cloned/downloaded.\ -Main script file is [`headless.start`](https://github.com/macmpi/alpine-linux-headless-bootstrap/blob/main/overlay/etc/local.d/headless.start).\ +Main script file is [`headless.start`](https://github.com/macmpi/alpine-linux-headless-bootstrap/tree/main/overlay/usr/local/bin/headless_bootstrap).\ Execute `./make.sh` to rebuild `headless.apkovl.tar.gz` after changes. diff --git a/headless.apkovl.tar.gz b/headless.apkovl.tar.gz index 3b0f2e2c62dbc6d56e0b396a4fd57135f8b45f48..a375c385770084206f5f37959a789255ed61406c 100644 GIT binary patch literal 7196 zcmV+%9OL63iwFP!000021MEC$TiZy|`HFr;BbabSmMtHEv&=3S1LiW=93jX0k)^gQ zEK5R%4>J7sx2jw62_)GCX5Jm^Nxlj@;CdO%4C&{ zlFg)Z(4I=B)0uZ9`|b^%&<|9PkawQr1ka88QUCwbhXVgi+YA!A{1@VXDyyVs{C{Kq zH>s*y)c0$eMO8a=-;Dp6lrq8p%z7sMjwEONfAwc!UiRyzEjuo?Jx%gLTM!mV^=$W( zv}anh;JAIyG@8K$?FD%dAOzP#yN$Elm zbn0uK=?11_Z;1zytdU682=5aGTW! z8J-iujKB-2;F@{^_5;&li}K?4N}*Q0Dx7Yg6dw}M2MfIWTRxBJe;q{K2LiiqrT^gU z$Mrv(PG@KO|98;;wrbi01dhp@>zTIklqncU<(M%DeNVO=O|@j)#k2^|EW)qre`u>g zKy97sZ>9g)N&U}cGc*1FJLvyV)`(AoK_Ebu9X3+6R;CQWaLQCz@Q4V{48RZdKcUlx z8d?Ffnl+z#9pIt=viV;wllg`GFOyrJ`TsZLf7{Vr&#BYbssa3Z{;#BS*;)SoC-8q@ zKn+zlXpqnxyYcG!pPq{UbLsT@O#k1+|G$c_Um$+yx{enRqo(N^xvcI;#h^(&n?e&4 zEdo+Ui5jrDo-`bfoS8(Y%=HKY_x^q~9hz&Zu6tWl&F3s(;gr{@3*%!XJxvsO7&>3HU#;|Jn4+{=Yr{qd-vpv-zK1Utgc` z|Ig$9%a#He|AC=D0SQ+-MnVE{30VQvo6{S~qRpMiCjMI77e6SF~VCm^cjfe81B>ro#Nfx&aoGf!YG z7~gQnAH?veOQh}{5f{J18V}+}5;Q5o1;{WAphFk*h~)iyf=7};gDm~#lcm87^84>( z^cq5h3Dpru!-St7Kf({4T8Jn7q5&PC38-~UC-m`cbRKk#pvKmJbPWP)>$D@=A?#iG z{e5Zze^_n=e;+1DCRy_3ul1!E|D~^WSzh{?Ev&BKJJp&F5!nWSIZ_XY0Q_Y<@H&(;`3sR61o=z8AzqrxUjU`lL&5>+64@k}6F6Qp2S^f4 z8w?`f2HS0Sh$M~mvCkktkjhXP;I)b5H3qG)s|Qw^YEsM6np6W?nZ8=L=vK9$q%sNt zX0>dmYZKq4nu#(Af+wh!srsa5Ifg;KttI}ONPa*Tzb6+KR^^8!0E-VP+B$K7#i|#O zz#(-?^w70TO${i@E;LQvSRu>06G?7Bz4;YRH#hEPbO69-C>dBJYnV3RH33h0U}xp7=+F2d@V3-!^gp((14u*sg(^}1eAlO=@UE-L%drQR|{>872yOXLpE8G z|GluvwwPFruw9hzKDr=CKk_1ETwkWp2Lh3Q{6i|1_a;?qYUtbosZibYs1^`E(48<~ zo1>oR!1e)o4N!JqAHcKdD2sF*dkIE2oi1quETjksNsnqI^*ht0cn+!)*qeygL@eN< zKr<i)fyI1 zQF82H;+56pM}duZPXV)O&~>~PXgIK}p=#8hMiZ;3c*+Vrs5A{C|HQd~fV}W%+v!l! z@SHZUc<*GsRK_1Rb_|3>XKXT+eWuejFeFcLwkgF=h9H@)r zvDpy?#dwydNik6$9geVsF}h7wOjajF8swSij!i!hYTr72GB2W&4aPLDuG%(Ci1rd` zNNkfe-Wmk`{c3W=CZh=iD&p2a+S<%m!OEu1tzR@Awu9Cc?O6`rG&a))-EBHdcABaU z@kI;4xBy1A!Iwj)Y8ykc&gVY)#?G^_JJ&8@X6`;0m@Ndh z7Y!145(6Ls zSPg{C*65bVb`=GkHQu+?M~D2JD2hmo8awD*#Yw9}Dm^K95aO`_7fmch$}oxRnh@sM z(e@*(^XUB=cq{6|_`bPRV5bQtVYsB!R(m>igC~#C&eCL8U&v*m{tnOq%Q}mA$ORIo!{a8Xx26hZ=zhi{<5E5n>%IEkh6Hb8_mqJ(}P!oui%3qDMheWGC)&<+VaKToc> zU=f@OqB0D`!8(!sX?Y^^^Ws!Iy%>WWbPek^U+u5<(%6uR6f=q-Kwk#%N|-C*T@{rP zGfx&ddBu^~Ey}CF2#iN?r@WmE^DTkL%sPZZe&3fh3%nGjWpdY2?PNrlxX>{OKpxBb zhLQF#o@Z7hWH;x#X2Xo~)HrUNN)9LDvY$#dpN^6qMe-*BKN2F~28M(~FUv0lv%&Gp0hLR^>JmX*iGZLkxBfO3ak*tE$0BlLUKe8jX5yy^F zWe62OqQ^-v0%~m6AHda^mX2(WR*gLqPzg|kiDW{&RzE_L$Lf$BGz4I7Df83fBD9F8 z*imKmJG}q)8`JsK2VP^Hvm93Xn|sWKP1A`Fh3L*92%Nu~Y|D!rIf@C4m zVjozgaSnpI7hh3tYVPB!q3T7;eHrA`9)Oa6(2k*_{jrvqF zkx>%Kgd!`MVXpzWpe&Y#{L=HmBU-$g#tPduMnNpYIL1JeTtfO0gc4eI zon3_;P%00gBnAWB4ePQg)&1yLf}Kz}4Wa|Vu!N4^i*d=aN10>t=v`>CR2PZKAVo>* zu$sGlL833#L^%)U|Lqfq=8fT+5-StzbM{14Kja92>^mW8gLTn7ifNq6B{W1@cp)eR zBqp1i5|9PrYd_*}g7sig!lfSWFpiRW*d3Uz4^rHg0~rt5s2xaH#Arjt%CM3GN77U` z2t6u+d6hUqQgrE9RyH!Sf;q^=MWy=P)UT+mJKn?AmYB>cS#i*Q<~e$(1=yixR8{;y zil9jrSA-VvY36qCR}OBm-C6$MLaKF5rvTxSy<{}Bq52SYQDPZMS&)?ia3vl`p5~@ z)0zZF64k)({sQUS4lvqvaN#Cap6is_QMF$u7FY)lgIidJht0?g5FvCk=Qmu z48z+PCm6u_*ame`M=DrKRKe1LZ-~;;s+_>Gl?$0RMY@d_xCX!_rZVw1zdw+#AAww` z7(_~ul^CyKsu4|$ijf6w^P&?L7q3K$sKe zZX%1{MP&!;#P3Bw$jFG8;57~|^L5(5!^y}UEq+JjAM(N+C!;X89F>DG6vUdc)5gFS zvOHeneB_8vLaRZ1we6x4vrX5f!3tPr9?edr^g>kRV&VtChE<8E5Qf(|fE>t7j*8zx zm@M66E)A7UB}vzd5O zI`-Yzgx`$!9yvqEO@pC){d?iGT&q?O7RBd|IHyiB!KxuD!{e)o3k#z)6p$}0o-ri( zX(Q+~S|Q27;0_&BbQmcLj~#Lj{x%4+<9Nk{BxhghdD#aw{Y~zF|IGdWk^A4X?|;4> z|07HPy8NH!`?G)lE0de?|Bd-Sd5(nVV=u=4XTJZNO0MT7|NcjA_V>Tu=D|B1z;Ch* z&rbUEUM-~hno`8q-R<-39c(tvcXz61J9n0mJT@}v(x>CBsi$`v{k={%Xs26uRl9hz zQ%W8mG|lj$bCtZ!RI+R5SJsWutQt3Gt!m@qFsv3%KDTbQ^}^=P!IfOl4>wPm^}SNr zC?7Ukl~&KL7R-tkDg`B!PjZ^SyLNT3e!AJaT|d4KdRLt=cj@=9Hd5V;eq1SqN88(n zw?+^^WK5Ul)p>j;;>RI?3d1`L?Val?&aYjb3=+Hf`lPb6&Ym?lsr; zkF&SC%?iESS1y8*y?2;0o5|aDQ9eAl%kF5pf7&rZxtKP>qk5{ORLke}a_Z=|RQ}}A zqt5n`erIQ7x>GsqbaDrNp(QulrlTKS-R(ErR=86=JuP2uRt^rXjEyxVn=-GoQ|H!G z`{9)ptR1x7l)rnZ75iqf-_e8g{ia&HxUbaJi>vg`#eRX>`zPJ|%jLu&iH?A6cYmKuYS$nI_&?$+DY+w0by+(>Jk_Vq^c=JxZM*J~w{z0Xe9x;biHHN$kh z7;GHWkB|Ge{zcl~A}Ig=ny6XMg0cTe`#*B!;N|T9GyXr9PEXo@KwxJ7-{O(**(vUq zPD%N!c#4-^&MKv=?TaEgD&DYOp`PBc>NfKY-sexPK)+@FVS8sk+o`uN@nf;x-V8sV zZ^sY%jqQ^z{tWIl#RxuM3+MgQi_2aa2NibCv%AgC{{6;w`sVEFKuK*>3O9Pkzt7!x z+um-kUeQn1&K+Y-qX^^BJgK^;^tO0+A$&4Mq5h{p1w^{g4y_&Sq_Jo8ZZEx(8J^kY z+hVJGsvWqSYp3PFFz(HulhHGKsohq2f1`fZ-PMiF%SQcEuXxy0)`a^r*Ypei*%P2KHu6B7-@^AyHAt-ub}@g z5(EC|@4uunx#{nJrDpp7R`FlNZx#aOjQ`#L_}`xYT>mfs^M6U5jeuGIdvyN1{ChAG+0$>&WR6T8f19^-gVC6u7+t5d z9)F2fH(xUAn*4=FYeylu3W`P90a6^Osz1pBA&&5!c2z6KD)v4P+d;~6{Fyb?;BtjY zXrfq*jTo+`F4Vu(cRil>sIyS-fKLfz5qKkO_DQL|*Nwh3v$x{HuHIBEAKi-4bsX|k z)ed4QNnU+bOLy;j6l1E-^n9DP+W5mrfsTw~rr$*|W4NfS8!$R?KV0f>N4eVN=Fya^ zL~Y#;x!a+V3MsB1mx=4mz_@1DcJlLeryCG@5^L6zACnohu*!E+3alx1w(xjb6t_?6 zk&N}qZ#}QKeCI+U%lhtl)Q3W%lIXVfaaz^@vE(Uxhnz9h#)_pm4F$DG!vWeM`RQNP zIA5({Akvo}=Ky6kxM>X#4aYIXd{Vx(&#Fq{1(f4M<--28-CW^(5N5prDl*^EFN+WP zc*hf^7Y)(1b107C0KUKR8@it0y6EeRJjKKh%=nqp?QIDi!(=>v_&;ovzqV$Vi zddZ>pO;41_P``DlF)jZD6%R42`hV{tfM2o69BW7_EiZv!??Ri^Ka;D5WhSHs5RayX zJHIlg5?gyKG+)!%Hu&0v$kOjDb+qYjDkH(_t|WX$}5SkhJ*~$>M|T z$ex_5FHO3sNKYvGneWpg{BpxE+4^w_ObS2>nB7MUpHQgbuh`%d%MM2UXd0VmxT+st z6x_EsQk`t}I}q|*gEXjU!gY3iWa2%@Wt`TWVgYub;L$M{Z%HDjG=9TP=8wTkao>?g zcg5OC7kZ)~Ptjh%6kmLy*%@7cvSn~d96)F)&X)(u$*YFvV2Z)P z#U=H}TRlX~@<4c~bPTl5i8k-=`XYh=!;54LPNP4)mkM|<2E|GXnH&^GjUf2%5N}EAEz-p@Bn1b^Q58WKh@#{GL>&G zcIMl5yxx~p{PPs!$6x-*f(>#kz`Ihxchv7tS{TYCyF!Ngg226KiOeJ~u!wK@=COy5 zMgRDi;Zq~BE4(<#yyYW^NOte6*4c8rX@{pGc)69~slK{tq0J4T2JAb?V|EEofl!51 ziWl74`))?LiriJlVLMA&=oS)COcY};nF-ma*)|gId7()7B|b@A18>@5?)&MC7aSh2 zdcx9MA6ECpN(h2X)>l=<@4*>_e!eqLu@;@iZ!GK9DPgFNuxwdI)0BY(UuDNMaN%Pv0Es)EvdXy{ zmT{iQiHQQt#UD>1t=AV@@Ss2r3d$|g-Z7on>!Wf*0Tc5Ps_pbu#V&A-U0{Q6 z&qN;>?eVGEWqq$4DL!dOjpTNX86rcJn`!rln-#-Ue>^RGVwvS@uneW^_LMf3GRXA}QSjB})&F*J>d1#j_goP(*nO%-m^2^PqA2fyP@gPZ3l94CFO%o6( zIRw#>oS^lgbDi;9P{Gg)b0_o=4NrC%SNO=vK_HR69I%X&OQKA)MQ`yka@ZXO@21NE z&5u(sxKfuuDb$>0H7Uq!+T%&^-hH!w^?J~}W`{ZbQ&&7b3=Uj*MK^hsqfSH2Fs(v= z3xKuYW60%bCU7XF6lR%N{KG-DcO(@{lU%N`mp)hTtT(!S`WLXaz#NKa2d|o0SM&6g z0S9Dvw987w*6ovW1ONW`rimDkU4Y+6UBL?s!1?Dps-dbD$53b}76`5ry69A2JE%c6 z)LH@us5A*ng1uCAm>z05ivoMQCSmT6(3HbRLCy%<;w_RX8X*z*u6_0WP(BA~%zUIO zY}0oet-vVt6a(HS3q|#$`UHDyx&78jhf?SwByvb2JTp=f@13UQse33(cX4Vjnj!uc zikxg%^O^z3WFuBbjdf+a{xZboF_o{SR?M~;xo**B zc=tW65UDf&6eCjdF~Gh>%^t}T)1cqB#W{D1GO#U6TPF8=>>^VfNL?y_p6vY3Sx5Zw e#~*+E@y8#3{PD*hfBfjcmM!?%pS%7 literal 6156 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

=cZJ2c|s)>)!i3r_7m4xjAn7TLd5C&2R zO`}h=r6yOD4{%?_iIpV(7gFpXOg6$S-Qk$x3O5^%XWIKEC!Gta%e=W=rYiYrO1^&o zUCez`e9dxQ!1`9R6vQ|=fi+WyOwM0WVF}{FJtua;o(h77(goDVfE^lVznJD zzTV^tYnJCH@+AOrr1K~4zyfQBF$H;@shvOj5q|SP$FB`u^CM+>4`>c2YdVDf^}}W; zW3SY z)}MRyG{WP4t1e>`ksBWkTv99gM9JP!x7Qx@)Hl7>qu!&g;EIFC21mW4{Z7w0 z=qYK!!I#jQ%q$J8r+)r zA#E1;YNGt)PkICRcJKc#{{O$)9fSY(Lsk9f9hp0}gU{E0htdV3|9hbI`sYU6o$=85 z-|_AB-#$3#cK#yT+!XWzFGW^zw<4AFf3AN+h71`pWXO;qLxv0)GGxe*Awz}?88T$Z ekRd~c3>h+H$dDmJh7A4p(!T&GIzt8kcmM$M|2mHV diff --git a/make.sh b/make.sh index db2052c..7857dc6 100755 --- a/make.sh +++ b/make.sh @@ -1,8 +1,30 @@ -#!/bin/sh +#!/bin/busybox sh -# Copyright 2022 - 2023, macmpi +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-License-Identifier: MIT -chmod 600 overlay/etc/ssh/ssh_host_*_key -chmod +x overlay/etc/local.d/headless.start -tar czvf headless.apkovl.tar.gz -C overlay etc --owner=0 --group=0 + +command -v doas > /dev/null || alias doas="/usr/bin/sudo" + +build_path=$(mktemp -d) +if [ -n "$build_path" ]; then + cp -r overlay $build_path/. + find $build_path/overlay/ -exec touch -md "$(date '+%F 00:00:00')" {} \; + + # setting owner/groups for runtime (won't affect mtime) + find $build_path/overlay/etc -type d -exec chmod 755 {} \; + chmod +x $build_path/overlay/etc/init.d/* + find $build_path/overlay/usr -type d -exec chmod 755 {} \; + chmod +x $build_path/overlay/usr/local/bin/* + chmod 777 $build_path/overlay/tmp + chmod 700 $build_path/overlay/tmp/.trash + chmod 600 $build_path/overlay/tmp/.trash/ssh_host_*_key + doas chown -R 0:0 $build_path/overlay/* + + doas tar -cvf $build_path/headless.apkovl.tar -C $build_path/overlay etc usr tmp + gzip -nk9 $build_path/headless.apkovl.tar && mv $build_path/headless.apkovl.tar.gz . + touch -md "$(date '+%F 00:00:00')" headless.apkovl.tar.gz + + doas rm -rf $build_path +fi + diff --git a/overlay/etc/init.d/headless_bootstrap b/overlay/etc/init.d/headless_bootstrap new file mode 100755 index 0000000..5bd4980 --- /dev/null +++ b/overlay/etc/init.d/headless_bootstrap @@ -0,0 +1,12 @@ +#!/sbin/openrc-run + +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi +# SPDX-License-Identifier: MIT + +description="Headless main boostrappring script" +name="Headless bootstrap" + +command="/usr/local/bin/headless_bootstrap" +command_background=true +pidfile="/run/${RC_SVCNAME}.pid" + diff --git a/overlay/etc/init.d/headless_cleanup b/overlay/etc/init.d/headless_cleanup new file mode 100755 index 0000000..64a06e9 --- /dev/null +++ b/overlay/etc/init.d/headless_cleanup @@ -0,0 +1,12 @@ +#!/sbin/openrc-run + +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi +# SPDX-License-Identifier: MIT + +description="Headless cleanup script" +name="Headless cleanup" + +command="/tmp/.trash/headless_cleanup" +command_background=true +pidfile="/run/${RC_SVCNAME}.pid" + diff --git a/overlay/etc/init.d/headless_unattended b/overlay/etc/init.d/headless_unattended new file mode 100755 index 0000000..a31f31c --- /dev/null +++ b/overlay/etc/init.d/headless_unattended @@ -0,0 +1,12 @@ +#!/sbin/openrc-run + +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi +# SPDX-License-Identifier: MIT + +description="Headless unattended setup script (optional)" +name="Headless unattended" + +command="/tmp/headless_unattended" +command_background=true +pidfile="/run/${RC_SVCNAME}.pid" + diff --git a/overlay/etc/local.d/headless.start b/overlay/etc/local.d/headless.start deleted file mode 100755 index f67e01e..0000000 --- a/overlay/etc/local.d/headless.start +++ /dev/null @@ -1,247 +0,0 @@ -#!/bin/sh - -# Copyright 2022 - 2023, macmpi -# SPDX-License-Identifier: MIT - -VERSION="0.9" - - -_apk() { - local cmd="$1" - local pkg="$2" - - case $cmd in - add) # install only if not already present - if ! apk info | grep -wq "${pkg}"; then - apk add "$pkg" && printf "${pkg} " >> /tmp/.trash/installed - fi - ;; - del) # delete only if previously installed - if grep -wq "$pkg" /tmp/.trash/installed; then - apk del "$pkg" && sed -i 's/\b'"${pkg}"'\b//' /tmp/.trash/installed - fi - ;; - *) - echo "only add/del: wrong usage"; exit - ;; - esac -} - -_preserve() { - [ -f "$1" ] && cp "$1" "${1}.orig" -} - -_restore() { - if [ -f "${1}.orig" ]; then - mv -- "${1}.orig" "${1}" - else - rm -rf "${1}" - fi -} - -##### End of part to be duplicated into post-cleanup (do not alter!) - - -# Redirect stdout and errors to console as rc.local does not log anything -exec 1>/dev/console 2>&1 - -logger -st ${0##*/} "Alpine Linux headless bootstrap v$VERSION by macmpi" - -install -dm 0700 /tmp/.trash - -# grab used ovl filename from dmesg -ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )" -ovl="$( basename "${ovl}" )" -# search path again as mountpoint may have been changed later in the boot process... -ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 ) - -# Help randomness for wpa_supplicant and ssh server -rc-service seedrng start - -## Setup Network interfaces -if [ -f "${ovlpath}/wpa_supplicant.conf" ]; then - logger -st ${0##*/} "Configuring wifi..." - _apk add wpa_supplicant - _preserve "/etc/wpa_supplicant/wpa_supplicant.conf" - install -m600 "${ovlpath}/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf -else - logger -st ${0##*/} "No wifi setup supplied !" -fi - -_preserve "/etc/network/interfaces" -if ! install -m644 "${ovlpath}/interfaces" /etc/network/interfaces; then - # set default interfaces if not specified by interface file on boot storage - logger -st ${0##*/} "No interfaces file supplied, building defaults..." - for dev in $(ls /sys/class/net) - do - case ${dev%%[0-9]*} in - lo) - cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet loopback - - EOF - ;; - eth) - cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet dhcp - - EOF - ;; - wlan) - [ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet dhcp - - EOF - ;; - usb) - cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet static - address 10.42.0.2/24 - gateway 10.42.0.1 - - EOF - - cat <<-EOF > /etc/resolv.conf - nameserver 208.67.222.222 - nameserver 208.67.220.220 - - EOF - ;; - esac - done -fi - -echo "Using following network interfaces:" -cat /etc/network/interfaces - -_preserve "/etc/hostname" -echo "alpine-headless" > /etc/hostname -hostname -F /etc/hostname - -grep -q "wlan" /etc/network/interfaces && \ - [ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && \ - rc-service wpa_supplicant start -rc-service networking start - - -## Setup temporary SSH server (root login, no password) -## We use some bundled (or optionaly provided) keys to avoid generation at startup and save time -_apk add openssh -_preserve "/etc/ssh/sshd_config" -_preserve "/etc/conf.d/sshd" - -cat <<-EOF > /etc/ssh/sshd_config - PermitRootLogin yes - Banner /tmp/.trash/banner - EOF - -if install -m600 "${ovlpath}/authorized_keys" /tmp/.trash/authorized_keys; then - logger -st ${0##*/} "Enabling public key SSH authentication..." - cat <<-EOF >> /etc/ssh/sshd_config - AuthenticationMethods publickey - AuthorizedKeysFile /tmp/.trash/authorized_keys - # relax strict mode as authorized_keys are inside /tmp - StrictModes no - EOF -else - logger -st ${0##*/} "No SSH authentication." - cat <<-EOF >> /etc/ssh/sshd_config - AuthenticationMethods none - PermitEmptyPasswords yes - EOF -fi - -# Banner file -warn="" -grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$? -[ "$RO" -eq "0" ] && warn="(remount partition rw!)" - -cat <<-EOF > /tmp/.trash/banner - - Alpine Linux headless bootstrap v$VERSION by macmpi - -You may want to delete/rename .apkovl file before reboot ${warn}: -${ovlpath}/${ovl} -(can be done automatically with unattended script - see sample snippet) - - - EOF - -# Bundled temporary keys are moved in RAM /tmp so they won't be stored -# within permanent config later (new ones will then be generated at reboot) -KEYGEN_STANCE="sshd_disable_keygen=yes" -mv /etc/ssh/ssh_host_*_key* /tmp/.trash/. - -# Inject optional custom keys (those might be stored) -if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/; then - # check for empty key within injected ones: if found, generate new keys - if find /etc/ssh/ -maxdepth 1 -type f -name 'ssh_host_*_key*' -empty | grep -q .; then - rm /etc/ssh/ssh_host_*_key* - KEYGEN_STANCE="" - logger -st ${0##*/} "Will generate new SSH keys..." - else - chmod 644 /etc/ssh/ssh_host_*_key.pub - logger -st ${0##*/} "Using injected SSH keys..." - fi -else - logger -st ${0##*/} "Using bundled ssh keys from RAM..." - cat <<-EOF >> /etc/ssh/sshd_config - HostKey /tmp/.trash/ssh_host_ed25519_key - HostKey /tmp/.trash/ssh_host_rsa_key - EOF -fi - -echo "$KEYGEN_STANCE" >> /etc/conf.d/sshd -rc-service sshd start - -## Prep for final post-cleanup -## clears any installed packages and settings -# copy begininng of this file to keep functions -sed -n '/^#* End .*alter!)$/q;p' /etc/local.d/headless.start > /tmp/.trash/post-cleanup - -cat <<-EOF >> /tmp/.trash/post-cleanup - - _tst_inet() { - ## Tested URL redirects to github project page: is.gd shortener provides basic analytics. - ## Analytics are public and can be checked at https://is.gd/stats.php?url=apkovl_run - ## Privacy policy: https://is.gd/privacy.php - INET="failed" - wget -q -T 10 --spider https://is.gd/apkovl_run > /dev/null 2>&1 && - INET="success" - logger -st ${0##*/} "Internet access: \$INET" - } - - logger -st ${0##*/} "Cleaning-up..." - _restore "/etc/ssh/sshd_config" - _restore "/etc/conf.d/sshd" - _apk del openssh - _restore "/etc/wpa_supplicant/wpa_supplicant.conf" - _apk del wpa_supplicant - _restore "/etc/network/interfaces" - _restore "/etc/hostname" - rm /etc/modules-load.d/g_ether.conf - rm /etc/modprobe.d/g_ether.conf - rc-update del local default - rm /etc/local.d/headless.start - - # Internet connectivity test - # Can be skipped by creating a 'opt-out'-named dummy file aside apkovl file - [ -f "${ovlpath}/opt-out" ] || _tst_inet & - - # Run unattended script if available - if [ -f "${ovlpath}/unattended.sh" ]; then - install -m755 "${ovlpath}/unattended.sh" /tmp/unattended.sh - /tmp/unattended.sh >/dev/console 2>&1 & - logger -st ${0##*/} "/tmp/unattended.sh script launched in the background with PID \$!" - fi - - logger -st ${0##*/} "Done !!" - EOF - -chmod +x /tmp/.trash/post-cleanup -exec /tmp/.trash/post-cleanup - diff --git a/overlay/etc/modprobe.d/g_ether.conf b/overlay/etc/modprobe.d/g_ether.conf deleted file mode 100644 index a3fa924..0000000 --- a/overlay/etc/modprobe.d/g_ether.conf +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright 2022 - 2023, macmpi -# SPDX-License-Identifier: MIT - -# added to support USB-Ethernet gadget mode at boot for Pi devices - -options g_ether dev_addr=ea:64:2f:e8:19:94 host_addr=f6:67:ce:b3:c0:ea diff --git a/overlay/etc/modprobe.d/headless_gadget.conf b/overlay/etc/modprobe.d/headless_gadget.conf new file mode 100644 index 0000000..e621238 --- /dev/null +++ b/overlay/etc/modprobe.d/headless_gadget.conf @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi +# SPDX-License-Identifier: MIT + +# support g_cdc USB-Ethernet gadget mode at boot for Pi devices + +options g_cdc dev_addr=ea:64:2f:e8:19:94 host_addr=f6:67:ce:b3:c0:ea diff --git a/overlay/etc/modules-load.d/g_ether.conf b/overlay/etc/modules-load.d/g_ether.conf deleted file mode 100644 index a9b7ea2..0000000 --- a/overlay/etc/modules-load.d/g_ether.conf +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2022 - 2023, macmpi -# SPDX-License-Identifier: MIT - -# added to support USB-Ethernet gadget mode at boot for Pi devices -# also requires dtoverlay=dwc2 is added to usercfg.txt or config.txt - -dwc2 -g_ether - diff --git a/overlay/etc/runlevels/default/headless_bootstrap b/overlay/etc/runlevels/default/headless_bootstrap new file mode 120000 index 0000000..359480a --- /dev/null +++ b/overlay/etc/runlevels/default/headless_bootstrap @@ -0,0 +1 @@ +../../init.d/headless_bootstrap \ No newline at end of file diff --git a/overlay/etc/runlevels/default/local b/overlay/etc/runlevels/default/local deleted file mode 120000 index ddda14b..0000000 --- a/overlay/etc/runlevels/default/local +++ /dev/null @@ -1 +0,0 @@ -/etc/init.d/local \ No newline at end of file diff --git a/overlay/etc/ssh/ssh_host_ed25519_key b/overlay/tmp/.trash/ssh_host_ed25519_key similarity index 100% rename from overlay/etc/ssh/ssh_host_ed25519_key rename to overlay/tmp/.trash/ssh_host_ed25519_key diff --git a/overlay/etc/ssh/ssh_host_ed25519_key.pub b/overlay/tmp/.trash/ssh_host_ed25519_key.pub similarity index 100% rename from overlay/etc/ssh/ssh_host_ed25519_key.pub rename to overlay/tmp/.trash/ssh_host_ed25519_key.pub diff --git a/overlay/etc/ssh/ssh_host_rsa_key b/overlay/tmp/.trash/ssh_host_rsa_key similarity index 100% rename from overlay/etc/ssh/ssh_host_rsa_key rename to overlay/tmp/.trash/ssh_host_rsa_key diff --git a/overlay/etc/ssh/ssh_host_rsa_key.pub b/overlay/tmp/.trash/ssh_host_rsa_key.pub similarity index 100% rename from overlay/etc/ssh/ssh_host_rsa_key.pub rename to overlay/tmp/.trash/ssh_host_rsa_key.pub diff --git a/overlay/usr/local/bin/headless_bootstrap b/overlay/usr/local/bin/headless_bootstrap new file mode 100755 index 0000000..103128e --- /dev/null +++ b/overlay/usr/local/bin/headless_bootstrap @@ -0,0 +1,303 @@ +#!/bin/sh + +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi +# SPDX-License-Identifier: MIT + +HDLSBSTRP_VERSION="1.0" + +_apk() { + local cmd="$1" + local pkg="$2" + + case $cmd in + add) # install only if not already present + if ! apk info | grep -wq "${pkg}"; then + apk add "$pkg" && printf '%s ' "${pkg}" >> /tmp/.trash/installed + fi + ;; + del) # delete only if previously installed + if grep -wq "$pkg" /tmp/.trash/installed > /dev/null 2>&1; then + apk del "$pkg" && sed -i 's/\b'"${pkg}"'\b//' /tmp/.trash/installed + fi + ;; + *) + echo "only add/del: wrong usage"; exit + ;; + esac +} + +_preserve() { + [ -f "$1" ] && cp "$1" "${1}.orig" +} + +_restore() { + if [ -f "${1}.orig" ]; then + mv -- "${1}.orig" "${1}" + else + rm -rf "${1}" + fi +} + +# shellcheck disable=SC2142 # known special case +alias _logger='logger -st ${0##*/}' + +##### End of part to be duplicated into headless_cleanup (do not alter!) + +_prep_cleanup() { +## Prep for final headless_cleanup +# clears any installed packages and settings +# copy begininng of this file to keep functions +sed -n '/^#* End .*alter!)$/q;p' /usr/local/bin/headless_bootstrap > /tmp/.trash/headless_cleanup +cat <<-EOF >> /tmp/.trash/headless_cleanup + # Redirect stdout and errors to console as service won't show messages + exec 1>/dev/console 2>&1 + + _logger "Cleaning-up..." + _restore "/etc/ssh/sshd_config" + _restore "/etc/conf.d/sshd" + _apk del openssh-server + _restore "/etc/wpa_supplicant/wpa_supplicant.conf" + _apk del wpa_supplicant + _restore "/etc/network/interfaces" + _restore "/etc/hostname" + rm -f /etc/modprobe.d/headless_gadget.conf + + # remove from boot service to avoid spurious openrc recalls from unattended script + rm -f /etc/runlevels/default/headless_bootstrap + rm -f /usr/local/bin/headless_bootstrap + + # Run unattended script if available + install -m755 "${ovlpath}/unattended.sh" /tmp/headless_unattended > /dev/null 2>&1 && \ + _logger "Starting headless_unattended service" && \ + rc-service headless_unattended start + + rm -f /etc/init.d/headless_* + _logger "Clean-up done, enjoy !" + cat /tmp/.trash/banner > /dev/console + if [ -c /dev/ttyGS0 ]; then + # Enabling terminal login into ttyGS0 serial for 60 sec + # no choice than making permanent change to pre 3.19 versions of /etc/securetty + grep -q "ttyGS0" /etc/securetty || echo "ttyGS0" >> /etc/securetty + /sbin/getty -L 115200 ttyGS0 vt100 & + fi + EOF +chmod +x /tmp/.trash/headless_cleanup +} + +_setup_sshd() { +## Setup temporary SSH server (root login, no password) +# We use some bundled (or optionaly provided) keys to avoid generation at startup and save time +_apk add openssh-server +_preserve "/etc/ssh/sshd_config" +_preserve "/etc/conf.d/sshd" + +cat <<-EOF > /etc/ssh/sshd_config + PermitRootLogin yes + Banner /tmp/.trash/banner + EOF + +# Client authorized_keys or no authentication +if install -m600 "${ovlpath}/authorized_keys" /tmp/.trash/authorized_keys > /dev/null 2>&1; then + _logger "Enabling public key SSH authentication..." + cat <<-EOF >> /etc/ssh/sshd_config + AuthenticationMethods publickey + AuthorizedKeysFile /tmp/.trash/authorized_keys + # relax strict mode as authorized_keys are inside /tmp + StrictModes no + EOF +else + _logger "No SSH authentication." + cat <<-EOF >> /etc/ssh/sshd_config + AuthenticationMethods none + PermitEmptyPasswords yes + EOF +fi + +# Server keys: inject optional custom keys, or generate new (might be stored), +# or use bundeled ones (not stored) +local keygen_stance="sshd_disable_keygen=yes" +if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/ > /dev/null 2>&1; then + # check for empty key within injected ones: if found, generate new keys + if find /etc/ssh/ -maxdepth 1 -type f -name 'ssh_host_*_key*' -empty | grep -q .; then + rm /etc/ssh/ssh_host_*_key* + keygen_stance="" + _logger "Will generate new SSH keys..." + else + chmod 644 /etc/ssh/ssh_host_*_key.pub + _logger "Using injected SSH keys..." + fi +else + _logger "Using bundled ssh keys from RAM..." + cat <<-EOF >> /etc/ssh/sshd_config + HostKey /tmp/.trash/ssh_host_ed25519_key + HostKey /tmp/.trash/ssh_host_rsa_key + EOF +fi + +echo "$keygen_stance" >> /etc/conf.d/sshd +rc-service sshd restart +} + +_tst_version() { +# Tested URL redirects to github project page: is.gd shortener provides basic analytics. +# Analytics are public and can be checked at https://is.gd/stats.php?url=apkovl_run +# Privacy policy: https://is.gd/privacy.php +local new_vers="" +local status="failed" +local ref="/macmpi/alpine-linux-headless-bootstrap/releases/tag/v" +if wget -q -O /tmp/homepg -T 10 https://is.gd/apkovl_run > /dev/null 2>&1; then + status="success" + ver="$( grep -o "$ref.*\"" /tmp/homepg | grep -Eo '[0-9]+[\.[0-9]+]*' )" + rm -f /tmp/homepg + [ -n "$ver" ] && ! [ "$ver" = "$HDLSBSTRP_VERSION" ] && \ + new_vers="!! Version $ver is available on Github project page !!" && \ + _logger "$new_vers" && \ + printf '%s\n\n' "$new_vers" >> /tmp/.trash/banner +fi +_logger "Internet access: $status" +} + +_setup_networking() { +## Setup Network interfaces +if [ -d "/sys/class/net/wlan0" ] && [ -f "${ovlpath}/wpa_supplicant.conf" ]; then + _logger "Configuring wifi..." + _apk add wpa_supplicant + _preserve "/etc/wpa_supplicant/wpa_supplicant.conf" + install -m600 "${ovlpath}/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf +else + _logger "No wifi interface or setup file supplied" +fi + +_preserve "/etc/network/interfaces" +if ! install -m644 "${ovlpath}/interfaces" /etc/network/interfaces > /dev/null 2>&1; then + # set default interfaces if not specified by interface file on boot storage + _logger "No interfaces file supplied, building defaults..." + for dev in /sys/class/net/*; do + dev="$(basename "$dev")" + case ${dev%%[0-9]*} in + lo) + cat <<-EOF >> /etc/network/interfaces + auto $dev + iface $dev inet loopback + + EOF + ;; + eth) + cat <<-EOF >> /etc/network/interfaces + auto $dev + iface $dev inet dhcp + + EOF + ;; + wlan) + [ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && cat <<-EOF >> /etc/network/interfaces + auto $dev + iface $dev inet dhcp + + EOF + ;; + usb) + cat <<-EOF >> /etc/network/interfaces + auto $dev + iface $dev inet static + address 10.42.0.2/24 + gateway 10.42.0.1 + + EOF + + cat <<-EOF > /etc/resolv.conf + nameserver 208.67.222.222 + nameserver 208.67.220.220 + + EOF + ;; + esac + done +fi + +echo "###################################" +echo "Using following network interfaces:" +cat /etc/network/interfaces +echo "###################################" + +_preserve "/etc/hostname" +echo "alpine-headless" > /etc/hostname +hostname -F /etc/hostname + +grep -q "wlan" /etc/network/interfaces && \ + [ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && \ + rc-service wpa_supplicant restart +rc-service networking restart +} + +_setup_gadget() { +# load composite USB Serial/USB Ethernel driver & setup terminal +_logger "Enabling USB-gadget Serial and Ethernet ports" +lsmod | grep -q "dwc2" || modprobe -qs dwc2 +modprobe -qs g_cdc +# default config: xon/xoff flow control +stty -g -F /dev/ttyGS0 >/dev/null 2>&1 && setconsole /dev/ttyGS0 +# notes to users willing to connect from Linux Ubuntu-based host terminal: +# - user on host needs to be part of dialout group (reboot required), and +# - disable spurious AT commands from ModemManager on host-side Gadget serial port +# you may create a /etc/udev/rules.d/99-ttyacms-gadget.rules as per: +# https://linux-tips.com/t/prevent-modem-manager-to-capture-usb-serial-devices/284/2 +# ATTRS{idVendor}=="0525" ATTRS{idProduct}=="a4aa", ENV{ID_MM_DEVICE_IGNORE}="1" +} + + +############################################################################# +## Main + +# Redirect stdout and errors to console as service won't show messages +exec 1>/dev/console 2>&1 +_logger "Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi" + +# help randomness for wpa_supplicant and sshd (urandom until 3.16) +rc-service seedrng restart || rc-service urandom restart + +# setup USB gadget mode if device has compatible device-tree +find /proc/device-tree/soc/usb* -name "dr_mode" -print0 | \ + xargs -0 grep -q "peripheral" && _setup_gadget + +# Determine ovl file location +# grab used ovl filename from dmesg +ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )" +if [ -f "${ovl}" ]; then + ovlpath="$( dirname "$ovl" )" +else + # search path again as mountpoint have been changed later in the boot process... + ovl="$( basename "${ovl}" )" + ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 ) + ovl="${ovlpath}/${ovl}" +fi + +# Create banner file +warn="" +grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$? +[ "$RO" -eq "0" ] && warn="(remount partition rw!)" +cat <<-EOF > /tmp/.trash/banner + + Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi + + You may want to delete/rename .apkovl file before reboot ${warn}: + ${ovl} + (can be done automatically with unattended script - see sample snippet) + + + EOF + +_setup_networking + +# Test latest available version online +# Can be skipped by creating a 'opt-out'-named dummy file aside apkovl file +[ -f "${ovlpath}/opt-out" ] || _tst_version & + +# setup sshd unless unattended.sh script prevents it +grep -q "^#NO_SSH$" "${ovlpath}/unattended.sh" > /dev/null 2>&1 \ + || _setup_sshd + +_prep_cleanup +_logger "Initial setup done, handing-over to clean-up" +rc-service headless_cleanup start +exit 0 diff --git a/sample_interfaces b/sample_interfaces index e3f9acc..224f317 100644 --- a/sample_interfaces +++ b/sample_interfaces @@ -1,4 +1,4 @@ -# Copyright 2022 - 2023, macmpi +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-License-Identifier: MIT # Sample network interfaces file @@ -8,15 +8,12 @@ iface lo inet loopback auto eth0 iface eth0 inet dhcp - hostname localhost auto wlan0 iface wlan0 inet dhcp - hostname localhost auto usb0 iface usb0 inet static - address 10.42.0.2/24 + address 10.42.0.2/24 gateway 10.42.0.1 - hostname localhost diff --git a/sample_unattended.sh b/sample_unattended.sh index 1538555..4000603 100644 --- a/sample_unattended.sh +++ b/sample_unattended.sh @@ -1,29 +1,46 @@ -#/bin/sh +#!/bin/sh -# Copyright 2022 - 2023, macmpi +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-License-Identifier: MIT ## collection of few code snippets as sample unnatteded actions some may find usefull +## will run encapusated within headless_unattended OpenRC service -## Obvious one; reminder: is run in the background -echo hello world !! +# To prevent headless bootstrap script from starting sshd +# only keep a single starting # on the line below +##NO_SSH + +# Enable stdout and errors redirection to console if desired (service won't show messages) +# exec 1>/dev/console 2>&1 + +# shellcheck disable=SC2142 # known special case +alias _logger='logger -st ${0##*/}' + + +## Obvious one; reminder: is run as background service +_logger "hello world !!" sleep 60 - +_logger "Finished script" ######################################################## ## This snippet removes apkovl file on volume after initial boot # grab used ovl filename from dmesg ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )" -ovl="$( basename "${ovl}" )" -# search path again as mountpoint may have been changed later in the boot process... -ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 ) +if [ -f "${ovl}" ]; then + ovlpath="$( dirname "$ovl" )" +else + # search path again as mountpoint have been changed later in the boot process... + ovl="$( basename "${ovl}" )" + ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 ) + ovl="${ovlpath}/${ovl}" +fi # also works in case volume is mounted read-only grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$? [ "$RO" -eq "0" ] && mount -o remount,rw "${ovlpath}" -rm -f "${ovlpath}/${ovl}" +rm -f "${ovl}" [ "$RO" -eq "0" ] && mount -o remount,ro "${ovlpath}" ######################################################## @@ -33,7 +50,7 @@ rm -f "${ovlpath}/${ovl}" # note: with INTERFACESOPTS=none, no networking will be setup so it won't work after reboot! # Change it or run setup-interfaces in interractive mode afterwards (and lbu commit -d thenafter) -logger -st ${0##*/} "Setting-up minimal environment" +_logger "Setting-up minimal environment" cat <<-EOF > /tmp/ANSWERFILE # base answer file for setup-alpine script @@ -88,5 +105,5 @@ lbu commit -d ######################################################## -logger -st ${0##*/} "Finished unattended script" +_logger "Finished unattended script" diff --git a/sample_wpa_supplicant.conf b/sample_wpa_supplicant.conf index ba349e1..845f8c2 100644 --- a/sample_wpa_supplicant.conf +++ b/sample_wpa_supplicant.conf @@ -1,4 +1,4 @@ -# Copyright 2022 - 2023, macmpi +# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-License-Identifier: MIT # Sample wpa_supplicant.conf