# NASG (Not Another Static Generator) This is a tiny static site generator, written in Python, to scratch my own itches. It is most probably not suitable for anyone else. ## Why not [insert static generator here]? - DRY -Don't Repeat Yourself - is good, so instead of sidefiles for images, I'm using XMP metadata, which most of the ones availabe don't handle well; - writing a proper plugin to existing generators - Pelican, Nicola, etc - might have taken longer and I wanted to extend my Python knowledge - I wanted to use the best available utilities for some tasks, like `Pandoc` and `exiftool` instead of Python libraries trying to achive the same - I needed to handle webmentions and comments Don't expect anything fancy: my Python Fu has much to learn. ## How content is organized The directory structure of the "source" is something like this: ``` ├── content │   ├── category1 (containing YAML + MD files) │   ├── category2 (containing YAML + MD files) │   ├── photo (containing jpg files) │   ├── _category_excluded_from_listing_1 (containing YAML + MD files) ├── files │   ├── image (my own pictures) │   ├── photo -> ../content/photo │   └── pic (random images) ├── nasg │   ├── archive.py │   ├── config.ini │   ├── db.py │   ├── LICENSE │   ├── nasg.py │   ├── README.md │   ├── requirements.txt │   ├── router.py │   ├── shared.py │   └── templates ├── static │   ├── favicon.ico │   ├── favicon.png │   └── pgp.asc └── var ├── gone.tsv ├── redirects.tsv ├── s.sqlite ├── tokens.json └── webmention.sqlite ``` Content files can be in either YAML and Markdown, with `.md` extension, or JPG with metadata, with `.jpg` extension. Inline images in the content are checked against all subdirectories in `files` ; they get their EXIF read and displayed as well if they match the regex in the configuration for the Artist and/or Copyright EXIF fields. `gone.tsv` is a simple list of URIs that should return a `410 Gone` message while `redirect.tsv` is a tab separated file of `from to` entries that should be `301` redirected. These go into a magic.php file, so if the host supports executing PHP, it will take care of this. ## Output `nasg.py` generates a `build` directory which will have an directory per entry, with an `index.html`, so urls can be `https://domain.com/filename/`. Categories are rendered into `category/category_name`. Pagination is under `category/category_name/page/X`. They include a feed as well, `category/category_name/feed`, in form if an `index.atom` ATOM feed. ## Webserver configuration A minimal nginx configuration for the virtualhost: ``` # --- Virtual Host --- upstream {{ domain }} { server unix:/var/run/php/{{ domain }}.sock; } server { listen 80; server_name .{{ domain }}; rewrite ^ https://$server_name$request_uri redirect; access_log /dev/null; error_log /dev/null; } server { listen 443 ssl http2; server_name .{{ domain }}; ssl_certificate /etc/letsencrypt/live/{{ domain }}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/{{ domain }}/privkey.pem; ssl_dhparam dh.pem; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;"; root /[path to root]/{{ domain }}; location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; } location ~ ^(?.+?\.php)(?.*)$ { try_files $uri $script_name =404; fastcgi_param SCRIPT_FILENAME $document_root$script_name; fastcgi_param SCRIPT_NAME $script_name; fastcgi_param PATH_INFO $path_info; fastcgi_param PATH_TRANSLATED $document_root$path_info; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_param HTTP_PROXY ""; fastcgi_param HTTPS $https if_not_empty; fastcgi_param SSL_PROTOCOL $ssl_protocol if_not_empty; fastcgi_param SSL_CIPHER $ssl_cipher if_not_empty; fastcgi_param SSL_SESSION_ID $ssl_session_id if_not_empty; fastcgi_param SSL_CLIENT_VERIFY $ssl_client_verify if_not_empty; fastcgi_param REDIRECT_STATUS 200; fastcgi_index index.php; fastcgi_connect_timeout 10; fastcgi_send_timeout 360; fastcgi_read_timeout 3600; fastcgi_buffer_size 512k; fastcgi_buffers 512 512k; fastcgi_keep_conn on; fastcgi_intercept_errors on; fastcgi_split_path_info ^(?.+?\.php)(?.*)$; fastcgi_pass {{ domain }}; } location / { try_files $uri $uri/ $uri.html $uri/index.html $uri/index.xml $uri/index.atom index.php @rewrites; } location @rewrites { rewrite ^ /magic.php?$args last; } location ~* \.(css|js|eot|woff|ttf|woff2)$ { expires 1d; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; add_header "Vary" "Accept-Encoding"; } location ~* \.(png|ico|gif|svg|jpg|jpeg|webp|avi|mpg|mpeg|mp4|mp3)$ { expires 7d; add_header Cache-Control "public, must-revalidate, proxy-revalidate"; add_header "Vary" "Accept-Encoding"; } } ```