Compare commits
No commits in common. "master" and "d368f33be9cd7334efa9bb1f03a9bb23a86a4f6d" have entirely different histories.
master
...
d368f33be9
|
@ -1,5 +0,0 @@
|
|||
_site
|
||||
_cache
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
31
Dockerfile
31
Dockerfile
|
@ -1,31 +0,0 @@
|
|||
FROM haskell:9.6-slim AS haskell-builder
|
||||
WORKDIR /opt/site
|
||||
RUN cabal update
|
||||
COPY ./Site.cabal /opt/site/Site.cabal
|
||||
RUN cabal build --only-dependencies -j
|
||||
COPY ./site.hs /opt/site/site.hs
|
||||
RUN cabal install
|
||||
RUN mv $(readlink -f /root/.local/bin/site) /opt/site/site
|
||||
|
||||
FROM debian:bookworm AS site-env
|
||||
WORKDIR /opt/site
|
||||
RUN apt update && apt install -y texlive texlive-luatex texlive-latex-extra texlive-lang-italian latexmk curl git
|
||||
RUN mkdir -p /usr/share/fonts/opentype/alegreya-sans && \
|
||||
curl -fsSL -o - https://github.com/huertatipografica/Alegreya-Sans/archive/refs/tags/v2.008.tar.gz | \
|
||||
tar --strip-components 3 -C /usr/share/fonts/opentype/alegreya-sans -xzf - Alegreya-Sans-2.008/fonts/otf/
|
||||
COPY --from=haskell-builder /opt/site/site /opt/site/site
|
||||
RUN curl -fsSL https://github.com/sass/dart-sass/releases/download/1.71.0/dart-sass-1.71.0-linux-x64.tar.gz | tar xz -C /opt
|
||||
ENV PATH="${PATH}:/opt/dart-sass"
|
||||
|
||||
ENV LANG=C.utf8
|
||||
ENV LANGUAGE=C.utf8
|
||||
ENV LC_ALL=C.utf8
|
||||
ENTRYPOINT ["/opt/site/site"]
|
||||
|
||||
FROM site-env AS site-builder
|
||||
WORKDIR /opt/site
|
||||
COPY . /opt/site
|
||||
RUN ["/opt/site/site", "build"]
|
||||
|
||||
FROM scratch AS site
|
||||
COPY --from=site-builder /opt/site/_site/ /
|
16
Site.cabal
16
Site.cabal
|
@ -1,16 +0,0 @@
|
|||
name: Site
|
||||
version: 1.0
|
||||
synopsis: Site builder with Hakyll
|
||||
author: Tito Sacchi
|
||||
build-type: Simple
|
||||
|
||||
executable site
|
||||
build-depends: hakyll >= 4.16 && < 5
|
||||
, base >= 4 && < 5
|
||||
, mtl >= 2 && < 3
|
||||
, filepath >= 1.4 && < 2
|
||||
, process >= 1.6 && < 2
|
||||
, pandoc >= 3.1 && < 4
|
||||
main-is: site.hs
|
||||
hs-source-dirs: .
|
||||
default-language: Haskell2010
|
|
@ -1,17 +1,12 @@
|
|||
% vim: et:ts=2:sts=2:sw=2
|
||||
\documentclass[parskip=half,oneside,usegeometry]{scrartcl}
|
||||
\documentclass[parskip=half,oneside]{scrartcl}
|
||||
\usepackage[italian]{babel}
|
||||
% \PassOptionsToPackage{hyphens}{url}
|
||||
\usepackage[pdfusetitle,colorlinks=true,urlcolor=blue]{hyperref}
|
||||
\usepackage[dvipsnames]{xcolor}
|
||||
% \usepackage[showframe,pass]{geometry}
|
||||
\usepackage{fontspec}
|
||||
\usepackage{tabularx}
|
||||
\usepackage{textcomp}
|
||||
\usepackage{makecell}
|
||||
\usepackage{eso-pic}
|
||||
\usepackage{geometry}
|
||||
% \usepackage{showframe}
|
||||
\AddToShipoutPictureFG{
|
||||
\AtPageUpperLeft{%
|
||||
\raisebox{-\height}{%
|
||||
|
@ -109,80 +104,46 @@
|
|||
\end{tabularx}
|
||||
|
||||
\section{Presentazione}
|
||||
Sono interessato principalmente alla matematica e all'informatica.
|
||||
Mi piace particolarmente ciò che si trova agli estremi: la sicurezza
|
||||
informatica (low level) e la programmazione funzionale (high level e astratta a
|
||||
tal punto da diventare matematica).
|
||||
Frequento il liceo scientifico e nutro un forte interesse verso l'informatica --
|
||||
specialmente la sicurezza e la programmazione funzionale -- e la matematica.
|
||||
Cerco opportunit\`a di svolgere stage presso aziende del settore nell'ambito del
|
||||
progetto di alternanza scuola-lavoro (PCTO) previsto nel triennio della
|
||||
formazione superiore.
|
||||
|
||||
\section{Istruzione e formazione}
|
||||
\begin{tabularx}{\textwidth}{p{0.2\textwidth}|l}
|
||||
\hfill\textsf{09/2023 -- Oggi} &
|
||||
\textsf{09/2015 -- 06/2018} &
|
||||
\makecell[Xt]{\hphantom{}%
|
||||
{\usekomafont{subsection}Corso di Laurea triennale in Matematica}\\
|
||||
\textit{Università di Pavia}\\
|
||||
Allievo della \href{https://www.iusspavia.it}{Scuola Universitaria Superiore IUSS}\\
|
||||
Allievo del \href{https://www.ghislieri.it/collegio}{Collegio Ghislieri}\hfill
|
||||
\vspace{5pt}\break
|
||||
{\url{https://matematica.unipv.it}}\\
|
||||
Via A. Ferrata, 5, 27100~Pavia~(Italia)\\
|
||||
\vspace{0.8\baselineskip}
|
||||
{\usekomafont{subsection}Licenza di scuola media (secondaria di I grado)}\\
|
||||
\textit{Scuola superiore di I grado ``S. Boezio''}\\
|
||||
\\
|
||||
Indirizzo: Via C. Simonetta, 19, 27100 Pavia (Italia)\\
|
||||
\small{\url{https://icacerbi.edu.it/scuole-secondarie-i-grado/secondaria-boezio}}\\
|
||||
Voto finale: 10/10 con lode\\
|
||||
\vspace{0.5\baselineskip}
|
||||
} \\
|
||||
|
||||
\hfill\textsf{09/2018 -- 06/2023} &
|
||||
\textsf{09/2018 -- Attuale} &
|
||||
\makecell[Xt]{\hphantom{}%
|
||||
{\usekomafont{subsection}Diploma di maturità scientifica}\\
|
||||
\textit{Liceo scientifico ``T. Taramelli''}\hfill
|
||||
\vspace{5pt}\break
|
||||
Valutazione finale: 100/100 con lode\\
|
||||
{\url{https://www.istaramellifoscolo.edu.it}}\\
|
||||
Via L. Mascheroni, 53, 27100~Pavia~(Italia)\\
|
||||
\vspace{0.8\baselineskip}
|
||||
} \\
|
||||
|
||||
\hfill\textsf{07/2022} &
|
||||
\makecell[Xt]{\hphantom{}%
|
||||
{\usekomafont{subsection}115° Corso di Orientamento}\\
|
||||
\textit{Scuola Normale Superiore} (selezione e organizzazione)\hfill
|
||||
\vspace{5pt}\break
|
||||
{\url{https://www.sns.it/it/orientamento-universitario}}\\
|
||||
Presso Accademia Nazionale dei Lincei, Via della Lungara, 10,
|
||||
00165~Roma~(Italia)\\
|
||||
}
|
||||
|
||||
% \textsf{09/2015 -- 06/2018} &
|
||||
% \makecell[Xt]{\hphantom{}%
|
||||
% {\usekomafont{subsection}Licenza di scuola media (secondaria di I grado)}\\
|
||||
% \textit{Scuola superiore di I grado ``S. Boezio''}\\
|
||||
% \\
|
||||
% Indirizzo: Via C. Simonetta, 19, 27100 Pavia (Italia)\\
|
||||
% \small{\url{https://icacerbi.edu.it/scuole-secondarie-i-grado/secondaria-boezio}}\\
|
||||
% Voto finale: 10/10 con lode
|
||||
% } \\
|
||||
\end{tabularx}
|
||||
|
||||
\section{Esperienza lavorativa}
|
||||
\begin{tabularx}{\textwidth}{p{0.2\textwidth}|l}
|
||||
\hfill\textsf{09/2022 -- Attuale} &
|
||||
\makecell[Xt]{\hphantom{}%
|
||||
{\usekomafont{subsection}Pentester / Red team operator}\\
|
||||
\texttt{[undisclosed]}\\
|
||||
{\usekomafont{subsection}Liceo scientifico (ammissione alla classe IV)}\\
|
||||
\textit{Liceo scientifico ``T. Taramelli''}\\
|
||||
\\
|
||||
Indirizzo: Via L. Mascheroni, 53, 27100 Pavia (Italia)\\
|
||||
\small{\url{https://www.istaramellifoscolo.edu.it}}\\
|
||||
Media attuale delle valutazioni: 9,5
|
||||
} \\
|
||||
\end{tabularx}
|
||||
|
||||
\section{Competenze}
|
||||
\subsection{Framework e linguaggi di programmazione}
|
||||
Haskell; C/C++; Rust; Clojure; Python; SQL; Assembly x86; Coq; Shell UNIX; JavaScript/React
|
||||
\subsubsection{Framework e linguaggi di programmazione}
|
||||
Haskell; C/C++; Rust; Python; SQL; Assembly x86; Coq; Shell UNIX; JavaScript/React
|
||||
|
||||
\subsection{Sysadmin e infrastruttura}
|
||||
Docker, Podman; systemd; RHEL administration; Ansible/AWX; K8s; server OpenVPN,
|
||||
WireGuard, NGINX, PostgreSQL; networking e firewall setup su Linux (iptables,
|
||||
nftables, tc); pfSense/OPNsense; Grafana, Telegraf, InfluxDB; Proxmox VE,
|
||||
VMware ESXi; ZFS
|
||||
\subsubsection{Sysadmin}
|
||||
Docker; systemd; server OpenVPN, WireGuard, NGINX, PostgreSQL; networking e firewall setup su Linux
|
||||
|
||||
\subsection{DevOps}
|
||||
Git; CI/CD; Docker, Podman
|
||||
\subsubsection{DevOps}
|
||||
Git; CI/CD; Docker
|
||||
|
||||
\subsection{Software}
|
||||
\subsubsection{Software}
|
||||
Wolfram Mathematica; MATLAB; Adobe Photoshop; QGIS/PostGIS; \LaTeX
|
||||
|
||||
\section{Pubblicazioni}
|
||||
|
@ -192,7 +153,6 @@ Wolfram Mathematica; MATLAB; Adobe Photoshop; QGIS/PostGIS; \LaTeX
|
|||
Progetto in compartecipazione con il prof. Giuseppe Camerini volto a documentare
|
||||
la distribuzione e la biologia delle specie di lampiridi sul territorio
|
||||
italiano, analogamente a quanto svolto in altri Paesi da associazioni analoghe.
|
||||
|
||||
Mi occupo della gestione tecnica e della creazione del sito web; ho partecipato
|
||||
alla stesura dei testi divulgativi; alcune delle fotografie (artistiche e
|
||||
documentative) pubblicate sul sito sono miei scatti.
|
||||
|
@ -217,15 +177,6 @@ supportate dalla GNU Scientific Library. Ho interfacciato il software con
|
|||
Wolfram Mathematica per ease-of-use da parte degli altri autori.
|
||||
|
||||
\section{Progetti}
|
||||
\subsection{TeamItaly (2022)}
|
||||
|
||||
Nel 2022 sono stato parte di \href{https://teamitaly.eu}{TeamItaly}, la
|
||||
squadra nazionale italiana di ethical hacking e Capture The Flag. Con TeamItaly
|
||||
ho partecipato ad \href{https://www.ecsc2022.eu}{ECSC 2022}, la competizione
|
||||
europea di sicurezza informatica supportata da ENISA, svoltasi a Vienna
|
||||
(14-15/09/2022).
|
||||
Siamo arrivati quarti nella classifica finale tra CTF Jeopardy e A/D.
|
||||
|
||||
\subsection{CyberChallenge.IT 2021}
|
||||
Ho partecipato al progetto \href{https://cyberchallenge.it}{CyberChallenge.IT}
|
||||
proposto dal CINI nell'edizione del 2021, rivolto a studenti universitari e
|
||||
|
@ -243,61 +194,21 @@ deployment su container e macchine virtuali degli strumenti e della configurazio
|
|||
rete).
|
||||
La nostra squadra si \`e classificata quarta.
|
||||
|
||||
% CyberChallenge mi ha lasciato un acceso interesse verso le competizioni CTF,
|
||||
% a cui partecipo spesso con la squadra del Politecnico di Milano
|
||||
% (\href{https://toh.necst.it}{Tower of Hanoi}).
|
||||
% Mi piace particolarmente la sicurezza dei binari e la crittografia (per
|
||||
% il suo legame con l'algebra astratta).
|
||||
CyberChallenge mi ha lasciato un acceso interesse verso le competizioni CTF,
|
||||
a cui partecipo spesso con la squadra del Politecnico di Milano (Tower of
|
||||
Hanoi). Mi piace particolarmente la sicurezza dei binari e la crittografia (per
|
||||
il suo legame con l'algebra astratta).
|
||||
|
||||
\subsection{Olimpiadi di Cybersecurity (2021, 2022)}
|
||||
|
||||
% Ho preso parte alla competizione nazionale in entrambe le edizioni delle
|
||||
% Olimpiadi di Cybersecurity organizzate dal CINI, classificandomi al secondo
|
||||
% posto nel 2021 e al primo posto assoluto nel 2022
|
||||
% (\url{https://olicyber.it/nazionale}). Entrambe le edizioni hanno coinvolto in
|
||||
% tutto più di 1000 iscritti, da oltre 300 scuole federate nel 2022. La finali
|
||||
% nazionali hanno avuto una forte risonanza mediatica con diffusione sulla stampa
|
||||
% nazionale%
|
||||
|
||||
Ho preso parte ad entrambe le edizioni delle \href{https://olicyber.it}{Olimpiadi di Cybersecurity}
|
||||
organizzate dal CINI, un progetto rivolto agli studenti delle scuole superiori
|
||||
che vengono selezionati per partecipare ad una finale nazionale individuale in
|
||||
stile CTF Jeopardy.
|
||||
|
||||
Ho ottenuto la medaglia d'oro in entrambe le edizioni, classificandomi al
|
||||
secondo posto nel 2021 ed al primo posto assoluto nella competizione svoltasi
|
||||
al campus ONU ITCILO di Torino nel 2022 (\url{https://olicyber.it/nazionale}).
|
||||
Le finali nazionali hanno avuto una forte risonanza mediatica con diffusione sulla stampa nazionale e locale
|
||||
(%
|
||||
2022:
|
||||
\href{https://www.cybersecitalia.it/tito-sacchi-17-anni-1-posto-olimpiadi-italiane-cyber-sono-un-hacker-buono-sogno-lacn/19519/}{CyberSecurity~Italia},
|
||||
\href{https://www.repubblica.it/tecnologia/2022/05/30/news/sicurezza_informatica_quando_il_gioco_e_una_cosa_seria-351812332/}{La~Repubblica},
|
||||
\href{https://www.repubblica.it/tecnologia/2022/05/30/news/sicurezza_informatica_quando_il_gioco_e_una_cosa_seria-351812332/}{Wired},
|
||||
\href{https://laprovinciapavese.gelocal.it/pavia/cronaca/2022/06/02/news/il-campione-italiano-della-cybersecurity-e-tito-sacchi-17enne-studente-del-taramelli-di-pavia-1.41484632}{La Provincia Pavese};
|
||||
2021:
|
||||
\href{https://www.repubblica.it/tecnologia/2021/06/15/news/olicyber_ecco_il_medagliere_delle_prime_olimpiadi_italiane_di_cybersicurezza-306190140/}{La~Repubblica},
|
||||
\href{https://www.wired.it/attualita/scuola/2021/06/15/cybersecurity-olimpiadi-italia-vincitori/}{Wired}%
|
||||
).
|
||||
%
|
||||
Entrambe le edizioni hanno coinvolto in
|
||||
tutto più di 1000 iscritti, da oltre 300 diverse scuole federate nel 2022.
|
||||
|
||||
\subsection{Olimpiadi della Matematica (2023)}
|
||||
|
||||
Ho ottenuto una medaglia di bronzo alla finale nazionale delle Olimpiadi di
|
||||
Matematica, svoltasi a Cesenatico il 05/05/2023, dopo aver passato con successo
|
||||
la gara provinciale.
|
||||
Con la squadra del mio istituto ho sempre partecipato alle gare a squadre e
|
||||
negli ultimi tre anni del liceo siamo sempre arrivati alla finale,
|
||||
classificandoci al 36° (2021), 18° (2022) e 24° posto (2023).
|
||||
|
||||
% Faccio parte della squadra delle Olimpiadi della Matematica del mio Liceo sin
|
||||
% dal primo anno. Nei quattro anni in cui ho partecipato ci siamo qualificati
|
||||
% sempre alle semifinali nazionali e nelle edizioni 2020/2021 e 2021/2022 abbiamo
|
||||
% preso parte alla finale a squadre, classificandoci rispettivamente 36esimi e
|
||||
% 18esimi.
|
||||
% YAAS abbiamo spaccato a Cese :]
|
||||
% -- 12/05/2022
|
||||
\subsection{Olimpiadi di Cybersecurity (prima edizione, 2021)}
|
||||
In quanto studente delle scuole superiori partecipante a CyberChallenge.IT, sono
|
||||
stato invitato a prendere parte il 12/06/2021 alla competizione nazionale della prima edizione
|
||||
delle Olimpiadi di Cybersecurity organizzate dal CINI, classificandomi al
|
||||
secondo posto (\url{https://olicyber.it/nazionale}). Questa prima edizione ha
|
||||
coinvolto 182 istituti superiori federati e 1150 studenti.
|
||||
La competizione ha avuto una forte risonanza mediatica con diffusione sulla
|
||||
stampa nazionale%
|
||||
\footnote{\url{https://www.wired.it/attualita/scuola/2021/06/15/cybersecurity-olimpiadi-italia-vincitori/}}%
|
||||
\footnote{\url{https://www.repubblica.it/tecnologia/2021/06/15/news/olicyber_ecco_il_medagliere_delle_prime_olimpiadi_italiane_di_cybersicurezza-306190140/}}.
|
||||
|
||||
\section{Hobby e interessi}
|
||||
\subsection{Matematica}
|
||||
|
@ -311,14 +222,19 @@ all'informatica poiché mostrano interessanti parallelismi con linguaggi
|
|||
puramente funzionali di collocazione sia accademica sia applicativa come Haskell
|
||||
e con proof assistant come Coq e Agda.
|
||||
|
||||
% Anche riguardo questi interessi più astratti mi piace imparare applicando: ho
|
||||
% sviluppato una parziale formalizzazione nel proof assistant Coq di alcune
|
||||
% strutture condivise tra teoria delle categorie e fisica introdotte inizialmente
|
||||
% da B. Coecke e S. Abramsky dell'Università di Oxford
|
||||
% (\href{https://doi.org/10.1109/LICS.2004.1319636}{\textsf{DOI:10.1109/LICS.2004.1319636}})
|
||||
% e poi ampiamente estese da altri ricercatori negli anni
|
||||
% successivi. I miei file sono reperibili al repository
|
||||
% \url{https://github.com/jabberabbe/CatQM}.
|
||||
Anche riguardo questi interessi più astratti mi piace imparare applicando: ho
|
||||
sviluppato una parziale formalizzazione nel proof assistant Coq di alcune
|
||||
strutture condivise tra teoria delle categorie e fisica introdotte inizialmente
|
||||
da B. Coecke e S. Abramsky dell'Università di Oxford
|
||||
(\href{https://doi.org/10.1109/LICS.2004.1319636}{\textsf{DOI:10.1109/LICS.2004.1319636}})
|
||||
e poi ampiamente estese da altri ricercatori negli anni
|
||||
successivi. I miei file sono reperibili al repository
|
||||
\url{https://github.com/jabberabbe/CatQM}.
|
||||
|
||||
Faccio parte della squadra delle Olimpiadi della Matematica del mio Liceo sin dal
|
||||
primo anno. Nei tre anni in cui ho partecipato ci siamo qualificati sempre alle
|
||||
semifinali nazionali e nell'edizione 2020/2021 abbiamo preso parte alla finale a
|
||||
squadre (36esimo posto).
|
||||
|
||||
\subsection{Informatica}
|
||||
Parallelamente alla matematica, da tempo mi dedico all'informatica da più punti
|
||||
|
@ -329,7 +245,20 @@ collaborare allo sviluppo del compilatore Haskell
|
|||
(\href{https://gitlab.haskell.org/ghc/ghc}{GHC}), sfruttando l'accogliente
|
||||
community di contributor e mentor che circonda il progetto. Mi è sempre piaciuto
|
||||
esplorare il funzionamento interno di ogni linguaggio, sistema operativo e
|
||||
software che uso e mi sto dedicando al red teaming.
|
||||
software che uso e per questo un'altra mia prospettiva, ora che sono stato
|
||||
introdotto alla cybersecurity, è quella di partecipare a programmi di bug
|
||||
bounty.
|
||||
|
||||
\subsection{Musica}
|
||||
Suono il pianoforte da oltre dieci anni e di recente ho iniziato a studiare la
|
||||
chitarra elettrica, per ampliare il mio background di musicista classico.
|
||||
Tra il repertorio classico ho una particolare preferenza per il periodo
|
||||
romantico.
|
||||
|
||||
\subsection{Sport}
|
||||
Ho praticato nuoto agonistico per quattro stagioni (2015-2019) e sono salito sul
|
||||
podio di svariate competizioni regionali con la mia squadra; ora pratico canoa a
|
||||
livello amatoriale presso il Centro Universitario Sportivo di Pavia.
|
||||
|
||||
\subsection{Fotografia}
|
||||
Negli ultimi anni ho sviluppato un particolare interesse per la fotografia, sia
|
||||
|
|
61
about_me.md
61
about_me.md
|
@ -5,20 +5,33 @@ showtitle: true
|
|||
|
||||
---
|
||||
|
||||
I'm Tito, alias "tauroh". I'm studying for a bachelor's degree in Maths at the
|
||||
University of Pavia, in Italy. I'm into maths and pretty much everything that
|
||||
has to do with computers, except front-end development.
|
||||
I'm Tito, alias "tauroh". I'm a high school student living in Pavia, Italy. I
|
||||
was born in 2004. I have many more interests than I'm able to pursue and I still
|
||||
have to decide what to do after high school. Unless I undergo a sudden change in
|
||||
personality and hobbies, it will be something science-related. I'm quite a geek;
|
||||
I'm into maths and pretty much everything that has to do with computers. I used
|
||||
to play the piano regularly until last year but I don't have enough free time to
|
||||
study music seriously right now.
|
||||
|
||||
## Maths
|
||||
|
||||
When you get to really appreciate maths, you discover a brand new world.
|
||||
Unfortunately, that's something that school is not able to teach -- that's why
|
||||
many people still think that maths is made of formulas, exercises and grades.
|
||||
I have been fascinated by maths since I was a child, and my parents taught me
|
||||
where the elegance that some people (like them, and like me) see in mathematics
|
||||
really resides. Abstraction and formal logic are in my opinion the most advanced
|
||||
capabilities of the human mind -- the latest that evolution gave us from a
|
||||
biological perspective. When you get to really appreciate maths, you discover a
|
||||
brand new world. Unfortunately, that's something that school is not able to
|
||||
teach -- that's why many people still think that maths is made of formulas,
|
||||
exercises and grades.
|
||||
|
||||
As you might have understood, I like pure mathematics, and specifically its most
|
||||
As you might have understood, I like pure mathematics, and specifically the most
|
||||
abstract and foundational aspects, often related to logic and philosophy:
|
||||
[category theory][ct], [abstract algebra][abstract-algebra], [type
|
||||
theory][type-theory], [model structures][simplicial-sets]...
|
||||
theory][type-theory], [model structures][simplicial-sets]... Problem is, you're
|
||||
supposed to study a lot of undergraduate mathematics before proceeding with this
|
||||
areas. I haven't done that (yet); my mathematical background is fragile and
|
||||
therefore my knowledge is quite fragmented. I would like to study maths with a
|
||||
more consistent approach. Hopefully, that's what I will do after high school.
|
||||
|
||||
I spent some time studying [Categorical Quantum Mechanics][cqm], a mathematical
|
||||
setting for quantum physics in dagger-compact categories (such as the one of
|
||||
|
@ -30,17 +43,22 @@ two things together! I wrote a partial formalization of these categorical
|
|||
structures in [Coq][coq]. The sources can be found [on my GitHub][titos-catqm]
|
||||
(it uses @jwiegley's [category theory library][coq-ct]).
|
||||
|
||||
While attending high school, I took part in the Italian [Mathematical Olympiad][olimate].
|
||||
This exciting experience led me to study maths.
|
||||
I have taken part in the Italian [Mathematical Olympiad][olimate] with my school
|
||||
since my first year here. However, training and competitions are fun only with a
|
||||
team -- I perform much better in the team olympiad, and I don't really put the
|
||||
required effort and training in the individual competitions.
|
||||
|
||||
The [nLab][nLab] is a nice play to get lost in during cold winter nights with a
|
||||
cup of tea.
|
||||
|
||||
## Computer science
|
||||
|
||||
I have to stress that I really happen to hate frontend development and weakly
|
||||
typed programming languages. This website doesn't depend on 50KB-sized
|
||||
DOM-diffing JavaScript frameworks and it never will.
|
||||
Mathematics is hard: it's something I still haven't been able to get around. CS
|
||||
and programming are easier and that's why I spend a considerable amount of hours
|
||||
a day playing with my computer. Before examining my interests, I have to stress
|
||||
that I really happen to hate frontend development and weakly typed programming
|
||||
languages. This website doesn't depend on 50KB-sized DOM-diffing JavaScript
|
||||
frameworks and it never will.
|
||||
|
||||
My approach to computer science is dual. Just like in mathematics, I like
|
||||
abstract and theoretical areas of CS: functional programming,
|
||||
|
@ -53,24 +71,27 @@ operating systems internals.
|
|||
|
||||
[Haskell][hs] is an awesome language and [GHC][ghc] is an astonishingly
|
||||
well-engineered piece of software. Functional programming in general has some
|
||||
interesting properties related to pure mathematics and
|
||||
inherently interesting properties related to pure mathematics and
|
||||
logics[^curry-howard-lambek]. Apart from theoretical and academic topics, I'm
|
||||
interested in the implementation of call-by-need referentially transparent
|
||||
functional languages and I've spent some months studying the Haskell RTS and the STG
|
||||
machine (mostly during boring high school classes).
|
||||
functional languages and I'm currently studying the Haskell RTS and the STG
|
||||
machine (mostly during boring school classes). The GHC codebase is quite hard to
|
||||
read for a newcomer, and I'm still looking for a mentor! Sometimes I hang out on
|
||||
`#haskell-it` on `libera.chat`, the IRC channel of [Haskell-ITA][haskell-ita].
|
||||
|
||||
#### Hacking and cybersecurity
|
||||
|
||||
I like playing hacking competitions called [CTFs][ctf]. I
|
||||
am part of [Tower of Hanoi][toh], the CTF team from Politecnico di
|
||||
My low-level geek soul sometimes needs to take a break from lambda-calculi and
|
||||
theoretical CS and gets involved into hacking competitions called [CTFs][ctf]. I
|
||||
am part of [Tower of Hanoi][toh], the CTF and hacking team from Politecnico di
|
||||
Milano (although I'm not a student there). Staying up all night looking at
|
||||
disassemblies and memory dumps has some kind of inherently mystical meaning, and
|
||||
it's also good fun.
|
||||
|
||||
People from ToH were the first to introduce me to offensive cybersecurity during
|
||||
the [CyberChallenge.IT 2021][cyberchallenge] project. I also took part in the
|
||||
the [CyberChallenge.IT 2021][cyberchallenge] project. I also take part in the
|
||||
Italian [Cybersecurity Olympiad][olicyber], which targets high-school students.
|
||||
[I won the finals in 2022][olicyber-classifica22].
|
||||
I got the second place at the finals in 2021.
|
||||
|
||||
Apart from my technical interests, hacking history is an interesting topic on
|
||||
its own, and the underground scene that started to fade away a few years before
|
||||
|
|
|
@ -24,7 +24,7 @@ body {
|
|||
|
||||
.container {
|
||||
padding: 30px 60px 30px 60px;
|
||||
max-width: 900px;
|
||||
max-width: 850px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 20px 10px 20px 10px;
|
||||
|
|
14
index.html
14
index.html
|
@ -1,11 +1,11 @@
|
|||
<h1>~tito</h1>
|
||||
|
||||
<p>
|
||||
Hi! I'm Tito Sacchi, alias "tauroh". I'm currently studying maths at the
|
||||
<a href="https://web.unipv.it">University of Pavia</a>, and I'm staying at
|
||||
<a href="https://www.ghislieri.it/collegio/">Collegio Ghislieri</a>.
|
||||
I like computers, classical piano, swimming, Pallas cats and a lot of
|
||||
other things.
|
||||
Hi! I'm Tito Sacchi, alias "tauroh", an Italian high school student trying to
|
||||
have fun while deciding which of my interests I'm willing to spend more time
|
||||
on in the forecoming years. I like maths, computers, classical piano,
|
||||
swimming, electric guitars, hiking, nature photography and a lot of other
|
||||
things that I'd be glad to share with someone else.
|
||||
<a href="about_me.html">Read more about me.</a>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -14,8 +14,8 @@
|
|||
find some (hopefully) accurate contact information on the
|
||||
<a href="contacts.html">contact page</a>.
|
||||
A copy of my CV is available <a href="Tito_Sacchi_CV.pdf">here</a> (in
|
||||
Italian, out of date). Some sensitive information is stripped; send me an
|
||||
email if you want the complete file.
|
||||
Italian). Some sensitive information is stripped; send me an email if you want
|
||||
the complete file.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
---
|
||||
title: Dual booting pfSense and Linux
|
||||
tags: sysadmin, linux, pfsense, uefi
|
||||
summary: Yeah, I'm trying out pfSense. I use Arch btw
|
||||
|
||||
---
|
||||
|
||||
I bought a low-cost computer on AliExpress recently to experiment with some
|
||||
network security and administration tools and techniques. I wanted to give a try
|
||||
to the [pfSense firewall distribution](https://www.pfsense.org) but at the same
|
||||
time I'm familiar with the Linux networking stack and I honestly don't know
|
||||
where to start to perform ARP spoofing on a BSD-like platform. I wanted my usual
|
||||
Arch Linux box to try out nftables, bettercap and tc on the field :)
|
||||
|
||||
Dual booting is the best option IMHO. AliExpress standard shipping isn't..
|
||||
well.. fast so I started experimenting in VirtualBox. Google wasn't very helpful
|
||||
in telling me how to dual boot pfSense and Linux. It's not officially supported
|
||||
by Netgate and I had some trouble setting up the bootloader. Anyone with some
|
||||
experience and knowledge on UEFI systems could have solved the problem in 5
|
||||
minutes but it took me an entire day. Thought sharing my solution could help
|
||||
someone.
|
||||
|
||||
## Formatting and installing pfSense
|
||||
|
||||
I wanted a UEFI-only setup and apparently that's not the most common setup for
|
||||
pfSense boxes. But installing a bootloader and dual-booting on UEFI is way
|
||||
easier than on BIOS/MBR so I wanted to go that way. I started by formatting the
|
||||
disk on Linux; you have to create a FAT32 EFI System Partition (ESP) to store
|
||||
the bootloader. You can also create now the partitions you need to install Linux
|
||||
or whatever you want; leave at least 2 GB of free space for pfSense. If you
|
||||
install your distro now, don't install the bootloader because it will probably
|
||||
be overwritten by the pfSense installer. To my knowledge the pfSense bootloader
|
||||
takes less than 2 MB on the filesystem so you can keep the standard ESP size
|
||||
recommended by your Linux distro; 200 MB is usually a safe choice.
|
||||
|
||||
Then boot the machine from the pfSense ISO (you will have to disable UEFI to do
|
||||
so). Create a partition and set `/` as its mountpoint; you can choose between
|
||||
UFS and ZFS as the filesystem. UFS is the default choice, while ZFS has more
|
||||
advanced features and some people say that it is more reliable in case of power
|
||||
failure[^1]. When you create the partition the installer will ask you whether
|
||||
you want to create a boot partition; it's basically asking you to create a 512
|
||||
KB boot sector for MBR (I think). We don't need that as it's completely ignored
|
||||
on UEFI systems. Answer "No" and finish the installation -- the installer
|
||||
apparently doesn't install the bootloader on EFI automatically, we'll do that in
|
||||
a moment.
|
||||
|
||||
![The amazing ncurses UI of the pfSense installer. My art history teacher would
|
||||
say it deserves special praise for the colors and the advanced
|
||||
shading.](/resources/pfsense-linux-dual-boot/pfsense-installer-1.png)
|
||||
|
||||
## Installing the pfSense bootloader
|
||||
|
||||
When the installation is completed it will ask you whether you want to open a
|
||||
shell to perform final modifications; choose "Yes". Now mount your ESP on some
|
||||
temporary mountpoint, e.g. `/mnt`. Assuming `/dev/ada0p1` is your ESP (it was
|
||||
shown in the partition editor):
|
||||
|
||||
```
|
||||
$ mount -v -t msdosfs /dev/ada0p1 /mnt
|
||||
```
|
||||
|
||||
Now you have to manually copy the EFI binary on the ESP. It took me some time to
|
||||
figure out where the binary was stored on the root filesystem. I ended up
|
||||
looking at the [`bsdinstall` sources][bsdinstall-source]. I'm installing it the
|
||||
`/efi/pfsense` subdirectory (I like keeping PE/EFI binaries in a different
|
||||
directory than bootloader configuration files and Linux `vmlinuz`/`initrd`s).
|
||||
|
||||
```
|
||||
$ mkdir -p /mnt/efi/pfsense
|
||||
$ cp /boot/loader.efi /mnt/efi/pfsense
|
||||
```
|
||||
|
||||
## Installing the Linux bootloader
|
||||
|
||||
Now we'll have to set up an EFI bootloader that will allow us to choose between
|
||||
Linux and pfSense at boot time. I'm using [systemd-boot][sd-boot-archwiki]
|
||||
(formerly gummiboot), a lightweight and clean EFI-only bootloader. The same can
|
||||
be achieved with GRUB2 by chainloading an EFI binary; an example can be found
|
||||
[on the Gentoo wiki][grub-chainloading-efi], just replace the path to the
|
||||
Microsoft Boot Manager with `/efi/pfsense/loader.efi`.
|
||||
|
||||
Boot again on Linux, preferably with UEFI, so that `efibootmgr` will be able to
|
||||
save the newly installed bootloader. `systemd-boot` usually comes packaged with
|
||||
systemd -- at least on Arch. In case you haven't installed your distro, you can
|
||||
do that now. Then mount your ESP (`/dev/sda1` here) and install the bootloader
|
||||
with `bootctl`. On some distros `/boot` may not be an empty directory on your
|
||||
root filesystem; `/efi` is a popular choice as an ESP mountpoint in that case.
|
||||
|
||||
```
|
||||
$ mount /dev/sda1 /boot
|
||||
$ bootctl --esp-path=/boot install
|
||||
```
|
||||
|
||||
Now setup bootloader entries in `/boot/loader/entries/` for Linux and then setup
|
||||
the entry to chainload the pfSense bootloader:
|
||||
|
||||
```
|
||||
# /boot/loader/entries/pfsense.conf
|
||||
title pfSense
|
||||
efi /efi/pfsense/loader.efi
|
||||
```
|
||||
|
||||
Edit your `loader.conf` accordingly (if you don't set a timeout it won't show
|
||||
you the boot menu):
|
||||
|
||||
```
|
||||
# /boot/loader/loader.conf
|
||||
timeout 3
|
||||
console-mode keep
|
||||
default pfsense.conf # or arch.conf/whatever entry you added for your distro
|
||||
```
|
||||
|
||||
Reboot and you're done! With systemd-boot you can also choose which bootloader
|
||||
entry you want to fire up on the next boot with `systemctl reboot
|
||||
--boot-loader-entry=pfsense.conf`. This could be useful for a headless-only
|
||||
setup, as my portable 150$ AliExpress mini PC will probably be.
|
||||
|
||||
[^1]: Considering that this mini PC won't have a fan, ZFS could be a wise choice
|
||||
in case the temperature rises above critical levels and the CPU does an
|
||||
emergency stop :)
|
||||
|
||||
[bsdinstall-source]: <https://github.com/freebsd/freebsd-src/blob/4042b356a00e12d6888a4e15e2373453804c5121/usr.sbin/bsdinstall/scripts/bootconfig#L67>
|
||||
[sd-boot-archwiki]: <https://wiki.archlinux.org/title/systemd-boot>
|
||||
[grub-chainloading-efi]: <https://wiki.gentoo.org/wiki/GRUB2/Chainloading#Dual-booting_Windows_on_UEFI_with_GPT>
|
||||
|
||||
[comment]: # vim: ts=2:sts=2:sw=2:et:nojoinspaces:tw=80
|
|
@ -1,441 +0,0 @@
|
|||
---
|
||||
title: dumb-cd-webhooks
|
||||
displaytitle: A dumb CD solution with GitHub Webhooks and a shell script
|
||||
summary: 'Or: how to write a webserver in Bash'
|
||||
tags: devops, ci, cd, git
|
||||
|
||||
---
|
||||
|
||||
I've been working on the backend of a school project (it will become public
|
||||
soon) for the last few weeks. It's a Python 3.10 +
|
||||
[FastAPI](https://fastapi.tiangolo.com) +
|
||||
[psycopg3](https://www.psycopg.org/psycopg3/) API backed by a PostgreSQL DB. Our
|
||||
server is a plain Ubuntu 20.04 box on Hetzner and our deployment is as simple as
|
||||
a Python venv and a systemd service (with socket activation!). No Docker, no
|
||||
Kubernetes, no supervisord. We follow the [KISS
|
||||
philosophy](https://wiki.archlinux.org/title/Arch_terminology#KISS). To be
|
||||
perfectly honest, I wanted to install Arch on our server, but we agreed that
|
||||
Ubuntu is a bit more reliable.
|
||||
|
||||
We're using GitHub Actions as a CI solution and it works well; it checks our
|
||||
code, builds a wheel and stores it as a build artifact. And something that I
|
||||
find *really* boring and time-consuming is manually downloading the wheel on
|
||||
our server and updating the Python venv. Wait, isn't this problem commonly
|
||||
solved with ✨ *CD* ✨?
|
||||
|
||||
Ok, there are tons of complete CD solutions for containerized and advanced
|
||||
workloads, using Docker, Kubernetes, AWS... But I'm a dumb idiot with some
|
||||
decent scripting knowledge and no will to learn any of these technologies. How
|
||||
can I start a script whenever CI builds a new wheel on master? Enter [*GitHub
|
||||
Webhooks*][gh-webhooks]!
|
||||
Basically GH will send a HTTP POST request to a chosen endpoint whenever certain
|
||||
events happen. In particular, we are interested in the `workflow_run` event.
|
||||
|
||||
If you create a webhook on GitHub it will ask you for a secret to be used to
|
||||
secure requests to your endpoint. Just choose a random string (`openssl rand
|
||||
-hex 8`) and write it down -- it will be used in our script to check that
|
||||
requests are actually signed by GitHub.
|
||||
|
||||
## Who needs an HTTP library to process requests?
|
||||
|
||||
I decided to take a very simple approach: 1 successful CI build = 1 script run.
|
||||
This means 1 webhook request = 1 script run. The simplest way that I came up
|
||||
with to do this is with a *systemd socket-activated Bash script*. Everytime
|
||||
systemd will receive a connection on a socket it will start a custom process
|
||||
that will handle the connection: this is the way most
|
||||
[`inetd`](https://en.wikipedia.org/wiki/Inetd)-style daemons work.
|
||||
|
||||
**UNIX history recap:** (feel free to skip!) Traditional UNIX network daemons
|
||||
(i.e., network services) would `accept()` connections on managed sockets and
|
||||
then `fork()` a different process to handle each of these. With socket
|
||||
activation (as implemented by inetd, xinetd or systemd) a single daemon (or
|
||||
directly the init system!) listens on the appropriate ports for *all services*
|
||||
on the machine and does the job of `accept`ing connections. Each connection will
|
||||
be handled by a different process, launched as a child of the socket manager.
|
||||
This minimizes load if the service is not always busy, because there won't be
|
||||
any processes stuck waiting on sockets. Everytime a connection is closed the
|
||||
corresponding process exits and the system remains in a clean state.
|
||||
|
||||
The socket manager is completely protocol-agnostic: it is up to the
|
||||
service-specific processes to implement the appropriate protocols. In our case,
|
||||
systemd will start our Bash script and pass the socket as file descriptors. This
|
||||
means that our Bash script will have to *talk HTTP*![^overengineering]
|
||||
|
||||
## How do we do this?
|
||||
|
||||
### systemd and socket activation
|
||||
|
||||
Let's start by configuring systemd. The [systemd.socket man
|
||||
page](https://man7.org/linux/man-pages/man5/systemd.socket.5.html). We have to
|
||||
create a socket unit and a corresponding service unit template. I'll use
|
||||
`cd-handler` as the unit name. I will setup systemd to listen on the UNIX domain
|
||||
socket `/run/myapp-cd.sock` that you can point your reverse proxy (e.g. NGINX)
|
||||
to. TCP port 8027 is mostly for debugging purposes - but if you don't need HTTPS
|
||||
you can use systemd's socket directly as the webhook endpoint.[^socat]
|
||||
|
||||
**Socket unit:**
|
||||
|
||||
```
|
||||
# /etc/systemd/system/cd-handler.socket
|
||||
[Unit]
|
||||
Description=Socket for CD webhooks
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/myapp-cd.sock
|
||||
ListenStream=0.0.0.0:8027
|
||||
Accept=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
```
|
||||
|
||||
**Service template unit:**
|
||||
|
||||
```
|
||||
# /etc/systemd/system/cd-handler@.socket
|
||||
[Unit]
|
||||
Description=GitHub webhook handler for CD
|
||||
Requires=cd-handler.socket
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/var/lib/cd/handle-webhook.sh # path to our Bash webhook handler
|
||||
StandardInput=socket
|
||||
StandardOutput=socket
|
||||
StandardError=journal
|
||||
TimeoutStopSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
`Accept=yes` will make systemd start a process for each connection. Create a
|
||||
Bash script in `/var/lib/cd/handle-webhook.sh`; for now we will only answer
|
||||
`204 No Content` to every possible request. Remember to make the script
|
||||
executable (`chmod +x`). We will communicate with the socket using standard
|
||||
streams (stdin/stdout; stderr will be sent to the system logs).
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# /var/lib/cd/handle-webhook.sh
|
||||
|
||||
printf "HTTP/1.1 204 No Content\r\n"
|
||||
printf "Server: TauroServer CI/CD\r\n"
|
||||
printf "Connection: close\r\n"
|
||||
printf "\r\n"
|
||||
```
|
||||
|
||||
`systemctl daemon-reload && systemctl enable --now cd-handler.socket` and you
|
||||
are ready to go. Test our dumb HTTP server with `curl -vv http://127.0.0.1:8027`
|
||||
or if you're using the awesome
|
||||
[HTTPie](https://httpie.io/docs/cli/main-features) `http -v :8027`. If you're
|
||||
successfully receiving a 204, we have just ~~ignored~~ processed a HTTP request
|
||||
with Bash ^^
|
||||
|
||||
### Parsing the HTTP request
|
||||
|
||||
The anatomy of HTTP requests and responses is standardised in [RFC
|
||||
2616][rfc2616], in sections 5 and 6 respectively.
|
||||
|
||||
UNIX systems come with powerful text-processing utilities. Bash itself has
|
||||
[parameter expansion][bash-expansion] features that will be useful in processing
|
||||
the HTTP request and we will use [jq][jq] to extract
|
||||
the fields we're interested in from the JSON payload.
|
||||
|
||||
We will build our script step by step. I'll define a function to log data (in my
|
||||
case, it will use `systemd-cat` to write directly to the journal; you can
|
||||
substitute its body to adapt it to your needs) and another function to send a
|
||||
response. `send_response` takes two parameters: the first one is the status code
|
||||
followed by its description (e.g. `204 No Content`) and the second one is the
|
||||
response body (optionally empty). We're using `wc` to count characters in the
|
||||
body (we're subtracting 1 for the extra `\n` that Bash sends to `wc`).
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail # Good practice!
|
||||
|
||||
function log {
|
||||
systemd-cat -t 'GitHub webhook handler' $@
|
||||
}
|
||||
|
||||
function send_response {
|
||||
printf "Sending response: %s\n" "$1" | log -p info
|
||||
printf "HTTP/1.1 %s\r\n" "$1"
|
||||
printf "Server: TauroServer CI/CD\r\n"
|
||||
printf "Connection: close\r\n"
|
||||
printf "Content-Type: application/octet-stream\r\n"
|
||||
printf "Content-Length: $(($(wc -c <<<"$2") - 1))\r\n"
|
||||
printf "\r\n"
|
||||
printf '%s' "$2"
|
||||
}
|
||||
```
|
||||
|
||||
If you add `send_response "200 OK" "Hello World!"` as a last line, you should be
|
||||
able to get a 200 response with cURL or HTTPie! You can also test webhooks from
|
||||
the GitHub web UI and see if an OK response is received as expected. We are not
|
||||
sending the `Date` response headers that *should* be set according to RFC 2616.
|
||||
|
||||
![socat and HTTPie talking nicely to each
|
||||
other.](/resources/dumb-cd-webhooks/socat-httpie-success.jpg)
|
||||
|
||||
**Parsing the request line.** As easy as `read method path version`. There will
|
||||
probably be a pending `\r` on `version` but we don't care this much (we will
|
||||
assume HTTP/1.1 everywhere ^^).
|
||||
|
||||
**Parsing headers.** Headers follow immediately the request line. Expert bash
|
||||
users would probably use an associative array to store request headers; we will
|
||||
just use a `case` statement to extract headers we're interested in. We split
|
||||
each line on `:` by setting the special variable `IFS`, input field separator,
|
||||
that defaults to a space. `tr` removes pending `\r\n` and brace substitution is
|
||||
used to remove the space that follows `:` in each header line.
|
||||
|
||||
```bash
|
||||
content_length=0
|
||||
delivery_id=none
|
||||
ghsig=none
|
||||
event_type=none
|
||||
|
||||
while IFS=: read -r key val; do
|
||||
[[ "$key" == "$(printf '\r\n')" ]] && break;
|
||||
val=$(tr -d '\r\n' <<<"$val")
|
||||
case "$key" in
|
||||
"Content-Length")
|
||||
content_length="${val# }"
|
||||
;;
|
||||
"X-GitHub-Delivery")
|
||||
delivery_id="${val# }"
|
||||
;;
|
||||
"X-GitHub-Event")
|
||||
event_type="${val# }"
|
||||
;;
|
||||
"X-Hub-Signature-256")
|
||||
# This will be trimmed later when comparing to OpenSSL's HMAC
|
||||
ghsig=$val
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
printf 'Header: %s: %s\n' "$key" "$val" | log -p debug
|
||||
done
|
||||
```
|
||||
|
||||
**Reading body and checking HMAC signature.** GitHub sends a hex-encoded
|
||||
HMAC-SHA-256 signature of the JSON body as the [`X-Hub-Signature-256`
|
||||
header][gh-securing-webhooks], signed with the secret chosen while creating the
|
||||
webhook. Without this layer of security, anyone could send a POST and trigger CD
|
||||
scripts, maybe making us download malicious builds. In a shell script the
|
||||
easiest way to calculate an HMAC is with the `openssl` command-line tool. We are
|
||||
using `dd` to read an exact amount of bytes from stdin and the body is passed to
|
||||
`openssl` with a pipe to avoid sending trailing newlines (using direct
|
||||
redirection, i.e., `<<<` did not work for me). Brace expansion is used to split
|
||||
strings. Place your WebHook secret in the `GITHUB_WEBHOOK_SECRET` variable and
|
||||
set `ENFORCE_HMAC` to something different from 0 (I thought disabling signature
|
||||
checking could be useful for debugging purposes). You can now play with
|
||||
cURL/HTTPie/*[insert your favourite HTTP client here]* to see if you receive 401
|
||||
Unauthorized responses as expected.
|
||||
|
||||
```bash
|
||||
printf "Trying to read request content... %s bytes\n" "$content_length" | log -p info
|
||||
content=$(dd bs=1 count="$content_length" 2> >(log -p debug) )
|
||||
mysig=$(printf '%s' "$content" | openssl dgst -sha256 -hmac $GITHUB_WEBHOOK_SECRET)
|
||||
if [[ "${mysig#* }" == "${ghsig#*=}" ]]; then
|
||||
log -p notice <<<"HMAC signatures match, proceeding further."
|
||||
else
|
||||
log -p warning <<<"HMAC signatures do not match! Request is not authenticated!"
|
||||
if [[ $ENFORCE_HMAC != 0 ]]; then
|
||||
send_response "401 Unauthorized" "Provide signature as HTTP header."
|
||||
log -p err <<<"Exiting now because HMAC signature enforcing is required."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Sending the HTTP response.** We will send an appropriate response to response
|
||||
to GitHub with the function defined earlier.
|
||||
|
||||
```bash
|
||||
if [[ "$event_type" == "none" ]]; then
|
||||
send_response "400 Bad Request" "Please provide event type as HTTP header."
|
||||
log -p err <<<"X-GitHub-Event header was not provided."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$delivery_id" == "none" ]]; then
|
||||
send_response "400 Bad Request" "Please provide delivery ID as HTTP header."
|
||||
log -p err <<<"X-GitHub-Delivery header was not provided."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "GitHub Delivery ID: %s\n" "$delivery_id" | log -p info
|
||||
printf "GitHub Event type: %s\n" "$event_type" | log -p info
|
||||
case "$event_type" in
|
||||
"workflow_run")
|
||||
send_response "200 OK" "Acknowledged workflow run!"
|
||||
;;
|
||||
*)
|
||||
send_response "204 No Content" ""
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
The "HTTP server" part of the script is complete! You can also test this by
|
||||
asking GitHub to resend past webhooks from the web UI.
|
||||
|
||||
### Parsing the JSON body and downloading artifacts
|
||||
|
||||
**JSON body.** The JSON schema of GitHub webhooks payloads can be found on the
|
||||
[official GH docs][gh-webhook-payloads]. We will use [jq][jq] to parse JSON and
|
||||
with its `-r` flag we will print the fields we're interested on standard output,
|
||||
each on a separate line. Its stream can be passed to the `read` builtin with
|
||||
`IFS` set to `\n`. The `|| true` disjunction at the end of the command makes the
|
||||
script continue with the execution even if jq doesn't find some of the fields we
|
||||
asked it to extract (e.g., in event that signal the start of a workflow,
|
||||
`artifacts_url` is not present).
|
||||
|
||||
I want to run CD workflows only on the main branch (`main`, `master`, ...), so I
|
||||
added a check against the variable `MAIN_BRANCH` that you can configure at the
|
||||
top of the script. GitHub sends `workflow_run` events even when CI workflows
|
||||
start, but we're only interested in running a custom action when a workflow
|
||||
succeeds.
|
||||
|
||||
```bash
|
||||
IFS=$'\n' read -r -d '' action branch workflow_status \
|
||||
name conclusion url artifacts \
|
||||
commit message author < <(
|
||||
jq -r '.action,
|
||||
.workflow_run.head_branch,
|
||||
.workflow_run.status,
|
||||
.workflow_run.name,
|
||||
.workflow_run.conclusion,
|
||||
.workflow_run.html_url,
|
||||
.workflow_run.artifacts_url,
|
||||
.workflow_run.head_commit.id,
|
||||
.workflow_run.head_commit.message,
|
||||
.workflow_run.head_commit.author.name' <<<"$content") || true
|
||||
|
||||
printf 'Workflow run "%s" %s! See %s\n' "$name" "$workflow_status" "$url" | log -p notice
|
||||
printf 'Head commit SHA: %s\n' "$commit" | log -p info
|
||||
printf 'Head commit message: %s\n' "$message" | log -p info
|
||||
printf 'Commit author: %s\n' "$author" | log -p info
|
||||
|
||||
if [[ "$action" != "completed" ]] \
|
||||
|| [[ "$conclusion" != "success" ]] \
|
||||
|| [[ "$branch" != "$MAIN_BRANCH" ]];
|
||||
then exit 0
|
||||
fi
|
||||
log -p notice <<<"Proceeding with continuous delivery!"
|
||||
```
|
||||
|
||||
**Build artifacts.** Before running the custom CD script that depends from your
|
||||
specific deployments scenario, we will download all artifacts build on GitHub
|
||||
Actions during CI. For example, in our Dart/Flutter webapp this could include
|
||||
the built website, with Dart already compiled to JavaScript. In the case of our
|
||||
Python backend the artifact is a Python wheel. This webhook script handler is
|
||||
completely language-agnostic though, meaning that you can use it with whatever
|
||||
language or build system you want.
|
||||
|
||||
We will download and extract all artifacts in a temporary directory and then
|
||||
pass its path as an argument to the CD script, with a bunch of other useful
|
||||
information such as the branch name and the commit SHA. The function
|
||||
`download_artifacts` downloads and extracts the ZIP files stored on GitHub using
|
||||
the [Artifacts API][gh-artifacts-api]. It iterates on the JSON array and
|
||||
extracts appropriate fields using jq's array access syntax. GitHub returns a 302
|
||||
temporary redirect when it receives a GET on the `archive_download_url` advised
|
||||
in the artifact body, so we use cURL's `-L` to make it follow redirects.
|
||||
|
||||
```bash
|
||||
function download_artifacts {
|
||||
# $1: URL
|
||||
# $2: directory to download artifacts to
|
||||
pushd "$2" &>/dev/null
|
||||
artifacts_payload=$(curl --no-progress-meter -u "$GITHUB_API_USER:$GITHUB_API_TOKEN" "$1" 2> >(log -p debug))
|
||||
artifacts_amount=$(jq -r '.total_count' <<<"$artifacts_payload")
|
||||
|
||||
for i in $(seq 1 "$artifacts_amount"); do
|
||||
printf 'Downloading artifact %s/%s...\n' "$i" "$artifacts_amount" | log -p info
|
||||
name=$(jq -r ".artifacts[$((i - 1))].name" <<<"$artifacts_payload")
|
||||
url=$(jq -r ".artifacts[$((i - 1))].archive_download_url" <<<"$artifacts_payload")
|
||||
printf 'Artifact name: "%s" (downloading from %s)\n' "$name" "$url" | log -p info
|
||||
|
||||
tmpfile=$(mktemp)
|
||||
printf 'Downloading ZIP to %s\n' "$tmpfile" | log -p debug
|
||||
curl --no-progress-meter -L -u "$GITHUB_API_USER:$GITHUB_API_TOKEN" --output "$tmpfile" "$url" 2> >(log -p debug)
|
||||
|
||||
mkdir "$name"
|
||||
printf 'Unzipping into %s...\n' "$2/$name" | log -p debug
|
||||
unzip "$tmpfile" -d "$2/$name" | log -p debug
|
||||
rm "$tmpfile"
|
||||
done
|
||||
popd &>/dev/null
|
||||
}
|
||||
|
||||
artifacts_dir=$(mktemp -d)
|
||||
printf 'Downloading artifacts to %s...\n' "$artifacts_dir" | log -p info
|
||||
download_artifacts "$artifacts" "$artifacts_dir"
|
||||
```
|
||||
|
||||
**Final step: running your CD script!** If you aliased `log` to `systemd-cat` as I
|
||||
did above, the `-t` flag will select a different identifier, to make the output
|
||||
of the custom script stand out from the garbage of our webhook handler in the
|
||||
system journal. Again, configure `$CD_SCRIPT` appropriately; it will be run from
|
||||
the directory specified above in the systemd unit file and it will receive the
|
||||
path to the directory containing the downloaded artifacts as an argument.
|
||||
**Note**: it will run with root privileges unless specified otherwise in the
|
||||
service unit file!
|
||||
|
||||
```bash
|
||||
printf 'Running CD script!\n' | log -p notice
|
||||
$CD_SCRIPT "$artifacts_dir" "$branch" "$commit" 2>&1 | log -t "CD script" -p info
|
||||
```
|
||||
|
||||
For example, our Python backend CD script looks something like this:
|
||||
|
||||
```bash
|
||||
cd "$1"
|
||||
systemctl stop my-awesome-backend.service
|
||||
source /var/lib/my-backend/virtualenv/bin/activate
|
||||
pip3 install --no-deps --force **/*.whl
|
||||
systemctl start my-awesome-backend.service
|
||||
```
|
||||
|
||||
**Bonus points for cleanup** :) Remove the tmpdir created earlier to store
|
||||
artifacts:
|
||||
|
||||
```bash
|
||||
printf 'Deleting artifacts directory...\n' | log -p info
|
||||
rm -r "$artifacts_dir"
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
You can find the complete script
|
||||
[here](/resources/dumb-cd-webhooks/handle-webhook.sh). It's 158LoC, not that
|
||||
much, and it's very flexible. There's room for improvement; e.g., selecting
|
||||
different scripts on different branches. Let me know if you extend this script
|
||||
or use a similar approach!
|
||||
|
||||
|
||||
[gh-webhooks]: https://docs.github.com/en/developers/webhooks-and-events/webhooks/about-webhooks
|
||||
|
||||
[rfc2616]: https://datatracker.ietf.org/doc/html/rfc2616
|
||||
|
||||
[bash-expansion]: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
|
||||
|
||||
[jq]: https://stedolan.github.io/jq/
|
||||
|
||||
[gh-securing-webhooks]: https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks#validating-payloads-from-github
|
||||
|
||||
[gh-webhook-payloads]: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
|
||||
|
||||
[gh-artifacts-api]: https://docs.github.com/en/rest/actions/artifacts
|
||||
|
||||
[^overengineering]: At this point, you might be wondering whether it is worth
|
||||
it. It probably isn't, and the simplest solution could be a 100LoC Python +
|
||||
FastAPI script to handle webhooks. But I *really* wanted to do this with basic
|
||||
UNIX tools.
|
||||
|
||||
[^socat]: You can also use `socat` to do what systemd is doing for us if you
|
||||
want to test your script on your local machine: `socat
|
||||
TCP-LISTEN:8027,reuseaddr,fork SYSTEM:/path/to/handle-webhook.sh`.
|
||||
|
||||
[comment]: # vim: ts=2:sts=2:sw=2:et:nojoinspaces:tw=80
|
|
@ -1,197 +0,0 @@
|
|||
---
|
||||
title: Unlocking ZFS datasets on boot with a YubiKey
|
||||
summary: 'Custom initramfs scripts to unlock ZFS datasets with a YubiKey.'
|
||||
tags: sysadmin, linux, zfs
|
||||
|
||||
---
|
||||
|
||||
My home server runs Rocky Linux 9 on ZFS. Its main root dataset is encrypted, and until a few weeks
|
||||
ago I had to manually enter the dataset password on the boot console (fortunately the iDRAC allows
|
||||
me to do that remotely). With a modern server, I would use the TPM2 chip to provide the decryption
|
||||
key; however, my home server is a PowerEdge R330 and only has the obsolete TPM1.2 chip. I had a
|
||||
spare YubiKey 5 I bought when Cloudflare offered them at $10 each, so I decided to put that YubiKey
|
||||
into the internal USB port and use it to unlock datasets without user input. You could argue that
|
||||
it's as useful as having no encryption, because the YubiKey has no way to detect whether the
|
||||
boot was from a trusted source or not. Still, I decided to encrypt my root ZFS pool mostly to be
|
||||
capable of sending encrypted raw sends for backup purposes.
|
||||
|
||||
I'm sharing the custom dracut module I built to serve this purpose. It simply loads a symmetrically
|
||||
encrypted GPG file stored in the initramfs and decrypts it with a passphrase generated by the
|
||||
YubiKey HMAC feature. The decrypted contents of the GPG file provide the decryption key to ZFS.
|
||||
|
||||
## How it works
|
||||
|
||||
* During boot, dracut calls our custom hook before mounting ZFS datasets and checks whether
|
||||
the rootfs dataset has the `zfs_yubikey:keylocation` and `zfs_yubikey:slot` custom attributes set.
|
||||
The former specifies where the encrypted GPG file containing the ZFS key resides in the initramfs;
|
||||
the latter is optional and suggests a particular YubiKey HMAC slot to use (defaults to 1).
|
||||
|
||||
* If the rootfs requires YubiKey decryption, a HMAC challenge will be generated from the SHA256 sum
|
||||
of the string `YUBIKEY_ZFS_V1;<machine_id>;<pool_guid>;<dataset_objsetid>`.
|
||||
|
||||
* The `ykchalresp` binary sends the aforementioned 256-bit challenge to the YubiKey on the specified
|
||||
slot and waits for a response.
|
||||
|
||||
* The HMAC response is used to decrypt the GPG file and the resulting plaintext is sent to `zfs
|
||||
load-key -L prompt <dataset>`.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
The code for the dracut module is provided below. To setup your dataset for automatic unlock,
|
||||
first setup your YubiKey for HMAC challenge-response on one of the available slots.
|
||||
Then open a shell prompt and source `zfs-yubikey-lib.sh`; run
|
||||
`get_response <dataset> [<yubikey_slot>]` to ask the YubiKey the generate the HMAC response and
|
||||
use it to encrypt the ZFS key with GnuPG. Save the resulting encrypted file in `/etc/zfs/yubikey/`
|
||||
and set the `zfs_yubikey:keylocation` property to the path of the file you just saved.
|
||||
Regenerate the initramfs and you're done.
|
||||
|
||||
Shell examples:
|
||||
|
||||
```sh
|
||||
source zfs-yubikey-lib.sh
|
||||
# Set DATASET to your ZFS dataset
|
||||
DATASET=pool/various/elements/to/dataset
|
||||
ykinfo -H || echo 'YubiKey not found!'
|
||||
|
||||
# Write your ZFS key to the stdin of the following command
|
||||
gpg --symmetric --pinentry-mode loopback --passphrase-fd 3 --armor \
|
||||
--output "/etc/zfs/yubikey/${DATASET##*/}.gpg" 3< <(get_response "${DATASET}")
|
||||
zfs set zfs_yubikey:keylocation="/etc/zfs/yubikey/${DATASET##*/}.gpg" "${DATASET}"
|
||||
dracut --regenerate-all --force
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
A dracut module is composed of a `module-setup.sh` (executed on initramfs generation) and an
|
||||
arbitrary number of hooks and files installed by the module. The directory structure of our module
|
||||
is the following:
|
||||
|
||||
```
|
||||
zfs-yubikey
|
||||
├── module-setup.sh (executable)
|
||||
├── zfs-yubikey-lib.sh (executable)
|
||||
└── zfs-yubikey-load-key.sh (executable)
|
||||
```
|
||||
|
||||
This directory should be copied to `/usr/lib/dracut/modules.d/91zfs-yubikey` and dracut should be
|
||||
configured to include this module (see `man 5 dracut.conf`). Code follows.
|
||||
|
||||
<br/>
|
||||
|
||||
* `module-setup.sh`
|
||||
|
||||
```sh
|
||||
#!/usr/bin/bash
|
||||
|
||||
check() {
|
||||
require_binaries sha256sum gpg ykchalresp ykinfo || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
depends() {
|
||||
echo zfs
|
||||
return 0
|
||||
}
|
||||
|
||||
install() {
|
||||
inst_multiple gpg gpg-agent gpg-connect-agent ykchalresp ykinfo sha256sum ||
|
||||
{ dfatal "Failed to install essential binaries"; exit 1; }
|
||||
|
||||
inst_hook pre-mount 85 "${moddir}/zfs-yubikey-load-key.sh"
|
||||
inst_script "${moddir}/zfs-yubikey-lib.sh" "/lib/dracut-zfs-yubikey-lib.sh"
|
||||
|
||||
inst_multiple -o -H /etc/zfs/yubikey/*
|
||||
}
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
* `zfs-yubikey-lib.sh`
|
||||
|
||||
```sh
|
||||
#!/usr/bin/sh
|
||||
|
||||
command -v ykchalresp &>/dev/null || return 127
|
||||
command -v ykinfo &>/dev/null || return 127
|
||||
command -v zpool &>/dev/null || return 127
|
||||
command -v zfs &>/dev/null || return 127
|
||||
command -v gpg &>/dev/null || return 127
|
||||
|
||||
generate_challenge () {
|
||||
local dataset="${1}"
|
||||
local pool="${dataset%%/*}"
|
||||
local machine_id=''
|
||||
if [ -n "$ZFS_YUBI_USE_MACHINE_ID" ]; then
|
||||
machine_id="$(< /etc/machine-id)"
|
||||
fi
|
||||
local pool_guid="$(zpool get -Ho value guid "$pool")"
|
||||
local dataset_objsetid="$(zfs get -Ho value objsetid "$dataset")"
|
||||
|
||||
local key="$(printf 'YUBIKEY_ZFS_V1;%s;%s;%s' "$machine_id" "$pool_guid" "$dataset_objsetid")"
|
||||
sha256sum < <(printf %s "$key") | cut -f1 -d' '
|
||||
}
|
||||
|
||||
get_response () {
|
||||
if [ -z "$1" ]; then return 1; fi
|
||||
local dataset="${1}"
|
||||
local slot="${2:-1}"
|
||||
if [ "$slot" != 1 -a "$slot" != 2 ]; then
|
||||
echo "Invalid slot number!" >&2; return 1
|
||||
fi
|
||||
|
||||
local challenge="$(generate_challenge "$dataset")"
|
||||
ykchalresp -"$slot" -x "$challenge"
|
||||
}
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
* `zfs-yubikey-load-key.sh`
|
||||
|
||||
```sh
|
||||
#!/usr/bin/sh
|
||||
|
||||
. /lib/dracut-zfs-lib.sh
|
||||
. /lib/dracut-zfs-yubikey-lib.sh
|
||||
|
||||
# decode_root_args || return 0
|
||||
decode_root_args
|
||||
|
||||
# There is a race between the zpool import and the pre-mount hooks, so we wait for a pool to be imported
|
||||
while ! systemctl is-active --quiet zfs-import.target; do
|
||||
systemctl is-failed --quiet zfs-import-cache.service zfs-import-scan.service && return 1
|
||||
sleep 0.1s
|
||||
done
|
||||
|
||||
BOOTFS="$root"
|
||||
if [ "$BOOTFS" = "zfs:AUTO" ]; then
|
||||
BOOTFS="$(zpool get -Ho value bootfs | grep -m1 -vFx -)"
|
||||
fi
|
||||
|
||||
[ "$(zpool get -Ho value feature@encryption "${BOOTFS%%/*}")" = 'active' ] || return 0
|
||||
|
||||
_load_key_yubi_cb() {
|
||||
ENCRYPTIONROOT="$(zfs get -Ho value encryptionroot "${1}")"
|
||||
[ "${ENCRYPTIONROOT}" = "-" ] && return 0
|
||||
|
||||
[ "$(zfs get -Ho value keystatus "${ENCRYPTIONROOT}")" = "unavailable" ] || return 0
|
||||
local yubi_keylocation="$(zfs get -Ho value zfs_yubikey:keylocation "${ENCRYPTIONROOT}")"
|
||||
[ "${yubi_keylocation}" = "-" ] && return 0
|
||||
[ -r "${yubi_keylocation}" ] || return 0
|
||||
|
||||
local yubi_slot="$(zfs get -Ho value zfs_yubikey:slot "${ENCRYPTIONROOT}")"
|
||||
[ "${yubi_slot}" = "-" ] && yubi_slot=1
|
||||
|
||||
udevadm settle
|
||||
info "ZFS-YubiKey: Checking for YubiKey..."
|
||||
ykinfo -v &>/dev/null && break
|
||||
|
||||
gpg --passphrase-file <(get_response "${ENCRYPTIONROOT}" "${yubi_slot}") --pinentry-mode loopback \
|
||||
--decrypt "${yubi_keylocation}" | zfs load-key -L prompt "${ENCRYPTIONROOT}"
|
||||
}
|
||||
|
||||
_load_key_yubi_cb "$BOOTFS"
|
||||
for_relevant_root_children "$BOOTFS" _load_key_yubi_cb
|
||||
```
|
|
@ -1,158 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
GITHUB_WEBHOOK_SECRET=REDACTED
|
||||
GITHUB_API_TOKEN=REDACTED
|
||||
GITHUB_API_USER=REDACTED
|
||||
MAIN_BRANCH=master
|
||||
CD_SCRIPT="/var/lib/path-to-your-backend/continuous-delivery.sh"
|
||||
ENFORCE_HMAC=1
|
||||
|
||||
function log {
|
||||
systemd-cat -t 'GitHub webhook handler' $@
|
||||
}
|
||||
|
||||
function send_response {
|
||||
printf "Sending response: %s\n" "$1" | log -p info
|
||||
printf "HTTP/1.1 %s\r\n" "$1"
|
||||
printf "Server: TauroServer CI/CD\r\n"
|
||||
printf "Connection: close\r\n"
|
||||
printf "Content-Type: application/octet-stream\r\n"
|
||||
printf "Content-Length: $(($(wc -c <<<"$2") - 1))\r\n"
|
||||
printf "\r\n"
|
||||
printf '%s' "$2"
|
||||
}
|
||||
|
||||
content_length=0
|
||||
delivery_id=none
|
||||
ghsig=none
|
||||
event_type=none
|
||||
|
||||
read method path version
|
||||
printf "Request method: $method\n" | log -p debug
|
||||
printf "Request path: $path\n" | log -p debug
|
||||
printf "HTTP version: $version\n" | log -p debug
|
||||
|
||||
while IFS=: read -r key val; do
|
||||
[[ "$key" == "$(printf '\r\n')" ]] && break;
|
||||
val=$(tr -d '\r\n' <<<"$val")
|
||||
case "$key" in
|
||||
"Content-Length")
|
||||
content_length="${val# }"
|
||||
;;
|
||||
"X-GitHub-Delivery")
|
||||
delivery_id="${val# }"
|
||||
;;
|
||||
"X-GitHub-Event")
|
||||
event_type="${val# }"
|
||||
;;
|
||||
"X-Hub-Signature-256")
|
||||
ghsig=$val
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
printf 'Header: %s: %s\n' "$key" "$val" | log -p debug
|
||||
done
|
||||
|
||||
printf "Trying to read request content... %s bytes\n" "$content_length" | log -p info
|
||||
content=$(dd bs=1 count="$content_length" 2> >(log -p debug) )
|
||||
# xxd <<<"$content" >&2
|
||||
mysig=$(printf '%s' "$content" | openssl dgst -sha256 -hmac $GITHUB_WEBHOOK_SECRET)
|
||||
if [[ "${mysig#* }" == "${ghsig#*=}" ]]; then
|
||||
log -p notice <<<"HMAC signatures match, proceeding further."
|
||||
else
|
||||
log -p warning <<<"HMAC signatures do not match! Request is not authenticated!"
|
||||
if [[ $ENFORCE_HMAC != 0 ]]; then
|
||||
send_response "401 Unauthorized" "Provide signature as HTTP header."
|
||||
log -p err <<<"Exiting now because HMAC signature enforcing is required."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$event_type" == "none" ]]; then
|
||||
send_response "400 Bad Request" "Please provide event type as HTTP header."
|
||||
log -p err <<<"X-GitHub-Event header was not provided."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$delivery_id" == "none" ]]; then
|
||||
send_response "400 Bad Request" "Please provide delivery ID as HTTP header."
|
||||
log -p err <<<"X-GitHub-Delivery header was not provided."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
printf "GitHub Delivery ID: %s\n" "$delivery_id" | log -p info
|
||||
printf "GitHub Event type: %s\n" "$event_type" | log -p info
|
||||
case "$event_type" in
|
||||
"ping")
|
||||
send_response "204 No Content" ""
|
||||
exit 0
|
||||
;;
|
||||
"workflow_run")
|
||||
send_response "200 OK" "Acknowledged workflow run!"
|
||||
;;
|
||||
*)
|
||||
send_response "204 No Content" ""
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
IFS=$'\n' read -r -d '' action branch workflow_status name conclusion url artifacts \
|
||||
commit message author < <(
|
||||
jq -r '.action,
|
||||
.workflow_run.head_branch,
|
||||
.workflow_run.status,
|
||||
.workflow_run.name,
|
||||
.workflow_run.conclusion,
|
||||
.workflow_run.html_url,
|
||||
.workflow_run.artifacts_url,
|
||||
.workflow_run.head_commit.id,
|
||||
.workflow_run.head_commit.message,
|
||||
.workflow_run.head_commit.author.name' <<<"$content") || true
|
||||
|
||||
printf 'Workflow run "%s" %s! See %s\n' "$name" "$workflow_status" "$url" | log -p notice
|
||||
printf 'Head commit SHA: %s\n' "$commit" | log -p info
|
||||
printf 'Head commit message: %s\n' "$message" | log -p info
|
||||
printf 'Commit author: %s\n' "$author" | log -p info
|
||||
|
||||
if [[ "$action" != "completed" ]] || \
|
||||
[[ "$conclusion" != "success" ]] || \
|
||||
[[ "$branch" != "$MAIN_BRANCH" ]]; then exit 0; fi
|
||||
log -p notice <<<"Proceeding with continuous delivery!"
|
||||
|
||||
function download_artifacts {
|
||||
# $1: URL
|
||||
# $2: directory to download artifacts to
|
||||
pushd "$2" &>/dev/null
|
||||
artifacts_payload=$(curl --no-progress-meter -u "$GITHUB_API_USER:$GITHUB_API_TOKEN" "$1" 2> >(log -p debug))
|
||||
artifacts_amount=$(jq -r '.total_count' <<<"$artifacts_payload")
|
||||
|
||||
for i in $(seq 1 "$artifacts_amount"); do
|
||||
printf 'Downloading artifact %s/%s...\n' "$i" "$artifacts_amount" | log -p info
|
||||
name=$(jq -r ".artifacts[$((i - 1))].name" <<<"$artifacts_payload")
|
||||
url=$(jq -r ".artifacts[$((i - 1))].archive_download_url" <<<"$artifacts_payload")
|
||||
printf 'Artifact name: "%s" (downloading from %s)\n' "$name" "$url" | log -p info
|
||||
|
||||
tmpfile=$(mktemp)
|
||||
printf 'Downloading ZIP to %s\n' "$tmpfile" | log -p debug
|
||||
curl --no-progress-meter -L -u "$GITHUB_API_USER:$GITHUB_API_TOKEN" --output "$tmpfile" "$url" 2> >(log -p debug)
|
||||
|
||||
mkdir "$name"
|
||||
printf 'Unzipping into %s...\n' "$2/$name" | log -p debug
|
||||
unzip "$tmpfile" -d "$2/$name" | log -p debug
|
||||
rm "$tmpfile"
|
||||
done
|
||||
popd &>/dev/null
|
||||
}
|
||||
|
||||
artifacts_dir=$(mktemp -d)
|
||||
printf 'Downloading artifacts to %s...\n' "$artifacts_dir" | log -p info
|
||||
download_artifacts "$artifacts" "$artifacts_dir"
|
||||
|
||||
printf 'Running CD script!\n' | log -p notice
|
||||
$CD_SCRIPT "$artifacts_dir" "$branch" "$commit" 2>&1 | log -t "CD script" -p info
|
||||
|
||||
printf 'Deleting artifacts directory...\n' | log -p info
|
||||
rm -r "$artifacts_dir"
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 81 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.2 KiB |
7
site.hs
7
site.hs
|
@ -1,11 +1,10 @@
|
|||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
import Control.Monad.State
|
||||
import Control.Monad
|
||||
import Hakyll
|
||||
import System.FilePath
|
||||
import System.Process
|
||||
import System.Process
|
||||
import Text.Pandoc.Extensions
|
||||
import System.FilePath
|
||||
import Text.Pandoc.Options
|
||||
|
||||
main :: IO ()
|
||||
|
@ -93,7 +92,7 @@ config :: Configuration
|
|||
config = defaultConfiguration {
|
||||
deployCommand = "rsync -avP --delete \
|
||||
\ --exclude blog --exclude cgi-bin --exclude .DS_Store \
|
||||
\ --exclude .well-known _site/ tito@tilde.team:~/public_html"
|
||||
\ _site/ tito@tilde.team:~/public_html"
|
||||
}
|
||||
|
||||
postCtx :: Tags -> Context String
|
||||
|
|
Loading…
Reference in New Issue