The main content of my blog mcol.xyz
git clone https://mcol.xyz/code/mcol.xyz
Log | Files | Refs

commit cb2b5bdeb43d0fdc99980446b6c23d35e1443050
Author: mcol <mcol@posteo.net>
Date:   Sun,  2 Aug 2020 20:49:58 +0100

Commit to fresh repository to rewrite history before hosting online

Diffstat:
A.gitignore | 4++++
Ablogroll.py | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/favicon.png | 0
Acontent/notes/foss/extend_ranger_with_git_bindings.rst | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/mpop_msmtp.rst | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/pelican_minify_fontawesome.rst | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/python_wallpaper_setter.rst | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/qtile_borders_and_pixmaps.rst | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/quick_n_dirty_permissions_container.rst | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/rewritefs.rst | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/ricing_early_userspace.rst | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/ubuntu_touch_pinephone.rst | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/foss/xonsh_first_impressions.rst | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/raspberrypi/git_backups.rst | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/raspberrypi/pi_home_music_system.rst | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/tor/onion_location_headers.rst | 47+++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/tor/tor_newsboat.rst | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/notes/tor/tor_vpn_nftables_killswitch.rst | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/pages/404.rst | 4++++
Acontent/pages/about.rst | 25+++++++++++++++++++++++++
Acontent/static/UT_pinephone.jpg | 0
Acontent/static/UT_pinephone.webm | 0
Acontent/static/boot_welcome.webm | 0
Acontent/static/nftables.conf | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/static/onion_location.png | 0
Acontent/static/onion_location_pref.png | 0
Acontent/static/pub.asc | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/static/qtile_cde.png | 0
Acontent/static/qtile_frame.png | 0
Acontent/static/qtile_multi.png | 0
Acontent/static/qtile_multi2.png | 0
Acontent/static/tamzen-Welcome.png | 0
Amakefile | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apelicanconf.py | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apublishconf.py | 17+++++++++++++++++
Atasks.py | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
36 files changed, 2202 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +drafts +fontawesome-* +output diff --git a/blogroll.py b/blogroll.py @@ -0,0 +1,80 @@ +# +# My blogroll +# + + +import random + +BLOGROLL = [] + +BLOGROLL.append([ + "Freddy's Ramblings", + "https://write.privacytools.io/freddy", + "Useful and interesting posts and guides about privacy.", + "write.privacytools.io/freddy", +]) + +BLOGROLL.append([ + "Nduli's World", + "https://jnduli.co.ke", + "Interesting posts on FOSS projects like vim and window managers.", + "jnduli.co.ke", +]) + +BLOGROLL.append([ + "Jonathan Borichevskiy's Up and to the Right", + "https://jborichevskiy.com", + "Thought-provoking and entertaining reads.", + "jborichevskiy.com", +]) + +BLOGROLL.append([ + "Daniel's CodeSections", + "https://www.codesections.com", + "Good posts on FOSS and the Fediverse.", + "codesections.com", +]) + +BLOGROLL.append([ + "Oscar Benedito", + "https://oscarbenedito.com", + "Well-written posts on topics such as FOSS tools and privacy.", + "oscarbenedito.com", +]) + +BLOGROLL.append([ + "Jake Bauer", + "https://paritybit.ca", + "Interesting posts on topics from FOSS to The Clone Wars.", + "paritybit.ca", +]) + +BLOGROLL.append([ + "Venam", + "https://venam.nixers.net", + "Insightful posts about Unix and philosophy.", + "venam.nixers.net", +]) + +BLOGROLL.append([ + "Addy", + "https://addy-dclxvi.github.io", + "Posts about cool desktop tools.", + "addy-dclxvi.github.io", +]) + +BLOGROLL.append([ + "Alejandro", + "https://baez.link", + "Nice posts about software development and other topics.", + "baez.link", +]) + +BLOGROLL.append([ + "Yarmo Mackenbach", + "https://yarmo.eu", + "Great posts about things from neuroscience to software.", + "yarmo.eu", +]) + +random.shuffle(BLOGROLL) diff --git a/content/favicon.png b/content/favicon.png Binary files differ. diff --git a/content/notes/foss/extend_ranger_with_git_bindings.rst b/content/notes/foss/extend_ranger_with_git_bindings.rst @@ -0,0 +1,66 @@ +extend ranger file manager with git bindings +============================================ + +:date: 2019-06-02 +:summary: extend ranger file manager with git bindings +:modified: 2020-08-02 + +This is a small but incredibly useful adjustment to the default configuration +that ships with `ranger <https://github.com/ranger/ranger>`_. + +If you have never used ranger, it is a fast terminal file manager with a boat +load of useful features out of the box. You can likely get it from your +distro's repos. + +One of the features it is missing, however, is functional git bindings that you +can use to manipulate your local git repositories. + +On the upside, ranger is highly customisable, so these features can simply be +added into the config at :code:`$HOME/.config/ranger/rc.conf:` + +.. code-block:: conf + + map bs shell git -c color.status=always status | less -r + map bp shell git push | less -r + map bl shell git pull | less -r + map bd shell git diff --color=always %s | less -r + map bD shell git diff --color=always | less -r + map ba shell git add %s + map bA shell git add -f %s + map bu shell git restore --cached %s + map br shell git restore %s + map bm console shell git commit -m ' + map bc console shell git checkout%space + +The second column is our keyboard bindings. By default the 'b' prefix is +unused, so we can stick all git-related commands to this prefix. The third +column we either have :code:`shell,` which executes the following text, or +:code:`console,` which types out the following text on ranger's command line. + +The first 5 lines pipe output into :code:`less`. If we didn't do this then +ranger would steal the screen again and we wouldn't see the output from the +command. The :code:`-r` option preserves any colour in the output from the git +command when presented in less. :code:`git status` is a bit of a weird one; I +found that it needed the :code:`-c color.status=always` to output color codes, +but this seemed to be inconsistent across git versions. + +Some commands contain :code:`%s`; in these lines the current file is +substituted, allowing us to interactively move around our repository +git-diffing or git-adding individual files as we need. Super useful! + +The final two lines pre-fill ranger's command line so you can the continue your +commit or checkout command before executing it. + +Of course, these are just the bare minimum for integrating git bindings into +ranger. Depending on your git workflow requirements you can extend or customise +these so you can glide around and play with your repos with ease. + +For example we can add a quick shortcut to check out tig for a closer look at +the commit history: + +.. code-block:: python + + map bt shell tig + +Let me know if you use similar bindings or extend these and have found some +cool tricks! diff --git a/content/notes/foss/mpop_msmtp.rst b/content/notes/foss/mpop_msmtp.rst @@ -0,0 +1,84 @@ +mpop and msmtp: a minimalist match made in heaven +================================================= + +:date: 2019-05-28 +:summary: mpop and msmtp: a minimalist match made in heaven + +Like many sane people, I have a soft spot for little C programs with little +config files that do exactly what they do and nothing else. `Martin Lambers' +<https://marlam.de/>`_ POP3 and SMTP clients, `mpop <https://marlam.de/mpop/>`_ +and `msmtp <https://marlam.de/msmtp/>`_, are perfect examples. + +In my quest for a minimal and straight forward setup, these bad boys have +become my daily email handlers, with :code:`mpop` downloading my emails into a +Maildir at :code:`~/.mail`, :code:`mutt` being a mostly offline interface to +deal with all the crap people send me, and :code:`msmtp` as the backend to send +mail from mutt. + +mpop can be run as a cronjob, downloading mail every 30 minutes, leaving no +trace on the remote server (if I trust my provider, that is). + +setup +----- + +mpop and msmtp can be installed from your distributions repos via the usual +route. + +Both programs have sane defaults that would work well for a standard user, so +the configuration files are very few lines and take only a minute to write. + +On top of that, they both share a chunk of code and its corresponding options, +resulting in a good bit of the two files sharing lines. + +My config for mpop, for example, is simply: + +.. code-block:: haskell + + defaults + uidls_file "~/.config/mpop/uidls_%U_at_%H" + tls on + tls_starttls off + + account posteo + host "posteo.de" + user "<myemailaddress>" + passwordeval "gpg --no-tty -q -d --for-your-eyes-only ~/.mail/posteo.gpg" + delivery maildir "~/.mail/posteo/INBOX" + port 995 + +Compared to my nearly identical msmtp config: + +.. code-block:: haskell + + defaults + auth on + tls on + tls_starttls off + + account posteo + host "posteo.de" + user "<myemailaddress>" + from "<myemailaddress>" + passwordeval "gpg --no-tty -q -d --for-your-eyes-only ~/.mail/posteo.gpg" + port 465 + +The first 4 lines of each are the defaults, which apply to all email accounts +that are set up. The second block is therefore my first account, in this case +just named :code:`posteo` for my provider. Everything is simple and self +explanatory. Of note though is the passwordeval lines, which is simply a shell +line executed that should give you the password for that account, in this case +taking it from my gpg-agent. + +torifying +--------- + +Both programs can be run straight through Tor with no trouble at all, just +stick this into the defaults sections: + +.. code-block:: haskell + + proxy_host localhost + proxy_port 9050 + +After 2 minutes of setup, these programs can work in the background and never +ask for your attention again. diff --git a/content/notes/foss/pelican_minify_fontawesome.rst b/content/notes/foss/pelican_minify_fontawesome.rst @@ -0,0 +1,98 @@ +minifying font awesome in pelican +================================= + +:date: 2019-10-20 +:summary: Minifying font awesome fonts and CSS in pelican state site generator + +I recently incorporated `font awesome <https://fontawesome.com/>`_ glyphs into +my website in an attempt to add a touch more :fas:`eye` :fas:`candy-cane`. I +wanted to self-host them, rather than use a CDN, for the slight privacy +benefit. + +Following installation, I found the generation of my site, done by `pelican +<https://blog.getpelican.com/>`_, was taking significantly longer. Perhaps I'm +crazy, but I also felt like Firefox was loading the pages for a noticably +longer few milliseconds and the change wasn't doing anything for the +perfectionist in me. + +Looking at the files I had just bundled with my pages, they had added at least +200KB in webfonts (possibly more, depending on which filetype is requested) and +around 55KB in CSS to my website. And for what, the odd glyph here or there? +Most of my blog posts are less than 10KB, as is the rest of my CSS, so I +determined to find a solution to incorporate only those glyphs that I wanted to +use. + +creating a pelican pugin +------------------------ + +The `documention +<https://docs.getpelican.com/en/stable/plugins.html#how-to-create-plugins>`_ +for creating pelican plugins is useful, but I found reading existing plugins in +the `great repository <https://github.com/getpelican/pelican-plugins>`_ of +plugins much more informative. + +Eventually I had put together a plugin that (so far!) appears to work wonders: +`pelican-minify-fontawesome +</code/pelican-minify-fontawesome>`_ :fas:`grin-beam`. + +First, after site generation, it identifies which font awesome icons are +present by scanning the output folder and getting all HTML class attributes +that start with :code:`fa-`. The words that follow this string are the names of +the icons. + +With a bit of regex magic, these icon names are then used to extract their +corresponding CSS blocks from a locally available font awesome download. These +CSS blocks are then copied into the output folder. + +Extracting the glyphs from the fonts was the tricky part. FontForge advertised +Python bindings, so guided by `their docs +<https://fontforge.github.io/python.html>`_ I tried to put the logic together. +FontForge's approach to manipulating fonts did take me a while to understand, +however it does work well. This is the gist of it: + +.. code-block:: python + + font = fontforge.open(font_path) + selected = False + for icon in icons: # The list of icon names + try: + font.selection.select(("more",), icon) + selected = True + except ValueError: + # icon not found in font + pass + + if selected: + font.selection.invert() + font.clear() + font.generate(output_path) + +One by one, it loads each font, :code:`select`\s our desired icons that are +present in the font, and, if there are any, it inverts the selection and +deletes it. This leaves only our desired icons in the font, which is saved into +our output folder. + +adding font awesome RST roles +----------------------------- + +I wanted to add something extra for convenience, that is reStructuredText roles +that allow me to include font awesome fonts inline in my posts. + +I added 3 roles: :code:`fas`, :code:`far`, and :code:`fab` for font awesome's +solid, regular and brands icon lists. These can be used like this: + +.. code-block:: RST + + Here is the classic :fas:`blender-phone`. + +Here is the classic :fas:`blender-phone`. + +result +------ + +Whenever I use a new icon, like the :fas:`poo-storm`, I simply use one of the +above RST roles in my draft and then pelican will include it when it copies +over font awesome's CSS and webfonts into the output folder. + +Then I think about how much of my time I waste over-optimising tiny, tiny +things. diff --git a/content/notes/foss/python_wallpaper_setter.rst b/content/notes/foss/python_wallpaper_setter.rst @@ -0,0 +1,214 @@ +writing a Python wallpaper setter for X11 +========================================= + +:date: 2019-11-23 +:summary: writing a Python wallpaper setter for X11 + +For the past few weeks I've been learning a lot about the communication +interface between the X11 server and its clients. As a devotee of Qtile_, I +have been using it as a testing ground for a number of tools that directly talk +with the X server, using the Python X bindings provided by xcffib_. + +This exercise has seen me spend a fair bit of time and effort trying to 'port' +C code using XCB_ or xlib_ to xcffib equivalents. At times this is tedious - as +anybody who has written X client code will know - but I like the idea of +extending Qtile to include more features typically outside of the scope of a +standalone window-manager. + +One such feature is the simple ability to set the background wallpaper. Sounds +easy, right? The X server manages the drawing of windows as regions within the +root window, which itself is handled in many ways like a normal window. The +appearance of each window (including the root window) is stored within a +pixmap, whose data can be rendered on-screen as pixels. This description is +super simplified; for a fantastic and more in-depth exploration of how this +works I recommend reading xplain_ by Jasper St. Pierre. + +Setting the desktop wallpaper therefore means colouring and rendering the +pixmap for the root window of each screen. Wrapping this behaviour as an X +client: we need to open a connection to the X server, load our wallpaper image +in a form that can be painted onto a pixmap, and then perform the painting. +I've packaged what I've described here with a convenient interface here_. + + +wallpaper setting with xcffib +----------------------------- + +Opening an X client connection is straightforward and requires the +:code:`DISPLAY` environmental variable. xcffib gives us a :code:`Connection` +object from which we can get information from the X server such as screen +setup. + +.. code-block:: python + + import os + import xcffib + import cairocffi # needed later + import cairocffi.pixbuf # needed later + import xcffib.xproto # needed later + + conn = xcffib.Connection(display=os.environ.get("DISPLAY")) + screens = conn.get_setup().roots + +Next we need to load our image. The cairocffi_ library provides Python bindings +for cairo_, a 2D graphics library that supports rendering graphics to X +pixmaps. We can load our image into what cairo calls a :code:`Surface` with: + +.. code-block:: python + + with open('/path/to/image.png', 'rb') as fd: + image, _ = cairocffi.pixbuf.decode_to_image_surface(fd.read()) + +As we need to paint the image to one screen at a time, we could load multiple +images and use a different image for each painting operation. + +The next part performs the painting of the root pixmap to set the wallpaper. +First we must ask the server for a new resource ID and use this to create our +pixmap for the current screen. Creating the pixmap requires the colour depth of +the screen (:code:`screen.root_depth`) as well as its own ID +(:code:`screen.root`), and its dimensions: + +.. code-block:: python + + screen = screens[0] + pixmap = conn.generate_id() + conn.core.CreatePixmap( + screen.root_depth, + pixmap, + screen.root, + screen.width_in_pixels, + screen.height_in_pixels, + ) + +We could iterate over :code:`screens` to paint each screen. + +Next we need to extract from the screen its :code:`visual`, which contains +information about how it manages colour maps and depths. We need this to create +a cairo surface that is compatible with the root window, onto which we can +paint our image: + +.. code-block:: python + + for depth in screen.allowed_depths: + for visual in depth.visuals: + if visual.visual_id == screen.root_visual: + root_visual = visual + break + + surface = cairocffi.xcb.XCBSurface( + conn, pixmap, root_visual, + screen.width_in_pixels, screen.height_in_pixels, + ) + +The cairocffi API for manipulating surfaces provides us a :code:`Context` in +which to modify and use our :code:`Surface` objects (the image and pixmap +surfaces). Surface manipulation is pretty nice, and it only takes one command +to set our image as a data source and another to paint it to the pixmap: + +.. code-block:: python + + with cairocffi.Context(surface) as context: + context.set_source_surface(image) + context.paint() + +It is at this point where we could add more image manipulations to the source +image before painting, such as stretching or tiling. + +Root windows have two properties named :code:`_XROOTPMAP_ID` and +:code:`ESETROOT_PMAP_ID` which it uses to publish the root pixmap so that other +X clients can have access to the pixel data. This is used for effects such as +the pseudo-transparency feature of urxvt_. We therefore need to set these +properties using our newly painted pixmap. + +The xcffib API for this might look a bit cryptic; we are passing the +property-setting mode :code:`Replace`, the root window concerned, the property +we want to change, the type of data we are passing (:code:`PIXMAP`) and lastly +the bit format, number of items and our list of items (just our pixmap): + +.. code-block:: python + + conn.core.ChangeProperty( + xcffib.xproto.PropMode.Replace, + screen.root, + conn.core.InternAtom(False, 13, '_XROOTPMAP_ID').reply().atom, + xcffib.xproto.Atom.PIXMAP, + 32, 1, [pixmap] + ) + conn.core.ChangeProperty( + xcffib.xproto.PropMode.Replace, + screen.root, + conn.core.InternAtom(False, 16, 'ESETROOT_PMAP_ID').reply().atom, + xcffib.xproto.Atom.PIXMAP, + 32, 1, [pixmap] + ) + +We can then change the root window's background pixmap to our pixmap and clear +the area that contains it, which refreshes those pixels to display their new +values: + +.. code-block:: python + + conn.core.ChangeWindowAttributes( + screen.root, xcffib.xproto.CW.BackPixmap, [pixmap] + ) + conn.core.ClearArea( + 0, screen.root, + 0, 0, # x and y position + screen.width_in_pixels, screen.height_in_pixels + ) + +Without the :code:`ClearArea` call background pixels will only refresh when you +move a window over them, which can be a cool effect. + +Lastly we should set our X client's :code:`CloseDown` mode to +:code:`RetainPermanent` to make the our changes to the root window persist +after the client closes, and then disconnect. + +.. code-block:: python + + conn.core.SetCloseDownMode(xcffib.xproto.CloseDown.RetainPermanent) + conn.disconnect() + +The logic we've looked at so far is sufficient to set the X wallpaper, and can +easily be extended to apply wallpapers to multiple screens and to manipulate +our desired image before painting it the pixmap. + +For example, if the dimensions of our image and screen might differ or if we +want to use only a subregion of an image, we can use cairocffi's +:code:`Context` API to change how we paint to our pixmap. The library exposes +:code:`Context.scale()` and :code:`Context.translate()` methods which can be +used right before the paint command to change how the image will map onto the +pixmap. + + +using a solid colour +-------------------- + +If we want to paint the wallpaper with a single colour instead of an image, we +can replace the call to :code:`context.set_source_surface()` with the +following, where the three arguments correspond to red, green and blue values: + +.. code-block:: python + + context.set_source_rgb(1, 1, 1) + + +see also +-------- + +I learnt a lot reading how these programs handle painting the root window: + + - xsri_ + - fvwm-root_ + + +.. _Qtile: https://github.com/qtile/qtile +.. _xcffib: https://github.com/tych0/xcffib +.. _XCB: https://xcb.freedesktop.org/ +.. _xlib: https://www.x.org/releases/current/doc/libX11/libX11/libX11.html +.. _xplain: https://magcius.github.io/xplain/article/x-basics.html +.. _here: /code/qpaper +.. _cairocffi: https://cairocffi.readthedocs.io +.. _cairo: https://www.cairographics.org/ +.. _urxvt: https://software.schmorp.de/pkg/rxvt-unicode.html +.. _xsri: https://github.com/tjackson/xsri +.. _fvwm-root: https://github.com/fvwmorg/fvwm/blob/master/bin/fvwm-root.c diff --git a/content/notes/foss/qtile_borders_and_pixmaps.rst b/content/notes/foss/qtile_borders_and_pixmaps.rst @@ -0,0 +1,100 @@ +hacking on Qtile: painting complex borders +========================================== + +:date: 2020-05-12 +:summary: Extending Qtile to create funky window border decorations + +The years of checking up on the unixporn subreddit has imprinted in me a want +for a unique and aesthetic desktop. One of the advantages I saw in the Qtile +window manager (while I was rapidly hopping between WMs) is how easy it is to +bend to my will, being made in Python. Recently I set out to satisfy my desire +for more than just single colour, single line window borders. + +The result of this endeavour has two parts. The first is a PR_ I submitted last +week to Qtile that lets users pass their layouts a list of border colours and a +list of border widths to paint multiple borders on their windows. In my head it +was a long time coming, having nice windows like these: + +.. raw:: html + + <img style="box-shadow: 10px 10px 5px black;" alt="Window with triple borders" src="/static/qtile_multi.png"/> + +The code accepts any number of border colours and widths, so if you *really* +wanted to, you could have borders like these: + +.. raw:: html + + <img style="box-shadow: 10px 10px 5px black;" alt="Window with a shitload of borders" src="/static/qtile_multi2.png"/> + + +Has user freedom come too far? + +The PR also aimed to unify all border-drawing code into one callable. This +leads to the second development: you can override this callable - +:code:`xcbq.Window.paint_borders` of Qtile's X11 backend - and replace the +border-painting logic with your wildest dreams. + +Normally, this method would loop over the configured colours and widths, using +them to draw increasingly small filled rectangles centred on a pixmap that will +be used to paint the window's borders. This results in the multi-border effect +above. A function we define to override this method will therefore have access +to a list of colours, as well as the window's geometry including border width, +and a canvas - the pixmap. + +The paintbrush is tycho0's xcffib_, which provides Python bindings to XCB_. The +:code:`xcffib.xproto` module gives us the functions we need to create some +shapes and paint them to our pixmap, which are reminiscent of their C +counterparts such as :code:`xcb_poly_fill_rectangle` from xcb.xproto.h. + +Using this information, we can do whatever we want. A simple example would be +to draw a couple of trapeziums (trapezia_? thefreedictionary suggests either, +TIL) using :code:`xcffib.xproto.POINT` and paint them using and +:code:`FillPoly` in two of the colours. This can create this picture frame-like +appearance: + +.. raw:: html + + <img style="box-shadow: 10px 10px 5px black;" alt="Window with frame" src="/static/qtile_frame.png"/> + +Pretty cool, no? Maybe too simple. + +With some faffing around in GIMP on a screenshot of the `Common Desktop +Environment`_ (CDE), I decoded the design for their old-school 3D borders. I'm +aware I could have hunted down the source code to find out how their borders +are drawn but where's the fun in that? + +The design is largely made of 1-pixel-wide lines, so we can use +:code:`xcffib.xproto.POINT` as before to join them up, then :code:`PolyLine` to +paint them to the pixmap. With a main colour, a lighter shade and a darker +shade, we can get this result: + +.. raw:: html + + <img style="box-shadow: 10px 10px 5px black;" alt="Window with triple borders" src="/static/qtile_cde.png"/> + +Pretty groovy borders if you ask me, and it's a design I can use without +sacrificing a solid, modern window manager. + +I've added these to my Qtile plugin repository qtools_, from which they can be +used by simply importing and enabling the borders plugin with the desired +style: + +.. code-block:: python + + import qtools.borders + borders.enable("CDE") + +The plugins might also serve as a simple starting point to implement more +border designs. + +I'd love to make a small library of different designs that can be used to make +borders but my own creativity is limited. Let me know if you have any design +ideas that you'd want implemented for your own Qtile config! + + +.. _PR: https://github.com/qtile/qtile/pull/1697 +.. _xcffib: https://github.com/tych0/xcffib +.. _XCB: https://en.wikipedia.org/wiki/XCB +.. _trapezia: https://www.thefreedictionary.com/trapezia +.. _`Common Desktop Environment`: https://en.wikipedia.org/wiki/Common_Desktop_Environment +.. _qtools: /code/qtools/tree/qtools/borders diff --git a/content/notes/foss/quick_n_dirty_permissions_container.rst b/content/notes/foss/quick_n_dirty_permissions_container.rst @@ -0,0 +1,56 @@ +a quick and dirty permissions container +======================================= + + +:date: 2020-03-29 +:summary: A quick and dirty permissions container for a single program + + +Although the vast majority of the software installed on my Linux daily driver +is fully free and open source, and in general is trustworthy, over time I've +had to make use of some programs that are closed source and created by entities +who I wouldn't want to trust on my system. + +The quick and easy (lazy) solution I've opted for is to give these programs +their own user and home directory and access to my normal user's X session. + +For example, we can create a user and home directory for steam (as root): + +.. code-block:: sh + + useradd --create-home steam + +We probably want our normal user to be able to launch the program with its user +without needing to enter a password every time. To do this, we need to add a +line equivalent to this to our sudoers rules: + +.. code-block:: sh + + mcol ALL=(steam) NOPASSWD: /usr/bin/steam + +If we were to launch it now, it wouldn't have access to the running X session +owned by our normal user. We can use :code:`xhost` to give the new steam user +access to the X session. This is best wrapped in a script that looks like: + +.. code-block:: sh + + #!/usr/bin/env bash + xhost +SI:localuser:steam + sudo --set-home -u steam /usr/bin/steam $@ + +I call this script :code:`steam` and put it in a folder on my path inside my +home folder. This way, I can just execute :code:`steam` as my normal user and +the program runs as expected in my X session, but its permissions keep it +restricted to its own home folder. + +This setup also lets us set firewall rules specific to the program, as nftables +and iptables support the configuration of rules that match a user's UID. For +example, if we wanted nftables to block connections from steam that weren't +going through a VPN we could add this line to our outbound chain: + +.. code-block:: sh + + oifname $vpn_interface skuid steam accept + oifname != $vpn_interface skuid steam reject + +Simple and great for those who wear tinfoil hats! diff --git a/content/notes/foss/rewritefs.rst b/content/notes/foss/rewritefs.rst @@ -0,0 +1,67 @@ +rewritefs: take back control over your dotfiles +=============================================== + +:date: 2019-08-17 +:summary: take back control over your home folder dotfiles with rewritefs + +Many of us have had the unfortunate inevitability of using a program written by +a sadistic programmer who sticks their program's files right into your neatly +organised home folder. Fortunately, some rebels against the +what-are-`XDG-directory-standards +<https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_ +terrorist group have come up with clever solutions to force all of your hidden +files to stay within the bounds of your .config folder. + +Inspired by the likes of Apache and nginx, `rewritefs +<https://github.com/sloonz/rewritefs>`_ uses arbitrary regex rules to rewrite +(surprise surprise) paths accessed in a mount point. Using a `FUSE +<https://en.wikipedia.org/wiki/Filesystem_in_Userspace>`_ approach, rewritefs +simply mounts a user's a folder and the rest of the functionality is invisible. + +This example ruleset is provided to solve the problem of dotfiles clogging your +home directory: + +.. code-block:: bbcbasic + + m#^(?!\.)# . + m#^\.(cache|config|local)# . + m#^\.# .config/ + +We have one rule per line, rewriting strings matched by the first part to the +content of the second part, with the special rule :code:`.` meaning 'do not +rewrite'. E.g. :code:`.vimrc` becomes :code:`.config/vimrc`. + +To use these rules all we have to do is mount the directory, in this example +our home directory from :code:`/mnt` into :code:`/home`, i.e.: + +.. code-block:: bash + + rewritefs -o config=/mnt/home/me/.config/rewritefs /mnt/home/me /home/me + +It's worth spending a bit of time going through all the junk in $HOME while +setting up a ruleset, as many folders might be more appropriate going into +:code:`.cache` or :code:`.local`. + +A massive plus of careful rulesetting and rewriting the appropriate files into +:code:`.config` is that you can backup or version :code:`.config` in its +entirety while putting things you don't care so much about into :code:`.cache` +or elsewhere. This is much tidier than having to deal with scripting symlinks +into :code:`$HOME` from your dotfiles repo, and more out-of-the-way than +version tracking your home directory directly. + +Another nifty feature is the ability to specify rules on a per-application +basis. For example, you could use this rule to make your annoying colleague's +vim mysteriously replace the letter s for the letter z: + +.. code-block:: bbcbasic + + - /^\S*vim/ + /s/ z + +In such a rule, the line starting with - match the program name, and any +following lines apply only to that scope. + +With great power comes great responsibility, so one must be careful with the +power of rewritefs. Despite its power, however, there do seem to be only few +real applications for such functionality. I would definitely be interested in +hearing what other use people have made for this program. diff --git a/content/notes/foss/ricing_early_userspace.rst b/content/notes/foss/ricing_early_userspace.rst @@ -0,0 +1,128 @@ +ricing early userspace +====================== + +:date: 2020-06-29 +:summary: ricing the TTY + +This weekend I wanted to make the boot process on my Arch Linux machine look +more consistent with the rest of its environment. A while back I configured a +quiet-boot setup to minimise unnecessary steps or messages appearing between +switching on the device and reaching the X11 desktop. The last thing that +remained in making the bootup spotless was the cryptsetup password prompt used +in early userspace to unlock my root partition. It was u g l y. + +After experimenting with plymouth_ and it's various themes (which *do* look +pretty nice), the extra 5 seconds of boot time just isn't acceptable. Like, +come on, I want it to be perfect. I'm a big fan of bitmap fonts and pixel art +so I figured I could hack together some kind of text-based display for the +password prompt using symbols and `box-drawing characters`_. + +Font +---- + +First though, I needed to change the TTY font before reaching that prompt. For +this the `arch wiki`_ had the answer ready so this was a quick job: simply +define :code:`FONT` in :code:`/etc/vconsole.conf` to the name of an available +:code:`psf` font (look inside :code:`/usr/share/kbd/consolefonts` to see +available fonts), then add :code:`consolefont` to the mkinitcpio hooks in +:code:`/etc/mkinitcpio.conf`. + +Drawing the shapes +------------------ + +Looking deeper into `mkinitcpio hooks`_, I discovered that creating your own +hook is actually pretty straight-forward, so I got to work writing one to draw +out the console's display immediately before the password prompt. I simply had +to write a hook to print whatever I wanted and add it to my +:code:`/etc/mkinitcpio.conf`. + +For a simple first-attempt interface, I decided on the message "Welcome" drawn +in big letters in my favourite font, tamzen_, with an input box drawn +underneath for when I'm asked to unlock the root partition. I figured I could +draw the big word by making "pixels" from symbols. + +I got the template by screenshotting my normal xterm (also using the tamzen +font) and scaling it up: + +.. image:: /static/tamzen-Welcome.png + :alt: The word 'Welcome' in tamzen font + +Following this, I typed out a bunch of :code:`echo` commands into my new hook +that would each print a single line of the console, and would together spell +"Welcome" in big letters. For this, I used pairs of block characters (i.e. +"██") as pixels to draw the word pixel-by-pixel. The input box is just an empty +rectangle frame drawn using these pixels underneath the Welcome message. Using +`ANSI escape codes`_ I could move the cursor position into the input box after +printing the display, e.g. printing :code:`"\x1b[32;96H"` will place the cursor +in the 32nd column, 96th row. + +Colours +------- + +Next, I needed to define the colours that all the elements should have: the +background, "Welcome", and the input box. My first instinct was to simply use +more escape codes to set the "current" terminal colour, which would look +something like this: + +.. code-block:: sh + + # This sets the current foreground to colour 0 + COLOUR=0 + echo -en "\x1b[38;5;${COLOUR}m" + + # While this sets the current background to colour 5 + COLOUR=5 + echo -en "\x1b[48;5;${COLOUR}m" + +I added lines like these in places in the pixel-drawing echo lines where the +foreground or background colour was to be changed, and it worked quite well. +However, the problem with this is that the colours that the TTY uses are +restricted to default values predefined by the linux framebuffer (which are +*not* pretty). + +A quick search on changing the TTY's colours lead me to `this askubuntu +question`_ describing how to change the colours that correspond to the +consoles's 0-15 colour definitions. Based on these lines (more escape codes!) I +cobbled together some logic at the top of my hook to redefine the TTY's colour +palette using the same hexadecimal colour values I use for my X desktop, and +stripped out the previous escape codes I was using. + +Great! The colours were being set! + +Right as I was planning to put the colour logic into a small package, I came +across Evan Purkhiser's `mkinitcpio-colors`_, which is a more polished version +of what I would have made to set the TTY colours at boot using variables +defined in :code:`/etc/vconsole.conf`. I installed this from the AUR so that my +welcome message would use my usual terminal colour scheme. + +Finishing up +------------ + +The output of fsck is printed out after entering the password. `Apparently`_ +this can be silenced, but I preferred to simply reposition the cursor into a +second line of the inpux box so it can be printed there. For this I added a +second hook after the :code:`encrypt` hook. + +This is the final version: + +.. raw:: html + + <video controls width="100%" class="align-center" src="/static/boot_welcome.webm"></video> + +Looking great, huh! + +I've saved the two mkinitcpio hooks in a git project here_, which installed +alongside `mkinitcpio-colors`_ should be enough to recreate this bootup. I +haven't tested it with any other font sizes though, so the thought of trying it +with a different size worries me. + +.. _plymouth: https://wiki.archlinux.org/index.php/Plymouth +.. _mkinitcpio-colors: https://github.com/EvanPurkhiser/mkinitcpio-colors +.. _`box-drawing characters`: https://en.wikipedia.org/wiki/Box-drawing_character +.. _tamzen: https://github.com/sunaku/tamzen-font +.. _`arch wiki`: https://wiki.archlinux.org/index.php/Linux_console#Fonts +.. _`mkinitcpio hooks`: https://wiki.archlinux.org/index.php/Mkinitcpio#HOOKS +.. _`ANSI escape codes`: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +.. _`this askubuntu question`: https://askubuntu.com/questions/147462/how-can-i-change-the-tty-colors +.. _Apparently: https://wiki.archlinux.org/index.php/Silent_boot#fsck +.. _here: /code/mkinitcpio-welcome diff --git a/content/notes/foss/ubuntu_touch_pinephone.rst b/content/notes/foss/ubuntu_touch_pinephone.rst @@ -0,0 +1,99 @@ +ubuntu-touch on the pinephone: first impressions +================================================ + +:date: 2020-02-11 +:summary: My first impression of testing ubuntu-touch on the pinephone + BraveHeart release + +My Braveheart edition PinePhone_ arrived a couple of days after a *long* +journey and I've finally got some time to play with it. The first thing I +wanted to do was test drive ubuntu-touch_, the mobile version of Ubuntu +maintained by the `UBports community`_, and I figured I'd write about my +experiences. + + +interface and responsiveness +---------------------------- + +My expectations were sensibly quite low given that it's still early days for +the pinephone, but I've got to say, I was pleasantly surprised by how +responsive and un-frustrating the UI is at this stage. + +The keyboard, which sometimes was an issue for me when I tested postmarketOS_, +works 99% perfectly. There does appear to be a minor bug where keyboard sounds, +when enabled in the settings, are only triggered on the first key press after +the keyboard is opened. A low priority bug to be sure. + +The app draw - displayed when swiping in from the left of the screen - feels +intuitive and looks great. Much more convenient access to apps than android +has. Swiping in the from the right of the screen gives a view of all running +apps, allow you to swipe them up to close them. These two very gesture-based +features are smooth and responsive and don't feel clunky at all. + +.. image:: /static/UT_pinephone.jpg + :width: 60% + :align: center + :alt: ubuntu-touch on pinephone: view of running apps + +Dragging the bar at the top of the screen down (although *not* swiping down +from off the top of the screen) dispays a number of informational tabs much +like in android and iOS. There has been a little :fas:`envelope` icon on the +bar since it first booted suggesting a notifications pending, but the +notifications tab is pretty convinced that that is not the case. + +.. raw:: html + + <video controls width="60%" class="align-center" src="/static/UT_pinephone.webm"></video> + + +hardware +-------- + +Much of the hardware is perfectly under control: + + - The volume buttons do indeed change the volume. + - The lock/power button locks the screen, and turns it back on again. + - Screen brightness control works. + +But some is not: + + - Wifi appears to connect and work beautifully initially, but then fails. + There is a working fix for when it does, however (discussion on `ubports + <https://forums.ubports.com/topic/3791/wifi-issues-on-pinephone-braveheart/8>`_ + and `pine64 <https://forum.pine64.org/showthread.php?tid=8969>`_). + - The *Rotation Lock* setting appears to do nothing; perhaps the gyro doesn't + quite work yet. + - Currently pinephone ubuntu-touch is stuck in headphone mode, and has other + issues with the speakers (`pine64 discussion + <https://forum.pine64.org/showthread.php?tid=8923>`_). + - There also appears to be a an issue with the battery usage, that `others + have also experienced`_, though this does appear to be an issue that can be + fixed by software changes (fortunately!). + + +software +-------- + +It does seem pretty strange, SSH-ing into and exploring a phone from my +computer. Using apt to install tor or vim or whatever the hell I want. On my +phone. That novelty is yet to wear off. + +A dozen or so apps came pre-installed with ubuntu-touch, and more can be +downloaded from the *OpenStore*. Many of these are simple web-apps, which has +enabled a huge range of 'apps' to be created already. Though some are buggy +(unsurprising, considering the webpages they display probably weren't designed +with this in mind), they do serve as stepping stones between having no apps and +eventually having native apps :fas:`praying-hands`. + +We are in the midst of an incredibly exciting time for linux smart phones, and +I can't wait to see what the next few months bring us. I'm sure very soon we +will have all the necessary components of the pinephone working smoothly, and +once people make that transition to using it as a(n imperfect, yet functional) +daily driver, the new apps and functionality will hopefully boom. + + +.. _PinePhone: https://wiki.pine64.org/index.php/PinePhone +.. _ubuntu-touch: https://ubuntu-touch.io +.. _`UBports community`: https://ubports.com +.. _postmarketOS: https://postmarketos.org +.. _`others have also experienced`: https://forum.pine64.org/showthread.php?tid=9063 diff --git a/content/notes/foss/xonsh_first_impressions.rst b/content/notes/foss/xonsh_first_impressions.rst @@ -0,0 +1,155 @@ +xonsh shell: first week, first impressions +========================================== + +:date: 2019-06-29 +:summary: xonsh shell: first week, first impressions + +If you've ever said "Oh boy I wish I could use all that cool pythonic syntax +and algebra in my shell!" then `xonsh <https://github.com/xonsh/xonsh>`_ is +definitely worth checking out. + +For the past week or so I've been using xonsh, a "python-powered, +cross-platform, Unix-gazing shell language and command prompt" as my main +shell. Exploring it has been an adventure and I've learnt much, but after one +week I can tell I've only scratched the surface. + +It is a superset of Python, extending it to include many aspects of shell +syntax and behaviour. This makes for a very powerful yet flexible and usable +language. + +First let me say it is awesome and worth a play if you like Python, but, +unfortunately, it doesn't quite feel as stable as bash or zsh. However, the +`docs <https://xon.sh/>`_ and github issues are fantastic resources to +configure and extend it to be just as (if not more) usable as other shells. + +powerful cli syntax +------------------- + +If you've ever gone from bash to zsh, it was likely a seamless change. In +contrast, the first time playing with xonsh's syntax requires one eye on the +docs. This is understandable, as it was clearly no easy feat creating the +lovechild of bash and python. Check this out: + +.. code-block:: bash + + path = $HOME + '/.xonshrc' + if not !(test -f @(path)): + return 'Error: %s not found.' % path + +Variables are pythonic; dollar signs are not required, but they prefix +environmental variables by convention. As demonstrated above, we can modify +variables, perform tests and format strings as in python. + +What's cool here are the syntax blocks we see on the second line: + +- :code:`@(...)` treats its contents more like python, so @(path) expands to + the path string. +- :code:`!(...)` treats its contents with a shell syntax. The result from a + :code:`!(...)` block can be used in tests based on its return code, as above, + but the object also contains the standard out and error streams, process id, + and some other information. + +This illustrates a central idea in xonsh: a contextual distinction between +python-like (*python-mode*) and shell-like (*subprocess-mode*) executions. + +xonsh also uses python's logical keywords, which act as drop in replacements +for :code:`&&` and :code:`||`: + +.. code-block:: bash + + a > 10 and echo "a is greater than 10" + +This is only a tiny sample but you get the idea. The shell-like syntax is very +comfortable, and it feels natural to use it alongside python code. + +setup +----- + +Much like zsh, the first time it is run, a wizard will guide you through +initial setup and then get out of the way. This wizard generates your +:code:`~/.xonshrc` file which is run when you launch xonsh. + +The first step is 'Foreign Shell Setup'. This feature aims to help users use +pre-existing bash or zsh configs in xonsh, though currently there are some +limitations. For example, imported aliases that 'cd /some/path' don't seem to +propagate their directory change to the shell. Surfing the github issues it +seems there are a couple of other minor bugs that prevent use of things like +:code:`;` or :code:`&&` in aliases. + +The wizard next helps you set up other useful features and behaviour you see in +other shells: + +- syntax highlighting +- command completion +- vi-mode +- prompts +- plugins + +Most of these are simply controlled by variables set in your :code:`.xonshrc`. +Overall the xonsh initial setup is painless. + +plugins +------- + +xonsh's plugin framework allows for easy extension with xonsh or pure python +scripts called *xontribs*. Some xontribs come pre-packaged but you can download +or create more. + +I was pleased to find that the zsh plugin `z <https://github.com/rupa/z>`_ had +been `ported <https://github.com/astronouth7303/xontrib-z>`_ to xonsh, and a +read of its code makes it clear that porting other zsh plugins would be +relatively straightforward. + +xontribs can do anything you want, such as hooking functions to events, adding +more blocks that can be used in your prompt, adding functional aliases, you +name it. + +For example, we can create new aliases like this: + +.. code-block:: bash + + def _copy(args=None, stdin=None): + """ Copy stdin or args to the clipboard """ + text = stdin.read() if stdin else ' '.join(args) + echo -n @(text.strip()) | xclip -selection clipboard + return + + aliases['copy'] = _copy + +Aliases like this send the argument list and stdin to the function as the first +two arguments. This allows us to use shell-like syntax to call the function, +i.e.: + +.. code-block:: bash + + echo 'this will be copied' | copy + copy 'so will this' + +This seems much more natural in a shell. + +Setting up a comfy environment in xonsh has led to me reading all about +`prompt_toolkit <https://github.com/prompt-toolkit/python-prompt-toolkit>`_. +prompt_toolkit is a really powerful python library for controlling what appears +in the terminal window, and does the heavy lifting for xonsh when it comes to +drawing prompts, watching for key bindings, and more. For these reasons, it is +often useful to use its tools directly, rather than xonsh, to extend your xonsh +setup. + +thoughts +-------- + +Overall I think xonsh is awesome and I'm excited to see how xonsh develops in +the future. It's a powerful concept, and the motivation appears to be there - +from both core devs and contibuting users - to iron out any issues and improve +the experience. + +It's pretty cool that you can use python libraries as in python: + +.. code-block:: python + + from pykeyboard import PyKeyboard + +Though I haven't actually found a use for that yet! + +It is usable and practical as a daily driver, so I will definitely continue to +use it and see how deep the rabbit hole goes. diff --git a/content/notes/raspberrypi/git_backups.rst b/content/notes/raspberrypi/git_backups.rst @@ -0,0 +1,143 @@ +minimal but complete nightly backups to raspberry pi +==================================================== + +:date: 2019-04-25 +:summary: minimal but complete nightly backups to raspberry pi + +I searched far and wide for an effective backup solution for my laptop that +wasn't overkill. + +When I first converted to linux I used programs like `timeshift +<https://github.com/teejee2008/timeshift>`_ and `backintime +<https://github.com/bit-team/backintime>`_ and although they worked well, I +wanted more fine grain control over what was backed up and less meat on what +should be a lightweight background process. + +In my quest I stumbled upon this `Hacker News discussion +<https://news.ycombinator.com/item?id=11070797>`_ about turning your home +directory into a git working tree, where its .git folder is in some other +folder, out of the way. This setup stops your home git repo from getting in the +way of any git folders you have in your home directory, but has some problems: + +- you need to remember to use the alias or folder names for this git repo to + use the correct .git folder and working tree + +- working tree changes won't get picked up by utilities like zsh prompts or + ranger, so you might not always remember when things have changed + +- to not pick up every single file in your home directory, these setups + generally ignore untracked files. This requires you to remember to manually + add any new config files in the future + +Maybe I just have a poor memory. + +A slight adjustment of this is to just use the :code:`$HOME/.config` folder for +what it was intended: storing your config files. Turning this folder into your +dotfiles repository, you can treat it as any other git repo. + +Most programs can keep their configs in this folder, but the few that don't can +simply have their configs symlinked into place from the repo. This symlinking +make up the only scripting that would be required to install the repo into +another machine. + +It makes sense to git track configuration files, but your photos, music and +documents? git is not going to like that. This calls for a small bit of +scripting to rsync these folders and push the .config repo to a remote +location. + +First we need the location: a raspberry pi, ticking away somewhere hidden in +your flat, wired to an external hard drive. With one up and running, stick your +hard drive into the pi's :code:`/etc/fstab`: + +.. code-block:: conf + + /dev/disk/by-uuid/1234-abcd /mnt/backups ext4 auto,rw,nofail,noatime,nosuid,noexec 0 0 + +Make sure your user has write access to the hard drive and then create the +remote repo for the configs: + +.. code-block:: bash + + mkdir -m 1777 /mnt/backups/dotfiles + cd /mnt/backups/dotfiles + git init --bare + +Make any additional folders for each folder in your home directory that will be +backed up: + +.. code-block:: bash + + mkdir /mnt/backups/{documents,music,pictures} + +While we're on the rasperry pi, let's configure some hard drive power +management so the hard drive spins down soon after the daily backup to save +some power. First install :code:`hdparm`, then set the power management and +spin down settings (see the `Arch wiki +<https://wiki.archlinux.org/index.php/Hdparm#Power_management_configuration>`_ +for more info). + +.. code-block:: bash + + apt-get install hdparm + hdparm -B 127 /dev/disk/by-uuid/1234-abcd # best performance permitting spin-down + hdparm -S 241 /dev/disk/by-uuid/1234-abcd # spin down after 30 minutes + +Back on the laptop, we write the backup script, substituting where necessary: + +.. code-block:: bash + + pi_IP=192.168.1.2 + pi_drive="pi@$pi_IP:/mnt/backups" + + rsyncargs="--recursive --links --times --partial --delete --compress --perms --verbose" + + # this environmental variable lets cron use notify-send for user ID 1000 + export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus + + if ping $pi_IP -c 1 + then + notify-send "Nightly backup to pi" "Backup starting..." + else + notify-send "Nightly backup to pi" "Couldn't ping pi" + exit # exit if we can't ping the pi - are you still at work? + fi + + { + # dotfiles + git --git-dir=$HOME/.config/.git/ --work-tree=$HOME/.config add -A + git --git-dir=$HOME/.config/.git/ --work-tree=$HOME/.config commit -m "cron commit" + git --git-dir=$HOME/.config/.git/ --work-tree=$HOME/.config push || errors=true + + # other folders + rsync $rsyncargs \ + --exclude=path/to/folder/you/want/to/exclude \ + $HOME/documents/ $pi_drive/documents/ || errors=true + + rsync $rsyncargs $HOME/pictures/ $pi_drive/pictures/ || errors=true + rsync $rsyncargs $HOME/music/ $pi_drive/music/ || errors=true + + # catch the output into a file in case we need it + } | tee -a /tmp/BACKUPLOG-$(date +%y%m%d-%H%M%S) + + # notify with outcome + if ${errors:-false} + then + notify-send "Nightly backup to pi" "Completed with error(s)" + else + notify-send "Nightly backup to pi" "Completed successfully." + fi + +This can easily be run by cron, e.g.: + +.. code-block:: bash + + # backup nightly at 9.30 pm + 30 21 * * * $HOME/bin/nightly_backup + +Now every night at 9.30 your home directory will get backed up to the raspberry +pi if you're at home. If it doesn't work for whatever reason, you'll get +notified of the error and you can check out the log in the /tmp folder. + +Complement this with `etckeeper +<https://wiki.archlinux.org/index.php/etckeeper>`_ to back up system files and +this simple and lightweight setup can restore your machine in no time. diff --git a/content/notes/raspberrypi/pi_home_music_system.rst b/content/notes/raspberrypi/pi_home_music_system.rst @@ -0,0 +1,194 @@ +music that follows you around the house: part 1. streaming to multiple players +============================================================================== + +:date: 2019-09-21 +:summary: music that follows you around the house: part 1. + +Often when I come across an amazing new record I'll play it on repeat all day, +every day for a week or two. My laptop's speakers are pretty decent, but +unfortunately they don't levitate nearby as I move around my home. Though that +is a problem I don't intend to solve, I figure the next best thing is have one +device stream music all around my home, but have it playing only in the room +that I am in. + +This post is the first step on the way to set up this system. The aim for the +final setup is to have three parts: + +* A central music server that has local access to my music library. +* Playback clients that, when somebody is in the room, play the music through connected speakers. +* Control clients that use the `MPD protocol + <https://www.musicpd.org/doc/html/protocol.html>`_ to control playback from + any other device on the network (i.e. PCs and phones). + +I opted to use Raspberry Pis for the server and playback clients, for ease of +use and low energy consumption. By the end, playback clients will (hopefully) +be able to fully disconnect their speakers from the power when they are not +playing. + + +the music server +---------------- + +Starting with a pi that we can access over SSH, we need to give it access to +our music library (remember to stick the storage disk into :code:`/etc/fstab` +with auto-mounting) and install mpd: + +.. code-block:: bash + + apt-get install mpd + +Next, we need to configure mpd to stream its music throughout the local +network. There are a number of ways to do this, each with their pros and cons. +The most straightforward method is to use mpd's built-in httpd server. + +To enable this, we need to add the following settings to :code:`/etc/mpd.conf` +(or alternatively :code:`$HOME/.config/mpd/mpd.conf`): + +.. code-block:: bash + + music_directory "/path/to/music" + db_file "/path/to/database" + log_file "syslog" + state_file "/path/to/state_file" + + audio_output { + type "httpd" + name "Raspberry Pi Music Server" + encoder "flac" + format "44100:16:1" + always_on "yes" # Stay connected to clients when music stops + tags "yes" # Also stream tags + port "8000" + max_clients "0" # Allow unlimited clients + } + + bind_to_address "any" # allow remote access + port "6600" + restore_paused "yes" + auto_update "yes" + user "pi" # run as user rather than root + +This configuration makes mpd output audio via http on port 8000. Playback +clients can then listen in on this stream and play it on connected speakers. + +On port 6600 we have the usual mpd protocol communication with the server, +which we can use to control music playback. + +I've used :code:`encoder "flac"` for lossless streaming, though other options +can be used (`described here +<https://www.musicpd.org/doc/html/plugins.html#encoder-plugins>`_). + +mpd is packaged with a systemd service that works well to manage its process +and logging. By using :code:`user "pi"` above we can take advantage of the +preconfigured sandboxing settings in this service by enabling it as is, before +the process is transferred to a non-root user. Enable it with: + +.. code-block:: bash + + systemctl enable --now mpd.service + +If we didn't use :code:`/etc/mpd.conf` and instead wanted to use a different +config file, we must change this line in :code:`/etc/default/mpd` to add the +config's path: + +.. code-block:: bash + + MPDCONF=/etc/mpd.conf + +Now on to the clients. + + +playback clients +---------------- + +mpd also works great for playing back http streams, so my playback clients are +also all Raspberry Pis with the mpd service enabled. + +However, on these guys we need to configure the mpd config differently: + +.. code-block:: bash + + bind_to_address "127.0.0.1" + log_file "syslog" + state_file "/path/to/state_file" + + audio_output { + type "alsa" + device "hw:0,0" + name "speaker" + } + + restore_paused "no" + auto_update "no" + +We don't need remote access to mpd on these clients, as they just relay +whatever is being played from the main server. As we aren't managing a library +with this mpd instance we don't need to specify any paths for the music +directory or database. For this same reason we don't need to enable +auto_update. We also don't want to restore mpd into a paused state, because we +want to use only the main server to control playback. + +With this audio_output configuration, music is output through the headphone +jack using alsa and out to speakers. + +To make these clients listen to the http stream coming from the main server, +simply add its URL to the client's playlist and enable repeat. We can do this +using mpc: + +.. code-block:: bash + + apt-get install mpc + mpc repeat 1 + mpc add http://<server ip address>:8000 + +Now they'll stay connected to the http stream, playing it non-stop, so pausing +the main server's mpd pauses playback by all clients. + +control clients +--------------- + +These clients are other devices we have on the network that we want to use to +control playback, such as phones or laptops. To do this, we communicate with +the main server using the MPD protocol to tell it what to do. Changes to +playback are propagated to the playback clients that are listening in on the +http stream. + +To control the music, we simply need an mpd client pointed at the music serving +pi. My favourite client is ncmpcpp, which can be installed from most distro +package managers onto your PC. Using these arguments we can control the server: + +.. code-block:: bash + + ncmpcpp --host <server ip address> --port 6600 + +I have not yet tested out many mpd clients so I will save that for a later +post. ncmpcpp is great for controlling the music from my PC, but if guests +wanted to control the music then a web interface served up from the main +raspberry pi might be a more convenient way for them to control it. + +With the playback clients listening to the http stream, when we start playback +from the main server and if we haven't had any bad luck then we should be +hearing the music play! + +If you don't hear anything playing from the pi's speakers, try with this line +added to :code:`/boot/config.txt`: + +.. code-block:: bash + + dtparam=audio=on + + +next steps +---------- + +* Fade in/out volume as somebody walks in/out of each room. +* Explore different mpd client options. +* Address possible future issues with synchronisation of multiple players. If + this becomes an issue, I may look into alternatives to the httpd output, such + as the `real-time transport protocol + <https://en.wikipedia.org/wiki/Real-time_Transport_Protocol>`_ via PulseAudio + or VLC, or the more purpose-built server `snapcast + <https://github.com/badaix/snapcast>`_. + +In any case, the steps in this post alone are enough to set up a sweet remote +controlled speaker system! diff --git a/content/notes/tor/onion_location_headers.rst b/content/notes/tor/onion_location_headers.rst @@ -0,0 +1,47 @@ +tor browser can now redirect clearnet sites to onion services +============================================================= + +:date: 2020-06-03 +:summary: tor browser has a new feature that redirects clearnet sites to their + onion services + +On Monday the Tor Project released_ version 9.5 of the tor browser and with it +a long-awaited new feature. The update will tell you if a website you visit in +the browser has a corresponding onion service. + +Initially, when you visit one of these websites (such as this one), the URL bar +will display this label: + +.. image:: /static/onion_location.png + :alt: Tor Browser displaying '.onion available' in URL bar. + +Clicking on this label will take you to the onion service version of the +website. + +And the cool part: you can set tor browser to always redirect you to these +onion services, by finding this preference and setting it to 'Always': + +.. image:: /static/onion_location_pref.png + :alt: Tor Browser onion-location pref + +This functionality depends on websites setting a :code:`Onion-Location` HTTP +header containing the link to their onion service. For example, all I had to do +was add this line into my nginx.conf in my clearnet site's server block: + +.. code-block:: sh + + add_header Onion-Location http://mcolxyzogp3cy4czf52oa2svu2vjge3otm3shxmtvwshyum47sis3iid.onion$request_uri; + +(In case of overflow: that is one line.) + +That header is all it takes for tor browser users to use onions by default on +all sites that host them, removing any need for manual identification of +whether websites that they visit host onion services. + +By improving awareness and accessibility of onion services in this way, not +only can we discover more onions for popular websites that may have them, but +we legitimise and encourage the use of onion services as a simple means of +making the web more accessible for those who experience internet censorship. + + +.. _released : https://blog.torproject.org/new-release-tor-browser-95 diff --git a/content/notes/tor/tor_newsboat.rst b/content/notes/tor/tor_newsboat.rst @@ -0,0 +1,58 @@ +read news and reddit anonymously with tor and newsboat +====================================================== + +:date: 2019-04-21 +:summary: read news and reddit anonymously with tor and newsboat + +ncurses based programs can be a bit hit-or-miss but newsboat is one of the +kings. + +`newsboat <https://newsboat.org/>`_ is an RSS reader, and as far as I'm +concerned, a reddit interface, as you can turn any reddit feed into an RSS +feed. Just stick something like this into your :code:`~/.config/newsboat/urls` +file: + +.. code-block:: conf + + https://www.reddit.com/r/onions/new/.rss?sort=new "~/r/onions" + +Each time your run newsboat you'll get posts to that subreddit sorted by new - +easy subscription without going near a web browser. + +The key to torifying newsboat is its socks5 proxy, which is enabled with these +config settings in :code:`~/.config/newsboat/config`: + +.. code-block:: conf + + use-proxy yes + proxy-type socks5h + proxy 127.0.0.1:9050 + +The proxy IP and port of course must correspond to the socks5 proxy set up in +your torrc config. The **h** in :code:`socks5h` is important - this makes tor +do all its own DNS resolutions so they aren't leaking out the usual route. + +You can take the functionality to the next level by opening any feed items in +tor-browser directly from newsboat, too. First, stick this into your newsboat +config: + +.. code-block:: bash + + browser "nohup tor-browser --allow-remote %u > /dev/null 2>&1 &" + +and set the setting :code:`browser.tabs.loadDivertedInBackground` in +tor-browser's :code:`about:config` to false. This allows feed items to be +opened from newsboat when hitting :code:`o` without tor-browser stealing focus. + +nohup is needed so tor-browser can stay alive even after your close newsboat, +if newsboat opened it. :code:`--allow-remote` will launch tor-browser and allow +it to receive remote commands to open urls as we are doing here. If +tor-browser, with this option, is already running then the url will be opened +in that instance but newsboat will stay focussed. The rest of the line sends +all output to oblivion and backgrounds the command so that it doesn't disrupt +your newsboating. + +With this setup I just open newsboat once a day and give it a couple minutes to +update its feeds while I do something else. Then I can quickly comb through +everything, opening anything I want to read more about in tor-browser as I go +and deleting the rest. diff --git a/content/notes/tor/tor_vpn_nftables_killswitch.rst b/content/notes/tor/tor_vpn_nftables_killswitch.rst @@ -0,0 +1,152 @@ +nftables as a tor and VPN killswitch +==================================== + +:date: 2019-05-05 +:summary: nftables as a tor and VPN killswitch + +Truth be told, I never got the hang of iptables. nftables has a much nicer +syntax and its improvements over iptables will lead to its dominance... +eventually. + +This nftables configuration restricts outward network traffic to a locally +running tor client, running as the user :code:`tor`, and a VPN interface, as two +independent streams of traffic. This way I can allow any programs that aren't +so tor friendly to use the VPN, and everything to go through tor directly. +The whole file can be found `here <{static}/static/nftables.conf>`_. + +Usually the configuration file is at :code:`/etc/nftables.conf`. + +First, we need to flush any current rules: + +.. code-block:: Java + + flush ruleset + +Next, we create our first :code:`table`, which is of type :code:`inet`, and +named :code:`restricted`. This type means is it applies to IPv4 and IPv6 both. + +.. code-block:: Java + + table inet restricted { + chain inbound { + type filter hook input priority 0; + +Within the table, we define our first :code:`chain` with the name +:code:`input`. We specify the chains behaviour on the first line in the chain. +This chain is of type :code:`filter`, i.e. it will filter packets. We then give +it the :code:`input` hook so it will act on incoming packets. + +Priorities can be set to order the chains relative to some internal Netfilter +operations, see the `nftables wiki +<https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_priority>`_ +for more info. For a non-routing setup such as this one, priority of 0 works +for everything, though it is important to note that packets will be tested +against all chains using the relevant hook. + +Next is the first rule. It starts with a 'selector', which here is :code:`ct` +for connection tracking, which uses packet metainformation to match packets. + +.. code-block:: Java + + ct state {established, related} accept + +This rule matches packets by **state**, and matches those with state +:code:`established` or :code:`related`. + +The rule ends with an action, in this case :code:`accept`. This rule will allow +incoming packets from connections we've previously established, which means +we've already allows them and they're safe. This accounts for most traffic so +is best put first. + +Next, we accept packets with incoming interface name :code:`lo`, the loopback +interface, which is required for many programs to work. Simple! + +.. code-block:: Java + + iifname lo accept + +Next, we match packets by :code:`ip protocol`, of type :code:`icmp`. We match +these more specifically by :code:`icmp type` with type :code:`echo-request`, +and accept them. These are pings, which are fine to allow. + +.. code-block:: Java + + ip protocol icmp icmp type echo-request accept + reject with icmp type port-unreachable + } + +We end the chain by rejecting all other inputs with the 'port unreachable' +message. + +We then create our output chain, another filter but this time with the +:code:`output` hook. Again, we should accept all traffic on the loopback +interface. + +.. code-block:: Java + + chain outbound { + type filter hook output priority 0; + oifname lo accept + +Now we start restricting our outward traffic. This rule matches connections +being output from my wireless interface, :code:`wlan0`, so substitute the name +of your main interface there. It accepts all traffic on this interface destined +for the address listed, for example, the VPN server you're connecting to, and +any local devices you wouldn't want to restrict. + +.. code-block:: Java + + oifname wlan0 ip daddr { 123.456.789.123, 192.168.1.3 } accept + oifname wlan0 skuid tor accept + +On the second line, we allow outbound connections on the same interface where +the UID of the originating socket (:code:`skuid`) is :code:`tor`. With a local +tor client run as the user :code:`tor`, its connections will be accepted here. + +.. code-block:: Java + + oifname wg0 accept + reject + } + +It is definitely getting straightforward. Here we allow outbound connections on +the :code:`wg0` interface, which is for my wireguard VPN. If your VPN might use +a :code:`tun0` interface, in which case substitute that. + +Some commercial VPNs provide a proxy within their VPN tunnels, which can be +used to further restrict what programs can can access the internet through the +VPN. To do this, add :code:`ip daddr <proxy ip>` before the accept, and make +any allowed programs use this proxy address for connections. + +We then close the chain rejecting all other traffic, blocking it from leaving +your device. + +.. code-block:: Java + + chain forward { + type filter hook forward priority 0; policy drop; + } + } + +Lastly, as we don't need to do any forwarding of packets we can create an empty +chain with the hook :code:`forward`. This declaration also contains a default +action, with :code:`policy drop`. We then close the table with :code:`}`. + +To enable this firewall, we can run this command with your config path substituted: + +.. code-block:: bash + + nft -f /etc/nftables/restricted.conf + +Most linux distros will package a systemd service file which can be edited and +enabled to set up this firewall at boot. + +The only connections that can go out to the internet are connections on the VPN +interface and any connections that the tor client makes, which can be accessed +by programs using it as a proxy. This way we can ensure we know exactly what +connections are allowed out and all connections that leave your machine are +encrypted while in your ISP's hands. + +I highly recommend the nftables wiki especially the `quickstart guide +<https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes>`_ +once you're comfortable with the syntax. diff --git a/content/pages/404.rst b/content/pages/404.rst @@ -0,0 +1,4 @@ +(ノಠ益ಠ)ノ彡┻━┻ +=============== + +:save_as: (ノಠ益ಠ)ノ彡┻━┻.html diff --git a/content/pages/about.rst b/content/pages/about.rst @@ -0,0 +1,25 @@ +about this website +================== + +:save_as: about.html +:template: blogroll + +This blog is a place for me to jot down and share my notes on free and open +source software projects I create or stumble upon, on programming, and about +digital freedom and privacy. + +I maintain this website for fun, but also want to support and encourage others +to use personal websites as an independent and decentralised means to share +ideas and communicate. + +Feel free to reach out to me! I can usually be found hanging around on mastodon +at `@mcol@fosstodon.org <https://fosstodon.org/@mcol>`_, mcol on IRC or you can +email me at :code:`mcol` at :code:`posteo` dot :code:`net` [`pgp key`_]. + + +blogs i like +------------ + +*in random order* + +.. _`pgp key`: {static}/static/pub.asc diff --git a/content/static/UT_pinephone.jpg b/content/static/UT_pinephone.jpg Binary files differ. diff --git a/content/static/UT_pinephone.webm b/content/static/UT_pinephone.webm Binary files differ. diff --git a/content/static/boot_welcome.webm b/content/static/boot_welcome.webm Binary files differ. diff --git a/content/static/nftables.conf b/content/static/nftables.conf @@ -0,0 +1,69 @@ +#!/usr/bin/nft -f +# +# Restricted nftables configuration: +# - Limit inbound traffic +# - Allow outbound traffic to VPN's socks5 proxy +# - Allow outbound traffic from UID 'tor' +# + + +# +# Definitions +# + +# VPN server +define vpn_ip = 123.456.789.123 + +# individual IPs to be unrestricted +define exceptions = { 192.168.0.0/24, 192.168.1.0/24 } + +# +# Rules +# + +# clear all rules +flush ruleset + +table inet restricted { + + chain inbound { + type filter hook input priority 0; + + # allow established/related connections + ct state {established, related} accept + + # allow localhost traffic + iifname lo accept + + # echo requests (pings) + ip protocol icmp icmp type echo-request ct state new accept + + # reject everything else + counter reject with icmp type port-unreachable + } + + chain outbound { + type filter hook output priority 0; + + # allow localhost traffic + oifname lo accept + + # allow VPN and exceptions directly + oifname wlp2s0 ip daddr { $vpn_ip, $exceptions } accept + + # allow all traffic from UID tor + oifname wlp2s0 skuid tor counter accept + + # allow wireguard traffic but only if destined to proxy + oifname wg0 ip daddr { 10.64.0.1 } accept + #oifname wg0 accept + + # reject remaining + counter reject + } + + # no forwarding + chain forward { + type filter hook forward priority 0; policy drop; + } +} diff --git a/content/static/onion_location.png b/content/static/onion_location.png Binary files differ. diff --git a/content/static/onion_location_pref.png b/content/static/onion_location_pref.png Binary files differ. diff --git a/content/static/pub.asc b/content/static/pub.asc @@ -0,0 +1,109 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=/Cm+ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/content/static/qtile_cde.png b/content/static/qtile_cde.png Binary files differ. diff --git a/content/static/qtile_frame.png b/content/static/qtile_frame.png Binary files differ. diff --git a/content/static/qtile_multi.png b/content/static/qtile_multi.png Binary files differ. diff --git a/content/static/qtile_multi2.png b/content/static/qtile_multi2.png Binary files differ. diff --git a/content/static/tamzen-Welcome.png b/content/static/tamzen-Welcome.png Binary files differ. diff --git a/makefile b/makefile @@ -0,0 +1,88 @@ +PY?=python3 +PELICAN?=pelican +PELICANOPTS= + +BASEDIR=$(CURDIR) +INPUTDIR=$(BASEDIR)/content +OUTPUTDIR=$(BASEDIR)/output +CONFFILE=$(BASEDIR)/pelicanconf.py +PUBLISHCONF=$(BASEDIR)/publishconf.py + +SSH_HOST=mcol.xyz +SSH_PORT=22 +SSH_USER=mcol +SSH_TARGET_DIR=/home/mcol/mcol.xyz +SSH_TARGET_DIR_ONION=/home/mcol/mcol.onion + +DEBUG ?= 0 +ifeq ($(DEBUG), 1) + PELICANOPTS += -D +endif + +RELATIVE ?= 0 +ifeq ($(RELATIVE), 1) + PELICANOPTS += --relative-urls +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 ssh_upload upload the web site via SSH ' + @echo ' make rsync_upload upload the web site via rsync+ssh ' + @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: clean + $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) + +clean: + [ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR) + +regenerate: + $(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) + +serve: +ifdef PORT + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) +else + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) +endif + +serve-global: +ifdef SERVER + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) -b $(SERVER) +else + $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) -b 0.0.0.0 +endif + + +devserver: + sed -i 's/^ONION[\ ]*=.*/ONION = False/1' $(CONFFILE) +ifdef PORT + $(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) +else + $(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) +endif + +publish: clean + $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS) + +ssh_upload: publish + scp -P $(SSH_PORT) -r $(OUTPUTDIR)/* $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) + +rsync_upload: publish + rsync -e "ssh -p $(SSH_PORT)" -P -rczz --cvs-exclude --delete --chmod=D755,F644 \ + $(OUTPUTDIR)/ $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) + + +.PHONY: html help clean regenerate serve serve-global devserver stopserver publish ssh_upload rsync_upload diff --git a/pelicanconf.py b/pelicanconf.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # + + +from __future__ import unicode_literals +import os +import sys +sys.path.append(os.curdir) +from blogroll import BLOGROLL + + +SITEURL = '//' +AUTHOR = 'mcol' +SITENAME = 'mcol.xyz' +PATH = 'content' +TIMEZONE = 'UTC' +THEME = '/home/mcol/git/rice-theme' +PAGE_PATHS = ['pages',] +ARTICLE_PATHS = ['notes',] +DEFAULT_DATE_FORMAT = '%Y-%m-%d' +#STATIC_PATHS = ['static', 'favicon.png'] +STATIC_PATHS = ['static'] +PLUGIN_PATHS = ["/home/mcol/git", "/home/mcol/git/pelican-plugins"] +PLUGINS = [] +RELATIVE_URLS = True + +ARCHIVES_TITLE = 'notes' +ARCHIVES_SAVE_AS = 'notes.html' +AUTHORS_SAVE_AS = '' +TAGS_SAVE_AS = '' +ARTICLE_URL = '{date:%Y}/{date:%m}/{slug}.html' +ARTICLE_SAVE_AS = '{date:%Y}/{date:%m}/{slug}.html' +USE_FOLDER_AS_CATEGORY = True +CATEGORIES_SAVE_AS = 'categories.html' +AUTHOR_SAVE_AS = '' +DISPLAY_CATEGORIES_ON_MENU = False + +MENUITEMS = ( + ('code', '/code'), + ('about', '/about.html'), + ('onion', 'http://mcolxyzogp3cy4czf52oa2svu2vjge3otm3shxmtvwshyum47sis3iid.onion'), +) + +ICONITEMS = ( + ('<i class="fab fa-mastodon"></i>', 'https://fosstodon.org/@mcol" rel="me'), + ('<i class="fas fa-envelope"></i>', 'mailto:mcol@posteol.net'), +) + +# minify-fontawesome +PLUGINS.append("pelican-minify-fontawesome") +MINIFY_FONTAWESOME = '/home/mcol/git/mcol.xyz/fontawesome-free-5.11.2-web' + +SITESUBTITLE = "A hobbyist's notes on FOSS, linux toys and privacy tools" +MENUPADTO = 18 +EXTRA_HEAD = """<link rel="stylesheet" href="/theme/css/fa.css" /> + <script async defer src="/matomo/404.js"></script>""" + +# development settings +DELETE_OUTPUT_DIRECTORY = True +LOAD_CONTENT_CACHE = False +FEED_ALL_ATOM = None +CATEGORY_FEED_ATOM = None +TRANSLATION_FEED_ATOM = None +AUTHOR_FEED_ATOM = None +AUTHOR_FEED_RSS = None diff --git a/publishconf.py b/publishconf.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- # +from __future__ import unicode_literals + +# 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 * + +FEED_ATOM = 'rss.xml' +FEED_DOMAIN = "" + +# css-html-js-minify +PLUGINS.append("css-html-js-minify") diff --git a/tasks.py b/tasks.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +import os +import shutil +import sys +import datetime + +from invoke import task +from invoke.util import cd +from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer + +CONFIG = { + # Local path configuration (can be absolute or relative to tasks.py) + 'deploy_path': 'output', + # Remote server configuration + 'production': '', + 'dest_path': '', + # Port for `serve` + '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""" + c.run('pelican -s pelicanconf.py') + +@task +def rebuild(c): + """`build` with the delete switch""" + c.run('pelican -d -s pelicanconf.py') + +@task +def regenerate(c): + """Automatically regenerate site upon file modification""" + c.run('pelican -r -s pelicanconf.py') + +@task +def serve(c): + """Serve site at http://localhost:8000/""" + + class AddressReuseTCPServer(RootedHTTPServer): + allow_reuse_address = True + + server = AddressReuseTCPServer( + CONFIG['deploy_path'], + ('', CONFIG['port']), + ComplexHTTPRequestHandler) + + sys.stderr.write('Serving on port {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""" + c.run('pelican -s publishconf.py') + + +@task +def publish(c): + """Publish to production via rsync""" + c.run('pelican -s publishconf.py') + c.run( + 'rsync --delete --exclude ".DS_Store" -pthrvz -c ' + '{} {production}:{dest_path}'.format( + CONFIG['deploy_path'].rstrip('/') + '/', + **CONFIG)) +