diff --git a/base/.bashrc b/base/.bashrc
new file mode 100755
index 00000000..63ef744f
--- /dev/null
+++ b/base/.bashrc
@@ -0,0 +1,14 @@
+#!/bin/bash
+#
+# ~/.bashrc
+#
+
+# If not running interactively, don't do anything
+[[ $- != *i* ]] && return
+
+# Source .profile
+if [ -r ~/.profile ]; then
+	# shellcheck disable=1090
+	source ~/.profile
+fi
+
diff --git a/base/.config/dircolors b/base/.config/dircolors
new file mode 100644
index 00000000..f802612f
--- /dev/null
+++ b/base/.config/dircolors
@@ -0,0 +1,252 @@
+# Copyright (c) 2017-present Arctic Ice Studio <development@arcticicestudio.com>
+# Copyright (c) 2017-present Sven Greb <code@svengreb.de>
+
+# Project:    Nord dircolors
+# Repository: https://github.com/arcticicestudio/nord-dircolors
+# License:    MIT
+
+COLOR tty
+
+TERM ansi
+TERM *color*
+TERM con[0-9]*x[0-9]*
+TERM cons25
+TERM console
+TERM cygwin
+TERM dtterm
+TERM dvtm
+TERM dvtm-256color
+TERM Eterm
+TERM eterm-color
+TERM fbterm
+TERM gnome
+TERM gnome-256color
+TERM hurd
+TERM jfbterm
+TERM konsole
+TERM konsole-256color
+TERM kterm
+TERM linux
+TERM linux-c
+TERM mlterm
+TERM putty
+TERM putty-256color
+TERM rxvt*
+TERM rxvt-unicode
+TERM rxvt-256color
+TERM rxvt-unicode256
+TERM screen*
+TERM screen-256color
+TERM st
+TERM st-256color
+TERM terminator
+TERM tmux*
+TERM tmux-256color
+TERM vt100
+TERM xterm*
+TERM xterm-color
+TERM xterm-88color
+TERM xterm-256color
+
+#+-----------------+
+#+ Global Defaults +
+#+-----------------+
+NORMAL 00
+RESET 0
+
+FILE 00
+DIR 01;34
+LINK 36
+MULTIHARDLINK 04;36
+
+FIFO 04;01;36
+SOCK 04;33
+DOOR 04;01;36
+BLK 01;33
+CHR 33
+
+ORPHAN 31
+MISSING 01;37;41
+
+EXEC 01;36
+
+SETUID 01;04;37
+SETGID 01;04;37
+CAPABILITY 01;37
+
+STICKY_OTHER_WRITABLE 01;37;44
+OTHER_WRITABLE 01;04;34
+STICKY 04;37;44
+
+#+-------------------+
+#+ Extension Pattern +
+#+-------------------+
+#+--- Archives ---+
+.7z 01;32
+.ace 01;32
+.alz 01;32
+.arc 01;32
+.arj 01;32
+.bz 01;32
+.bz2 01;32
+.cab 01;32
+.cpio 01;32
+.deb 01;32
+.dz 01;32
+.ear 01;32
+.gz 01;32
+.jar 01;32
+.lha 01;32
+.lrz 01;32
+.lz 01;32
+.lz4 01;32
+.lzh 01;32
+.lzma 01;32
+.lzo 01;32
+.rar 01;32
+.rpm 01;32
+.rz 01;32
+.sar 01;32
+.t7z 01;32
+.tar 01;32
+.taz 01;32
+.tbz 01;32
+.tbz2 01;32
+.tgz 01;32
+.tlz 01;32
+.txz 01;32
+.tz 01;32
+.tzo 01;32
+.tzst 01;32
+.war 01;32
+.xz 01;32
+.z 01;32
+.Z 01;32
+.zip 01;32
+.zoo 01;32
+.zst 01;32
+
+#+--- Audio ---+
+.aac 32
+.au 32
+.flac 32
+.m4a 32
+.mid 32
+.midi 32
+.mka 32
+.mp3 32
+.mpa 32
+.mpeg 32
+.mpg 32
+.ogg 32
+.opus 32
+.ra 32
+.wav 32
+
+#+--- Customs ---+
+.3des 01;35
+.aes 01;35
+.gpg 01;35
+.pgp 01;35
+
+#+--- Documents ---+
+.doc 32
+.docx 32
+.dot 32
+.odg 32
+.odp 32
+.ods 32
+.odt 32
+.otg 32
+.otp 32
+.ots 32
+.ott 32
+.pdf 32
+.ppt 32
+.pptx 32
+.xls 32
+.xlsx 32
+
+#+--- Executables ---+
+.app 01;36
+.bat 01;36
+.btm 01;36
+.cmd 01;36
+.com 01;36
+.exe 01;36
+.reg 01;36
+
+#+--- Ignores ---+
+*~ 02;37
+.bak 02;37
+.BAK 02;37
+.log 02;37
+.log 02;37
+.old 02;37
+.OLD 02;37
+.orig 02;37
+.ORIG 02;37
+.swo 02;37
+.swp 02;37
+
+#+--- Images ---+
+.bmp 32
+.cgm 32
+.dl 32
+.dvi 32
+.emf 32
+.eps 32
+.gif 32
+.jpeg 32
+.jpg 32
+.JPG 32
+.mng 32
+.pbm 32
+.pcx 32
+.pgm 32
+.png 32
+.PNG 32
+.ppm 32
+.pps 32
+.ppsx 32
+.ps 32
+.svg 32
+.svgz 32
+.tga 32
+.tif 32
+.tiff 32
+.xbm 32
+.xcf 32
+.xpm 32
+.xwd 32
+.xwd 32
+.yuv 32
+
+#+--- Video ---+
+.anx 32
+.asf 32
+.avi 32
+.axv 32
+.flc 32
+.fli 32
+.flv 32
+.gl 32
+.m2v 32
+.m4v 32
+.mkv 32
+.mov 32
+.MOV 32
+.mp4 32
+.mpeg 32
+.mpg 32
+.nuv 32
+.ogm 32
+.ogv 32
+.ogx 32
+.qt 32
+.rm 32
+.rmvb 32
+.swf 32
+.vob 32
+.webm 32
+.wmv 32
diff --git a/base/.config/htop/htoprc b/base/.config/htop/htoprc
new file mode 100644
index 00000000..96c93fcc
--- /dev/null
+++ b/base/.config/htop/htoprc
@@ -0,0 +1,26 @@
+# Beware! This file is rewritten by htop when settings are changed in the interface.
+# The parser is also very primitive, and not human-friendly.
+fields=0 5 48 17 18 38 39 2 46 47 49 1
+sort_key=46
+sort_direction=1
+hide_threads=0
+hide_kernel_threads=1
+hide_userland_threads=1
+shadow_other_users=1
+show_thread_names=1
+show_program_path=1
+highlight_base_name=0
+highlight_megabytes=1
+highlight_threads=1
+tree_view=1
+header_margin=1
+detailed_cpu_time=0
+cpu_count_from_zero=0
+update_process_names=0
+account_guest_in_cpu_meter=0
+color_scheme=0
+delay=15
+left_meters=AllCPUs2 Memory Swap
+left_meter_modes=1 1 1
+right_meters=Tasks LoadAverage Uptime
+right_meter_modes=2 2 2
diff --git a/base/.config/systemd/user/dot-fetch.service b/base/.config/systemd/user/dot-fetch.service
new file mode 100644
index 00000000..fbb9a461
--- /dev/null
+++ b/base/.config/systemd/user/dot-fetch.service
@@ -0,0 +1,9 @@
+# vim:ft=systemd:
+[Unit]
+Description=Dotfile fetch service
+
+[Service]
+ExecStart=git --git-dir=%h/.dotfiles/ --work-tree=%h fetch
+
+[Install]
+WantedBy=default.target
diff --git a/base/.config/systemd/user/dot-fetch.timer b/base/.config/systemd/user/dot-fetch.timer
new file mode 100644
index 00000000..09ae63ef
--- /dev/null
+++ b/base/.config/systemd/user/dot-fetch.timer
@@ -0,0 +1,11 @@
+# vim:ft=systemd:
+[Unit]
+Description=Dot fetch timer
+
+[Timer]
+Persistent=true
+OnBootSec=15min
+OnCalendar=*-*-* 22:00:00
+
+[Install]
+WantedBy=default.target
diff --git a/base/.config/systemd/user/proj-fetch.service b/base/.config/systemd/user/proj-fetch.service
new file mode 100644
index 00000000..a3cb35d6
--- /dev/null
+++ b/base/.config/systemd/user/proj-fetch.service
@@ -0,0 +1,9 @@
+# vim:ft=systemd:
+[Unit]
+Description=Project fetch service
+
+[Service]
+ExecStart=find %h/Projects -type d -iname .git -execdir git fetch \;
+
+[Install]
+WantedBy=default.target
diff --git a/base/.config/systemd/user/proj-fetch.timer b/base/.config/systemd/user/proj-fetch.timer
new file mode 100644
index 00000000..fc5a4fea
--- /dev/null
+++ b/base/.config/systemd/user/proj-fetch.timer
@@ -0,0 +1,11 @@
+# vim:ft=systemd:
+[Unit]
+Description=Project fetch timer
+
+[Timer]
+Persistent=true
+OnBootSec=15min
+OnCalendar=*-*-* 23:00:00
+
+[Install]
+WantedBy=default.target
diff --git a/base/.functions b/base/.functions
new file mode 100644
index 00000000..731d491f
--- /dev/null
+++ b/base/.functions
@@ -0,0 +1,70 @@
+#! /bin/sh
+#
+# .functions
+# Functions for interactive shells
+#
+
+proj() {
+	# Ensure we have an argument
+	if [ -z ${1+x} ]; then
+		echo "proj: requires argument"
+		return 1
+	fi
+	# POSIX mandates this external call to sed
+	projname="$(echo "$1" | sed -re 's/[^ a-zA-Z0-9.-]//g')"
+	projdir="$HOME/Projects/$projname"
+	# Ensure we have a ~/Projects directory
+	mkdir -p "$HOME/Projects" > /dev/null 2>&1
+	# cd into the project or make it if necessary
+	if [ -d "$projdir" ]; then
+		# It exists
+		cd "$projdir" || return 50
+	else
+		# It does not exist
+		echo "Creating new project \"$projname\""
+		mkdir -p "$projdir"
+		cd "$projdir" || return 51
+		if command -v git > /dev/null 2>&1; then
+			# Initialize git
+			echo "Initializing git with .gitignore"
+			git init > /dev/null 2>&1
+			echo '*.swp' > .gitignore
+			git add .gitignore > /dev/null 2>&1
+			git commit -am "Create gitignore" > /dev/null 2>&1
+			git status
+		fi
+	fi
+}
+# Autocompletion for bash
+# A note on the shellcheck disable: that's fine, because this is also a test
+# If it fails, we don't even define a completion function
+# shellcheck disable=2039
+complete -F _proj proj > /dev/null 2>&1 && \
+_proj() {
+	[ "${#COMP_WORDS[@]}" != "2" ] && return 0
+	for dir in "$HOME"/Projects/*; do
+		reply="$(basename "$dir")"
+		reply="${reply//[^ \-a-zA-Z0-9.]/}"
+		# shellcheck disable=2179
+		COMPREPLY+=" $reply"
+	done
+	unset reply
+	# shellcheck disable=2178
+	COMPREPLY=($(compgen -W "$COMPREPLY" "${COMP_WORDS[COMP_CWORD]}"))
+	return 0
+}
+# Autocompletion for zsh
+compdef _proj proj > /dev/null 2>&1 && \
+_proj() {
+	#! /usr/bin/env zsh
+	# It's okay, shellcheck
+	# We zsh now
+	# shellcheck disable=2039
+	for dir in "$HOME"/Projects/*; do
+		temp="$(basename "$dir")"
+		temp="${reply//[^ a-zA-Z0-9.]/}"
+		reply+=" $temp"
+	done
+	return 0
+}
+
diff --git a/base/.inputrc b/base/.inputrc
new file mode 100644
index 00000000..d614f92e
--- /dev/null
+++ b/base/.inputrc
@@ -0,0 +1,17 @@
+## COMPLETION
+set colored-stats on
+set completion-ignore-case on
+# Only need a single tab press to show results
+set show-all-if-ambiguous on
+# You ever do that thing where you put your cursorright in the middle of a word,
+# like you're at the "e" in "Makefile", and hit tab, and bash oh-so-helpfully
+# inserts the latter half of the word right in the middle of it -- so you've
+# got "Makefilefile" sitting in your terminal?
+# Yeah fuck that
+set skip-completed-text on
+
+## KEYBINDINGS
+# History searching
+"\e[A": history-search-backward
+"\e[B": history-search-forward
+
diff --git a/base/.profile b/base/.profile
new file mode 100755
index 00000000..0e1bb204
--- /dev/null
+++ b/base/.profile
@@ -0,0 +1,405 @@
+#!/bin/sh
+
+# Copyright (c) 2017 rehashedsalt/vintagesalt
+# Licensed under the terms of the MIT license
+
+## POSIX NOTICE
+# This script, or at least the parts expected to be run by a standard sh
+# implementation, should be fully POSIX-compliant. If it is not, open a bug
+# report at git.9iron.club/salt/home and I'll take care of it.
+
+## SHELLCHECK
+# Not finding these sources is none of my concern; they're out of scope
+# shellcheck disable=1091
+# shellcheck disable=1090
+# I'm well aware of when functions are defined vs used
+# Those choices are deliberate
+# shellcheck disable=2139
+# shellcheck disable=2016
+# Some variables will not be used here, but in the shell
+# shellcheck disable=2034
+# Quit being pedantic
+# shellcheck disable=1117
+
+# Environment variables
+_baseshell="$(basename -- "$0")"
+
+# Patch PATH
+for dir in \
+	"$HOME/.bin" \
+	"$HOME/.local/bin" \
+	"$HOME/.firestarter" \
+	"/usr/local/opt/coreutils/libexec/gnubin" \
+	"/usr/local/opt/gnu-sed/libexec/gnubin" \
+	"/usr/local/opt/grep/libexec/gnubin" \
+	"/usr/local/opt/util-linux/bin" \
+	"/usr/local/opt/util-linux/sbin"
+do
+	if [ -d "$dir" ]; then
+		PATH="$dir:$PATH"
+	fi
+done
+
+# Grab bash_completion, if it exists
+[ -f "/etc/profile.d/bash_completion.sh" ] && . "/etc/profile.d/bash_completion.sh"
+
+# Source ~/.functions, if it exists
+[ -r "$HOME/.functions" ] \
+	&& [ "$_baseshell" != "sh" ] \
+	&& [ "$_baseshell" != "dash" ] \
+	&& . "$HOME/.functions"
+
+# Define a require function
+has() {
+	[ -z "$1" ] && return 1
+	command -v "$1" > /dev/null 2>&1
+}
+
+# Doot
+if ! has doot; then
+	alias doot="echo Doot doot."
+fi
+
+# Grab pip completion, if it exists
+if has pip; then
+	case "$_baseshell" in
+		*bash)
+			if ! [ -f "$HOME/.pip-completion-bash" ]; then
+				pip completion --bash > "$HOME/.pip-completion-bash"
+				echo ".profile: Created pip completion for bash"
+			fi
+			. "$HOME/.pip-completion-bash"
+			;;
+		zsh)
+			if ! [ -f "$HOME/.pip-completion-zsh" ]; then
+				pip completion --zsh > "$HOME/.pip-completion-zsh"
+				echo ".profile: Created pip completion for zsh"
+			fi
+			. "$HOME/.pip-completion-zsh"
+			;;
+		*)
+			;;
+	esac
+fi
+
+# Set up go, if we have it
+if has go; then
+	export GOPATH="$HOME/.local/go"
+	[ "${PATH#*$GOPATH}" = "$PATH" ] && export PATH="$PATH:$GOPATH/bin"
+fi
+
+# Grab dircolors, if it exists
+if has dircolors; then
+	dircolorsfile="$HOME/.config/dircolors"
+	if [ -r "$dircolorsfile" ]; then
+		eval "$(dircolors "$dircolorsfile")"
+	else
+		eval "$(dircolors -b)"
+	fi
+fi
+
+# Set up our text editor
+for editor in vim vi nano; do
+	if has "$editor"; then
+		export EDITOR="$editor"
+		break
+	fi
+done
+alias e='$EDITOR'
+
+# Now for a shitton of aliases
+if ! has define; then
+	if has mate-dictionary; then
+		alias define='mate-dictionary -n --look-up'
+	fi
+fi
+if ! has helpme; then
+	# https://breanneboland.com/blog/2020/02/28/you-can-put-what-in-dns-txt-records-a-blog-post-for-con-west-2020/
+	alias helpme='dig +short txt "$(( RANDOM % 50 )).maybethiscould.work"'
+fi
+if has emerge; then
+	alias e-depclean='sudo emerge -a --depclean'
+	alias e-inst='sudo emerge -a --jobs --tree --quiet-build y'
+	alias e-upgrade='sudo emerge -DNUua --jobs --tree --quiet-build y --with-bdeps=y --keep-going --backtrack=1000 @world'
+	alias e-newuse='sudo emerge -Uva --jobs --tree --quiet-build y @world'
+	alias e-search='emerge -s'
+	alias e-sync='sudo emerge --sync'
+	if has eclean; then
+		alias e-cleanup='sudo eclean -d distfiles && sudo eclean -d packages'
+	fi
+fi
+if has firestarter and ! has fs; then
+	alias fs="firestarter"
+fi
+if has fork; then
+	alias dup="fork $TERMINAL $PWD"
+fi
+if has git; then
+	# Thanks Bash-it!
+	alias g='git'
+	alias ga='git add'
+	alias gall='git add -A'
+	alias gap='git add -p'
+	alias gb='git branch'
+	alias gbD='git branch -D'
+	alias gba='git branch -a'
+	alias gbd='git branch -d'
+	alias gbm='git branch -m'
+	alias gbt='git branch --track'
+	alias gc='git commit -v'
+	alias gca='git commit -v -a'
+	alias gcam="git commit -v -am"
+	alias gcb='git checkout -b'
+	alias gci='git commit --interactive'
+	alias gcl='git clone'
+	alias gclean='git clean -fd'
+	alias gcm='git commit -v -m'
+	alias gco='git checkout'
+	alias gcob='git checkout -b'
+	alias gcom='git checkout master'
+	alias gcount='git shortlog -sn'
+	alias gcp='git cherry-pick'
+	alias gcpd='git checkout master; git pull; git branch -D'
+	alias gcpx='git cherry-pick -x'
+	alias gcsam="git commit -S -am"
+	alias gct='git checkout --track'
+	alias gd='git diff'
+	alias gdel='git branch -D'
+	alias gds='git diff --staged'
+	alias gdv='git diff -w "$@" | vim -R -'
+	alias get='git'
+	alias gexport='git archive --format zip --output'
+	alias gf='git fetch --all --prune'
+	alias gft='git fetch --all --prune --tags'
+	alias gftv='git fetch --all --prune --tags --verbose'
+	alias gfv='git fetch --all --prune --verbose'
+	alias ggs="gg --stat"
+	alias ggui="git gui"
+	alias gh='cd "$(git rev-parse --show-toplevel)"'
+	alias gl='git pull'
+	alias gll='git log --graph --pretty=oneline --abbrev-commit'
+	alias glum='git pull upstream master'
+	alias gm="git merge"
+	alias gmu='git fetch origin -v; git fetch upstream -v; git merge upstream/master'
+	alias gmv='git mv'
+	alias gp='git push'
+	alias gpatch="git format-patch -1"
+	alias gpo='git push origin'
+	alias gpom='git push origin master'
+	alias gpp='git pull && git push'
+	alias gpr='git pull --rebase'
+	alias gpristine='git reset --hard && git clean -dfx'
+	alias gpu='git push --set-upstream'
+	alias gpuo='git push --set-upstream origin'
+	alias gpuoc='git push --set-upstream origin $(git symbolic-ref --short HEAD)'
+	alias gr='git remote'
+	alias gra='git remote add'
+	alias grm='git rm'
+	alias grv='git remote -v'
+	alias gs='git status'
+	alias gsl="git shortlog -sn"
+	alias gss='git status -s'
+	alias gst="git stash"
+	alias gstb="git stash branch"
+	alias gstd="git stash drop"
+	alias gstl="git stash list"
+	alias gstp="git stash pop"
+	alias gsu='git submodule update --init --recursive'
+	alias gt="git tag"
+	alias gta="git tag -a"
+	alias gtd="git tag -d"
+	alias gtl="git tag -l"
+	alias gtls='git tag -l | sort -V'
+	alias gup='git fetch && git rebase'
+	alias gus='git reset HEAD'
+	alias gwc="git whatchanged"
+	# Add uncommitted and unstaged changes to the last commit
+	alias gcaa="git commit -a --amend -C HEAD"
+	# From http://blogs.atlassian.com/2014/10/advanced-git-aliases/
+	# Show commits since last pull
+	alias gnew="git log HEAD@{1}..HEAD@{0}"
+	# Show untracked files
+	alias gu='git ls-files . --exclude-standard --others'
+fi
+if has nc; then
+	# I'm not sorry
+	alias blinkenlights='nc -v towel.blinkenlights.nl 23'
+fi
+if has ptgdp; then
+	song() {
+		if [ -z "$*" ]; then
+			echo "song: Requires an argument"
+			return 1
+		fi
+		echo "$*" | ptgdp -p
+	}
+fi
+if has sed && has find; then
+	replace() {
+		if [ $# -ne 2 ]; then
+			echo "replace: Requires two arguments"
+			return 1
+		fi
+		find . -type f -exec sed -i "s/$1/$2/g" {} \;
+	}
+fi
+if has sudo; then
+	case $_baseshell in
+		*bash|*zsh)
+			export SUDO_PROMPT=$'[\e[34msudo\e[0m as %U]: Password for %p: '
+			;;
+		*)
+			export SUDO_PROMPT='[sudo as %U]: Password for %p: '
+	esac
+fi
+if has tree; then
+	treeargs='-qF --dirsfirst'
+	alias tree="tree $treeargs"
+	alias t="tree $treeargs -L 2"
+	alias tl="tree $treeargs -pughL 2"
+	alias tp="tree $treeargs -pL 2"
+	alias ts="tree $treeargs -hL 2"
+	unset treeargs
+fi
+if has vault; then
+	alias vlogin="vault login -method=ldap username=$(whoami)"
+	alias vls="vault list"
+	alias vr="vault read"
+fi
+
+# Dotfile management aliases
+if [ -d "$HOME/.dotfiles" ]; then
+	dotcmd='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
+	alias dot="$dotcmd"
+	if [ -f "/usr/share/bash-completion/completions/git" ] && [ "$_baseshell" == "bash" ]; then
+		. "/usr/share/bash-completion/completions/git"
+		__git_complete dot _git
+	fi
+	alias dotupdate="\
+		printf 'Pulling...\n'
+		$dotcmd pull
+		printf 'Updating submodules...\n'
+		$dotcmd submodule update --init --recursive --remote
+		$dotcmd status"
+	alias dotupgrade="\
+		printf 'Pulling...\n'
+		$dotcmd pull
+		printf 'Updating submodules...\n'
+		$dotcmd submodule update --init
+		printf 'Checking out masters...\n'
+		$dotcmd submodule -q foreach --recursive 'git checkout -q master && git pull' && \
+		$dotcmd status"
+	unset dotcmd
+fi
+
+# Aliases for common utilities
+if [ "$(uname)" = "Linux" ] || has brew; then
+	# Assume we have GNU coreutils
+	lsarguments='-Fh --color=auto --group-directories-first'
+	alias l="ls -l --file-type $lsarguments"
+	alias la="ls -A --file-type $lsarguments"
+	alias ls="ls $lsarguments"
+	alias ll="ls -Al --file-type $lsarguments"
+	unset lsarguments
+
+	alias rm='rm -I'
+else
+	# Else only assume POSIX/BSD
+	lsarguments='-F'
+	alias l="ls -$lsarguments"
+	alias la="ls -A $lsarguments"
+	alias ls="ls $lsarguments"
+	alias ll="ls -Ahl $lsarguments"
+fi
+alias cp='cp -i'
+alias d='du -hs'
+
+# Set up a default PS1 for bash
+_ps1bash() {
+	exitcode="$?"
+	r="\[\e[0m\]"
+	bg_red="\[\e[41m\]"
+	bg_yellow="\[\e[43m\]"
+	bg_blue="\[\e[44m\]"
+	fg_black="\[\e[30m\]"
+	fg_red="\[\e[31m\]"
+	fg_green="\[\e[32m\]"
+	fg_yellow="\[\e[33m\]"
+	fg_blue="\[\e[34m\]"
+	fg_magenta="\[\e[35m\]"
+	fg_grey="\[\e[37m\]"
+	fg_bold="\[\e[1m\]"
+
+	# Add hostname prefix in SSH sessions
+	unset _prefix
+	# Alert if in an SSH session
+	if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then
+		_prefix="${fg_bold}${fg_red}$(hostname -s)${r}${fg_red}:${r}${_prefix}"
+	elif [ "$USER" != "salt" ]; then
+		_prefix="${fg_bold}${fg_yellow}$USER${r}${fg_yellow}:${r}${_prefix}"
+	fi
+	# Color PWD
+	_prefix="${_prefix}${fg_blue}"
+	# Show the tilde instead of $HOME
+	_cpwd='\w'
+	# Show read-only status in certain directories
+	if [ "$UID" = 0 ]; then
+		# Root gets the usual "#" prompt
+		_prompt="${fg_red}#${r}"
+	elif ! [ -d "$PWD" ]; then
+		# Something very bad has happened to our PWD, probably got deleted
+		_prompt="${bg_red}${fg_black}!${r}"
+	elif ! [ -r "$PWD" ]; then
+		# Guess we lost privileges
+		_prompt="${fg_red}"'$'"${r}"
+	elif ! [ -x "$PWD" ]; then
+		# Missing execution perms
+		if [ -w "$PWD" ]; then
+			# Fixable
+			_prompt="${bg_blue}${fg_black}"'$'"${r}"
+		else
+			# Unfixable
+			_prompt="${bg_yellow}${fg_black}"'$'"${r}"
+		fi
+	elif ! [ -w "$PWD" ]; then
+		# Can't write is really common but also good to know
+		_prompt="${fg_magenta}"'~'"${r}"
+	else
+		# Otherwise we should be fine
+		_prompt="${fg_green}"'$'"${r}"
+	fi
+	# Alert us if the last command failed
+	unset _fail
+	if ! [ "$exitcode" = "0" ]; then
+		_fail="${fg_bold}${fg_red}?"
+	fi
+	# shellcheck disable=2059
+	PS1="[${_prefix}${_cpwd}${r}]${_fail}${r}${_prompt}${r} "
+}
+
+# And export our PS1
+case "$_baseshell" in
+	zsh)
+		# Don't do this on ZSH
+		# I have a custom theme for that
+		;;
+	*bash)
+		export PS1=""
+		export PROMPT_COMMAND=_ps1bash
+		;;
+	*)
+		export PS1='[\e[31m\w\e[0m]\e[32m\$\e[0m '
+		;;
+esac
+
+# Clean up
+unset gnubin
+unset -v _baseshell
+unset -f has
+
+# Source in a site-specific profile
+localprofile="$HOME/.local/profile"
+if [ -f "$localprofile" ]; then
+	. "$localprofile"
+fi
+