Jan 03, 2020

Dynamic blacklisting configuration for nginx access via custom module

This is a fix for a common "bots hammering on all doors on the internet" issue, applied in this case to nginx http daemon, where random bots keep creating bunch of pointless server load by indexing or requesing stuff that they never should bother with.

Example can be large dump of static files like distro packages mirror or any kind of dynamic content prohibited by robots.txt, which nice bots tend to respect, but dumb and malicious bots keep scraping over and over again without any limits or restraint.

One way of blocking such pointless busywork-activity is to process access_log and block IPs via ipsets, nftables sets or such, but this approach blocks ALL content on http/https port instead of just hosts and URLs where such bots have no need to be.

So ideally you'd have something like ipsets in nginx itself, blocking only "not for bots" locations, and it actually does have that, but paywalled behind Nginx Plus subscription (premium version) in keyval module, where dynamic blacklist can be updated in-memory via JSON API.

Thinking about how to reimplement this as a custom module for myself, in some dead-simple and efficient way, thought of this nginx.conf hack:

try_files /some/block-dir/$remote_addr @backend;

This will have nginx try to serve templated /some/block-dir/$remote_addr file path, or go to @backend location if it doesn't exists.

But if it does exist, yet can't be accessed due to filesystem permissions, nginx will faithfully return "403 Forbidden", which is pretty much the desired result for me.

Except this is hard to get working with autoindex module (i.e. have nginx listing static filesystem directories), looks up paths relative to root/alias dirs, has ton of other limitations, and is a bit clunky and non-obvious.

So, in the same spirit, implemented "stat_check" command via nginx-stat-check module:

load_module /usr/lib/nginx/modules/ngx_http_stat_check.so;
...
location /my-files {
  alias /srv/www/my-files;
  autoindex on;
  stat_check /tmp/blacklist/$remote_addr;
}

This check runs handler on NGX_HTTP_ACCESS_PHASE that either returns NGX_OK or NGX_HTTP_FORBIDDEN, latter resulting in 403 error (which can be further handled in config, e.g. via custom error page).

Check itself is what it says on the tin - very simple and liteweight stat() call, checking if specified path exists, and - as it is for blacklisting - returning http 403 status code if it does when accessing that location block.

This also allows to use any of the vast number of nginx variables, including those matched by regexps (e.g. from location URL), mapped via "map", provided by modules like bundled realip, geoip, ssl and such, any third-party ones or assembled via "set" directive, i.e. good for use with pretty much any parameter known to nginx.

stat() looks up entry in a memory-cached B-tree or hash table dentry list (depends on filesystem), with only a single syscall and minimal overhead possible for such operation, except for maybe pure in-nginx-memory lookups, so might even be better solution for persistent blacklists than keyval module.

Custom dynamic nginx module .so is very easy to build, see "Build / install" section of README in the repo for exact commands.

Also wrote corresponding nginx-access-log-stat-block script that maintains such filesystem-database blacklist from access.log-like file (only cares about remote_addr being first field there), produced for some honeypot URL, e.g. via:

log_format stat-block '$remote_addr';

location = /distro/package/mirror/open-and-get-banned.txt {
  alias /srv/pkg-mirror/open-and-get-banned.txt;
  access_log /run/nginx/bots.log stat-block;
}

Add corresponding stat_check for dir that script maintains in "location" blocks where it's needed and done.

tmpfs (e.g. at /tmp or /run) can be used to keep such block-list completely in memory, or otherwise I'd recommend using good old ReiserFS (3.6 one that's in mainline linux) with tail packing, which is enabled by default there, as it's incredibly efficient with large number of small files and lookups for them.

Files created by nginx-access-log-stat-block contain blocking timestamp and duration (which are used to unblock addresses after --block-timeout), and are only 24B in size, so ReiserFS should pack these right into inodes (file metadata structure) instead of allocating extents and such (as e.g. ext4 would do), being pretty much as efficient for such data as any disk-backed format can possibly be.

Note that if you're reading this post in some future, aforementioned premium "keyval" module might be already merged into plebeian open-source nginx release, allowing on-the-fly highly-dynamic configuration from external tools out of the box, and is probably good enough option for this purpose, if that's the case.

Jan 03, 2020

Editor/code font legibility hacks

Doesn't seem to be a common thing to pay attention to outside of graphic/UI/UX design world, but if you stare at the code for significant chunk of the day, it's a good thing to customize/optimize at least a bit.

I've always used var-width fonts for code in emacs, and like them vs monospace ones for general readability and being much more compact, but noticed one major shortcoming over the years: some punctuation marks are very hard to tell apart.

While this is not an issue in free-form text, where you don't care much whether some tiny dot is a comma or period, it's essential to differentiate these in code.

And in fonts which I tend to use (like Luxi Sans or Liberation Sans), "." and "," in particular tend to differ by something like 1-2 on-screen pixels, which is bad, as I've noticed straining to distinguish the two sometimes, or putting one instead of another via typo and not noticing, because it's hard to notice.

It's a kind of thing that's like a thorn that always torments, but easy to fix once you identify it as a problem and actually look into it.

Emacs in particular allows to replace one char with another visually:

(unless standard-display-table
  (setq standard-display-table (make-display-table)))
(aset standard-display-table ?, (vconcat "˾"))
(aset standard-display-table ?. (vconcat "❟"))

Most fonts have ton of never-used-in-code unicode chars to choose distinctive replacements from, which are easy to browse via gucharmap or such.

One problem can be emacs using faces with different font somewhere after such replacement, which might not have these chars in them, so will garble these, but that's rare and also easy to fix (via e.g. custom-set-faces).

Another notable (original) use of this trick - "visual tabs":

(aset standard-display-table ?\t (vconcat "˙ "))

I.e. marking each "tab" character by a small dot, which helps a lot with telling apart indentation levels, esp. in langs like python where it matters just as much as commas vs periods.

Recently also wanted to extend this to colons and semicolons, which are just as hard to tell apart as these dots (: vs ;), but replacing them with something weird everywhere seem to be more jarring, and there's a proper fix for all of these issues - edit the glyphs in the font directly.

fontforge is a common app for that, and for same-component ".:,;" chars there's an easy way to scale them, copy-paste parts between glyphs, positioning them precisely at the same levels.

Scaling outlines by e.g. 200% makes it easy to tell them apart by itself, but I've also found it useful to make a dot slightly horizontally stretched, while leaving comma vertical - eye immediately latches onto this distinction, unlike with just "dot" vs "dot plus a tiny beard".

It's definitely less of an issue in monospace fonts, and there seem to be a large selection of "coding fonts" optimized for legibility, but still worth remembering that glyphs in these are not immutable at all - you're not stuck with whatever aesthetics-vs-legibility trade-offs their creators made for all chars, and can easily customize them according to your own issues and needs, same as with editor, shell/terminal, browser or desktop environemnt.

Dec 30, 2019

My list of essential firefox setup hacks in 2019

I've been hopping between browsers for as long as I remember using them, and in this last iteration, had a chance to setup waterfox from scratch.

"Waterfox" is a fork of Mozilla Firefox Browser with no ads, tracking and other user-monetization nonsense, and with mandatory extension signing disabled.

So thought to collect my (incomplete) list of hacks which had to be applied on top and around it, to make the thing work like I want it to, though it's impossible to remember them all, especially most obvious must-have stuff that you don't even think about.

This list should get outdated fast, and probably won't be updated, so be sure to check whether stuff on it is still relevant first.

  • waterfox itself, built from stable source, with any kind of local extra patches applied and tracked in that build script (Arch PKGBUILD).

    Note that it comes in two variants - Current and Classic, where "Classic" is almost certainly not the one you want, unless you know exactly what you want it for (old pre-webext addons, some integration features, memory constraints, etc).

    Key feature for me, also mentioned above, is that it allows installing any modified extensions - want to be able to edit anything I install, plus add my own without asking mozilla for permission.

    Removal of various Mozilla's adware and malware from it is also a plus.
    Oh, and it has Dark Theme out of the box too!
    Build takes a while and uses ~8G of RAM for linking libxul.so.
    Not really a problem though - plenty of spare cpu in the background and overnight.
  • Restrictive AppArmor profile for waterfox-current

    Modern browsers are very complex bloatware with ton of bugs and impossible to secure, despite devs' monumental efforts to contain and patch these pillars of crap from leaking, so something like this is absolutely essential.

    I use custom AppArmor profile as I've been writing them for years, but something pre-made like firejail would probably work too.

  • CGroups for controlling resource usage.

    Again, browsers are notorious for this. Nuff said.

    See earlier cgroup-v2-resource-limits-for-apps post here and cgrc tool for more info, as well as kernel docs on cgroup-v2 (or cgroup-v1 docs if you still use these).

    They are very simple to use, even via something like mkdir ff && echo 3G > ff/memory.max && pgrep waterfox > ff/cgroup.procs, without any extra tools.

  • ghacks user.js - basic cleanup of Mozilla junk and generally useless features.

    There are some other similar user.js templates/collections, see compare-user.js and Tor Browser user.js hacks somewhere. Be sure to start from "what is user.js" page, and go through the whole thing, overriding settings there that don't make sense for you.

    I would suggest installing it not as a modified user.js, but as a vendor.js file without any modification, which would make it easier to diff and maintain later, as it won't copy all its junk to your prefs.js forever, and just use values as about:config defaults instead.

    vendor.js files are drop-in .js files in dir like /opt/waterfox-current/browser/defaults/preferences, which are read last-to-first alphabetically, so I'd suggest putting ghacks user.js as "%ghacks.js" or such there, and it'll override anything.

    Important note: replace user_pref( with pref( there, which should be easy to replace back for diffs/patches later.

    I'm used to browser always working in "Private Mode", storing anything I want to remember in bookmarks or text files for later reference, and never remembering anything between browser restarts, so most severe UI changes there make sense for me, but might annoy someone more used to having e.g. urlbar suggestions, persistent logins or password storage.

  • Notable user.js tweaks:

    • user_pref("privacy.resistFingerprinting.letterboxing", false);

      Obnoxious privacy setting in ghacks to avoid fingerprinting by window size.
      It looks really ugly and tbh I don't care that much about privacy.
    • user_pref("permissions.default.shortcuts", 2);

      Disallows sites to be able to override basic browser controls.

    • Lower audio volume - prevents sites from deafening you every time:

      user_pref("media.default_volume", "0.1");
      user_pref("media.volume_scale", "0.01");
    • Various tabs-related behavior - order of adding, switching, closing, etc:

      user_pref("browser.tabs.closeWindowWithLastTab", false);
      user_pref("browser.tabs.loadBookmarksInTabs", true);
      user_pref("browser.tabs.insertAfterCurrent", true);
      user_pref("browser.ctrlTab.recentlyUsedOrder", false);
    • Disable all "where do you want to download?" dialogs, disable opening .mp3 and such in browser, disable "open with" (won't work from container anyway):

      user_pref("browser.download.useDownloadDir", true);
      user_pref("browser.download.forbid_open_with", true);
      user_pref("media.play-stand-alone", false);

      See also handlers.json file for tweaking filetype-specific behavior.

    • Disable media autoplay: user_pref("media.autoplay.default", 5);

    • Disable all web-notification garbage:

      user_pref("dom.webnotifications.enabled", false);
      user_pref("dom.webnotifications.serviceworker.enabled", false);
    • Disable browser-UI/remote debugging in user.js, so that you'd have to enable it every time on per-session basis, when it's (rarely) needed:

      user_pref("devtools.chrome.enabled", false);
      user_pref("devtools.debugger.remote-enabled", false);
    • Default charset to utf-8 (it's 2019 ffs!):

      user_pref("intl.charset.fallback.override", "utf-8");
    • Disable as many webapis and protocols that I never use as possible:

      user_pref("permissions.default.camera", 2);
      user_pref("permissions.default.microphone", 2);
      user_pref("geo.enabled", false);
      user_pref("permissions.default.geo", 2);
      user_pref("network.ftp.enabled", false);
      user_pref("full-screen-api.enabled", false);
      user_pref("dom.battery.enabled", false);
      user_pref("dom.vr.enabled", false);

      Note that some of such APIs are disabled by ghacks, but not all of them, as presumably some people want them, sometimes, maybe, not sure why.

    • Reader Mode (about:reader=<url>, see also keybinding hack below):

      user_pref("reader.color_scheme", "dark");
      user_pref("reader.content_width", 5);
    • Disable lots of "What's New", "Greetings!" pages, "Are you sure?" warnings, "pocket" (malware) and "identity" (Mozilla tracking account) buttons:

      user_pref("browser.startup.homepage_override.mstone", "ignore");
      user_pref("startup.homepage_welcome_url", "");
      user_pref("startup.homepage_welcome_url.additional", "");
      user_pref("startup.homepage_override_url", "");
      user_pref("browser.messaging-system.whatsNewPanel.enabled", false);
      user_pref("extensions.pocket.enabled", false);
      user_pref("identity.fxaccounts.enabled", false);
      user_pref("browser.tabs.warnOnClose", false);
      user_pref("browser.tabs.warnOnCloseOtherTabs", false);
      user_pref("browser.tabs.warnOnOpen", false);
      user_pref("full-screen-api.warning.delay", 0);
      user_pref("full-screen-api.warning.timeout", 0);
    • Misc other stuff:

      user_pref("browser.urlbar.decodeURLsOnCopy", true);
      user_pref("browser.download.autohideButton", false);
      user_pref("accessibility.typeaheadfind", false); - disable "Find As You Type"
      user_pref("findbar.highlightAll", true);
      user_pref("clipboard.autocopy", false); - Linux Xorg auto-copy
      user_pref("layout.spellcheckDefault", 0);
      user_pref("browser.backspace_action", 2); - 2=do-nothing
      user_pref("general.autoScroll", false); - middle-click scrolling
      user_pref("ui.key.menuAccessKey", 0); - alt-key for menu bar on top

    Most other stuff I have there are overrides for ghacks vendor.js file, so again, be sure to scroll through that one and override as necessary.

  • omni.ja keybinding hacks - browser quit key and reader key.

    Linux-default Ctrl+Q key is too close to Ctrl+W (close tab), and is frustrating to mis-press and kill all your tabs sometimes.

    Easy to rebind to e.g. Ctrl+Alt+Shift+Q by unpacking /opt/waterfox-current/omni.ja zip file and changing stuff there.

    File you want in there is chrome/browser/content/browser/browser.xul, set modifiers="accel,shift,alt" for key_quitApplication there, and remove disabled="true" from key_toggleReaderMode (also use modifiers="alt" for it, as Ctrl+Alt+R is already used for browser restart).

    zip -qr0XD ../omni.ja * command can be used to pack stuff back into "omni.ja".

    After replacing omni.ja, do rm -Rf ~/.cache/waterfox/*/startupCache/ too.

    Note that bunch of other non-hardcoded stuff can also be changed there easily, see e.g. shallowsky.com modifying-omni.ja post.

  • Increase browser UI font size and default page fonts.

    First of all, user.js needs user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true); line to easily change UI stuff from profile dir (instead of omni.ja or such).

    Then <profile>/chrome/userChrome.css can be used to set UI font size:

    * { font-size: 15px !important; }
    

    Page font sizes can be configured via Preferences or user.js:

    user_pref("font.name.monospace.x-western", "Liberation Mono");
    user_pref("font.name.sans-serif.x-western", "Liberation Sans");
    user_pref("font.name.serif.x-western", "Liberation Sans");
    user_pref("font.size.monospace.x-western", 14);
    user_pref("font.size.variable.x-western", 14);
    

    I also keep pref("browser.display.use_document_fonts", 0); from ghacks enabled, so it's important to set some sane defaults here.

  • Hide all "search with" nonsense from URL bar and junk from context menus.

    Also done via userChrome.css - see "UI font size" above for more details:

    #urlbar-results .search-one-offs { display: none !important; }
    

    If context menus (right-click) have options you never use, they can also be removed:

    #context-bookmarklink, #context-searchselect,
      #context-openlinkprivate { display: none !important; }
    

    See UserChrome.css_Element_Names/IDs page on mozillazine.org for IDs of these, or enable "browser chrome" + "remote" debugging (two last ones) in F12 - F1 menu and use Ctrl+Shift+Alt+I to inspect browser GUI (note that all menu elements are already there, even if not displayed - look them up via css selectors).

  • Remove crazy/hideous white backgrounds blinding you every time you open browser windows or tabs there.

    AFAIK this is not possible to do cleanly with extension only - needs userChrome.css / userContent.css hacks as well.

    All of these tweaks I've documented in mk-fg/waterfox#new-tab, with end result being removing all white backgrounds in new browser/window/tab pages and loading 5-liner html with static image background there.

    Had to make my own extension, as all others doing this are overcomplicated, and load background js into every tab, use angular.js and bunch of other junk.

  • Extensions!

    I always install and update these manually after basic code check and understanding how they work, as it's fun and helps to keep the bloat as well as any unexpected surprises at bay.

    Absolutely essential multipurpose ones:

    • uBlock Origin

      Be sure to also check how to add "My Filters" there, as these are just as useful as adblocking for me.

      Modern web pages are bloated with useless headers, sidebars, stars, modal popups, social crap, buttons, etc - just as much as with ads, so it's very useful to remove all this shit, except for actual content. For example - stackoverflow:

      stackoverflow.com## .top-bar
      stackoverflow.com## #left-sidebar
      stackoverflow.com## #sidebar
      stackoverflow.com## #js-gdpr-consent-banner
      stackoverflow.com## .js-dismissable-hero
      

      Just use Ctrl+Shift+C and tree to find junk elements and add their classes/ids there on per-site basis like that, they very rarely change.

    • uMatrix - best NoScript-type addon.

      Blocks all junk-js, tracking and useless integrations with minimal setup, and is very easy to configure for sites on-the-fly.

    General usability ones:

    • Add custom search engine - I use these via urlbar keywords all the time (e.g. "d some query" for ddg), not just for search, and have few dozen of them, all created via this handy extension.

      Alternative can be using https://ready.to/search/en/ - which also generates OpenSearch XML from whatever you enter there.

      Firefox search is actually a bit limited wrt how it builds resulting URLs due to forced encoding (e.g. can't transform "ghr mk-fg/blog" to github repo URL), which can be fixed via an external tool - see mk-fg/waterfox#redirectorml for more details.

    • Mouse Gesture Events - simplest/fastest one for gestures that I could find.

      Some other ones are quite appalling wrt bloat they bring in, unlike this one.

    • HTTPS by default - better version of "HTTPS Everywhere" - much simpler and more well-suited for modern web, where defaulting to http:// is just wrong, as everyone and their dog are either logging these or putting ads/malware into them on-the-fly.

    • Proxy Toggle with some modifications (see mk-fg/waterfox#proxy-toggle-local).

      Allows to toggle proxy on/off in one keypress or click, with good visual indication, and is very simple internally - only does what it says on the tin.

    • force-english-language - my fix for otherwise-useful ghacks' anti-fingerprinting settings confusing sites into thinking that I want them to guess language from my IP address.

      This is never a good thing, so this simple 10-js-lines addon adds back necessary headers and JS values to make sites always use english.

    • flush-site-data - clears all stuff that sites store in browser without needing to restart it. Useful to log out of all sites and opt out of all tracking.

  • Handling for bittorrent magnet URLs.

    Given AppArmor container (see above), using xdg-open for these is quite "meh" - opens up a really fat security exception.

    But there is another - simpler (for me at least) - way, to use some trivial wrapper binary - see all details in mk-fg/waterfox#url-handler-c.

  • RSS and Atom feeds.

    Browsers stopped supporting these, but they're still useful for some periodic content.

    Used to work around this limitation via extensions (rendering feeds in browser) and webapps like feedjack, but it's not 2010 anymore, and remaining feed contents are mostly good for notifications or for download links (e.g. podcast feeds), both of which don't need browser at all, so ended up making and using external tools for that - rss-get and riet.

Was kinda surprised to be able to work around most usability issues I had with FF so far, without any actual C++ code patches, and mostly without patches at all (keybindings kinda count, but can be done without rebuild).

People love to hate on browsers (me too), but looking at any of the issues above (like "why can't I do X easier?"), there's almost always an open bug (which you can subscribe to), often with some design, blockers and a roadmap even, so can at least understand how these hang around for years in such a massive project.

Also, comparing it to ungoogled-chromium that I've used for about a year before migrating here, FF still offers much more customization and power-user-friendliness, even if not always out of the box, and not as much as it used to.

Oct 05, 2019

Splitting pids from flatpak into their own cgroup scopes

As a bit of a follow-up on earlier post about cgroup-v2 resource control - what if you want to impose cgroup limits on some subprocess within a flatpak-managed scope?

Had this issue with Steam games in particular, where limits really do come in handy, but on a regular amd64 distro, only sane way to run Steam would be via flatpak or something like that (32-bit container/chroot).

flatpak is clever enough to cooperate with systemd and flatpak --user run com.valvesoftware.Steam creates e.g. flatpak-com.valvesoftware.Steam-165893.scope (number there representing initial pid), into which it puts itself and by extension all things related to that particular app.

Scope itself can be immediately limited via e.g. systemctl set-property flatpak-com.valvesoftware.Steam-165893.scope MemoryHigh=4G, and that can be good enough for most purposes.

Specific issue I had though was with the game connecting and hanging forever on some remote API, and wanted to limit network access for it without affecting Steam, so was figuring a way to move it from that flatpak.scope to its own one.

Since flatpak runs apps in a separate filesystem namespaces (among other restrictions - see "flatpak info --show-permissions ..."), its apps - unless grossly misconfigured by developers - don't have access to pretty much anything on the original rootfs, as well as linux API mounts such as /sys or dbus, so using something as involved as "systemd-run" in there to wrap things into a new scope is rather tricky.

Here's how it can be done in this particular case:

  • Create a scope for a process subtree before starting it, e.g. in a wrapper before "flatpak run ...":

    #!/bin/bash
    systemd-run -q --user --scope --unit game -- sleep infinity &
    trap "kill $!" EXIT
    flatpak run com.valvesoftware.Steam "$@"
    

    ("sleep infinity" process there is just to keep the scope around)

  • Allow specific flatpak app to access that specific scope (before starting it):

    % systemd-run -q --user --scope --unit game -- sleep infinity &
    % cg=$(systemctl -q --user show -p ControlGroup --value -- game.scope)
    % kill %1
    % flatpak --user override --filesystem "$cg" com.valvesoftware.Steam
    

    Overrides can be listed later via --show option or tweaked directly via ini files in ~/.local/share/flatpak/overrides/

  • Add wrapper to affected subprocess (running within flatpak container) that'd move it into such special scope:

    #!/bin/bash
    echo $$ > /sys/fs/cgroup/user.slice/.../game.scope/cgroup.procs
    /path/to/original/app
    

    For most apps, replacing original binary with that is probably good enough.

    With Steam in particular, this is even easier to add via "Set Lauch Command..." in properties of a specific game, and use something like /path/to/wrapper %command% there, which will also pass $1 as the original app path to it.

    (one other cool use for such wrappers with Steam, can be killing it after app exits when it was started with "-silent -applaunch ..." directly, as there's no option to stop it from hanging around afterwards)

And that should do it.

To impose any limits on game.scope, some pre-configured slice unit (or hierarchy of such) can be used, and systemd-run to have something like "--slice games.slice" (see other post for more on that).

Also, as I wanted to limit network access except for localhost (which is used by steam api libs to talk to main process), needed some additional firewall configuration.

iptables can filter by cgroup2 path, so a rule with "-m cgroup --path ..." can work for that, but since I've switched to nftables here a while ago, couldn't do that, as kernel patch to implement cgroup2 filtering there apparently fell through the cracks back in 2016 and was never revisited :(

Solution I've ended up with instead was to use cgroup-attached eBPF filter:
(nice to have so many options, but that's a whole different story at this point)

Oct 02, 2019

cgroup-v2 resource limits for apps with systemd scopes and slices

Something like 8y ago, back in 2011, it was ok to manage cgroup-imposed resource limits in parallel to systemd (e.g. via libcgroup or custom tools), but over years cgroups moved increasingly towards "unified hierarchy" (for many good reasons, outlined in kernel docs), joining controllers at first, and then creating new unified cgroup-v2 interface for it.

Latter no longer allows to separate systemd process-tracking tree from ones for controlling specific resources, so you can't put restrictions on pids without either pulling them from hierarchy that systemd uses (making it loose track of them - bad idea) or changing cgroup configuration in parallel to systemd, potentially stepping on its toes (also bad idea).

So with systemd running in unified cgroup-v2 hierarchy mode (can be compiled-in default, esp. in the future, or enabled via systemd.unified_cgroup_hierarchy on cmdline), there are two supported options to manage custom resource limits:

  • By using systemd, via its slices, scopes and services.
  • By taking over control of some cgroup within systemd hierarchy via Delegate= in units.

Delegation necessitates managing pids within that subtree outside of systemd entirely, while first one is simpler of the two, where instead of libcgroup, cgmanager or cgconf config file, you'd define all these limits in systemd .slice unit-file hierarchy like this:

# apps.slice
[Slice]

CPUWeight=30
IOWeight=30

MemoryHigh=5G
MemoryMax=8G
MemorySwapMax=1G

# apps-browser.slice
[Slice]
CPUWeight=30
IOWeight=30
MemoryHigh=3G

# apps-misc.slice
[Slice]
MemoryHigh=700M
MemoryMax=1500M

These can reside under whatever pre-defined slices (see systemctl status for full tree), including "systemd --user" slices, where users can set these up as well.

Running arbitrary desktop app under such slices can be done as a .service with all Type=/ExecStart=/ExecStop= complications or just .scope as a bunch of arbitrary unmanaged processes, using systemd-run:

% systemd-run -q --user --scope \
  --unit chrome --slice apps-browser -- chrominum

Scope will inherit all limits from specified slices, as composed into hierarchy (with the usual hyphen-to-slash translation in unit names), and auto-start/stop the scope (when all pids there exit) and all slices required.

So no extra scripts for mucking about in /sys/fs/cgroup are needed anymore, whole subtree is visible and inspectable via systemd tools (e.g. systemctl status apps.slice, systemd-cgls, systemd-cgtop and such), and can be adjusted on the fly via e.g. systemctl set-property apps-misc.slice CPUWeight=30.

My old cgroup-tools provided few extra things for checking cgroup contents from scripts easily (cgls), queueing apps from shell via cgroups and such (cgwait, "cgrc -q"), which systemctl and systemd-run don't provide, but easy to implement on top as:

% cg=$(systemctl -q --user show -p ControlGroup --value -- apps-browser.scope)
% procs=$( [ -z "$cg" ] ||
    find /sys/fs/cgroup"$cg" -name cgroup.procs -exec cat '{}' + 2>/dev/null )

Ended up wrapping long systemd-run commands along with these into cgrc wrapper shell script in spirit of old tools:

% cgrc -h
Usage: cgrc [-x] { -l | -c | -q [opts] } { -u unit | slice }
       cgrc [-x] [-q [-i N] [-t N]] [-u unit] slice -- cmd [args...]

Run command via 'systemd-run --scope'
  within specified slice, or inspect slice/scope.
Slice should be pre-defined via .slice unit and starts/stops automatically.
--system/--user mode detected from uid (--system for root, --user otherwise).

Extra options:
-u name - scope unit name, derived from command basename by default.
   Starting scope with same unit name as already running one will fail.
   With -l/-c list/check opts, restricts check to that scope instead of slice.
-q - wait for all pids in slice/scope to exit before start (if any).
   -i - delay between checks in seconds, default=7s.
   -t - timeout for -q wait (default=none), exiting with code 36 afterwards.
-l - list all pids within specified slice recursively.
-c - same as -l, but for exit code check: 35 = pids exist, 0 = empty.
-x - 'set -x' debug mode.

% cgrc apps-browser -- chrominum &
% cgrc -l apps | xargs ps u
...
% cgrc -c apps-browser || notify-send 'browser running'
% cgrc -q apps-browser ; notify-send 'browser exited'

That + systemd slice units seem to replace all old resource-management cruft with modern unified v2 tree nicely, and should probably work for another decade, at least.

Jul 17, 2019

Extending Zsh Line Editor (ZLE) with python widgets

Was finally annoyed enough by lack of one-hotkey way to swap arguments in stuff like cat /some/log/path/to/file1 > /even/longer/path/to/file2, which is commonly needed for "edit and copy back" or various rsync tasks.

Zsh has transpose-words widget (in zle terminology) bound to ESC-t, and its transpose-words-match extension, but they all fail miserably for stuff above - either transpose parts (e.g. path components, as they are "words"), wrong args entirely or don't take stuff like ">" into account (should be ignored).

These widgets are just small zsh funcs to edit/set BUFFER and CURSOR though, and binding/replacing/extending them is easy, except that doing any non-trivial string manipulation in shell is sort of a nightmare.

Hence sh-embedded python code for that last part:

_zle-transpose-args() {
  res=$(python3 - 3<<<"$CURSOR.$BUFFER" <<'EOF'
import re

pos, line = open(3).read().rstrip('\r\n').split('.', 1)
pos, suffix = int(pos) if pos else -1, ''

# Ignore words after cursor, incl. if it's on first letter
n = max(0, pos-1)
if pos > 0 and line[n:n+1]:
  if line[n:n+1].strip(): n += len(line[n:].split(None, 1)[0])
  line, suffix = line[:n], line[n:]

line = re.split(r'(\s+)', line.rstrip('\r\n'))
arg_ns = list( n for n, arg in enumerate(line)
  if arg.strip() and not re.search(r'^[-<>|&]{1,4}$', arg) )
line[arg_ns[-2]], line[arg_ns[-1]] = line[arg_ns[-1]], line[arg_ns[-2]]
line = ''.join(line) + suffix

if pos < 0: pos = len(line)
print(f'{pos:02d}{line}\n', end='')
EOF
)
  [[ $? -eq 0 ]] || return
  BUFFER=${res:2}
  CURSOR=${res:0:2}
}
zle -N transpose-words _zle-transpose-args # bound to ESC-t by default

Given that such keys are pressed sparingly, there's really no downside in using saner language for any non-oneliner stuff, passing code to stdin and any extra args/values via file descriptors like 3<<<"some value" above.

Opens up a lot of freedom wrt making shell prompt more friendly and avoiding mouse and copy-pasting to/from there for common tasks like that.

Jan 26, 2019

Old IRC interface for new IRC - Discord

While Matrix still gaining traction and is somewhat in flux, Slack slowly dies in a fire (hopefully!), Discord seem to be the most popular IRC of the day, especially in any kind of gaming and non-technical communities.

So it's only fitting to use same familiar IRC clients for the new thing, even though it doesn't officially support it.

Started using it initially via bitlbee and bitlbee-discord, but these don't seem to work well for that - you can't just /list or /join channels, multiple discord guilds aren't handled too well, some useful features like chat history aren't easily available, (surprisingly common) channel renames are constant pain, hard to debug, etc - and worst of all, it just kept loosing my messages, making some conversations very confusing.

So given relatively clear protocols for both, wrote my own proxy-ircd bridge eventually - https://github.com/mk-fg/reliable-discord-client-irc-daemon

Which seem to address all things that annoyed me in bitlbee-discord nicely, as obviously that was the point of implementing whole thing too.

Being a single dependency-light (only aiohttp for discord websockets) python3 script, should be much easier to tweak and test to one's liking.

Quite proud of all debug and logging stuff there in particular, where you can easily get full debug logs from code and full protocol logs of irc, websocket and http traffic at the moment of any issue and understand, find and fix is easily.

Hopefully Matrix will eventually get to be more common, as it - being more open and dev-friendly - seem to have plenty of nice GUI clients, which are probably better fit for all the new features that it has over good old IRC.

Jan 10, 2019

TUI console dec/hex/binary converter tool

Really simple curses-based tool which I've been kinda-missing for years:

TUI dec/hex/binary converter tool

There's probably something similar built into any decent reverse-engineering suite or hex editor (though ghex doesn't have one!), but don't use either much, don't want to start either to quickly check mental math, and bet GUI conventions will work against how this should work ideally.

Converts between dec/hex/binary and helps to visualize all three at the same time, as well as how flipping bits or digits affects them all.

Nov 10, 2018

Best D-Bus lib for python out of the box

These days D-Bus is used in lot of places, on both desktop machines where it started, and servers too, with e.g. systemd APIs all over the place.

Never liked bindings offered for it in python though, as it's an extra half-C lib to worry about, and one that doesn't work too well either (had a lot of problems with it in earlier pulseaudio-mixer-cli script iterations).

But with systemd and its sd-bus everywhere, there's no longer a need for such extra lib, as its API is very easy to use via ctypes, e.g.:

import os, ctypes as ct

class sd_bus(ct.Structure): pass
class sd_bus_error(ct.Structure):
  _fields_ = [ ('name', ct.c_char_p),
    ('message', ct.c_char_p), ('need_free', ct.c_int) ]
class sd_bus_msg(ct.Structure): pass
lib = ct.CDLL('libsystemd.so')

def run(call, *args, sig=None, check=True):
  func = getattr(lib, call)
  if sig: func.argtypes = sig
  res = func(*args)
  if check and res < 0: raise OSError(-res, os.strerror(-res))
  return res

bus = ct.POINTER(sd_bus)()
run( 'sd_bus_open_system', ct.byref(bus),
  sig=[ct.POINTER(ct.POINTER(sd_bus))] )

error = sd_bus_error()
reply = ct.POINTER(sd_bus_msg)()
try:
  run( 'sd_bus_call_method',
    bus,
    b'org.freedesktop.systemd1',
    b'/org/freedesktop/systemd1',
    b'org.freedesktop.systemd1.Manager',
    b'Dump',
    ct.byref(error),
    ct.byref(reply),
    b'',
    sig=[
      ct.POINTER(sd_bus),
      ct.c_char_p, ct.c_char_p, ct.c_char_p, ct.c_char_p,
      ct.POINTER(sd_bus_error),
      ct.POINTER(ct.POINTER(sd_bus_msg)),
      ct.c_char_p ] )
  dump = ct.c_char_p()
  run( 'sd_bus_message_read', reply, b's', ct.byref(dump),
    sig=[ct.POINTER(sd_bus_msg), ct.c_char_p, ct.POINTER(ct.c_char_p)] )
  print(dump.value.decode())
finally: run('sd_bus_flush_close_unref', bus, check=False)

Which can be a bit more verbose than dbus-python API, but only needs libsystemd, which is already there on any modern systemd-enabled machine.

It's surprisingly easy to put/parse any amount of arrays, maps or any kind of variant data there, as sd-bus has convention of how each one is serialized to a flat list of values.

For instance, "as" (array of strings) would expect an int count with corresponding number of strings following it, or just NULL for empty array, with no complicated structs or interfaces.

Same thing for maps and variants, where former just have keys/values in a row after element count, and latter is a type string (e.g. "s") followed by value.

Example of using a more complicated desktop notification interface, which has all of the above stuff can be found here:

https://github.com/mk-fg/fgtk/blob/4eaa44a/desktop/notifications/power#L22-L104

Whole function is a libsystemd-only drop-in replacement for a bunch of crappy modules which provide that on top of libdbus.

sd-bus api is relatively new, but really happy that it exists in systemd now, given how systemd itself uses dbus all over the place.

Few script examples using it:

For small system scripts in particular, not needing to install any deps except python/systemd (which are always there), is definitely quite a good thing.

Sep 06, 2018

GNU find command (findutils) with xattrs and posix capabilities support

It's been bugging me for a while that "find" does not have matches for extended attributes and posix capabilities on filesystems.

Finally got around to looking for a solution, and there apparently is a recent patch (Jul 2018), which addresses the issue nicely:

https://www.mail-archive.com/bug-findutils@gnu.org/msg05483.html

It finally allows to easily track modern-suid binaries with capabilities that allow root access (almost all of them do in one way or another), as well as occasional ACLs and such, e.g.:

% find /usr/ -type f -xattr .
/usr/bin/rcp
/usr/bin/rsh
/usr/bin/ping
/usr/bin/mtr-packet
/usr/bin/dumpcap
/usr/bin/gnome-keyring-daemon
/usr/bin/rlogin
/usr/lib/gstreamer-1.0/gst-ptp-helper

% getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw+ep

(find -type f -cap . -o -xattr . -o -perm /u=s,g=s to match either of caps/xattrs/suid-bits, though -xattr . includes all -cap matches already)

These -cap/-xattr flags allow matching stuff by regexps too, so they work for pretty much any kind of matching.

Same as with --json in iproute2 and other tools, a very welcome update, which hopefully will make it into one of the upstream versions eventually.

(but probably not very soon, since last findutils-4.6.0 release is from 2015 about 3 years ago, and this patch isn't even merged to current git yet)

Easy to use now on Arch though, as there's findutils-git in AUR, which only needs a small patch (as of 2101eda) to add all this great stuff - e.g. findutils-git-2101eda-xattrs.patch (includes xattrs.patch itself too).

It also applies with minor changes to non-git findutils version, but as these are from back 2015 atm, it's a bit PITA to build them already due to various auto-hell and gnulib changes.

← Previous Next → Page 4 of 17