Γράφοντας ένα static page generator από το μηδέν

Theodoros Deligiannidis
Τετάρτη, 25 Μαρτίου 2020

Σίγουρα static page generators υπάρχουν πάρα πολλοί, αλλά μιας και μένουμε σπίτι αυτό τον καιρό, είπα να πιάσω κάτι καινούργιο.

Οι επιλογές για blog πλατφόρμες είναι πάρα πολλές και σίγουρα ο #1 προορισμός είναι το wordpress το οποίο είναι too much για το σκοπό που θέλω. Θα έλεγα ότι μου αρμόζει περισσότερο η πλατφορμα ghost αλλά και πάλι τα ~30$/μήνα πολλά. Στο παρελθόν είχα δοκιμάσει το Jekyll αλλά αν θυμάμαι καλά, είχα ένα πρόβλημα με τα αρχεία markdown και να πω την αλήθεια, ποτέ δεν μου άρεσε η ruby!

Concept

Το concept είναι απλό: Γράφεις τα άρθρα σου σε markdown, τρέχεις μια CLI εφαρμογή και σου φτιάχνει ένα αρχείο HTML με όλα τα άρθρα σε μορφή blog.

Για το σκοπό αυτό, το Node.js πιστεύω είναι το καταλληλότερο.

Πως δουλεύει

Η εφαρμογή βασίζεται κυρίως στο showdown.js , του οποίου η δουλειά είναι να μετατρέπει markdown σε html. Επειδή στην πορεία είδα ότι χρειάζομαι έναν τρόπο να αποθηκεύω και metadata μέσα στο markdown αρχείο αλλά να μην φαίνονται στο τελικό αποτέλεσμα, αποφάσισα να τα συμπεριλάβω σε μορφή κλειδιών / τιμής στην αρχή του αρχείου και να τα επεξεργάζομαι με το parse-markdown-metadata. Τα metadata αυτά θα μπορούν να είναι οτιδήποτε πχ title, author, date, tags κτλ.

Τέλος χρειαζόμαστε μια templating γλώσσα, ώστε να γράψουμε τον σκελετό της σελίδας σε html και να ορίσουμε πως θα φαίνονται οι τίτλοι, το κείμενο, η ημερομηνία και φυσικά το layout(css). Τη δουλειά αυτή θα την αναλάβει η Handlebars .

Κώδικας

Πρώτα θα πρέπει να κάνουμε εγκατάσταση μέσω του npm τα πακέτα από τα οποία εξαρτάται η εφαρμογή.

npm install showdown jsdom js-beautify glob handlebars parse-markdown-metadata

και φυσικά να τα χρησιμοποιήσουμε:

const fs = require('fs');
const glob = require('glob');
const beautify = require('js-beautify').html;
const showdown = require('showdown');
const ParseMarkdownMetadata = require('parse-markdown-metadata');
const Handlebars = require("handlebars");

H function που μετατρέπει το markdown σε html είναι:

function toHtml(input)
{
    converter.setFlavor('vanilla');
    return converter.makeHtml(input);
}

Ανάλογα με το spec του markdown που θέλουμε, το ορίζουμε με την αντίστοιχη επιλογή setFlavor('vanilla') (περισσότερα εδώ ).

Έπειτα, θα αρχικοποιήσουμε τη showdown με το αρχείο html που θέλουμε να έχουμε σαν οδηγό.

let html = fs.readFileSync('index.html').toString();
let template = Handlebars.compile(html);
let data = {"posts":[]};

Το αρχείο html μπορεί να μοιάζει κάπως έτσι:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Renderview - Just a static page generator</title>
    </head>
    <body>
        {{#posts}}
        <div id="block">
            <h1 id="title">{{title}}</h1>
            <div id="author">{{author}}</div>
            <div id="date">{{date}}</div>
            <div id="content">{{{content}}}</div>
            <div>{{email}}</div>
            <hr>
        </div>
        {{/posts}}
    </body>
</html>

Έδω στην ουσία λέμε την handlebars οτι μέσα στο αρχείο html, θα πρέπει να αντικαταστήσεις οτιδήποτε υπάρχει μέσα σε αγκύλες με τιμές τις οποίες θα τις βρείς στη μεταβλητή data. Επειδή προκειται για blog, τότε ο κώδικας HTML που περικλείεται μέσα στα {{#posts}} {{/posts}} , θα πρέπει να τον επαναλάμβάνεται(loop) για όσα αρχεία md υπάρχουν μέσα στο root φάκελο. Μια μικρή λεπτομέρεια είναι ότι το content, δηλαδή το κείμενο του άρθρου θα πρέπει να είναι ανάμεσα σε τριπλές αγκύλες {{{ }}} διότι δε θελουμε να γίνει escaped απο την handlebar, το θέλουμε άθικτο(περισσότερα εδώ)

Τέλος έμεινε να γράψουμε τον κώδικα που θα βρίσκει όλα τα αρχεία που τελειώνουν σε .md μέσα στο φάκελο μας, να τραβήξει τα metadata που υπάρχουν αλλά και το κυρίως κείμενο το οποίο θα το μετατρέψει σε html. Όλα αυτά θα αποθηκευτούν στο πίνακα(array) data.

Ο πίνακας για παράδειγμα θα έχει αυτά τα περιεχόμενα:

data = {"posts":[
{"title":"This is a title", "author":"Theodoros Deligiannidis", "date":"11/01/2020", "content":"<html>...</html>"},
{...}
]};

To loop για όλη την παραπάνω διαδικασία είναι:

glob(__dirname + '/*.md', {}, (err, files)=>{
    let mdfile,md;
    files.forEach(function (file) {
        mdfile = fs.readFileSync(file).toString();
        md = new ParseMarkdownMetadata(mdfile);
        md.props.content = toHtml(md.content);
        data.posts.push(md.props);
    });
    let result = template(data);
    let stream = fs.createWriteStream(outputFilename);

    stream.once('open', function(fd) {
        stream.end(beautify(result, { indent_size: 2, space_in_empty_paren: true }));   
    });
});

Το νέο αρχείο html που δημιουργείται, βρίσκεται στο φάκελο _public και είναι έτοιμο να το ανεβάσουμε στο github ή όπου αλλού θέλουμε! Στο τέλος θα παρατηρήσατε ότι έκανα και ένα beautify στο αρχείο html για να διαβάζεται πιο εύκολα αλλά και για να έχει σωστή στοίχιση.

Τρέχουμε την εφαρμοφή δίνοντας:

node app.js

Όπως είδαμε, με node.js, λίγα πακέτα και λίγο φαντασία, γράφουμε ένα ισάξιο κλώνο του Jekyll. Βέβαια, λείπουν αρκετά χαρακτηριστικά όπως tags και η σειρά των άρθρων αλλά αυτά αργότερα, σε ένα άλλο άρθρο!

Ακολουθούν όλα τα αρχεία.

app.js

const fs = require('fs');
const glob = require('glob');
const beautify = require('js-beautify').html;
const showdown  = require('showdown');
const ParseMarkdownMetadata = require('parse-markdown-metadata');
const Handlebars = require("handlebars");

let publicDir = './_public';
let outputFilename = publicDir+'/index.html'

const conf = {};
let converter = new showdown.Converter(conf);

if (!fs.existsSync(publicDir)){
    fs.mkdirSync(publicDir);
}

function toHtml(input)
{
    converter.setFlavor('vanilla');
    return converter.makeHtml(input);
}   

let html = fs.readFileSync('index.html').toString();
let template = Handlebars.compile(html);
let data = {"posts":[]};

glob(__dirname + '/*.md', {}, (err, files)=>{
    let mdfile,md;
    files.forEach(function (file) {
        mdfile = fs.readFileSync(file).toString();
        md = new ParseMarkdownMetadata(mdfile);
        md.props.content = toHtml(md.content);
        data.posts.push(md.props);
    });
    let result = template(data);
    let stream = fs.createWriteStream(outputFilename);

    stream.once('open', function(fd) {
        stream.end(beautify(result, { indent_size: 2, space_in_empty_paren: true }));   
    });
});

myfirstpost. md

title: My first post
author: Theodoros Deligiannidis
date: 20/03/2020
email: thiodor@gmail.com

#Hello word

index_skeleton.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <link rel="stylesheet" href="https://unpkg.com/sakura.css/css/sakura.css" type="text/css">
        <title>Just a blog</title>
    </head>
    <body>
        {{#posts}}
        <div id="block">
            <h1 id="title">{{title}}</h1>
            <div id="author">{{author}}</div>
            <div id="date">{{date}}</div>
            <div id="content">{{{content}}}</div>
            <div>{{email}}</div>
            <hr>
        </div>
        {{/posts}}
    </body>
</html>

Happy compiling!


Πού πάμε;

Theodoros Deligiannidis
Παρασκευή, 20 Μαρτίου 2020

Μέσα στο 2020 είπα ότι θα ξεκινήσω να γράφω τεχνικά άρθρα, 1ον γιατί μου δίνει ευχαρίστηση να μοιράζομαι γνώση με άλλους και 2ον γιατί δεν το έχω καθόλου με τη γραφή και θέλω απλά να γίνω καλύτερος σε αυτό.

Το 3ο μου post κατά σειρά δεν έχει να κάνει καθόλου με linux ή web applications, αλλά με όλα αυτά που γίνονται τελευταία στον πλανήτη/χώρα/πόλη/σπίτι μας, με πρωταγωνιστή την πανδημία που προέκυψε.

Δε θα αναφερθώ στο γιατί και το πώς, αν είναι hoax ή παιχνίδι των διαφόρων φαρμακευτικών εταιριών, αυτό το αφήνω στους ειδικούς, στους τρομολάγνους και στα troll. Η ερώτηση που κάνω στον εαυτό μου. αλλά και στην σύζυγό μου, είναι μία: Πού πάμε;

Δυστυχώς, απάντηση ακόμα δεν πήρα και η αλήθεια είναι ότι θα αργήσω να πάρω, ωστόσο θα προσπαθήσω να βρω μία.

Η αρχή

Ακόμα θυμάμαι εκείνη την Τρίτη, που πιθανότατα είχα ξυπνήσει για να πάω στο πανεπιστήμιο, όταν στη μικροσκοπική μου φοιτητική τηλεόραση έβλεπα στο Euronews τους δίδυμους πύργους να πέφτουν, ο ένας πίσω από τον άλλο.

Ήταν σοκαριστική εικόνα και είμαι πεπεισμένος ότι αυτή ήταν η αρχή των δεινών, τουλάχιστον για τη γενιά μας. Νομίζω πως τότε η ανθρωπότητα γύρισε σελίδα στην ιστορία της, η επόμενη σελίδα όμως δεν θα ήταν ευχάριστη.

Fast forward έως 2015

Σίγουρα η 9/11 δεν έχει σχέση με όλα αυτά που συμβαίνουν γύρω μας, αλλά για μένα τώρα που το βλέπω εκ των υστέρων, σίγουρα αποτελεί την αρχή. Μεταξύ 2001 και 2015 έγιναν πολλά, που τα περισσότερα δεν μας άγγιξαν, τουλάχιστον άμεσα. Στο διάστημα αυτό, σιγά σιγά συντελέστηκαν γεγονότα και καταστρώθηκαν (κρυφά;) σχέδια που θα άλλαζαν τον κόσμο μας. Μέσα στα θετικά ήταν η τεχνολογική ανάπτυξη και η πρόοδος που είχαμε, με αποτέλεσμα τα κινητά, οι υπολογιστές και το software να γίνουν αναπόσπαστα εργαλεία της καθημερινότητάς μας.

Όλα τα θετικά όμως πράγματα μας βόλεψαν και μας έφεραν ακόμα πιο κοντά στην αδράνεια, όχι απαραίτητα την σωματική αλλά περισσότερο στην κοινωνική αδράνεια.

Έτσι βολευτήκαμε, ξεχάσαμε αξίες και αφεθήκαμε στις selfie φωτογραφίες που έγιναν το talk of the town. Μαθημένοι επίσης σε καλούς μισθούς, ψώνια και lux εκδρομές ( βλέπε εποχές ΠΑΣΟΚ, όπως λέμε τώρα αστειευόμενοι) , το αποτέλεσμα ήταν να γίνουμε λιγότερο παραγωγικοί.

Πώς, θα ρωτάτε, έχουν σχέση όλα αυτά με το 2015;

Νομίζω πως όταν κορυφώθηκε η κρίση του 2008, μας έπιασε απροετοίμαστους - οικονομικά, κοινωνικά και επαγγελματικά.

Κοινώς, οι περισσότεροι από εμάς δεν είχαμε ένα back up plan, δεν είχαμε έτοιμες απαντήσεις σε καίρια ερωτήματα, όπως :

Οι ερωτήσεις σίγουρα διαφέρουν από άτομο σε άτομο, αλλά σίγουρα πολλές είναι κοινές ερωτήσεις για όλους μας.

Όσον αφορά την κορύφωση της κρίσης του 2015, η οποία ξεκίνησε το 2008, ίσως θα έπρεπε να το είχαμε δει να έρχεται, αλλά ήμασταν "αλλού" (βλέπε πιο πάνω-talk of the town). Αφεθήκαμε τόσο πολύ στην καθημερινότητα, άλλοι στο μόχθο για τα προς το ζην και άλλοι στην καλοπέραση, η οποία αποδείχτηκε ψεύτικη.

Το αποτέλεσμα; Κλειστές τράπεζες και όλα τα επακόλουθα, που τόσο πολύ μας κόστισαν.

Skip to 2019

Και κάπου εδώ, λίγους μήνες πριν, φαίνεται πως βγαίνουμε από το σκοτάδι και ότι υπάρχει ελπίδα. Δε χρωματίζω την κατάσταση, αλλά πιθανώς η κυβέρνηση να έμαθε από τα λάθη των προηγουμένων.

Και πάνω που νομίζαμε οτι οι επιλογές που κάναμε τόσα χρόνια ήταν οι σωστές (και σίγουρα για πολλούς είναι), έρχεται μία πανδημία να σαρώσει τα πάντα. Επειδή τυχαίνει να είμαι ιατρός, βλέπω τι εστί αυτή η νέα κατάσταση εκ των έσω και ξέρω ότι ο ιός αυτός ήρθε για να μείνει.

Και ποια η σχέση με τις επιλογές μας; Ο ιός είναι μια "φυσική καταστροφή", για την οποία δεν μπορούμε να κάνουμε πολλά. Αυτό που μας το κάνει πιο δύσκολο, είναι ότι δε φροντίσαμε για τις εναλλακτικές μας, που θα έκαναν τη ζωή λίγο πιο εύκολη αλλά και ευχάριστη.

Μία από τις λάθος επιλογές, ή ίσως μία από τις παγίδες στις οποίες πέσαμε, είναι ότι διαλέξαμε τις μεγαλουπόλεις ως το ιδανικότερο μέρος για να ζήσουμε. Εδώ έχει τα καλύτερα πολυκαταστήματα, τα καλύτερα και πιο in bars, τα περισσότερα μέλη του Tinder για άμεση "εκτόνωση" και πολλά άλλα.

Και όλα αυτά; Έχουν τώρα σημασία; Καμία!

Σίγουρα η επόμενη λέξη που θα έρχεται στο μυαλό σας είναι η απαισιοδοξία. Ίσως εκ φύσεώς μου να τα βλέπω όλα μαύρα, αλλά δεν μπορώ να παραβλέψω μια επίσημη πανδημία και τις επιπτώσεις της.

Εκτός του φόβου που με διακατέχει, πρώτα για την οικογένειά μου και μετά για όλους τους άλλους, είμαι φοβερά νευριασμένος.

Νευριασμένος γιατί δεν μπόρεσα να το προβλέψω (με μια πιο ευρεία έννοια και όχι αυτή καθεαυτή την πανδημία) και να φτιάξω τη ζωή μου πιο απλή και άνετη.

Ένα παράδειγμα θα δώσω, από τα πολλά που έχω σκεφτεί. Εδώ που μένουμε και σε ακτίνα 1χλμ. τουλάχιστον, δεν υπάρχει μια παιδική χαρά, μια κούνια να πάω την κόρη μου να κάνει! Για σκεφτείτε το λίγο. Τόσες ευκαιρίες και επιλογές σε μια μεγάλη πόλη και δεν μπορώ να πάω την κόρη μου να κάνει κούνια.

Και πού πάμε τελικά;

Ο καθένας πιστεύω πρέπει να πάει εκεί που ανήκει, πίσω στη φύση. Να ξεβολευτούμε από τον καναπέ μας και να γυρίσουμε στις ρίζες μας, εκεί όπου είμαστε φτιαγμένοι σαν άνθρωποι να ζούμε. Είμαστε φτιαγμένοι για απλά και ανθρώπινα πράγματα. Και λέγοντας ανθρώπινα, ΔΕΝ μπορώ να φανταστώ ότι είμαστε φτιαγμένοι για να ζούμε σε τσιμεντουπόλεις, με θερμοκρασίες φούρνου ή ακόμα χειρότερα να προσπερνάμε χιλιάδες αγνώστους καθημερινά δίχως ένα καλημέρα.

Αν ικανοποιήσουμε τις παραπάνω ανάγκες, τότε είμαστε σε σωστό δρόμο, ο καθένας οπλισμένος με την πυξίδα του.

Θεόδωρος Δεληγιαννίδης thiodor@gmail.com


Οδηγίες για compile της PHP από τον πηγαίο κώδικα

Theodoros Deligiannidis
Τετάρτη, 18 Μαρτίου 2020

Ο δεύτερος οδηγός μου είναι η μεταγλώττιση της PHP από τον πηγαίο κώδικα που στην ουσία είναι συμπληρωματικός στον πρώτο οδηγό μιας και php(και συγκεκριμένα php-fpm) και nginx συνήθως πάνε πακέτο.

Όταν τελειώσετε με τα παρακάτω βήματα θα έχετε στη διάθεση σας την τελευταία έκδοση της PHP και του PHP-FPM.

Βήμα 1ο: Πηγαίος κώδικας και ρυθμίσεις περιβάλλοντος

Κάνουμε ένα φάκελο μέσα στο home όπου θα είναι εγκαταστημένα τα εκτελέσιμα αρχεία και τα αρχεία ρυθμίσεων.

mkdir ~/php

Κατεβάζουμε την τελευταία έκδοση PHP 7.4

wget https://www.php.net/distributions/php-7.4.3.tar.gz
tar xzvf php-7.4.3.tar.gz
cd php-7.4.3

θα χρειαστείτε μερικά αναγκαία προγράμματα(και μερικά προαιρετικά όπως webp):

sudo apt-get install -y libargon2-0-dev libzip-dev libzip4 libsqlite3-dev libonig-dev webp

Βήμα 2ο: Μεταγλώττιση

Για τη δικιά σας ευκολία αλλά και σε περίπτωση που θέλετε να πειραματιστείτε πολλές φορές, μπορείτε το configure να το ενσωματώσετε σε ένα σκριπτάκι bash που μπορείτε να το ονομάσετε όπως θέλετε:

#!/bin/sh
INSTALL_DIR=$HOME/bin/php7
mkdir -p $INSTALL_DIR

./configure 
    --prefix=$INSTALL_DIR \
    --enable-bcmath \
    --enable-fpm \
    --with-fpm-user=www-data \
    --with-fpm-group=www-data \
    --disable-cgi \
    --enable-mbstring \
    --enable-shmop \
    --enable-sockets \
    --enable-sysvmsg \
    --enable-sysvsem \
    --enable-sysvshm \
    --with-zlib \
    --with-curl \
    --without-pear \
    --with-openssl \
    --enable-pcntl \
    --with-password-argon2 \
    --with-sodium \
    --enable-mysqlnd \
    --with-pdo-mysql \
    --with-pdo-mysql=mysqlnd \
    --enable-gd \
    --with-jpeg  \
    --enable-opcache \
    --enable-zip \
    --enable-exif

Στο παραπνω σετ ρυθμίσεων, εκτός των άλλων, ενεργοποιούμε το opcache το οποίο είναι must για την PHP και φυσικά την υποστηριξη mysql(--enable-mysqlnd), επεξεργασία εικόνων(--enable-gd) και άλλα.

Όταν είμαστε έτοιμοι, διαλέγουμε τις παραμέτρους για τον gcc:

export CFLAGS='-Ofast -pipe -fomit-frame-pointer -march=native -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2'  
export CXXFLAGS="${CFLAGS}"
export LDFLAGS='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now'

και τρέχουμε το σκριπτάκι:

sh buildPHP.sh

Αφού τελειώσει χωρίς σφάλματα προχωράμε στην μεταγλώττιση:

make -j 4

Η παράμετρος -j 4 είναι προαιρετική και έχει να κάνει με τους διαθέσιμους πυρήνες του CPU σας επιταχύνοντας έτσι την μεταγλώττιση. Μπορείτε να το βρείτε δίνοντας:

lscpu  |grep 'CPU(s)'
CPU(s):              4

Όταν το make τελειώσει χωρίς σφάλματα, κάνουμε εγκατάσταση δίνοντας:

sudo make install

Βήμα 3ο - Παραμετροποίηση της PHP

Αρχικά κάνουμε αντιγραφή του php.ini που θα χρησιμοποιήσουμε:

cp php.ini-development ~/php/php7/lib/php.ini
nano ~/php/php7/lib/php.ini

Θα χρειαστεί να ρυθμίσουμε μερικές παραμέτρους της PHP.

Ζώνη ώρας

[Date]
date.timezone=Europe/Athens

Ο οδηγός της Mysql(PDO) αν αυτή τρέχει τοπικά

pdo_mysql.default_socket=/var/run/mysqld/mysqld.sock

Απενεργοποίηση του pathinfo για λόγους ασφαλείας:

cgi.fix_pathinfo=0:

Αφού τελειώσουμε με τις ρυθμίσεις της PHP, προχωράμε στις ρυθμίσεις του PHP-FPM. Θα χρειαστούμε ένα πρότυπο για να δουλέψουμε, το οποίο βρίσκεται στο φακελο ~/php/php7/etc:

cd ~/php/php7/etc/; mv php-fpm.conf.default php-fpm.conf
cd ~/php/php7/etc/php-fpm.d/; mv www.conf.default www.conf

Ανοίγουμε το αρχείο www.conf και ελέγχουμε αν ο user και το group είναι το σωστό(www-data).

user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Σε περίπτωση που θέλουμε να χρησιμοποιήσουμε sockets αντί για TCP(είναι πιο γρήγορα αλλά με μερικούς περιορισμούς) τότε αλλάζουμε την επιλογή listen

listen = /var/run/php-fpm.sock

Επίσης, για λόγους ταχύτητας, καλό είναι να ενεργοποιήσουμε την επέκταση opcache που είναι διαθέσιμη γιατί κατά το configure δώσαμε --enable-opcache. Ανοίγουμε το αρχείο ~/php/php7/lib/php.ini και προσθέτουμε την γραμμή

zend_extension=~/php/php7/lib/php/extensions/no-debug-non-zts-20190902/opcache.so

Τέλος ξεκινάμε τo php-fpm με την εντολή:

sudo $HOME/php/php7/sbin/php-fpm

Βήμα 4ο - Σύνδεση με τον NGINX

Αν και δεν είναι στο πλαίσιο αυτού του οδηγού το πως θα ρυθμίσετε τη PHP με τον NGINX, παρακάτω θα βρείτε ένα παράδειγμα για το πως ρυθμίζεται η PHP μέσα στο server block του NGINX:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_intercept_errors on;
    fastcgi_pass unix:/var/run/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
}

Βλέπουμε οτι στη παράμετρο fastcgi_pass δώσαμε socket αντί για TCP μιας και στο 3ο βήμα το αλλάξαμε από listen = 127.0.0.1:9000 που ήταν το default.

Happy compiling!

Ο οδηγός βασίστηκε πάνω σε αυτό το άρθρο: https://github.com/gitKearney/php7-from-scratch

Θεόδωρος Δεληγιαννίδης thiodor@gmail.com


Οδηγίες για compile του Nginx από τον πηγαίο κώδικα

Theodoros Deligiannidis
Κυριακή, 15 Μαρτίου 2020

Ο παρακάτω οδηγός θα σας βοηθήσει να μεταγλωττίσετε(compile) τον διακομιστή HTTP NGINX. Ο οδηγός γράφτηκε για Ubuntu server 18.04.4 αλλά προσαρμόζεται εύκολα και σε άλλες εκδόσεις.

Για ποιο λόγο να εγκαταστήσω τον NGINX με αυτό τον τρόπο και όχι μέσω της διανομής μου;

Οι λόγοι είναι αρκετοί και διαφέρουν από χρήστη σε χρήστη. Ένας λόγος είναι ότι θα έχετε πάντα την τελευταία έκδοση η οποία περιέχει και τις πιο πρόσφατες ενημερώσεις ασφαλείας.

Επίσης, πολλές φορές τα έτοιμα πακέτα των διανομών, υστερούν σε ορισμένα χαρακτηριστικά τα οποία για μερικούς είναι αναγκαία, πχ υποστήριξη για streaming αρχεία MP4 (ngxhttpmp4_module) ή όπως θα δείτε πιο κάτω υποστήριξη brotli compression. Ένα άλλο πλεονέκτημα είναι μια μικρή αύξηση στις επιδόσεις του nginx αλλά και το μικρότερο memory footprint.

Βήμα 1ο: Πηγαίος κώδικας και ρυθμίσεις περιβάλλοντος

Κατεβάστε τον πηγαίο κώδικα. Ο nginx είναι διαθέσιμος σε 2 εκδόσεις: Mainline και Stable. Πατήστε εδώ για να δείτε ποια έκδοση σας ταιριάζει.

wget https://nginx.org/download/nginx-1.16.1.tar.gz
tar xfv nginx-1.16.1.tar.gz

θα χρειαστούμε άλλες 3 βιβλιοθήκες τις οποίες χρησιμοποιεί ο nginx:

wget https://www.openssl.org/source/openssl-1.1.1d.tar.gz && tar xzvf openssl-1.1.1d.tar.gz  
wget https://www.zlib.net/zlib-1.2.11.tar.gz && tar xzvf zlib-1.2.11.tar.gz  
wget https://ftp.pcre.org/pub/pcre/pcre-8.44.tar.gz && tar xzvf pcre-8.44.tar.gz  

Για να μεταγλωττίσουμε τον nginx είναι αναγκαία μερικά πακέτα όπως gcc, make κτλ:

sudo apt install build-essential -y

Τέλος, έμεινε να ρυθμίσουμε τις παραμέτρους μεταγλώττισης για τον compiler gcc(προαιρετικό).

export CFLAGS='-Ofast -pipe -fomit-frame-pointer -march=native -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2'  
export CXXFLAGS="${CFLAGS}"
export LDFLAGS='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now'

Για περισσότερες πληροφορίες, μπορείτε να διαβάσετε περισσότερα εδώ https://developers.redhat.com/blog/2018/03/21/compiler-and-linker-flags-gcc και εδώ https://wiki.gentoo.org/wiki/GCC_optimization .

Βήμα 2ο: Μεταγλώττιση

Πρώτα κάνουμε configure που θα θέσει τις απαραίτητες οδηγίες για την μεταγλώττιση. Αυτό το βήμα είναι το πιο σημαντικό μιας και εδώ θα ορίσουμε τι θα υποστηρίζει ο ngixn, ποια modules θα είναι διαθέσιμα(στατικά ή δυναμικά - static / dynamic), ποιοι θα είναι οι φάκελοι εγκατάστασης και άλλα:

./configure 
    --with-openssl=../openssl-1.1.1d 
    --with-pcre=../pcre-8.44 
    --with-zlib=../zlib-1.2.11 
    --user=www-data 
    --group=www-data 
    --build=my-nginx 
    --with-openssl-opt=no-nextprotoneg 
    --with-openssl-opt=no-weak-ssl-ciphers 
    --with-pcre-jit 
    --with-file-aio  
    --with-threads 
    --with-http_gzip_static_module 
    --with-debug 
    --with-http_ssl_module 
    --with-http_geoip_module 
    --with-http_v2_module

Η πλήρης λίστα με τις διαθέσιμες επιλογές βρίσκεται online https://nginx.org/en/docs/configure.html ή δίνοντας

./configure --help

Τα παραπάνω είναι ενδεικτικά και εξαρτάται από την χρήση που θα κάνετε, τα εξωτερικά modules που θέλετε να είναι διαθέσιμα. Για παράδειγμα αν θέλουμε συμπίεση brotli τότε προσθέτουμε --add-module=../ngx_brotli ή --add-dynamic-module=../brotli για δυναμικά modules που τα φορτώνουμε με την παράμετρο load_module

load_module /path/to/modules/ngx_http_execute_module.so;

Αφού τελειώσει χωρίς σφάλματα προχωράμε στην μεταγλώττιση:

make -j 4

Η παράμετρος -j 4 είναι προαιρετική και έχει να κάνει με τους διαθέσιμους πυρήνες του CPU σας επιταχύνοντας έτσι την μεταγλώττιση. Μπορείτε να το βρείτε δίνοντας:

lscpu  |grep 'CPU(s)'
CPU(s):              4

Όταν το make τελειώσει χωρίς σφάλματα, κάνουμε εγκατάσταση δίνοντας:

sudo make install

Ο προεπιλεγμένος φάκελος εγκατάστασης είναι /usr/local/nginx/ αλλά αυτό αλλάζει με την παράμετρο --prefix=/path. Εκεί μέσα θα βρείτε και το αρχείο nginx.conf που βρίσκονται οι ρυθμίσεις του nginx. Οι προεπιλεγμένες ρυθμίσεις είναι αρκέτε για να ξεκινήσετε και μπορείτε να βεβαιωθείτε ότι όλα δουλεύουν σωστά με την εντολή:

sudo /usr/local/nginx/sbin/nginx -t

Τέλος, ξεκινάμε τον nginx:

sudo /usr/local/nginx/sbin/nginx

Βήμα 3ο(προαιρετικό): Ενσωμάτωση module κατά την μεταγλώττιση

Σαν παράδειγμα θα προσθέσουμε το brotli(συμπίεση δεδομένων) στον nginx ως στατικό module με την παράμετρο --add-module= . Στο κεντρικό φάκελο κατεβάστε με git το project:

git clone https://github.com/eustas/ngx_brotli.git
cd ngx_brotli && git submodule update --init

Προσθέτουμε την παράμετρο --add-module=../ngx_brotli στο configure του nginx και επαναλαμβάνουμε το 2ο βήμα.

Στο άρχειο ρυθμίσεων του nginx μέσα στο http{..} θα πρέπει να προσθέσουμε τα εξής:

sudo nano /usr/local/nginx/conf/nginx.conf
brotli on;
brotli_comp_level 11;    # τιμές μεταξύ 1 και 11
brotli_types text/plain text/css application/javascript application/json image/svg+xml application/xml+rss;

Εδώ μπορείτε να δείτε ποιοι browsers υποστηρίζουν την συμπίεση brotli: https://caniuse.com/#feat=brotli.

Για να δείτε αν δουλεύει η συμπίεση brotli ανοίξτε την ιστοσελίδα σας και μέσα από τα developers tools ψάξτε για τη λέξη br στα response headers.

Happy compiling!

Θεόδωρος Δεληγιαννίδης thiodor@gmail.com