apt install nginx git python3-pip sqlite3 # root
cat requirements.txt | xargs pip3 install --user # with the right user
L'hub per eduhack e` composto da un applicativo in flask e da uno scraper da schedulare con cron. L'interfaccia di admin si raggiunge tramite /admin, la password e` contenuta in conf.py Tutte le funzioni non commentate sono ovvie. La configurazione e` contenuta in config.py.
L'applicativo di flask usa parti dello scraper come libreria. L'applicativo dipende da sqlalchemy e jinja, un manager per i login e requests per validare le immagini.
Quasi ogni funzione definisce un endpoint:
/index/<int>/, GET: indice paginato dei post. 10 post alla volta.
/blogs/, GET: indice dei blog utenti, filtrati secondo config.py.
/submit, GET and POST: form per postare un link di un blog esterno.
/search, GET and POST: form per cercare posts per tags o categories
/tag/<int>/, GET: ritorna i post con quel tagid.
/categories/<int>/, GET: ritorna i post con quel categoriesid.
/login GET and POST: serve per fare il login ed avere accesso agli endpoint successivi.
/admin GET and POST, login richiesto: lista delle funzioni successive.
/remove_title, GET and POST, login richiesto: form per rimuovere un titolo o una url dal database
/filter, GET and POST, login richiesto: form per inserire o rimuovere dalla blacklist un domain o un URL
Le pagine vengono rese attraverso l'uso di templates. Header, includes e footer sono definite in base.html. E` uno schifo totale copiato da http://eduhack.eu/wall.
Consiglio di girare flask usando gunicorn e nginx come reverse proxy. Mysql (o meglio) per evitare i malditesta per le scritture concorrenti di sqlite.
Lo scraper e` fatto utilizzando requests, beautifulsoup e sqlalchemy per scrivere su db.
La lista dei siti dell'installazione di wordpress multisite non e` accessibile tramite api.
Per questo motivo lo scraper fa login utilizzando le credenziali provviste e con beautifulsoup parsa una lista dei blogs.
Una volta ottenuta la lista dei blog da parsare vengono utilizzate le api di wordpress senza autenticazione.
Gli endpoint utilizzati sono /posts, /users, /categories, /tags.
Tutti le url o i domini (blogurl
) contenute in urls_blacklist
o domains_blacklist
vengono ignorate.
Gunicorn e` molto semplice da avviare:
gunicorn app:app -b localhost:8000 -w N
dove N sono il numero di workers e app si riferisce al modulo app.py della root directory del progetto.
Gunicorn non logga gli errori di app.py ma lascia che l'output venga stampato. Si consiglia di ridirezionare stdout e stderr.
Nginx viene usato come reverse proxy. In caso si preferisca usare apache2 senza fare reverse_proxy bisogna configurare qualche mod per python e poi usare app.py come cgi.
server {
listen 80; # The default is 80 but this here if you want to change it.
server_name hub.eduhack.eu;
location / {
proxy_pass http://localhost:<port>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 150;
proxy_send_timeout 100;
proxy_read_timeout 100;
proxy_buffers 4 32k;
client_max_body_size 500m; # Big number is we can post big commits.
client_body_buffer_size 128k;
}
}
Si possono pensare strategie di caching:
location ~* \.(ico|css|js|jpg|png|)$ {
expires 1d;
access_log off;
add_header Pragma public;
add_header Cache-Control "public, max-age=86400";
add_header X-Asset "yes";
}
Si suppone che al Nexa venga ancora usato systemd.
path: /etc/systemd/system/
systemctl enable hub.service
Si devono sostituire le variabili chiaramente!
[Unit]
Description=EduHack hub
After=network.target
[Service]
Type=simple
User=<user>
WorkingDirectory=/home/<user>/<path to repo>
ExecStart=</PATH_TO_local/bin/>gunicorn --options
Restart=on-failure
# oppure: or always, on-abort, etc
[Install]
WantedBy=multi-user.target
Lo scraper va eseguito secondo un intervallo di tempo (esempio ogni ora).
0 0 * * * * python3 /path/scrape.py > /logpath/scrape.log 2>&1
Per rigenerare il database partendo da zero alle cinque minuti prima della mezzanotte si può usare questo cron job
55 23 * * * cd /path/to/database/dir && sqlite3 edu.db "delete from author; delete from post; delete from category; delete from posthascategory;" > delete_db.log 2>&1
In config.py sono contenuti vari parametri di configurazione.
password = 'prova' # il codice di autenticazione per accedere alle funzioni di amministrazione
secret = '123upudnpomi8r2yn' # un secret code per flask, puo` essere generato casualmente e viene utilizzato per le sessioni
db = 'sqlite:///edu.db' # URI del db per sqlalchemy. Considerare: "mysql+pymysql://user:passwd@localhost/nomedb",
wp_pwd = 'provaprova2' # password per accedere a wordpress
email = '[email protected]' # email da usare come username per wordpress
do_not_list = ['test', 'admin', 'atester'] # blogs that should not displayed in /blogs
Il codice e` il risultato dell'imposizione di deadline stringente. Se si volesse migliorarlo vedi il seguente schema:
- Portare i file di blacklist dentro il db
- muovere i moduli dentro una cartella propria
- usare sqlalchemy per le query invece di query testuali
- creare un endpoint per la lista dei blog in php in modo da poterci accedere tramite le api rest
- impostare l'autenticazione per le api di wp
- il codice e` abbastanza leggibile ma alcune funzioni vanno spostati in moduli (principalmente login e inizializzazione)
- il codice e` abbastanza leggibile ma si puo` fare refactoring di codice duplicato