First commit.

This commit is contained in:
Ed 2023-12-28 14:17:31 +01:00
commit 2e678edfea
32 changed files with 1573 additions and 0 deletions

163
.gitignore vendored Normal file
View File

@ -0,0 +1,163 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Output pelican folder
output/

72
Makefile Normal file
View File

@ -0,0 +1,72 @@
PY?=
PELICAN?=pelican
PELICANOPTS=
BASEDIR=$(CURDIR)
INPUTDIR=$(BASEDIR)/content
OUTPUTDIR=$(BASEDIR)/output
CONFFILE=$(BASEDIR)/pelicanconf.py
PUBLISHCONF=$(BASEDIR)/publishconf.py
DEBUG ?= 0
ifeq ($(DEBUG), 1)
PELICANOPTS += -D
endif
RELATIVE ?= 0
ifeq ($(RELATIVE), 1)
PELICANOPTS += --relative-urls
endif
SERVER ?= "0.0.0.0"
PORT ?= 0
ifneq ($(PORT), 0)
PELICANOPTS += -p $(PORT)
endif
help:
@echo 'Makefile for a pelican Web site '
@echo ' '
@echo 'Usage: '
@echo ' make html (re)generate the web site '
@echo ' make clean remove the generated files '
@echo ' make regenerate regenerate files upon modification '
@echo ' make publish generate using production settings '
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
@echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 '
@echo ' make devserver [PORT=8000] serve and regenerate together '
@echo ' make devserver-global regenerate and serve on 0.0.0.0 '
@echo ' '
@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html '
@echo 'Set the RELATIVE variable to 1 to enable relative urls '
@echo ' '
html:
"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
clean:
[ ! -d "$(OUTPUTDIR)" ] || rm -rf "$(OUTPUTDIR)"
regenerate:
"$(PELICAN)" -r "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
serve:
"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
serve-global:
"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b $(SERVER)
devserver:
"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
devserver-global:
"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b 0.0.0.0
publish:
"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS)
.PHONY: html help clean regenerate serve serve-global devserver devserver-global publish

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

64
content/page/index.html Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<title>Foxolab</title>
<meta name="tags" content="thats, awesome" />
<meta name="authors" content="foxo" />
</head>
<body>
<h1>Homepage</h1>
<h2>Cosa è foxolab?</h2>
<p>È un hackerspace, un luogo dove appassionati di tecnologia possono riunirsi, condividere idee e lavorare su progetti in comune.<br />
Foxolab mette a disposizione spazio e attrezzature. I propri membri mettono a disposizione idee.</p>
<p>Viene definito "hackerspace" in quanto i membri che lo partecipano solitamente si ritrovano nei principi della "hacker ethic", tuttavia qualsiasi persona può partecipare se passa il <em>vibe check</em>.</p>
<h2>Spazio e attrezzature?</h2>
<p>Foxolab è diviso in tre spazi, utilizzabili in tre modi differenti.</p>
<ul class="boxes">
<h3 style="background-image:url(/images/index/lounge.jpg)">Sala</h3>
<h3 style="background-image:url(/images/index/officina.jpg)">Officina</h3>
<h3 style="background-image:url(/images/index/server.jpg)">Server room</h3>
</ul>
<p>Questi spazi e le attrezzature in loro contenute sono a disposizione dei membri. A loro volta possono ospitare "minilab", ovvero piccoli sotto laboratori o progetti tematici che vengono gestiti in modo autonomo da membri diversi.</p>
<h2>Ma quindi voi fate cose illegali?</h2>
<p>No. Sebbene la parola "hacker" sia conosciuta in ambito collettivo come "malintenzionato informatico", il significato della parola è ben diverso.</p>
<p>Un hacker è colui che [cit. <a href="https://it.wikipedia.org/wiki/Hacker">Wikipedia</a>] <cite>utilizza le proprie competenze [...] per esplorare i dettagli dei sistemi [...] e sperimenta come estenderne l'utilizzo.</cite></p>
<h2>Cos'è il <em>vibe check</em>?</h2>
<p>Per fare in modo che il foxolab resti un ambiente sano e gradevole, tutti i membri devono essere positivi, rispettosi e inclusivi degli altri. Qualsiasi siano le capacità tecniche, idee e opinioni di una persona, desideriamo che il benessere di tutti sia messo al primo posto.</p>
<p>Ciò significa in generale seguire la <a href="https://it.wikipedia.org/wiki/Netiquette">Netiquette</a>, le regole dell'<a href="https://it.wikipedia.org/wiki/Etica_hacker#L'ethical_hacker">Hacker Etico</a> e la <dfn title="Don't be a dick">Legge di Wheaton</dfn>.</p>
<p>I membri del Foxolab hanno solitamente a cuore cinque concetti fondamentali:</p>
<ul>
<li>Condivisione</li>
<li>Apertura</li>
<li>Decentralizzazione</li>
<li>Libero accesso alle tecnologie informatiche</li>
<li>Miglioramento del mondo</li>
</ul>
<h2>Cosa NON è foxolab?</h2>
<p>Il foxolab non è un pub, un bar, un ostello o una sala giochi. Per quanto lo svago possa servire a ristorare l'animo di un hacker affaticato, ricordiamo che l'obbiettivo resta sempre produrre e condividere idee. È possibile certamente divertirsi al suo interno, ma sempre in modo costruttivo e senza recare danni o disturbo agli altri ospiti.</p>
<h2>Posso partecipare?</h2>
<p>Correntemente foxolab è in costruzione, pertanto è necessario ancora un po' di tempo prima che aprano le adesioni ufficiali. Tuttavia, se desideri passare e curiosare dentro sei il benvenuto. Scrivi un messaggio :)</p>
<p><strong>A costruzione ultimata, l'ingresso sarà limitato ai soli membri.</strong></p>
<h2>Membri?</h2>
<p>Lo stabile in cui sorge Foxolab presenta limiti dimensionali e di accesso che non ne consentono una completa apertura al pubblico. Ciò che significa che, legalmente, solo le persone facenti parte dell'associazione e che avranno versato la quota associativa potranno accedere ai servizi offerti dall'hackerspace.</p>
<h2>Quanto costa?</h2>
<p>La quota associativa è ancora in fase di progettazione ma si spera di poter offrire un accesso sotto la soglia dei 100€ annuali, con sconti per studenti e membri che contribuiscono in modo sostanziale alla vita dell'hackerspace.</p>
<h2>Contatti?</h2>
<script>
function genMail(element) {
const email = `hackerspace-ext${Math.floor(Math.random() * 100000000).toString().padStart(8, '0')}@foxo.me`;
element.innerHTML = email;
element.href = 'mailto:'+email;
}
</script>
<a href="#" onclick="genMail(this)">Premi qui per ricevere un indirizzo mail univoco a cui poter scrivere una mail.</a>
</body>
</html>

49
content/page/net.html Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<title>FOXO-NET</title>
<meta name="summary" content="FOXO-NET è una rete sperimentale che permette di studiare e esplorare i meccanismi di internet senza creare danni nel mondo reale." />
</head>
<body>
<h1>FOXO-NET</h1>
<blockquote>"Cosa sarebbe un hackerspace senza server?"</blockquote>
<p>Foxo-net è una rete sperimentale creata per poter studiare e esplorare i meccanismi di internet e delle reti senza creare danni nel mondo reale, e per promuovere la decentralizzazione e il <em>self-hosting</em>.</a>
<h2>L'upstream del foxolab</h2>
<p>Fortunatamente il palazzo dove sorge foxolab è dotato di FTTH in grado di raggiungere fino a 2.5/1.0gbit. La vicinanza a Milano e la diretta connessione con i datacenter più importanti della zona la rende molto appetibile per colocation e altri servizi.</p>
<p>Oltre alla fibra è presente anche una rete secondaria basata su 4G in grado di sopperire ad eventuali guasti della linea principale.</p>
<h2>Il ferro</h2>
<p>Una piccola stanza è stata addobbata a "server-room" e agisce da "base" della FOXO-NET.</p>
<h3>Colocation</h3>
<p>Ad ogni membro è permesso portare e colocare i propri dispositivi a patto che siano di piccole dimensioni e con consumi contenuti (SBC, router, embedded...). Eventuali consumi e dimensioni superiori saranno considerati caso per caso.</p>
<ul>
<li><strong>Alimentazione</strong>: AC220 o DC 12v (barrel) o 5v (USB) - max 10w - switch on/off remoto e conteggio kWh</li>
<li><strong>Rete</strong>: IPv4 /32 (DN42) + IPv4 /32 (NAT) + IPv6 /64 - fair use</li>
<li><strong>SLA</strong>: 0% (best effort)</li>
<li><strong>Formato fisico</strong>: standard rack 1-4U o Eurorack</li>
</ul>
<p><strong>Lo scopo di questo servizio è quello di poter studiare il comportamento di una rete composta da dispositivi eterogenei, con i connessi rischi.</strong></p>
<h3>Backup su nastro</h3>
<p>Un server Proxmox Backup Server collegato a una tape library con 8 nastri LTO6. È possibile richiedere dati di accesso per effettuare backup.</p>
<p>Grazie alla caratteristica "disconnessione" fisica tra il robot che carica e scarica i nastri ed il lettore collegato al server, è possibile fare in modo che i backup più recenti siano "irraggiungibili" da eventuali ransomware o malintenzionati che possano volerne prendere possesso.</p>
<p><strong>Lo scopo di questo servizio è lo studio di un sistema di backup zero-trust (criptato su client) parzialmente disconnesso (air gap)</strong></p>
<h3>DN42 IXP</h3>
<p>Foxo-NET è interconnesso con dn42 e permette ai propri membri di annunciare le proprie sottoreti e fare "peering" localmente o con altri membri di dn42. Attraverso dn42 è possibile raggiungere altri hackerspace e sperimentare tecnologie comunemente usate sul web come BGP e DNS, ma senza i costi e i rischi di lavorare "in produzione" su internet.</p>
<p>Ogni membro può decidere, a tal proposito, di annunciare il proprio prefisso sul router locale (bring your own IP) o utilizzare IP dalla sottorete annunciata direttamente da foxolab.</p>
<p><strong>Lo scopo di questo servizio è quello di sperimentare meccanismi di peering e tunneling per creare collegamenti fra sistemi autonomi.</strong></p>
<h2>I servizi</h2>
<h3>Postgresql</h3>
<p></p>
<p><strong>Postgresql può essere utilizzato per sperimentare su database SQL avanzato moderno.</strong></p>
</body>
</html>

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<title>Regolamento interno</title>
<meta name="summary" content="Il non - regolamento dell'associazione foxolab e dell'hackerspace" />
</head>
<body>
<h1>Regolamento</h1>
<h2>Preambolo: Hacker Ethic</h2>
<blockquote>
<ul>
<li>L'accesso ai computer e tutto ciò che può mostrarti come funziona questo mondo dovrebbe essere illimitato e completo.</li>
<li>Tutte le informazioni devono essere libere.</li>
<li>Dubitare dell'autorità. Promuovere il decentramento.</li>
<li>Valutare un hacker da ciò che fa e non in base a falsi criteri come l'aspetto, l'età, l'origine, la specie, il genere o la posizione sociale.</li>
<li>È possibile creare arte e bellezza con un computer.</li>
<li>I computer possono cambiare la tua vita in meglio.</li>
<li>Utilizza i dati pubblici, proteggi i dati privati.</li>
</ul>
</blockquote>
<h2><em>Regole</em></h2>
<h3>0: Don't be a dick // Don't ruin the vibe</h3>
<p>Un mondo ideale non ha bisogno di regole e pace e armonia ne farebbero da padrone. Purtroppo, però, non viviamo in quel tipo di mondo.</p>
<p>Ciò non significa non poterci provare.</p>
<p>Le regole qui sotto più che essere ferree imposizioni vogliono essere consigli su come vivere al meglio la propria presenza nell'associazione e - di conseguenza - nell'hackerspace.</p>
<h3>1: Entra</h3>
<p>L'accesso all'hackerspace è consentito a tutti i membri, ogni qualvolta l'hackerspace sia ufficialmente aperto (open day o eventi, come comunicato sul sito). Per evitare problemi logistici (la metratura è limitata!) è gradito prenotarsi prima dell'arrivo, anche se imminente.</p>
<p>Potrebbero avvenire aperture sporadiche da parte di membri con accesso 24/7. In tal caso, è bene chiedere se è possibile entrare a chi è presente per evitare spiacevoli inconvenienti (ad esempio, trovare l'hackerspace chiuso all'arrivo!).</p>
<h3>2: Usa</h3>
<p>L'hackerspace è pieno di attrezzature e spazi di libero utilizzo da parte degli associati. Nessuno può impedire a nessun'altro di goderne, a meno che ciò non vada a ledere a sua volta la possibilità di farlo.</p>
<h3>3: Contribuisci</h3>
<p>L'hackerspace richiede costi operativi mensili che possono essere più o meno ampi in base alle persone che lo abitano. Spese condominiali, bollette, assicurazione, consumo delle attrezzature...</p>
<p>Un hackerspace viene mantenuto dalla sua comunità, ed buona cosa che ogni membro abbia il buonsenso di contribuire almeno quanto pensi di aver giovato dall'hackerspace.</p>
<p>Il modo migliore per farlo è economicamente, ma può essere persino più efficace farlo aiutando con idee e azioni fisiche. Nel dubbio, chiedi in giro se qualcuno ha bisogno di qualcosa!</p>
<strong>Attenzione: alcuni macchinari hanno costi operativi orari prefissati da cui non è possibile scappare!</strong>
<h3>4: Stai al sicuro</h3>
<p>La sicurezza di tutti è in prima linea, e al Foxolab sono presenti attrezzature che possono mordere e fare male. Utilizzare qualcosa in modo pericoloso significa prendersene piena responsabilità, ma non è meno responsabilità vedere qualcuno farlo e non impedire che accada.</p>
<p>Nel caso tu abbia bisogno di aiuto o di formazione, chiedi a qualcuno che ha già usato quelle macchine.</p>
<p><strong>Per la sicurezza di tutti, alcune macchine possono essere usate solo in presenza dei loro responsabili!</strong></p>
</body>
</html>

114
content/page/statuto.html Normal file
View File

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<title>Statuto</title>
<meta name="summary" content="Lo statuto del foxolab" />
</head>
<body>
<h1>Statuto</h1>
<pre>
1. È costituita l'Associazione "Foxolab APS" (di seguito, Associazione), una libera Associazione di fatto, apartitica e apolitica, con durata illimitata nel tempo e senza scopo di lucro nè diretto nè indiretto, regolata a norma del Titolo I Cap. III, art. 36 e segg. del codice civile, nonché del presente Statuto.
2. L'Associazione persegue i seguenti scopi:
- ampliare la conoscenza della cultura informatica e tecnologica, più precisamente ricalcando i principi della "hacker ethic"
- promuovere il libero uso delle risorse informatiche, e la sperimentazione per scopi didattici, ricreativi e artistici - con l'obbiettivo di abbattere barriere economiche e sociali che possano impedirlo
- gestire un "Hackerspace" con impronta nord-europea, un ambiente collaborativo e aperto concepito per favorire la condivisione di conoscenze, la realizzazione di progetti tecnologici, e la creazione di una rete di professionisti e appassionati del settore
3. L'Associazione per il raggiungimento dei suoi fini, intende promuovere varie attività, in particolare:
- attività culturali: convegni, conferenze, dibattiti, seminari, proiezioni di films e documenti, concerti, lezioni e altro, in presenza o avvalendosi di mezzi informatici, anche in modo ibrido
- attività di formazione: corsi e laboratori teorico/pratici per i propri membri, istituzione di gruppi di studio e di ricerca;
- attività editoriale: pubblicazione di contenuti testuali e audiovisivi, artistici, informatici e di ricerca, che perseguiscano i propri scopi
4. L'associazione è offerta a tutti coloro che, interessati alla realizzazione delle finalità istituzionali, ne condividono lo spirito e gli ideali.
- soci fondatori: le persone che hanno sottoscritto l'atto costitutivo
- soci ordinari: persone o enti che si impegnano a pagare, per tutta la permanenza del vincolo associativo, la quota annuale stabilita dal Consiglio direttivo
- soci ordinari: persone, enti o istituzioni che abbiano contribuito in maniera determinante, con la loro opera od il loro sostegno ideale ovvero economico alla costituzione dell'associazione. Hanno carattere e sono esonerati dal versamento di quote annuali
- soci locali: persone fisiche aventi residenza o domicilio presso il medesimo stabile della sede dell'Associazione. Sono esonerati dal versamento della quota annuale.
- soci speciali: persone fisiche minorenni, studenti o disoccupati. Sono esonerati dal versamento della quota annuale.
Le quote o il contributo associativo non è trasmissibile ad eccezione dei trasferimenti a causa di morte e non è soggetta a rivalutazione.
5. L'ammissione dei soci ordinari è deliberata, su domanda scritta del richiedente controfirmata da almeno tre soci, dal Consiglio direttivo.
Contro il rifiuto di ammissione è ammesso appello, entro 30 giorni, al collegio dei probiviri.
6. Tutti i soci sono tenuti a rispettare le norme del presente statuto e il regolamento interno, secondo le deliberazioni assunte dagli organi preposti. In caso di comportamento difforme, che rechi pregiudizio agli scopi o al patrimonio dell'associazione il Consiglio direttivo dovrà intervenire ed potrà applicare le seguenti sanzioni: richiamo, diffida, espulsione della Associazione.
I soci espulsi possono ricorrere per iscritto contro il provvedimento entro trenta giorni al Collegio dei probiviri.
7. Tutti i soci maggiorenni hanno diritto di voto per l'approvazione e le modificazioni dello statuto e dei regolamenti e per la nomina degli organi direttivi dell'associazione. Il diritto di voto non può essere escluso neppure in caso di partecipazione temporanea alla vita associativa.
8. Le risorse economiche dell'associazione sono costituite da:
- beni, immobili e mobili;
- contributi;
- donazioni e lasciti;
- rimborsi;
- attività marginali di carattere commerciale e produttivo;
- ogni altro tipo di entrate.
I contributi degli aderenti sono costituiti dalle quote di associazione annuale, stabilite dal Consiglio direttivo e da eventuali contributi straordinari stabiliti dall'assemblea, che ne determina l'ammontare.
Le elargizioni in danaro, le donazioni e i lasciti, sono accettate dall'assemblea, che delibera sulla utilizzazione di esse, in armonia con finalità statuarie dell'organizzazione.
È vietato distribuire, anche in modo indiretto, utili o avanzi di gestione nonché fondi, riserve o capitale durante la vita dell'Associazione, salvo che la destinazione o la distribuzione non siano imposte dalla legge.
9. L'anno finanziario inizia il 1° Gennaio e termina il 31 Dicembre di ogni anno.
Il Consiglio direttivo deve redigere il bilancio preventivo e quello consuntivo.
Il bilancio preventivo e consuntivo devono essere approvati dall'Assemblea ordinaria ogni anno entro il mese di Aprile.
Esso deve essere depositato presso la sede dell'Associazione entro i 15 giorni precedenti la seduta per poter essere consultato da ogni associato.
10. Gli organi dell'Associazione sono:
- l'assemblea dei soci;
- il Consiglio direttivo;
- il Presidente;
- il Collegio dei revisori;
- il Collegio dei probiviri;
11. L'assemblea dei soci è il momento fondamentale di confronto, atto ad assicurare una corretta gestione dell'Associazione ed è composta da tutti i soci, ognuno dei quali ha diritto ad un voto, qualunque sia il valore della quota. Essa è convocata almeno una volta all'anno in via ordinaria, ed in via straordinaria quando sia necessaria o sia necessaria o sia richiesta dal Consiglio direttivo o da almeno un decimo degli associati.
In prima convocazione l'assemblea ordinaria è valida se è presente la maggioranza dei soci, e delibera validamente con la maggioranza dei presenti; in seconda convocazione la validità prescinde dal numero dei presenti.
L'assemblea straordinaria delibera in prima convocazione con la presenza e col voto favorevole della maggioranza dei soci e in seconda convocazione la validità prescinde dal numero dei presenti.
La convocazione va fatta con avviso pubblico affisso all'albo della sede almeno 15 giorni prima della data dell'assemblea.
Delle delibere assembleari deve essere data pubblicità mediante affissione all'albo della sede del relativo verbale.
Gli avvisi pubblici e i verbali verranno altresi pubblicati digitalmente sul sito istituzionale a visione dei membri dell'associazione.
12. L'assemblea ordinaria ha i seguenti compiti:
- elegge il Consiglio direttivo, il Collegio dei revisori e il Collegio dei probiviri;
- approva il bilancio preventivi e consuntivo;
- approva il regolamento interno.
L'assemblea straordinaria delibera sulle modifiche dello Statuto e l'eventuale scioglimento dell'Associazione.
All'apertura di ogni seduta l'assemblea elegge un presidente ed un segretario che dovranno sottoscrivere il verbale finale.
13. Il consiglio direttivo è composto da 3 membri, eletti dall'Assemblea fra i propri componenti.
Il Consiglio direttivo è validamente costituito quando sono presenti 2 membri. I membri del Consiglio direttivo svolgono la loro attività gratuitamente e durano in carica 3 anni. Il consiglio direttivo può essere revocato dall'assemblea con la maggioranza di 2/3 dei soci.ù
14. Il Consiglio direttivo è l'organo esecutivo dell'Associazione. Si riunisce in media 2 volte all'anno ed è convocato da:
- il presidente;
- da almeno 2 dei componenti, su richiesta motivata;
- richiesta motivata e scritta di almeno il 30% dei soci.
Il consiglio direttivo ha tutti i poteri di ordinaria e straordinaria amministrazione.
Nella gestione ordinaria i suoi compiti sono:
- predisporre gli atti da sottoporre all'assemblea;
- formalizzare le proposte per la gestione dell'Associazione;
- elaborare il bilancio consuntivo che deve contenere le singole voci di spesa e di entrata relative al periodo di un anno;
- elaborare il bilancio preventivo che deve contenere, suddivise in singole voci, le previsioni delle spese e delle entrate relative all'esercizio annuale successivo;
- stabilire gli importi delle quote annuali delle varie categorie di soci;
Di ogni riunione deve essere redatto verbale da affliggere all'albo dell'Associazione.
15. Il presidente dura in carica tre anni ed è legale rappresentante dell'Associazione a tutti gli effetti.
Egli convoca e presiede il Consiglio direttivo, sottoscrive tutti gli atti amministrativi compiuti dall'Associazione; può aprire e chiudere conti correnti bancari e postali e procedure agli incassi.
Conferisce ai soci procura speciale per la gestione di attività varie, previa approvazione del Consiglio direttivo.
16. Il Collegio dei revisori è composto da tre soci eletti dall'Assemblea al di fuori dei componenti del Consiglio direttivo. Verifica periodicamente la regolarità formale e sostanziale della contabilità, redige apposita relazione da allegare al bilancio preventivo e consuntivo.
17. Il Collegio dei probiviri è composto da tre soci eletti in assemblea. Dura in carica tre anni.
Decide insindacabilmente, entro trenta giorni dalla presentazione del ricorso, sulle decisioni di espulsione e sui dinieghi di ammissione.
18. Lo scioglimento dell'Associazione è deliberato da assemblea straordinaria. Il patrimonio residuo dell'ente deve essere devoluto ad associazione con finalità analoghe o per fini di pubblica utilità, sentito l'organismo di controllo di cui all'art. 3, comma 190 della legge 23.12.96, n. 662.
19. Tutte le cariche elettive sono gratuite.
Ai soci compete solo il rimborso delle spese varie regolarmente documentate.
20. Per quanto non previsto dal presente statuto valgono le norme di legge vigente in maniera.
</pre>
</body>
</html>

14
content/post/test.html Normal file
View File

@ -0,0 +1,14 @@
<html>
<head>
<title>My super title</title>
<meta name="tags" content="thats, awesome" />
<meta name="date" content="2012-07-09 22:28" />
<meta name="modified" content="2012-07-10 20:14" />
<meta name="category" content="yeah" />
<meta name="authors" content="Alexis Métaireau, Conan Doyle" />
<meta name="summary" content="Short version for index and feeds" />
</head>
<body>
This is the content of my super blog post.
</body>
</html>

12
content/test.html Normal file
View File

@ -0,0 +1,12 @@
<html>
<head>
<title>My super title</title>
<meta name="tags" content="thats, awesome" />
<meta name="category" content="yeah" />
<meta name="authors" content="foxo" />
<meta name="summary" content="Short version for index and feeds" />
</head>
<body>
This is the content of my super blog post. sex
</body>
</html>

View File

@ -0,0 +1,58 @@
:root {
--accent: #1e90ff;
--main: #111;
--bg: #eee;
--bg2: #e3e3e3;
--bg3: #fff;
}
/* Dark mode alt colors */
@media (prefers-color-scheme: dark) {
:root {
--main: #ddd;
--bg: #111;
--bg2: #131313;
--bg3: #000;
}
.dark-invert {filter: invert(1);}
}
/* Layout */
body {color:var(--main);background-color: var(--bg);text-align:justify;font-family:'IBM Plex Serif', serif;font-size: 1.1em;}
main {display:block;max-width:55em;margin:1em auto;background:var(--bg);padding:2em;}
header {background: #000;line-height:2.8rem;padding:0.5em 2em;display:flex;align-items: center;justify-content:space-between;}
footer {margin: 1em;text-align: center;}
nav {display: grid;grid-auto-flow: column;grid-column-gap: 2em}
header a {text-decoration:none;font-family: 'Chakra Petch', sans-serif;}
/* Text elements */
p {line-height: 1.5em;font-weight:300;}
dfn {color:var(--accent);text-decoration: underline dashed var(--main) 1px;}
a {color: var(--accent);}
strong {font-weight: 700}
ul {list-style: none;list-style-position:inside;padding-left:1em}
ul li::before {content: "> ";color:var(--accent);margin:0;}
/* Headers */
h1,h2,h3,h4,h5,h6 {font-family: 'Chakra Petch', sans-serif;}
h1 img {height:1.8em;padding:.3em .3em .3em 0;vertical-align:middle;}
h2::before {content: "> ";color:var(--accent);}
h3::before {content: "# ";color:var(--accent);}
/* Quotes */
blockquote {display: block;font-style:italic;overflow-x: scroll;padding:1em;margin:1em;border-left: 3px solid var(--accent);}
/* Code blocks */
code {font-family: monospace; background: var(--bg3);padding:0.5em 0.5em;}
pre code {display: block;white-space: pre;overflow-x: scroll;padding:1em;margin:1em;border-left: 3px solid var(--accent);}
.boxes {margin:0;padding:0;display:flex;justify-content:space-around;gap:0.5em;}
.boxes > * {padding: 3em 1em;background:var(--accent);flex: 1 1 0;text-align:center;border-radius:0.4em;background-size:cover;text-shadow: 0 0 5px #000000;}
.status-closed {color: #FF441F;}
.status-open {color: #1FFF1F;}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME|striptags }} - Archives{% endblock %}
{% block content %}
<h2>Archives for {{ SITENAME }}</h2>
<dl>
{% for article in dates %}
<dt>{{ article.locale_date }}</dt>
<dd><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></dd>
{% endfor %}
</dl>
{% endblock %}

View File

@ -0,0 +1,65 @@
{% extends "base.html" %}
{% block html_lang %}{{ article.lang }}{% endblock %}
{% block title %}{{ SITENAME|striptags }} - {{ article.title|striptags }}{% endblock %}
{% block head %}
{{ super() }}
{% import 'translations.html' as translations with context %}
{% if translations.entry_hreflang(article) %}
{{ translations.entry_hreflang(article) }}
{% endif %}
{% if article.description %}
<meta name="description" content="{{article.description}}" />
{% endif %}
{% for tag in article.tags %}
<meta name="tags" content="{{tag}}" />
{% endfor %}
{% endblock %}
{% block content %}
<article>
<header>
<h2>
<a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark"
title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h2>
{% import 'translations.html' as translations with context %}
{{ translations.translations_for(article) }}
</header>
{{ article.content }}
<footer>
<p>Published: <time datetime="{{ article.date.isoformat() }}">
{{ article.locale_date }}
</time></p>
{% if article.modified %}
<p>Last updated: <time datetime="{{ article.modified.isoformat() }}">
{{ article.locale_modified }}
</time></p>
{% endif %}
{% if article.authors %}
<address>
By {% for author in article.authors %}
<a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
{% endfor %}
</address>
{% endif %}
{% if article.category %}
<p>
Category: <a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a>
</p>
{% endif %}
{% if article.tags %}
<p>
Tags:
{% for tag in article.tags %}
<a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a>
{% endfor %}
</p>
{% endif %}
</footer>
</article>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME|striptags }} - Articles by {{ author }}{% endblock %}
{% block content_title %}
<h2>Articles by {{ author }}</h2>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME|striptags }} - Authors{% endblock %}
{% block content %}
<h2>Authors on {{ SITENAME }}</h2>
<ul>
{% for author, articles in authors|sort %}
<li><a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a> ({{ articles|count }})</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="{% block html_lang %}{{ DEFAULT_LANG }}{% endblock html_lang %}">
<head>
{% block head %}
<title>{% block title %}{{ SITENAME|striptags }}{% endblock title %}</title>
<meta charset="utf-8" />
<link rel="icon" href="{{ SITEURL }}/img/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/modern-normalize/modern-normalize.min.css" media="screen" />
<link rel="stylesheet" href="https://fonts.bunny.net/css?family=ibm-plex-serif|chakra-petch" />
<link rel="stylesheet" type="text/css" href="{{ SITEURL }}/css/{{CSS_FILE}}" />
<meta name="viewport" content="width=device-width, initial-scale=0.87">
{% if SITESUBTITLE %}
<meta name="description" content="{{ SITESUBTITLE }}" />
{% endif %}
{% if STYLESHEET_URL %}
<link rel="stylesheet" type="text/css" href="{{ STYLESHEET_URL }}" />
{% endif %}
{% if FEED_ALL_ATOM %}
<link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_ATOM_URL %}{{ FEED_ALL_ATOM_URL }}{% else %}{{ FEED_ALL_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME|striptags }} Full Atom Feed" />
{% endif %}
{% if FEED_ALL_RSS %}
<link href="{{ FEED_DOMAIN }}/{% if FEED_ALL_RSS_URL %}{{ FEED_ALL_RSS_URL }}{% else %}{{ FEED_ALL_RSS }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME|striptags }} Full RSS Feed" />
{% endif %}
{% if FEED_ATOM %}
<link href="{{ FEED_DOMAIN }}/{%if FEED_ATOM_URL %}{{ FEED_ATOM_URL }}{% else %}{{ FEED_ATOM }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME|striptags }} Atom Feed" />
{% endif %}
{% if FEED_RSS %}
<link href="{{ FEED_DOMAIN }}/{% if FEED_RSS_URL %}{{ FEED_RSS_URL }}{% else %}{{ FEED_RSS }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME|striptags }} RSS Feed" />
{% endif %}
{% if CATEGORY_FEED_ATOM and category %}
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_ATOM_URL %}{{ CATEGORY_FEED_ATOM_URL.format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_ATOM.format(slug=category.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME|striptags }} Categories Atom Feed" />
{% endif %}
{% if CATEGORY_FEED_RSS and category %}
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_RSS_URL %}{{ CATEGORY_FEED_RSS_URL.format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_RSS.format(slug=category.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME|striptags }} Categories RSS Feed" />
{% endif %}
{% if TAG_FEED_ATOM and tag %}
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_ATOM_URL %}{{ TAG_FEED_ATOM_URL.format(slug=tag.slug) }}{% else %}{{ TAG_FEED_ATOM.format(slug=tag.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME|striptags }} Tags Atom Feed" />
{% endif %}
{% if TAG_FEED_RSS and tag %}
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_RSS_URL %}{{ TAG_FEED_RSS_URL.format(slug=tag.slug) }}{% else %}{{ TAG_FEED_RSS.format(slug=tag.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME|striptags }} Tags RSS Feed" />
{% endif %}
{% endblock head %}
</head>
<body>
<header>
<h1><a href="{{SITEURL}}"><img class="dark-invert" src="{{ SITEURL }}/img/silhouette.svg" alt="" />{{ SITENAME }}</a><sup>WIP</sup></h1>
{% if SITESUBTITLE %}<p>{{ SITESUBTITLE }}</p>{% endif %}
<nav>
{% for title, link in MENUITEMS %}
<a href="{{link}}">{{title}}</a>
{% endfor %}
<a href="#" class="status-closed">⬤ Chiuso ora</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}{% endblock %}
<p>Foxolab è ancora in costruzione!</p>
</footer>
</body>
</html>

View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME|striptags }} - Categories{% endblock %}
{% block content %}
<h2>Categories on {{ SITENAME }}</h2>
<ul>
{% for category, articles in categories|sort %}
<li><a href="{{ SITEURL }}/{{ category.url }}">{{ category }}</a> ({{ articles|count }})</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME|striptags }} - {{ category }} category{% endblock %}
{% block content_title %}
<h2>Articles in the {{ category }} category</h2>
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block content %}
{% block content_title %}
<h2>All articles</h2>
{% endblock %}
{% for article in articles_page.object_list %}
<article>
<header> <h2><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h2> </header>
<section>{{ article.summary }}</section>
<footer>
<p>Published: <time datetime="{{ article.date.isoformat() }}"> {{ article.locale_date }} </time></p>
<address>By
{% for author in article.authors %}
<a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
{% endfor %}
</address>
</footer>
</article>
{% endfor %}
{% if articles_page.has_other_pages() %}
{% include 'pagination.html' %}
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block html_lang %}{{ page.lang }}{% endblock %}
{% block title %}{{ SITENAME|striptags }} - {{ page.title|striptags }}{%endblock%}
{% block head %}
{{ super() }}
{% import 'translations.html' as translations with context %}
{% if translations.entry_hreflang(page) %}
{{ translations.entry_hreflang(page) }}
{% endif %}
{% endblock %}
{% block content %}
{% import 'translations.html' as translations with context %}
{{ translations.translations_for(page) }}
{{ page.content }}
{% endblock %}
{% block footer %}
{% if page.modified %}
<p>Last updated: {{page.locale_modified}}</p>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,17 @@
{% if DEFAULT_PAGINATION %}
{% set first_page = articles_paginator.page(1) %}
{% set last_page = articles_paginator.page(articles_paginator.num_pages) %}
<nav>
<ul>
{% if articles_page.has_previous() %}
<li><a href="{{ SITEURL }}/{{ first_page.url }}">&Lang;</a></li>
<li><a href="{{ SITEURL }}/{{ articles_previous_page.url }}">&lang;</a></li>
{% endif %}
<li>Page {{ articles_page.number }} / {{ articles_paginator.num_pages }}</li>
{% if articles_page.has_next() %}
<li><a href="{{ SITEURL }}/{{ articles_next_page.url }}">&rang;</a></li>
<li><a href="{{ SITEURL }}/{{ last_page.url }}">&Rang;</a></li>
{% endif %}
</ul>
</nav>
{% endif %}

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME|striptags }} - {{ period | reverse | join(' ') }} archives{% endblock %}
{% block content %}
<h2>Archives for {{ period | reverse | join(' ') }}</h2>
<dl>
{% for article in dates %}
<dt>{{ article.locale_date }}</dt>
<dd><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></dd>
{% endfor %}
</dl>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "index.html" %}
{% block title %}{{ SITENAME|striptags }} - {{ tag }} tag{% endblock %}
{% block content_title %}
<h2>Articles tagged with {{ tag }}</h2>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}{{ SITENAME|striptags }} - Tags{% endblock %}
{% block content %}
<h2>Tags for {{ SITENAME }}</h2>
<ul>
{% for tag, articles in tags|sort %}
<li><a href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a> ({{ articles|count }})</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% macro translations_for(article) %}
{% if article.translations %}
Translations:
{% for translation in article.translations %}
<a href="{{ SITEURL }}/{{ translation.url }}" hreflang="{{ translation.lang }}">{{ translation.lang }}</a>
{% endfor %}
{% endif %}
{% endmacro %}
{% macro entry_hreflang(entry) %}
{% if entry.translations %}
{% for translation in entry.translations %}
<link rel="alternate" hreflang="{{ translation.lang }}" href="{{ SITEURL }}/{{ translation.url }}">
{% endfor %}
{% endif %}
{% endmacro %}

70
pelicanconf.py Normal file
View File

@ -0,0 +1,70 @@
# Site info
AUTHOR = 'Foxolab APS'
SITENAME = 'Foxolab'
SITEURL = "https://lab.foxo.me"
# Paths
PATH = "content"
PAGE_PATHS = ['page']
ARTICLE_PATHS = ['post']
# Assume every article/page has no category and is in italian
DEFAULT_METADATA = {
'lang': 'it'
}
# Time and Language
TIMEZONE = 'Europe/Rome'
DEFAULT_LANG = 'it'
DEFAULT_DATE = 'fs'
# Feed generation is usually not desired when developing
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None
TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
# Theme
THEME = 'foxotheme'
THEME_STATIC_DIR = ''
CSS_FILE = 'style.css'
# Menu items
MENUITEMS = (
("Homepage", "/"),
# ("Net", "/net.html"),
# ("Labs", "/labs/"),
# ("Where", "/where.html"),
)
# Blogroll
LINKS = (
("Pelicaan", "https://getpelican.com/"),
("Python.org", "https://www.python.org/"),
("Jinja2", "https://palletsprojects.com/p/jinja/"),
("You can modify those links in your config file", "#"),
)
# Social widget
SOCIAL = (
("You can add links in your config file", "#"),
("Another social link", "#"),
)
SLUGIFY_SOURCE = 'basename'
DEFAULT_PAGINATION = False
# Uncomment following line if you want document-relative URLs when developing
RELATIVE_URLS = True
ARTICLE_SAVE_AS = 'post/{slug}.html'
ARTICLE_URL = 'post/{slug}.html'
PAGE_SAVE_AS = '{slug}.html'
PAGE_URL = '{slug}.html'
INDEX_SAVE_AS = ''

22
publishconf.py Normal file
View File

@ -0,0 +1,22 @@
# This file is only used if you use `make publish` or
# explicitly specify it as your config file.
import os
import sys
sys.path.append(os.curdir)
from pelicanconf import *
# If your site is available via HTTPS, make sure SITEURL begins with https://
SITEURL = "https://lab.foxo.me"
RELATIVE_URLS = False
FEED_ALL_ATOM = "feeds/all.atom.xml"
CATEGORY_FEED_ATOM = "feeds/{slug}.atom.xml"
DELETE_OUTPUT_DIRECTORY = True
# Following items are often useful when publishing
# DISQUS_SITENAME = ""
# GOOGLE_ANALYTICS = ""

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
pelican

147
tasks.py Normal file
View File

@ -0,0 +1,147 @@
import os
import shlex
import shutil
import sys
import datetime
from invoke import task
from invoke.main import program
from invoke.util import cd
from pelican import main as pelican_main
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
OPEN_BROWSER_ON_SERVE = True
SETTINGS_FILE_BASE = "pelicanconf.py"
SETTINGS = {}
SETTINGS.update(DEFAULT_CONFIG)
LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
SETTINGS.update(LOCAL_SETTINGS)
CONFIG = {
"settings_base": SETTINGS_FILE_BASE,
"settings_publish": "publishconf.py",
# Output path. Can be absolute or relative to tasks.py. Default: 'output'
"deploy_path": SETTINGS["OUTPUT_PATH"],
# Host and port for `serve`
"host": "localhost",
"port": 8000,
}
@task
def clean(c):
"""Remove generated files"""
if os.path.isdir(CONFIG["deploy_path"]):
shutil.rmtree(CONFIG["deploy_path"])
os.makedirs(CONFIG["deploy_path"])
@task
def build(c):
"""Build local version of site"""
pelican_run("-s {settings_base}".format(**CONFIG))
@task
def rebuild(c):
"""`build` with the delete switch"""
pelican_run("-d -s {settings_base}".format(**CONFIG))
@task
def regenerate(c):
"""Automatically regenerate site upon file modification"""
pelican_run("-r -s {settings_base}".format(**CONFIG))
@task
def serve(c):
"""Serve site at http://$HOST:$PORT/ (default is localhost:8000)"""
class AddressReuseTCPServer(RootedHTTPServer):
allow_reuse_address = True
server = AddressReuseTCPServer(
CONFIG["deploy_path"],
(CONFIG["host"], CONFIG["port"]),
ComplexHTTPRequestHandler,
)
if OPEN_BROWSER_ON_SERVE:
# Open site in default browser
import webbrowser
webbrowser.open("http://{host}:{port}".format(**CONFIG))
sys.stderr.write("Serving at {host}:{port} ...\n".format(**CONFIG))
server.serve_forever()
@task
def reserve(c):
"""`build`, then `serve`"""
build(c)
serve(c)
@task
def preview(c):
"""Build production version of site"""
pelican_run("-s {settings_publish}".format(**CONFIG))
@task
def livereload(c):
"""Automatically reload browser tab upon file modification."""
from livereload import Server
def cached_build():
cmd = "-s {settings_base} -e CACHE_CONTENT=true LOAD_CONTENT_CACHE=true"
pelican_run(cmd.format(**CONFIG))
cached_build()
server = Server()
theme_path = SETTINGS["THEME"]
watched_globs = [
CONFIG["settings_base"],
f"{theme_path}/templates/**/*.html",
]
content_file_extensions = [".md", ".rst"]
for extension in content_file_extensions:
content_glob = "{}/**/*{}".format(SETTINGS["PATH"], extension)
watched_globs.append(content_glob)
static_file_extensions = [".css", ".js"]
for extension in static_file_extensions:
static_file_glob = f"{theme_path}/static/**/*{extension}"
watched_globs.append(static_file_glob)
for glob in watched_globs:
server.watch(glob, cached_build)
if OPEN_BROWSER_ON_SERVE:
# Open site in default browser
import webbrowser
webbrowser.open("http://{host}:{port}".format(**CONFIG))
server.serve(host=CONFIG["host"], port=CONFIG["port"], root=CONFIG["deploy_path"])
@task
def publish(c):
"""Publish to production via rsync"""
pelican_run("-s {settings_publish}".format(**CONFIG))
c.run(
'rsync --delete --exclude ".DS_Store" -pthrvz -c '
'-e "ssh -p {ssh_port}" '
"{} {ssh_user}@{ssh_host}:{ssh_path}".format(
CONFIG["deploy_path"].rstrip("/") + "/", **CONFIG
)
)
def pelican_run(cmd):
cmd += " " + program.core.remainder # allows to pass-through args to pelican
pelican_main(shlex.split(cmd))