Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


ico-freq ico-size ico-ci
ico-os ico-wm ico-editor ico-theme
shimeji miku © 2010 canary yellow

This is my primary computing setup, a self-contained graphical shell environment for Debian GNU/Linux.

  • Git is used to maintain an identical and reproducible setup across multiple machines.
  • A series of post-install scripts in ~/.once.d document and reproduce system-wide deviations from a fresh install.

Detailed installation instructions are provided, along with some documentation for the most essential components.

scrot Pictured: Debian stable, a "graphical shell" environment consisting mostly of xorg, dwm, sxhkd and various urxvt clients.

Quick start

[OPTIONAL] Instructions for a Debian base install with debootstrap for BIOS/UEFI x86 systems.

Installing Debian using debootstrap

This is a quick reference on using debootstrap to install Debian manually without using the official Debian installer. This is not a comprehensive tutorial on *NIX concepts, you should have some familiarity with administrating a GNU/Linux system before continuing.

  1. Boot into a Debian Live CD environment with any DE and partition your boot disk with gparted.

    You should always keep a Live CD install media around for use as a rescue disk, regardless of installation method. I only do it this way because I don't feel like using fdisk.

    To install packages in the live environment, apt-get update first and then apt-get install gparted.

    Suggested boot disk layouts:

    • Legacy BIOS systems that support MBR/msdos partition tables
    # MBR disks support a maximum of 4 primary partitions
    [ primary part. (root) ] [ extended partition                            ]
                             [ logical part. (swap) ] [ logical part. (home) ]
    # example /etc/fstab
    /dev/sda1	/       ext4	defaults	0	1
    /dev/sda5	none    swap	defaults	0	0
    /dev/sda6	/home   ext4	defaults	0	2
    • Modern UEFI systems that support GPT/gpt partition tables
    # EFI partition must be FAT32 and at least 32MiB
    [ EFI partition ] [ root partition ] [ swap partition ] [ home partition ]
    # example /etc/fstab
    /dev/sda1	/boot/efi   vfat	defaults	0	2
    /dev/sda2	/           ext4	defaults	0	1
    /dev/sda3	none        swap	defaults	0	0
    /dev/sda3	/home       ext4	defaults	0	2

    If your machine uses a slow eMMC-based boot disk, I recommend f2fs for modestly improved performance instead of ext4. Support for booting from f2fs is not provided by default in Debian.
    See this tutorial on adding required f2fs modules to initramfs for more info.

  2. Mount your newly created filesystem in /mnt, including your home partition to /mnt/home if you made one.

  3. Install debootstrap and install the Debian base system into /mnt.

    • debootstrap --arch [eg. i386, amd64] stable /mnt
  4. Chroot into your new system, all actions from this point onward are within your chrooted system.

    $ sudo su -
    $ for f in proc sys dev run; do mount --make-rslave --rbind /$f /mnt/$f; done
    $ chroot /mnt /bin/bash
  5. Configure your /etc/fstab to taste.

    • Try lsblk -f >> /etc/fstab to identify disks by UUID=... instead of device name.
  6. Customize your locale by installing and dpkg-reconfigure'ng locales, and tzdata.

  7. Edit /etc/hostname and /etc/hosts with your preferred hostname.

  8. Install a suitable linux kernel.

    • Find a suitable kernel meta-package to install with apt-cache search ^linux-image | grep 'meta'.
  9. Install network-manager and the bootloader package grub2.

    grub2 does not install to your boot disk automatically, use the following:

    • Build initial grub configuration with /sbin/update-grub
    • For BIOS (installs to magic sector at start of disk)
      • /sbin/grub-install --root-directory=/ /dev/sda
    • For UEFI (installs to EFI partition mounted in /boot/efi)
      • /sbin/grub-install --root-directory=/ --efi-directory=/boot/efi /dev/sda
  10. Give your root user a password, create your normal user, and assign it a password also.

    • eg. useradd -m USERNAME -s /bin/bash; passwd USERNAME
  11. You should now have a working system, login as your user and skip to Step 2 in the Quick start below.

    • You can reboot from the Live CD environment at this point to check your work but it's not required.
  1. Install Debian stable, perform a base install with no DE selected and no standard utilities when prompted.
    • Do not perform these steps on tty1, xinit will launch without dwm present and you will be kicked.
  2. Install git, wget, and sudo, then add yourself to the sudo group.
    • Log back in to apply changes to group membership.
  3. Bootstrap the system automatically with a hard git reset from this repo, this is done only once.
    $ git clone --bare {GIT_REMOTE}/atelier ~/.config/meta
    $ git --git-dir=$HOME/.config/meta --work-tree=$HOME reset --hard
    # Invoke the login shell to apply changes made to the environment
    $ exec $SHELL -l
  4. Run post-install in the shell to run post-install scripts automatically. Do not run as root.
    • Sets up the package manager, installs essential packages, window manager, text editor, etc.
  5. Reboot to finish.
    • xinit starts automatically upon login to tty1.
mobile scrot

Quick start on Termux for Android

This is meant to be a lightweight port with modifications, do not initiate a full post-install.

  1. Install git, and bootstrap the system using git reset --hard as described above.
  2. Post-install: Run only ~/.once.d/
    • Applies android-specific hacks and termux specific dotfiles for theming and softkeys.
  3. When pulling from upstream, stash changes or git reset --hard to prevent merge conflicts.
    • Use patch -p1 < ~/.termux/diff.patch to restore changes if stash is lost.

See attached notes for explanations of changes from a standard Linux environment.

List of supported platforms

Full graphical shell environment

  • Any conventional BIOS/UEFI-compliant x86-based Personal Computer
  • x86-based Chromebooks in Developer Mode (SeaBIOS), or liberated with UEFI firmware (Coreboot).
  • Next Thing Co. PocketC.H.I.P armhf-based portable toy computer linux handheld
    • Final NTC-provided Debian 8 (jessie) OS images from 2016 come with out-of-tree 4.4.13-ntc-mlc kernel pinned, upgradeable to 10 (buster).

Single-user minimal shell environment

  • Bootstrapping in virtualized container instances for use in CI/CD workflows
  • Termux terminal emulator and Linux environment for Android
    • Non-standard *NIX environment, currently only supports a subset of available features.

Usage notes

Using git meta

For local-scope changes, files in $HOME are versioned and mangled in place using Git.

  • $HOME is treated as the detached working tree for a git bare repo located at ~/.config/meta
  • The meta alias prefixes all git commands with --git-dir=$HOME/.config/meta --work-tree=$HOME
  • meta status will ignore files not manually added or tracked by this git repo.
    • This is achieved using the status.showUntrackedFiles option and not via manually updating ~/.gitignore as is commonly done.
  • Invoking git outside of a valid git directory will append the meta alias automatically.
    • init and clone commands are unaffected.

Using ~/.once.d post-install scripts

All system-wide changes are performed through automated scripts located in ~/.once.d, you can run them all at once with shell function post-install. Each script is self-contained, you can run them individually, anytime.

  • Some scripts apply only to specific hardware configurations, and will exit even if they are run.
  • Scripts affecting systemd or the bootloader will be skipped in virtualized container contexts.
  • Locally installed software is installed to ~/.local/bin when possible.
series function
0* System-wide changes performed through the package manager.
1* Changes to ~/.local file hierarchy, such as locally installed software and resources.
2* System-wide changes that bypass the package manager, such as changes to /etc.
These are hacks.
c* System-wide changes affecting chromebook hardware only.
a* Android-specific hacks only.
p* NTC PocketCHIP-specific hacks only.

Essential and *optional package groups

  • ~/.comforts describes a list of non-optional package groups that will be installed through the package manager.
    • Optional package groups are marked with an *asterisk, you will be prompted to approve these at runtime.

Essential and *persistent upstream utilities

  • ~/.comforts-git describes the full list of utilities compiled and installed from their upstream git sources.
    • Repos must have a typical ./configure and/or make install PREFIX=... metaphor to build correctly.
    • Sources marked with an *asterisk will be persistently installed to ~/.config/${URL##*/}

Installation can be customized with user-provided executable install hacks scripts, named {pre,post}-run. These can be placed in ~/.config/upstream or at the root of a persistently installed utility's install directory as described above

Rationale for doing things this way is summarized in commit 2fe1c3745.

Window manager

Keybinds are grabbed by dwm, sxkhd or fcitx5 to avoid keybind stomping.

dwm keybinds are the defaults with several exceptions, the modkey Mod1 is super instead of alt because many alt combinations are already taken by other applications I use.

shift + alt + key
kill window F4
counter-clockwise switch focused window tab
shift + super + key
float window[toggle] monocle window[toggle] space
set as master window[toggle] terminal return
launcher p
file manager e
ssh-add[toggle] backspace
partial screenshot screenshot print
reserved scroll lock
reserved pause
reboot shutdown F1
hibernate sleep F2
hibernate + reboot display off F3
configure networking calculator F4
configure displays switch active display[toggle] F5
minimum brightness lower brightness 10% F6
maximum brightness raise brightness 10% F7
configure audio mute[toggle] F8
lower volume 5% F9
raise volume 5% F10
randomize wallpaper F11
reserved F12
alt + ctrl + key[special]
switch input method[toggle] space
task manager delete
syslog insert

Reduced layout for Chromebooks

Search/Everything/Caps lock key serves as the super key. Same as above, with the following changes:

alt gr + key remarks
prior up
next down
home left
end right
delete backspace
F11 delete same as power key, keystroke repeat not available

Some environment notes

X server invocation

No display manager is used, login to tty1 to start the graphical shell.

All daemons and services required to support the graphical shell are initialized along with the X server and are terminated when the user terminates the session.

systemd unit services, cronjobs and similar mechanisms are avoided.

At startup, startx will pass hardware-specific xorg.conf files to the X server, to enable hardware compositing on supported hardware and eliminate screen tearing.

Xorg's security model forbids non-root users from passing arbitrary config files to the X server unless said configs are located in one of several "blessed" directories. Post-install scripts will create symlink /etc/X11/$(id -u)-override that points to ~/.config/xorg to override this behavior.

Optional X Window configuration


For use with multi-monitor and/or complicated display setups, you can override the default display layout with one or more commands to xrandr saved to optional config file ~/.xrandr

# e.g. two monitors, right is mounted vertically
--output HDMI-0 --auto --primary --rotate normal
--output HDMI-1 --auto --right-of HDMI-0 --rotate right

Commands in this file are passed to xrandr-cycle line by line at startup if it exists. For example, this configuration would suit a 2 monitor layout with the right monitor mounted vertically.


You can designate one or more paths to directories containing images or videos for use as a wallpaper using optional config file ~/.xdecor

# prefixing with ~/ is acceptable

If it exists, xwin-decor will randomly pick a directory and file within it and set it as the wallpaper on startup. In the case of video files, a random video frame from that file will be taken and set as the wallpaper using ffmpeg.

X resources and theming

For consistency, xinit, dwm and other scripts make use of the C preprocessor to mangle config files and configure color schemes.

Theme settings and individual color schemes are stored as C header files containing preprocessor macros representing color hex codes in ~/.local/include. This directory is appended to $C_INCLUDE_PATH at login.

  • Using shell function reload will reload changes to .xresources and hard-reset your current terminal instance.
  • Use command palette to soft-reset color scheme using OSC terminal escapes without losing the current shell.

Optionally, you can apply another existing color scheme by naming it as an argument. This can be useful when dealing with TUI applications that force their own background colors.

List of available macros

  • {FG,BG}COLOR for terminal fg/bg colors
  • {FG,BG}LIGHT for UX highlight colors
  • COLOR0..COLOR15 for the 16 standard ANSI terminal colors
  • FN_{TERM,HEADER,TEXT} for specific font faces
  • FN_{TERM,HEADER}_JP for matching fallback fonts
  • FN_{TERM,HEADER,TEXT}_SIZE for matching font sizes
  • FN_EMOJI for specifying fallback emoji glyphs
  • FN_EMOJI_SIZE for specifying fallback emoji glyph sizes

Issues with HiDPI scaling

HiDPI display setups are currently not supported, 96dpi is assumed everywhere.

HiDPI scaling brings up innumerable display issues in every category of graphical software including electron-based applications that require polluting scripts and dotfiles to smooth out toolkit scaling issues. Maintaining mixed-DPI multi-monitor setups in X11 is even more painful.

As of posting, I don't have a >1080p monitor to motivate such changes, I'm not about to pepper my scripts with toolkit-specific environment variables and conditional logic to support HiDPI scaling. See ~/.local/include/theme.h for more info.

Non-standard commands

Several commands are extended to include impure functions, such as purposefully mangling config files, and have the following precedence when multiple versions exist:

  1. Interactive shell functions defined in ~/.bashrc
  2. Non-interactive shell library executables in ~/.local/lib
    • Shell script snippets used by multiple scripts to reduce clutter.
  3. Normal executables and symlinks in ~/.local/bin
    • Some are shell functions promoted to scripts so they'll work in dmenu or outside of a terminal context.
  4. /usr/bin system-wide executables

Interactive shell


The prompt path will feature embedded git information provided by path-gitstatus highlighting the root of a git worktree and it's status.

Outside of git worktrees, the path component will be mangled by path-shorthand and be truncated to the last $PATH_WIDTH characters (default is 50) for improved usability.

Termux for Android

Single-user shell environment should work as expected on Termux without root access or changes to $PREFIX/etc with several caveats described below. Post-install scripts make the following adjustments statically for existing scripts.

Standard file descriptors

Shell scripts on Android systems without root access have no access to standard file descriptors /dev/std{in,out,err}, use /proc/self/fd/{0,1,2} instead.

ESC sequences

<backslash>e to insert escape literals in scripts works for some OSC codes, but not all, use octal <backslash>33 when in doubt.


Previously, termux-chroot was used to ensure FHS-compliance, but it introduced unacceptable performance speed.

Use Termux's own provided envvar $PREFIX to refer to standard filesystem locations within scripts or interactively, e.g. $PREFIX/tmp which expands to /data/data/com.termux/files/usr/tmp. In practice, shell script shebangs don't need to be rewritten, Termux already rewrites these with some hidden voodoo I don't care to understand.

Background processes since Android 11

The customized Android images that ship from Chinese and Korean manufacturers since version 11 have become far more aggressive in pruning "phantom" processes (daemons) in the pursuit of better battery life.

You may experience issues with processes backgrounded with the & operator being throttled or killed when multitasking outside of Termux. Daemons that fork without becoming a child process or exec'ing the same process that called it may be killed immediately or shortly after leaving Termux if not called in foreground mode.

In order to prevent Android from prematurely pruning ssh-agent while multitasking, it is called as the parent process for the current shell.

Termux developers recommend their very own termux-services for running common daemons. Launch daemons in foreground mode in another terminal instance without forking and preferably with wakelock acquired from the notification bar if you wish to run a long-running task without being throttled by the operating system.


  • The contents of $OLDPWD is preserved across bash sessions.

  • cd offers the following extensions:

    opt function
    ..., ...., etc. Shorthand for ../../, ../../../ and so on.
    -f <query> Interactive fuzzy find and jump into a sub-directory with fzf


On first-run, chromium will momentarily exit and restart to rebuild configuration and enable use of externally customized color options.

chromium is not meant to be user-serviceable or configurable through plaintext without using system-wide group policy features, chromium is a shell script extended to mangle user-hostile internal state files to match the persistent plaintext configs described below:

C preprocessor syntax is also accepted, hex color values in the form #RRGGBB will be converted to a signed integer representing 0xBBGGRRAA in two's complement hexadecimal with AA (alpha channel) always set to 0xFF

Managed policy overrides

chromium is managed by /etc/chromium/policies/managed/extensions.json, set up during post-install, which automatically installs several useful extensions on first-run, including uBlock Origin.

Configuring Vimium

Use of Vimium is considered optional, as I haven't figured out a way to configure it automatically on first-run. Its configuration resides in ~/.config/chromium/vimium

Run to rebuild vimium-options.json for importing back into Vimium by hand.

An ongoing experiment

chromium has proven difficult to configure non-interactively time and time again. Plaintext chromium configuration is an ongoing experiment of mine.

non-interactive functionality status
first-run config rebuild works
applying persistent chromium settings works
applying persistent chromium flags works
applying persistent omnibox settings works
extension install on first-run works (via group policy)
applying persistent extension settings no


git aliases are defined in ~/.gitconfig or implemented in interactive shell function git()

See Usage Notes for more information.

  • This is a critical component of the graphic shell environment, some aliases are cumulative in nature.

    alias function
    meta Appends --git-dir=$HOME/.config/meta --work-tree=$HOME to a git command.
    (Added implicitly when outside a git directory.)
    past, summary Outlines the last 17 commits made before HEAD with a commit graph.
    future Outlines the next 17 commits made after HEAD with a commit graph.
    rw checkout 1 commit backward, alias for checkout HEAD~1
    ff checkout 1 commit forward toward master
    list-files List all tracked filenames in repo, ideally for use with xargs.
    edit-tree [query] Interactive tracked plaintext file tree, opens file with $EDITOR in new window if X is running.
    flatten Automatically melds --fixup/squash commits out of existence starting from the root commit.
    recommit Stages changes to worktree and commit --amends them as part of the last commit.
    checkin Commit all changes immediately with a generic timestamp and hostname commit message.
    shove Runs checkin and pushes immediately.
    sync Runs git meta pull and then recurses through ~/Git and runs git pull on every existing git repo found.
    vacuum Runs git meta gc and then recurses through ~/Git and runs git gc on every existing git repo found.


nano keybind macros make use of inline non-printable control characters, you must use nano or cat -v to view ~/.nanorc correctly.

  • nano is an alias for nano-overlay which mangles config files and offers the following extended options:

    opt function
    -e, --ctags <tag> <#> Jumps into file containing ctags definition matching <tag>.
    Optional <#> selects from multiple matches, all will open all of them.
    -c, --ctags-dict <file1>... Enable project-wide autocomplete by appending condensed dictionary of all ctags keywords to all files.
    Dictionary will be removed upon exiting.
    -f, --encrypt <file> Open AES encrypted text file with a plaintext password.
    File will be created if it doesn't exist.
    -j, --rsa <file> Open AES encrypted text file with generic RSA keypair in PEM format.
    File will be created if it doesn't exist.
    -s, --ssh-sign <file> Open AES encrypted text file with a nonce value signed with SSH private key.
    File will be created if it doesn't exist.
    -i, --identity <key> Use an OpenSSL compatible keypair to encrypt/decrypt.
    Can be a private key or a public key with private half stored in ssh-agent
  • Once inside the actual nano, the following keybind macros are available:

    key function
    M-0 Execute current line as shell command and pipe contents of buffer as stdin.
    Destructively replaces entire contents of buffer, useful for formatting.
    M-1 Execute current line as shell command and paste output in current buffer.
    Commands within inline comments are accepted.
    M-2 Select token underneath cursor and jump into its ctags definition(s) within the same shell.
    Requires valid tags file in current or a parent directory.
    M-4 Select token underneath cursor and jump into its ctags definition(s) in a new terminal window.
    Requires valid tags file in current or a parent directory.


This particular notify-send implements only -t for expiration time in seconds, because it doesn't tie into any dbus-based notification daemon implementing the Desktop Notifications spec.

Instead, it's just a shell script that writes to a named pipe that gets picked up by xwin-statusd as a simple way to implement OSD text and single-line notifications.

Unlike other implementations, you can pass notifications/OSD text as an argument or via stdin without using xargs.

sc (spreadsheet calculator)

sc supports macros to some degree, but its macro implementation is difficult to understand and there aren't many examples of it being used successfully anywhere that I've managed to find.

Instead, the shell function sc() offers an easier to understand macro system for statically mangling .sc spreadsheet files at runtime.

  • sc will automatically run any executable sharing the same initial name as the .sc file.
    • eg. will run, sheet1.scx, etc. if they exist in the same directory and are executable at runtime.
  • You can write an arbitrarily complex pre-run macro script in any language, so long as it's made aware of its own filename at runtime.
    • Because the sc file format is plaintext, you can generate sc syntax with just a shell script.

sc pre-run macro example

  • This is an example of a conditional macro script for an inventory spreadsheet that color-codes cells when specific strings are found.

     #!/usr/bin/env sh
     # apply colors to specific strings in column B
     file="${0%.*}" # derive .sc file name from name of this script
     # remove all instances of color from the file in place
     { rm "$file"; egrep -v '^color' > "$file"; } < "$file"
     cat <<- EOF >> "$file" # set some non-default colors
     	color 3 = @black;@red
     	color 4 = @black;@yellow
     	color 5 = @black;@green
     # select only string cells from column B, apply colors based on string contents
     # sc format: leftstring B2 = "example string"
     egrep '^((left|right)string|label)' < "$file" | while read -r cmd cell _ str; do
     	case "$cell" in B*)
     		case "$str" in
     			*broken*) echo "color $cell:$cell 3";;
     			*bad*) echo "color $cell:$cell 4";;
     			*working*) echo "color $cell:$cell 5";;
     done >> "$file"


Personal dotfiles, shell scripts—my self-contained graphical shell environment for Debian GNU/Linux, based on xorg, dwm, sxhkd, and urxvt.