commit a1c44e502d36252d4447d345089a2e057f79654c Author: Ricardo (XenGi) Band Date: Fri Jan 7 19:51:45 2022 +0100 initial drop diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ec5a83 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +.idea +output diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..4b13773 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,11 @@ +image: python:slim + +before_script: + - pip install -r requirements.txt + +pages: + script: + - pelican content -s publishconf.py + artifacts: + paths: + - output diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..37e028c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "flex"] + path = flex + url = https://github.com/alexandrevicenzi/Flex.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..95c2f8e --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +PY?=python3 +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 publish diff --git a/content/images/avatar.png b/content/images/avatar.png new file mode 100644 index 0000000..a41a5de Binary files /dev/null and b/content/images/avatar.png differ diff --git a/content/images/favicon.ico b/content/images/favicon.ico new file mode 100644 index 0000000..8af9478 Binary files /dev/null and b/content/images/favicon.ico differ diff --git a/content/linux/archlinux_install_guide_2021.md b/content/linux/archlinux_install_guide_2021.md new file mode 100644 index 0000000..a6aa432 --- /dev/null +++ b/content/linux/archlinux_install_guide_2021.md @@ -0,0 +1,529 @@ +title: Arch Linux install guide 2022 Edition +date: 2021-12-24 +tags: arch linux +keywords: arch linux +category: linux +summary: This is how I setup Arch Linux in 2022 + + +# Introduction + +This is the way I usually install Arch Linux. It is heavily based on systemd services to avoid the installation of additional software. Use it as inspiration for your own custom install. + +Arch Linux is all about freedom of choice so these are the choices I made: + +| Component | Implementation | +| ---------------- | ----------------------------- | +| Kernel | Default `linux` | +| Partition layout | GPT | +| Bootloader | `systemd-boot` | +| Initramfs | `systemd` based `mkinitcpio` | +| Partitioning | Primary partitions, single partition for root and home | +| Encryption | LUKS2 for swap and rootfs | +| Root filesystem | Ext4 | +| Swap space | Dedicated partition | +| Network manager | `systemd-networkd` | +| Wifi | `wpa_supplicant` | +| Mobile broadband | Custom `modem-manager` script | +| Name resolution | `systemd-resolved` | +| Network Time | `systemd-timesyncd` | +| Text Editor | Neovim (`nvim`) | +| User shell | `fish` | +| `/bin/sh` Shell | `dash` | +| Privilege excalation | `sudo` | +| Desktop Environment | `xfce` | +| Window Manager | `i3` | +| Terminal | `xfce-terminal` | +| Keyboard layout | en-us international with AtlGr dead keys; caps lock as compose key | + + +Explanation for code examples: + +``` +# command executed as root +$ command executed as unprivileged user +% command executed inside chroot as root +``` + +## Assumptions + +- You install to a notebook or desktop computer (ignore battery related things) +- You install to an NVME drive +- You read through my commands carefully and understand what they do before blindly pasting them into your machine +- I'm not responsible for anything you do + + +__With that out of the way, let's get started!__ + +# Installation + +## Partition disks + +Create 3 primary partitions for: + + - ESP: 1GB, FAT32 + - Swap: + - With hibernation support: same as RAM size + - Without: ~2-8GB depending on the use case of the machine + - RootFS: Rest of your disk + +``` +# cfdisk -z /dev/nvme0n1 +# mkdosfs -F 32 -n ESP /dev/nvme0n1p1 +# cryptsetup LuksFormat --type luks2 --label CRYPTSWAP /dev/nvme0n1p2 +# cryptsetup LuksFormat --type luks2 --label CRYPTROOT /dev/nvme0n1p3 +# cryptsetup open --allow-discards --perf-no_read_workqueue --perf-no_write_workqueue --persistent --type luks2 /dev/nvme0n1p2 cryptswap +# cryptsetup open --allow-discards --perf-no_read_workqueue --perf-no_write_workqueue --persistent --type luks2 /dev/nvme0n1p3 cryptroot +# mkswap -L SWAP /dev/mapper/cryptswap +# mkfs.ext4 -L ROOT -O metadata_csum /dev/mapper/cryptroot +# mount /dev/mapper/cryptroot /mnt -o noatime,discard +# mkdir /mnt/boot +# mount /dev/nvme0n1p1 /mnt/boot -o noatime,discard +# swapon -d /dev/mapper/cryptswap +``` + +Final result should look like this: + +``` +# lsblk +NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS +nvme0n1 259:0 0 953.9G 0 disk +├─nvme0n1p1 259:1 0 1G 0 part /boot +├─nvme0n1p2 259:2 0 32G 0 part +│ └─cryptswap 254:1 0 32G 0 crypt [SWAP] +└─nvme0n1p3 259:3 0 920.9G 0 part + └─cryptroot 254:0 0 920.9G 0 crypt / +``` + + +## Install base system + +Some packages mentioned here depend on your hardware + +``` +# pacstrap /mnt base base-devel linux linux-firmware (intel-ucode|amd-ucode) [sof-firmware] neovim man-db man-pages texinfo +``` + +Generate fstab + +``` +# genfstab -U /mnt >> /mnt/etc/fstab +``` + +## Setup bootloader + +Get UUIDs of drives: + +``` +# blkid +/dev/nvme0n1p1: LABEL_FATBOOT="ESP" LABEL="ESP" UUID="1234-0001" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="deadbeef-1234-5678-90ab-10000001" +/dev/nvme0n1p2: UUID="deadbeef-1234-5678-90ab-00000002" LABEL="CRYPTSWAP" TYPE="crypto_LUKS" PARTUUID="deadbeef-1234-5678-90ab-10000002" +/dev/nvme0n1p3: UUID="deadbeef-1234-5678-90ab-00000003" LABEL="CRYPTROOT" TYPE="crypto_LUKS" PARTUUID="deadbeef-1234-5678-90ab-20000003" +/dev/mapper/cryptswap: LABEL="SWAP" UUID="deadbeef-1234-5678-90ab-20000002" TYPE="swap" +/dev/mapper/cryptroot: LABEL="ROOT" UUID="deadbeef-1234-5678-90ab-20000003" BLOCK_SIZE="4096" TYPE="ext4" +``` + +Install and configure `systemd-boot`: + +``` +# arch-chroot /mnt # we'll stay in chroot for a while so I'll not prefix every code block with that +% bootctl install +% nvim /boot/loader/loader.conf +default arch.conf +timeout 3 +console-mode keep +editor yes +auto-entries +auto-firmware +``` + +Setup boot entries: + +``` +% nvim /boot/loader/entries/arch.conf +title Arch Linux +linux /vmlinuz-linux +initrd /intel-ucode.img +initrd /initramfs-linux.img +options root="UUID=deadbeef-1234-5678-90ab-20000003" resume="UUID=deadbeef-1234-5678-90ab-20000002" rd.luks.name=deadbeef-1234-5678-90ab-00000003=cryptroot rd.luks.name=deadbeef-1234-5678-90ab-00000002=cryptswap rd.luks.options=discard rw + +% nvim /boot/loader/entries/arch-fallback.conf +title Arch Linux (fallback initramfs) +linux /vmlinuz-linux +initrd /intel-ucode.img +initrd /initramfs-linux-fallback.img +options root="UUID=deadbeef-1234-5678-90ab-20000003" resume="UUID=deadbeef-1234-5678-90ab-20000002" rd.luks.name=deadbeef-1234-5678-90ab-00000003=cryptroot rd.luks.name=deadbeef-1234-5678-90ab-00000002=cryptswap rd.luks.options=discard rw +``` + +## Setup vconsole + +``` +% nvim /etc/vconsole.conf +KEYMAP=us +FONT=lat2-16 +FONT_MAP=8859-2 +``` + +## Setup initramfs + +Switching to `systemd` based initramfs: + +``` +% nvim /etc/mkinitcpio.conf +[...] +HOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt filesystems fsck) +[...] + +% mkinitcpio -P +``` + +## Setup networking + +Setup for wired connections: + +``` +% nvim /etc/systemd/network/wired.network +[Match] +Name=enp* + +[Network] +DHCP=yes +MulticastDNS=resolve +DNSSEC=allow-downgrade + +[DHCP] +RouteMetric=10 +``` + +Setup for wireless connections: + +``` +% nvim /etc/systemd/network/wireless.network +[Match] +Name=wlp* + +[Network] +DHCP=yes +MulticastDNS=resolve +DNSSEC=allow-downgrade +IPv6PrivacyExtensions=kernel + +[DHCP] +RouteMetric=20 +``` + +Enable IPv6 privacy extensions: + +Execute this first: + +``` +% sysctl net.ipv6.conf.wlp3s0.addr_gen_mode=3 +% ip link set wlp3s0 down +% ip link set wlp3s0 up +% sysctl net.ipv6.conf.wlp3s0.stable_secret +``` + +Take the secret from the last command and put it here: + +``` +% nvim /etc/sysctl.d/40-ipv6.conf +# Enable IPv6 Privacy Extensions +net.ipv6.conf.all.use_tempaddr = 2 +net.ipv6.conf.default.use_tempaddr = 2 +net.ipv6.conf.wlp3s0.use_tempaddr = 2 +net.ipv6.conf.enp5s0.use_tempaddr = 2 + +# Enable IPv6 stable privacy mode +net.ipv6.conf.wlp3s0.stable_secret = 25a3:36e6:9384:1111:9866:def1:abcd:1234 +net.ipv6.conf.wlp3s0.addr_gen_mode = 2 +``` + +Install additional packages for wifi and enable daemon on your wifi interface: + +``` +$ yay -S wpa_supplicant +# systemdctl enable wpa_supplicant@wlp3s0.service +``` + +Setup for mobile broadband follows later. + +``` +% systemctl enable systemd-networkd +% systemctl enable systemd-resolved +% rm /etc/resolv.conf +% ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf +``` + +__The last command will break your DNS but that's fine because we will reboot in a moment.__ +__Maybe you have to do this after reboot. Definitly check if it worked.__ + +## Set hostname + +``` +% echo "myhostname" > /etc/hostname +``` + +## Setup network time and timezone + +``` +% nvim /etc/systemd/timesyncd.conf +[Time] +NTP=0.arch.pool.ntp.org 1.arch.pool.ntp.org 2.arch.pool.ntp.org 3.arch.pool.ntp.org +FallbackNTP=0.de.pool.ntp.org 1.de.pool.ntp.org 2.de.pool.ntp.org 3.de.pool.ntp.org + +% systemctl enable systemd-timesyncd +``` + +``` +% ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime +% hwclock --systohc +``` + +## Locale setup + +Edit `/etc/locale.gen` file in your chroot and uncomment `en_US.UTF-8 UTF-8` locales. Then run: + +``` +% locale-gen +``` + +``` +% nvim /etc/locale.conf +LANG=en_US.UTF-8 +LANGUAGE=en_US:en +LC_COLLATE=C +``` + +## Set root password + +``` +% passwd +``` + +## Reboot into new system + +``` +% exit # exit chroot +# umount -R /mnt +# swapoff -a +# cryptsetup close cryptroot +# cryptsetup close cryptswap +# reboot +``` + +## Check if time is correct + +``` +# timedatectl status + Local time: Thu 2021-08-05 18:08:59 CEST + Universal time: Thu 2021-08-05 16:08:59 UTC + RTC time: Thu 2021-08-05 16:09:09 + Time zone: Europe/Berlin (CEST, +0200) +System clock synchronized: no + NTP service: inactive + RTC in local TZ: no +``` + +Enable synchronisation with: + +``` +# timedatectl set-ntp true +``` + +## Create unprivileged user + +``` +# pacman -Syu sudo fish python pkgfile +# useradd -m -G wheel -s /usr/bin/fish bob +# EDITOR=nvim visudo +[...] +%wheel ALL=(ALL) ALL +[...] + +# passwd bob +``` + +## Setup AUR + +__Use unprivileged user from here on__ + +``` +$ sudo pacman -S git +$ git clone https://aur.archlinux.org/yay.git +$ cd yay +$ makepkg -csi +$ cd .. +$ rm -fr yay +``` + +## Some quality of live things + +### Update systemd-boot on ESP when systemd is updated + +``` +$ yay -S systemd-boot-pacman-hook +``` + +### System monitoring with temperatures + +``` +$ yay -S htop lsof strace lm_sensors +# sensors-detect # hit enter until it stops +``` + +### Use `dash` for `/bin/sh` + +This could cause problems, use `checkbashisms` to check your scripts: + +``` +$ yay -S checkbashisms +$ find /usr/bin/ -type f -perm -o=r -print0 | xargs -0 gawk '/^#!.*( |[/])sh/{printf "%s\0", FILENAME} {nextfile}' | xargs -0 checkbashisms +$ yay -S dashbinsh +``` + +### Make pacman nice and fast + +Edit `/etc/pacman.conf` and add/change these lines: + +``` +Color +VerbosePkgLists +ParallelDownloads = 10 +ILoveCandy +``` + +Also enable multilib: + +``` +[multilib] +Include = /etc/pacman.d/mirrorlist +``` + +# Grafical setup + +## Setup gdm + +``` +$ yay -S gdm +# systemctl enable gdm.service +``` + +## Setup XFCE with i3 as window manager + +Install packages first. Choose as many from these groups as you like. You can probably skip most panel plugins: + +``` +$ yay -S xfce4 xfce4-goodies +``` + +My favorites even under i3 are: `xfce4-screenshooter`, `xfce4-terminal`, `thunar` + +For `thunar` setup `gvfs`: + +``` +$ yay -S gvfs gvfs-afc gvfs-goa gvfs-google gvfs-gphoto2 gvfs-mtp gvfs-nfs gvfs-smb +``` + +Install i3 (or i3-gaps if you wanna waste precious space just for looks): + +``` +$ yay -S i3 i3blocks j4-dmenu-desktop +``` + +Switch to i3 as window manager: + +``` +xfconf-query -c xfce4-session -p /sessions/Failsafe/Client0_Command -t string -sa xfsettingsd +xfconf-query -c xfce4-session -p /sessions/Failsafe/Client1_Command -t string -sa i3 +``` + +This should change the value from `xfwm4` to `i3` in `~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-session.xml`. + +## Fonts + +``` +% yay -S ttf-droid ttf-dejavu \ # default stuff +noto-fonts noto-fonts-cjk noto-fonts-emoji noto-fonts-extra \ # emoji +adobe-source-code-pro-fonts \ # monospace +adobe-source-sans-fonts \ # sans-serif +adobe-source-han-sans-cn-fonts adobe-source-han-sans-tw-fonts adobe-source-han-serif-cn-fonts adobe-source-han-serif-tw-fonts \ # chinese +adobe-source-han-sans-jp-fonts adobe-source-han-serif-jp-fonts \ # japanese +adobe-source-han-sans-kr-fonts # korean +``` + +# Bonus! + +## Optimize pacman/yay + +``` +$ yay -S ccache +``` + +In `/etc/makepkg.conf`: + + - For `CFLAGS` change `-march=native` and `-mtune=native`. + - Set `MAKEFLAGS="-j8"` or any othe rnumber of cores you want to use + - For `BUILDENV` enable `ccache` + +## Wayland + +Enable wayland support for Qt5/6 + +``` +$ yay -S qt5-wayland qt6-wayland glfw-wayland +``` + +Enable wayland support for electron apps by editing `~/.config/electron-flags.conf`: + +``` +--enable-features=UseOzonePlatform +--ozone-platform=wayland +``` + +Install XWayland to support X11 applications: + +``` +$ yay -S xorg-xwayland +``` + +## Sway + +TBC + +``` +yay -S sway \ + xdg-desktop-portal xdg-desktop-portal-wlr \ # screen sharing/webrtc +``` + +## Gnome + +Simply install the gnome group which includes most gnome packages you'll need: + +``` +$ yay -S gnome gnome-tweaks webp-pixbuf-loader +``` + +After a few hundred lines of things scroll by this the stuff that was too fast to read: + +Install these packages that add features you propably want: + +``` +$ yay -S qt5-svg \ # svg icon set support + vulkan-mesa-layers \ # vulkan support + vulkan-intel \ # vulkan support (intel) + gst-libav gst-plugins-ugly \ # media file support + intel-media-driver libva-intel-driver \ # media file support (intel) + eog-plugins \ # image support for eye of gnome + poppler-data \ # pdf feature support + p7zip unrar \ # file-roller archive support + gedit-plugins \ + fwupd dmidecode \ # firmware update support + pipewire-alsa pipewire-pulse pipewire-zeroconf \ # pulseaudio replacement + xdg-desktop-portal xdg-desktop-portal-gtk \ # screen sharing/webrtc + gtk-engines \ # gtk2 themes + chromium \ # browser + evolution evolution-ews highlight \ # email client +``` + +Enable pipewire support in chromium by enabling: `chrome://flags/#enable-webrtc-pipewire-capturer` + diff --git a/content/linux/pokeshell.md b/content/linux/pokeshell.md new file mode 100644 index 0000000..9959191 --- /dev/null +++ b/content/linux/pokeshell.md @@ -0,0 +1,36 @@ +title: Pokéshell +date: 2016-01-01 23:30 +category: linux +tags: pokemon, shell, bash, fish, imagemagick +slug: pokeshell + +A friend of me had the idea of a shell displaying a random pokémon on launch. I liked the idea and couldn't resist to implement it over christmas. + + + +What the package does is, it downloads all pokémon images in a defined range which is 1-151 by default (I'm old these are the ones I know) and converts the images to escape sequences. These are saved in `/usr/share/pokeshell`. + +Now when you ipopen a new shell you could display one of them randomly by putting this code in your `~/.bashrc`: + +```sh +cat /usr/share/pokeshell/\$((\$RANDOM % 151 + 1)).pokemon +``` + +Or if you use fish like me put this in your `~/.config/fish/functions/fish_prompt.fish`: + +```sh +function fish_greeting + set r (random) + cat /usr/share/pokeshell/(math \"\$r % 151 + 1\").pokemon +end +``` + +The images are downloaded using the [Pokéapi][pokeapi]. For the conversation I use Imagemagick and a tool called [img2xterm][img2xterm]. + +You can find the package in the [AUR][aur-pokeshell]. + + +[pokeapi]: http://pokeapi.co +[img2xterm]: https://github.com/rossy/img2xterm +[aur-pokeshell]: https://aur.archlinux.org/packages/pokeshell/ + diff --git a/flex b/flex new file mode 160000 index 0000000..7e99f9b --- /dev/null +++ b/flex @@ -0,0 +1 @@ +Subproject commit 7e99f9bbe681a5b7edef9748bc6f6ae10f4862a7 diff --git a/pelicanconf.py b/pelicanconf.py new file mode 100644 index 0000000..a71d851 --- /dev/null +++ b/pelicanconf.py @@ -0,0 +1,140 @@ +from datetime import datetime + +AUTHOR = 'XenGi' +SITEURL = '' +SITENAME = 'XenGis blog' +SITETITLE = 'XenGi' +SITESUBTITLE = '' +SITEDESCRIPTION = 'A typical way to world domination' +SITELOGO = SITEURL + '/images/avatar.png' +FAVICON = SITEURL + '/images/favicon.ico' +BROWSER_COLOR = '#333' +ROBOTS = 'index, follow' +CC_LICENSE = { + 'name': 'Creative Commons Attribution-ShareAlike', + 'version': '4.0', + 'slug': 'by-sa', + 'icon': True, + 'language': 'en_US', +} +COPYRIGHT_NAME = AUTHOR +COPYRIGHT_YEAR = datetime.now().year +STATIC_PATHS = [ + 'extra/custom.css' +] +EXTRA_PATH_METADATA = { + 'extra/custom.css': {'path': 'static/custom.css'}, +} +CUSTOM_CSS = 'static/custom.css' +MAIN_MENU = True +PLUGINS = [ + 'post_stats', +] +PLUGIN_PATHS = [ + 'plugins/' +] +JINJA_ENVIRONMENT = {} +I18N_TEMPLATES_LANG = 'en' +DEFAULT_LANG = 'en' +OG_LOCALE = 'en_US' +LOCALE = 'en_US' +THEME = 'flex' +OUTPUT_PATH = 'output/' +PATH = 'content/' +TIMEZONE = 'Europe/Berlin' +# 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 +USE_FOLDER_AS_CATEGORY = False +# Social widget +MENUITEMS = [] +SOCIAL = [ + ('Email', 'mailto:blog@xengi.de'), +] +MENUITEMS = [ + ('Archives', '/archives.html'), + ('Categories', '/categories.html'), + ('Tags', '/tags.html'), +] +STATIC_PATHS = [ + 'images', +] +DEFAULT_PAGINATION = False +USE_GOOGLE_FONTS = True + +# Uncomment following line if you want document-relative URLs when developing +#RELATIVE_URLS = True + +DEFAULT_CATEGORY = 'misc' +DISPLAY_PAGES_ON_MENU = True +DISPLAY_CATEGORIES_ON_MENU = True +MARKDOWN = { + 'extension_configs': { + 'markdown.extensions.codehilite': {'css_class': 'highlight'}, + 'markdown.extensions.extra': {}, + 'markdown.extensions.meta': {}, + 'markdown.extensions.nl2br': {}, + 'markdown.extensions.smarty': {}, + 'markdown.extensions.sane_lists': {}, + }, + 'output_format': 'xhtml5', +} +PAGE_PATHS = ['pages'] +STATIC_PATHS = ['images'] +TYPOGRIFY = True +ARTICLE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/' +ARTICLE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html' +PAGE_URL = 'pages/{slug}/' +PAGE_SAVE_AS = 'pages/{slug}/index.html' +AUTHOR_URL = 'author/{slug}.html' +AUTHOR_SAVE_AS = 'author/{slug}.html' +CATEGORY_URL = 'category/{slug}.html' +CATEGORY_SAVE_AS = 'category/{slug}.html' +TAG_URL = 'tag/{slug}.html' +TAG_SAVE_AS = 'tag/{slug}.html' + +# Flex settings + +USE_LESS = True + +## dark mode + +THEME_COLOR = 'dark' +THEME_COLOR_AUTO_DETECT_BROWSER_PREFERENCE = True +THEME_COLOR_ENABLE_USER_OVERRIDE = True + +## code highlight + +PYGMENTS_STYLE = 'emacs' +PYGMENTS_STYLE_DARK = 'monokai' +#PYGMENTS_STYLE = 'solarized-light' +#PYGMENTS_STYLE_DARK = 'solarized-dark' + +# Plugin settings + +## related posts + +RELATED_POSTS_MAX = 10 + +## sitemap + +SITEMAP = { + 'format': 'xml', + 'priorities': { + 'articles': 0.5, + 'indexes': 0.5, + 'pages': 0.5 + }, + 'changefreqs': { + 'articles': 'monthly', + 'indexes': 'daily', + 'pages': 'monthly' + } +} + +## isso + +ISSO_URL = '' diff --git a/plugins/post_stats/__init__.py b/plugins/post_stats/__init__.py new file mode 100644 index 0000000..2313bd5 --- /dev/null +++ b/plugins/post_stats/__init__.py @@ -0,0 +1 @@ +from .post_stats import * diff --git a/plugins/post_stats/post_stats.py b/plugins/post_stats/post_stats.py new file mode 100644 index 0000000..80cfdcd --- /dev/null +++ b/plugins/post_stats/post_stats.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +Post Statistics +======================== + +This plugin calculates various statistics about a post and stores them in an article.stats dictionary: + +wc: how many words +read_mins: how many minutes to read this article, based on 250 wpm (http://en.wikipedia.org/wiki/Words_per_minute#Reading_and_comprehension) +word_counts: frquency count of all the words in the article; can be used for tag/word clouds/ +fi: Flesch-kincaid Index/ Reading Ease +fk: Flesch-kincaid Grade Level + +""" + +from pelican import signals +from bs4 import BeautifulSoup +import re +from collections import Counter + +from .readability import * + + +def calculate_stats(instance): + + if instance._content is not None: + stats = {} + content = instance._content + + # How fast do average people read? + WPM = 250 + + # Use BeautifulSoup to get readable/visible text + raw_text = BeautifulSoup(content, 'html.parser').getText() + + # Process the text to remove entities + entities = r'\&\#?.+?;' + raw_text = raw_text.replace(' ', ' ') + raw_text = re.sub(entities, '', raw_text) + + # Flesch-kincaid readbility stats counts sentances, + # so save before removing punctuation + tmp = raw_text + + # Process the text to remove punctuation + drop = u'.,?!@#$%^&*()_+-=\|/[]{}`~:;\'\"‘’—…“”' + raw_text = raw_text.translate(dict((ord(c), u'') for c in drop)) + + # Count the words in the text + words = raw_text.lower().split() + word_count = Counter(words) + + # Return the stats + stats['word_counts'] = word_count + stats['wc'] = sum(word_count.values()) + + # Calulate how long it'll take to read, rounding up + stats['read_mins'] = (stats['wc'] + WPM - 1) // WPM + if stats['read_mins'] == 0: + stats['read_mins'] = 1 + + # Calculate Flesch-kincaid readbility stats + readability_stats = stcs, words, sbls = text_stats(tmp, stats['wc']) + stats['fi'] = "{:.2f}".format(flesch_index(readability_stats)) + stats['fk'] = "{:.2f}".format(flesch_kincaid_level(readability_stats)) + + instance.stats = stats + + +def register(): + signals.content_object_init.connect(calculate_stats) diff --git a/plugins/post_stats/readability.py b/plugins/post_stats/readability.py new file mode 100644 index 0000000..b267098 --- /dev/null +++ b/plugins/post_stats/readability.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +# Adadpted from here: http://acdx.net/calculating-the-flesch-kincaid-level-in-python/ +# See here for details: http://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_test + +from __future__ import division +import re + + +def mean(seq): + return sum(seq) / len(seq) + + +def syllables(word): + if len(word) <= 3: + return 1 + + word = re.sub(r"(es|ed|(?= 2] + + if wc: + words = wc + else: + words = sum(len(s) for s in stcs) + + sbls = sum(syllables(w) for s in stcs for w in s) + + return len(stcs), words, sbls + + +def flesch_index(stats): + stcs, words, sbls = stats + if stcs == 0 or words == 0: + return 0 + return 206.835 - 1.015 * (words / stcs) - 84.6 * (sbls / words) + + +def flesch_kincaid_level(stats): + stcs, words, sbls = stats + if stcs == 0 or words == 0: + return 0 + return 0.39 * (words / stcs) + 11.8 * (sbls / words) - 15.59 diff --git a/plugins/post_stats/readme.rst b/plugins/post_stats/readme.rst new file mode 100644 index 0000000..89fc13b --- /dev/null +++ b/plugins/post_stats/readme.rst @@ -0,0 +1,49 @@ +Post Statistics +================== + +A Pelican plugin to calculate various statistics about a post and store them in an article.stats dictionary: + +- ``wc``: how many words +- ``read_mins``: how many minutes would it take to read this article, based on 250 wpm (http://en.wikipedia.org/wiki/Words_per_minute#Reading_and_comprehension) +- ``word_counts``: frquency count of all the words in the article; can be used for tag/word clouds +- ``fi``: Flesch-kincaid Index/ Reading Ease (see: http://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests) +- ``fk``: Flesch-kincaid Grade Level + +Example: + +.. code-block:: python + + { + 'wc': 2760, + 'fi': '65.94', + 'fk': '7.65', + 'word_counts': Counter({u'to': 98, u'a': 90, u'the': 83, u'of': 50, ...}), + 'read_mins': 12 + } + +This allows you to output these values in your templates, like this, for example: + +.. code-block:: html+jinja + +

~{{ article.stats['read_mins'] }} min read

+ + +The ``word_counts`` variable is a python ``Counter`` dictionary and looks something like this, with each unique word and it's frequency: + +.. code-block:: python + + Counter({u'to': 98, u'a': 90, u'the': 83, u'of': 50, u'karma': 50, ..... + +and can be used to create a tag/word cloud for a post. + +Requirements +---------------- + +`post_stats` requires BeautifulSoup. + +.. code-block:: console + + $ pip install beautifulsoup4 diff --git a/publishconf.py b/publishconf.py new file mode 100644 index 0000000..e23f1a5 --- /dev/null +++ b/publishconf.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +# 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://xengi.de' +RELATIVE_URLS = False + +FEED_ATOM = 'feeds/atom.xml' +FEED_RSS = 'feeds/rss.xml' +FEED_ALL_ATOM = 'feeds/all.atom.xml' +FEED_ALL_RSS = 'feeds/all.rss.xml' +CATEGORY_FEED_ATOM = 'feeds/category/%s.atom.xml' +CATEGORY_FEED_RSS = 'feeds/category/%s.rss.xml' +AUTHOR_FEED_ATOM = 'feeds/author/%s.atom.xml' +AUTHOR_FEED_RSS = 'feeds/author/%s.rss.xml' +TAG_FEED_ATOM = 'feeds/tag/%s.atom.xml' +TAG_FEED_RSS = 'feeds/tag/%s.rss.xml' + +DELETE_OUTPUT_DIRECTORY = True + +USE_LESS = False diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d97e816 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +pelican[markdown] +markdown +pelican-sitemap +pelican-related-posts +pelican-neighbors +typogrify +beautifulsoup4 # plugin: post_stats, summary diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..12634e8 --- /dev/null +++ b/tasks.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +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 + +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) + + 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'], + '{}/templates/**/*.html'.format(theme_path), + ] + + content_file_extensions = ['.md', '.rst'] + for extension in content_file_extensions: + content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension) + watched_globs.append(content_glob) + + static_file_extensions = ['.css', '.js'] + for extension in static_file_extensions: + static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension) + watched_globs.append(static_file_glob) + + for glob in watched_globs: + server.watch(glob, cached_build) + 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)) \ No newline at end of file