diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 142 | ||||
-rwxr-xr-x | bin/80 | 85 | ||||
-rwxr-xr-x | bin/archiveit | 98 | ||||
-rwxr-xr-x | bin/assert-arg | 78 | ||||
-rwxr-xr-x | bin/backup | 120 | ||||
-rwxr-xr-x | bin/bins | 84 | ||||
-rwxr-xr-x | bin/boop | 83 | ||||
-rwxr-xr-x | bin/brightness | 12 | ||||
-rwxr-xr-x | bin/check | 58 | ||||
-rwxr-xr-x | bin/cl | 315 | ||||
-rwxr-xr-x | bin/clamp | 86 | ||||
-rwxr-xr-x | bin/color | 215 | ||||
-rwxr-xr-x | bin/copy | 67 | ||||
-rwxr-xr-x | bin/dice | 73 | ||||
-rwxr-xr-x | bin/e | 90 | ||||
-rwxr-xr-x | bin/email | 73 | ||||
-rwxr-xr-x | bin/forever | 68 | ||||
-rwxr-xr-x | bin/gc | 150 | ||||
-rwxr-xr-x | bin/gen-password | 73 | ||||
-rwxr-xr-x | bin/git-cleanup | 74 | ||||
-rwxr-xr-x | bin/grun | 96 | ||||
-rwxr-xr-x | bin/htmlesc | 96 | ||||
-rwxr-xr-x | bin/httpno | 156 | ||||
-rwxr-xr-x | bin/lc | 70 | ||||
-rwxr-xr-x | bin/li | 104 | ||||
-rwxr-xr-x | bin/lines | 81 | ||||
-rwxr-xr-x | bin/m | 66 | ||||
-rwxr-xr-x | bin/mailcfg | 297 | ||||
-rwxr-xr-x | bin/max | 84 | ||||
-rwxr-xr-x | bin/menu | 1587 | ||||
-rwxr-xr-x | bin/min | 85 | ||||
-rwxr-xr-x | bin/mkdtemp | 64 | ||||
-rwxr-xr-x | bin/mkstemp | 64 | ||||
-rwxr-xr-x | bin/msg | 153 | ||||
-rwxr-xr-x | bin/n-times | 73 | ||||
-rwxr-xr-x | bin/nato | 102 | ||||
-rwxr-xr-x | bin/ootb | 103 | ||||
-rwxr-xr-x | bin/open | 101 | ||||
-rwxr-xr-x | bin/player | 136 | ||||
-rwxr-xr-x | bin/playlist | 103 | ||||
-rwxr-xr-x | bin/pre | 78 | ||||
-rwxr-xr-x | bin/print | 151 | ||||
-rwxr-xr-x | bin/prompt | 76 | ||||
-rwxr-xr-x | bin/qr | 71 | ||||
-rwxr-xr-x | bin/repos | 175 | ||||
-rwxr-xr-x | bin/rfc | 150 | ||||
-rwxr-xr-x | bin/serve | 78 | ||||
-rwxr-xr-x | bin/slugify | 69 | ||||
-rwxr-xr-x | bin/status-bar | 104 | ||||
-rwxr-xr-x | bin/stopwatch | 65 | ||||
-rwxr-xr-x | bin/tmp | 91 | ||||
-rwxr-xr-x | bin/tmpname | 66 | ||||
-rwxr-xr-x | bin/tuivid | 65 | ||||
-rwxr-xr-x | bin/uc | 70 | ||||
-rwxr-xr-x | bin/untill | 83 | ||||
-rwxr-xr-x | bin/update | 73 | ||||
-rwxr-xr-x | bin/upgrade | 66 | ||||
-rwxr-xr-x | bin/uri | 75 | ||||
-rwxr-xr-x | bin/uuid | 62 | ||||
-rwxr-xr-x | bin/vcs | 255 | ||||
-rwxr-xr-x | bin/vm | 176 | ||||
-rwxr-xr-x | bin/volume | 112 | ||||
-rwxr-xr-x | bin/with-email | 91 | ||||
-rwxr-xr-x | bin/without-env | 70 | ||||
-rwxr-xr-x | bin/wms | 95 | ||||
-rwxr-xr-x | bin/x | 124 | ||||
l--------- | bin/xdg-open | 1 | ||||
-rwxr-xr-x | bin/xmpp | 99 | ||||
-rwxr-xr-x | bin/yt | 113 | ||||
-rwxr-xr-x | bin/z | 153 | ||||
-rw-r--r-- | etc/afew/config | 6 | ||||
-rw-r--r-- | etc/bash/inputrc | 2 | ||||
-rw-r--r-- | etc/git/config | 25 | ||||
-rw-r--r-- | etc/git/ignore | 3 | ||||
-rw-r--r-- | etc/gnupg/gpg-agent.conf.tmpl | 6 | ||||
-rw-r--r-- | etc/gnupg/gpg.conf | 1 | ||||
-rw-r--r-- | etc/gnupg/sshcontrol | 1 | ||||
-rw-r--r-- | etc/guile/init.scm | 6 | ||||
-rw-r--r-- | etc/guix/channels.scm | 20 | ||||
-rw-r--r-- | etc/guix/home.scm | 625 | ||||
-rw-r--r-- | etc/guix/system.scm | 209 | ||||
-rw-r--r-- | etc/hg/hgrc | 2 | ||||
-rw-r--r-- | etc/i3/config | 185 | ||||
-rw-r--r-- | etc/i3status/config | 31 | ||||
-rw-r--r-- | etc/info/infokey | 7 | ||||
-rw-r--r-- | etc/khal/config | 16 | ||||
-rw-r--r-- | etc/khard/khard.conf | 13 | ||||
-rw-r--r-- | etc/lisp-cli/init.lisp | 33 | ||||
-rw-r--r-- | etc/mailcaps/config | 1 | ||||
-rw-r--r-- | etc/newsboat/config | 1 | ||||
l--------- | etc/newsboat/urls | 1 | ||||
-rw-r--r-- | etc/python/pythonrc.py | 15 | ||||
-rw-r--r-- | etc/ranger/rc.conf | 4 | ||||
-rw-r--r-- | etc/remhind/config.tmpl | 4 | ||||
-rwxr-xr-x | etc/sh/cronjob.sh | 74 | ||||
l--------- | etc/sh/privrc.sh | 1 | ||||
-rw-r--r-- | etc/sh/rc | 364 | ||||
-rw-r--r-- | etc/sh/root-rc | 87 | ||||
-rw-r--r-- | etc/ssh/config.tmpl | 6 | ||||
-rw-r--r-- | etc/ssh/known_hosts | 84 | ||||
-rw-r--r-- | etc/tmux/tmux.conf | 96 | ||||
l--------- | etc/weechat/irc.conf | 1 | ||||
-rwxr-xr-x | opt/aux/gen-e-list.sh | 24 | ||||
l--------- | opt/bin-dirs/clisp | 1 | ||||
l--------- | opt/bin-dirs/euandre.org | 1 | ||||
-rwxr-xr-x | opt/tests/assert-gpg-expiration.sh | 22 | ||||
-rw-r--r-- | share/msg/bad.ogg | bin | 0 -> 12217 bytes | |||
-rw-r--r-- | share/msg/good.ogg | bin | 0 -> 99826 bytes |
109 files changed, 10601 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9907b46 --- /dev/null +++ b/Makefile @@ -0,0 +1,142 @@ +.POSIX: + + +pod2man = \ + share/man/man1/x.1 \ + share/man/man1/z.1 \ + + +lisp-images = \ + $(XDG_DATA_HOME)/lisp-cli/clozure.image \ + $(XDG_DATA_HOME)/lisp-cli/clisp.image \ + $(XDG_DATA_HOME)/lisp-cli/sbcl.image \ + +derived-assets = \ + $(pod2man) \ + $(XDG_CONFIG_HOME)/ssh/id_rsa.pub \ + $(XDG_CONFIG_HOME)/git/config-extra \ + $(XDG_CONFIG_HOME)/gnupg/gpg-agent.conf \ + $(XDG_CONFIG_HOME)/remhind/config \ + $(XDG_CONFIG_HOME)/ssh/config \ + $(XDG_CONFIG_HOME)/alot/config \ + $(XDG_CONFIG_HOME)/mbsync/config \ + $(XDG_CONFIG_HOME)/msmtp/config \ + $(XDG_CONFIG_HOME)/notmuch/default/config \ + $(XDG_CONFIG_HOME)/notmuch/default/hooks/post-new \ + $(XDG_DATA_HOME)/euandreh/mailcfg-accounts.txt \ + $(XDG_DATA_HOME)/common-lisp/source \ + $(XDG_DATA_HOME)/euandreh/e.list.txt \ + $(lisp-images) + + +all: $(derived-assets) + + +share/man/man1/x.1: bin/x + pod2man bin/x > $@ + +share/man/man1/z.1: bin/z + pod2man bin/z > $@ + +$(XDG_DATA_HOME)/common-lisp/source: + ln -s $(SRC)/libre $@ + +$(XDG_CONFIG_HOME)/alot/config: bin/mailcfg + mailcfg alot > $@ + +$(XDG_CONFIG_HOME)/mbsync/config: bin/mailcfg + mailcfg mbsync > $@ + +$(XDG_CONFIG_HOME)/msmtp/config: bin/mailcfg + mailcfg msmtp > $@ + +$(XDG_CONFIG_HOME)/notmuch/default/config: bin/mailcfg + mailcfg notmuchcfg > $@ + +$(XDG_CONFIG_HOME)/notmuch/default/hooks/post-new: bin/mailcfg + mailcfg notmuchhook > $@ + chmod +x $@ + +$(XDG_DATA_HOME)/euandreh/mailcfg-accounts.txt: bin/mailcfg + mailcfg list > $@ + +$(XDG_CONFIG_HOME)/ssh/id_rsa.pub: + gpg --export-ssh-key eu@euandre.org > $@ + chmod 600 $@ + +$(XDG_CONFIG_HOME)/remhind/config: $(XDG_CONFIG_HOME)/remhind/config.tmpl + envsubst < $(XDG_CONFIG_HOME)/remhind/config.tmpl > $@ + +$(XDG_CONFIG_HOME)/ssh/config: $(XDG_CONFIG_HOME)/ssh/config.tmpl + envsubst < $(XDG_CONFIG_HOME)/ssh/config.tmpl > $@ + +$(XDG_CONFIG_HOME)/git/config-extra: + printf '[sendemail]\n smtpserver = ' > $@ + command -v msmtpq >> $@ + +$(XDG_CONFIG_HOME)/gnupg/gpg-agent.conf: $(XDG_CONFIG_HOME)/gnupg/gpg-agent.conf.tmpl + cp $(XDG_CONFIG_HOME)/gnupg/gpg-agent.conf.tmpl $@ + printf 'pinentry-program ' >> $@ + command -v pinentry-gtk-2 >> $@ + +$(XDG_DATA_HOME)/euandreh/e.list.txt: ~/Documents/txt/ opt/aux/gen-e-list.sh + sh opt/aux/gen-e-list.sh > $@ + +$(lisp-images): $(XDG_CONFIG_HOME)/lisp-cli/init.lisp bin/cl bin/li + I=`echo $@ | awk -F/ '$$0=$$(NF)' | cut -d. -f1` && \ + li -vI $$I -E "(format t \"Image for \\\"$$I\\\" created.~%\")" + + + +check-shellcheck: + git ls-files | \ + sor 'test -f' | \ + xargs awk '/^#!\/bin\/sh$$/ { print FILENAME } { nextfile }' | \ + xargs shellcheck -x + +check-perlcritic: + git ls-files | \ + sor 'test -f' | \ + xargs awk '/^#!\/usr\/bin\/env perl$$/ { print FILENAME } { nextfile }' | \ + xargs perlcritic --exclude=subroutine + +FIXME-excludes = \ + ':(exclude)Makefile' \ + ':(exclude)etc/git/ignore' \ + ':(exclude)opt/aux/gen-e-list.sh' +check-fixme: + if git grep FIXME -- $(FIXME-excludes); then \ + printf 'Leftover FIXME markers.\n' >&2; \ + exit 1; \ + fi + +check-dirty-public: + if ! git diff --quiet || ! git diff --quiet --staged; then \ + printf 'Dirty tilde repository.\n' >&2; \ + exit 1; \ + fi + +check-dirty-private: + if ! git -C $(PRIV_CONFIG) diff --quiet || \ + ! git -C $(PRIV_CONFIG) diff --quiet --staged; then \ + printf 'Dirty private tilde repository.\n' >&2; \ + exit 1; \ + fi + +check-opt: + find opt/tests/ -name '*.sh' -exec {} + + +check-pod: + podchecker bin/z + +check-sync: + if git status --short --branch --porcelain | head -n1 | grep -E '(ahead|behind)'; then \ + printf 'Out of sync with origin.\n' >&2; \ + exit 1; \ + fi + +check: check-shellcheck check-perlcritic check-fixme check-dirty-public \ + check-dirty-private check-opt check-pod check-sync + +clean: + rm -f $(derived-assets) @@ -0,0 +1,85 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + 80 [FILENAME...] + 80 -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + FILENAME the name of the file to work on (default: + the list of file in the VCS repository) + + + List the lines in the files that contain more than 80 columns. + + + Examples: + + Check for all the files in the current repository: + + $ 80 + + + Detect on the given two files: + + $ 80 f1 f2 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +len() { + awk ' + length > 80 { + printf "%s:%s:%s\n", FILENAME, FNR, $0 + } + ' "$1" +} + +if [ $# = 0 ]; then + vcs ls-files | while read -r f; do + len "$f" + done +else + for f in "$@"; do + len "$f" + done +fi diff --git a/bin/archiveit b/bin/archiveit new file mode 100755 index 0000000..da132d7 --- /dev/null +++ b/bin/archiveit @@ -0,0 +1,98 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + archiveit + archiveit -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + + Grab all bookmarks from Firefox that contains tags and archive + them with ArchiveBox. + + + Examples: + + Just run it: + + $ archiveit + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +# Derived from ArchiveBox: +# https://github.com/pirate/ArchiveBox/blob/db1f6efc934bbcdf53377bf51a064c6fd0fc5b1d/bin/archivebox-export-browser-history#L23-L37 +QUERY="$( + cat <<-'EOF' + SELECT json_object('timestamp', dateAdded, 'description', title, 'href', url) + FROM ( + SELECT b.dateAdded, b.title, p.url + FROM moz_bookmarks AS b + JOIN moz_places AS p + ON b.fk = p.id + WHERE b.fk IN ( + SELECT DISTINCT(fk) FROM moz_bookmarks + WHERE parent IN ( + -- get all tags + SELECT id FROM moz_bookmarks + WHERE parent = 4 + ) + ) + AND b.title IS NOT NULL + ORDER BY + b.dateAdded ASC, + b.title ASC, + p.url + ) + EOF +)" + +# Copy the database because it is locked. +DB="$(mkstemp)" +cp ~/.mozilla/firefox/*.default/places.sqlite "$DB" + +cd ~/Documents/Archive/ + +sqlite3 "$DB" "$QUERY" | archivebox add +archivebox update diff --git a/bin/assert-arg b/bin/assert-arg new file mode 100755 index 0000000..d7bc8f4 --- /dev/null +++ b/bin/assert-arg @@ -0,0 +1,78 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + assert-arg STRING MESSAGE + assert-arg -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + STRING the string to check if is empty + MESSAGE the message to print when STRING is empty + + + Examples: + + Assert that $1 contains an argument, named FILENAME: + + $ eval "$(assert-arg "${1:-}" 'FILENAME')" + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +STRING="${1:-}" +MESSAGE="${2:-}" + +if [ -z "$MESSAGE" ]; then + printf 'Missing MESSAGE, an argument to assert-arg.\n\n' >&2 + usage >&2 + exit 2 +fi + + +if [ -z "$STRING" ]; then + printf 'Missing %s.\n\n' "$MESSAGE" >&2 + cat <<-'EOF' + usage >&2 + exit 2 + EOF +fi diff --git a/bin/backup b/bin/backup new file mode 100755 index 0000000..be7d996 --- /dev/null +++ b/bin/backup @@ -0,0 +1,120 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + backup [-q] [-C COMMENT] [ARCHIVE_TAG] + backup -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -q disable verbose mode, useful for + non-interactive sessions + -C COMMENT the comment text to be attached to the archive + -h, --help show this message + + ARCHIVE_TAG the tag used to create the new + backup (default: "manual") + + + The repository is expected to have been created with: + + $ borg init -e repokey-blake2 + + The following environment variables are expected to be exported: + + $BORG_PASSCOMMAND + $BORG_REPO + $BORG_REMOTE_PATH + + Password-less SSH access is required, usually done via adding + ~/.ssh/id_rsa.pub to suyin:.ssh/authorized_keys. + + + Examples: + + Run backup manually: + + $ backup + + Create backup with comment, and verbose mode active: + + $ backup -qC 'The backup has a comment' my-backup + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +VERBOSE_FLAGS='--verbose --progress' +COMMENT='' +while getopts 'qC:h' flag; do + case "$flag" in + q) + VERBOSE_FLAGS='' + ;; + C) + COMMENT="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +ARCHIVE_TAG="${1:-manual}" + + +run() { + set -x + # The contents of $VERBOSE_FLAGS doesn't involve user input: + # shellcheck disable=2086 + borg create \ + $VERBOSE_FLAGS \ + --comment "$COMMENT" \ + --exclude "$XDG_CACHE_HOME" \ + --exclude ~/Downloads/ \ + --stats \ + --compression lzma,9 \ + "::{hostname}-{now}-$ARCHIVE_TAG" \ + ~/ + STATUS=$? + set +x + + if [ "$STATUS" = 0 ]; then + return 0 + elif [ "$STATUS" = 1 ]; then + printf 'WARNING, but no ERROR.\n' >&2 + return 0 + else + return "$STATUS" + fi +} + +run || exit $? diff --git a/bin/bins b/bin/bins new file mode 100755 index 0000000..9f869de --- /dev/null +++ b/bin/bins @@ -0,0 +1,84 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + bins [-F] + bins -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -F force remove the cache file + -h, --help show this message + + + List the available binaries in $PATH. The result is cached on + the $XDG_CACHE_HOME/euandreh/bins file if $PATH values didn't + change. + + + Examples: + + Pick an executable using `dmenu`: + + $ bins | dmenu + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +FORCE=false +while getopts 'Fh' flag; do + case "$flag" in + F) + FORCE=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND- 1)) + + +F="$XDG_CACHE_HOME/euandreh/bins" +if [ "$FORCE" = true ]; then + rm -f "$F" +fi + +IFS=: +# Word-splitting is the goal: +# shellcheck disable=2086 +if stest -rdq -n "$F" $PATH; then + T="$(mkstemp)" + trap 'rm -f $T' EXIT + stest -lxf $PATH | sort -u > "$T" + mv "$T" "$F" +fi + +cat "$F" diff --git a/bin/boop b/bin/boop new file mode 100755 index 0000000..a7792b3 --- /dev/null +++ b/bin/boop @@ -0,0 +1,83 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + boop [-m MESSAGE] -- COMMAND... + boop -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -m MESSAGE text message of the desktop + notification (default: COMMAND) + -h, --help show this message + + COMMAND the commands to be executed + + + Examples: + + Play the positive sound, using the command as message: + + $ boop echo 123 + + Fail with the underlying 127 return code with the + message "ERROR": + + $ boop -m ERROR ech 123 + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'm:h' flag; do + case "$flag" in + m) + MESSAGE="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +set +e +"$@" +STATUS=$? +set -e + +if [ "$STATUS" = 0 ]; then + N=0 +else + N=1 +fi + +CMD="$*" +msg -"$N" -bs -D "${MESSAGE:-$CMD}" + +exit "$STATUS" diff --git a/bin/brightness b/bin/brightness new file mode 100755 index 0000000..10bd198 --- /dev/null +++ b/bin/brightness @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu + +BRIGHTNESS_DIFF="$1" + +HANDLER=/sys/class/backlight/"$BACKLIGHT_DEVICE" + +OLD_BRIGHTNESS="$(cat "$HANDLER"/brightness)" +MAX_BRIGHTNESS="$(cat "$HANDLER"/max_brightness)" +SUM=$((OLD_BRIGHTNESS + BRIGHTNESS_DIFF)) +NEW_BRIGHTNESS="$(clamp -- "$SUM" 0 "$MAX_BRIGHTNESS")" +echo "$NEW_BRIGHTNESS" > "$HANDLER"/brightness || sudo chmod 666 "$HANDLER"/brightness diff --git a/bin/check b/bin/check new file mode 100755 index 0000000..8348041 --- /dev/null +++ b/bin/check @@ -0,0 +1,58 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + check + check -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + + Run Makefile tests. This binary is available to simplify the + cronjob. + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +cd "$XDG_PREFIX" +make check @@ -0,0 +1,315 @@ +#!/bin/sh +set -eu + + +uuid() { + od -xN20 /dev/urandom | + head -n1 | + awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +tmpname() { + echo "${TMPDIR:-/tmp}/cl.tmpfile.$(uuid)" +} + +mkstemp() { + name="$(tmpname)" + touch "$name" + echo "$name" +} + +escape_name() { + printf '%s' "$1" | + sed 's|"|\\"|g' | + printf '(load "%s")\n' "$(cat -)" +} + + +IMPLEMENTATIONS=' +abcl +allegro +clasp +clisp +clozure +cmucl +ecl +jscl +mkcl +sbcl +' + +usage() { + cat <<-'EOF' + Usage: + cl [-e EXP] [-f FILE] [-p] [-M IMAGE] [-I IMPL] [-n] [-v] [FILE...] [-- LISP_OPTIONS] + cl -l + cl -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -e EXP an sexp to be evaluated (can be given more than once) + -E EXP an sexp to be executed as a script + -f FILE a file to be evaluated (can be given more than once) + -p print the value of the last given expression + -M IMAGE load the given Lisp image + -I IMPL use the given implementation (default: $LISP_CLI_IMPL) + -n skip loading the implementation's init file + -v verbose mode + -l list the known types of implementations + -h, --help show this message + + FILE the file to be executed as a script + LISP_OPTIONS options to be forwarrded to the underlying Lisp command + + + Lauch the desired Lisp implementation, properly adapting the given + CLI options. + + When the implementation is not explicited on the command line, and + the $LISP_CLI_IMPL environment variable in unset, implementations are + searched for alphabetically in $PATH, untill one is found, otherwise + an error is emitted. + + The supported implementations are: + EOF + + + for i in $IMPLEMENTATIONS; do + printf -- '- %s\n' "$i" + done + + cat <<-'EOF' + + + Examples: + + Launch CLISP REPL, when $LISP_CLI_IMPL is set to 'clisp': + + $ cl + + + Lauch SBCL REPL, with the given Lisp image loaded, skipping the + loading of the '.sbclrc' file: + + $ cl -n -Isbcl sbcl.image + + + Run file1.lisp with ABCL: + + $ cl -Iabcl file1.lisp + + + Process STDIN: + + $ cat <<-EOS > process.lisp + + EOS + $ cat f1.txt f2.txt | cl -p process.lisp + + + Print a value on all types of implementations: + + $ for i in `cl -l`; do cl -I$i -pe 'call-arguments-list'; done + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +NO_RC=false +SCRIPT="$(mkstemp)" +LISP_CLI_RC="${XDG_CONFIG_HOME:-$HOME/.config}/lisp-cli/init.lisp" +VERBOSE=false +IMAGE='' +IMPL="${LISP_CLI_IMPL:-}" +INTERACTIVE=true +while getopts 'e:E:f:pM:I:nvlh' flag; do + case "$flag" in + e) + printf '%s\n' "$OPTARG" >> "$SCRIPT" + ;; + E) + printf '%s\n' "$OPTARG" >> "$SCRIPT" + INTERACTIVE=false + ;; + f) + escape_name "$OPTARG" >> "$SCRIPT" + ;; + M) + IMAGE="$OPTARG" + ;; + I) + IMPL="$OPTARG" + ;; + n) + NO_RC=true + ;; + v) + VERBOSE=true + ;; + l) + for i in $IMPLEMENTATIONS; do + printf '%s\n' "$i" + done + exit + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done + +shift $((OPTIND - 2)) +if [ "$1" != '--' ]; then + shift +fi + +PRESERVE_ARGS=false +for f in "$@"; do + if [ "$f" = '--' ]; then + PRESERVE_ARGS=true + shift + break + fi + INTERACTIVE=false + escape_name "$f" >> "$SCRIPT" +done + +if [ "$PRESERVE_ARGS" = false ]; then + set -- +fi + +MAIN="$(mkstemp)" +if [ "$NO_RC" = false ] && [ -e "$LISP_CLI_RC" ]; then + escape_name "$LISP_CLI_RC" > "$MAIN" +fi + +if [ "$INTERACTIVE" = true ]; then + escape_name "$SCRIPT" >> "$MAIN" +else + cat <<-EOF >> "$MAIN" + (handler-case + (progn + (load "$SCRIPT" + :verbose nil + :print nil) + (uiop:quit 0)) + (error (e) + (format *error-output* "~&~%error: ~a~%" e) + (uiop:quit 1))) + EOF +fi + +if [ -z "$IMPL" ]; then + for i in $IMPLEMENTATIONS; do + if command -v "$i" > /dev/null; then + IMPL="$i" + break + fi + done + if [ -z "$IMPL" ]; then + printf "Could not find any implementation in \$PATH.\n" >&2 + exit 2 + fi +fi + + +case "$IMPL" in + abcl) + exit 4 + ;; + allegro) + exit 4 + ;; + clasp) + exit 4 + ;; + clisp) + set -- -ansi -i "$MAIN" "$@" + if [ -n "$IMAGE" ]; then + set -- -M "$IMAGE" "$@" + fi + if [ "$NO_RC" = true ]; then + set -- -norc "$@" + fi + if [ "$VERBOSE" = false ]; then + set -- -q -q "$@" + else + set -x + fi + exec clisp "$@" + ;; + clozure) + set -- -l "$MAIN" "$@" + if [ -n "$IMAGE" ]; then + set -- -I "$IMAGE" "$@" + fi + if [ "$NO_RC" = true ]; then + set -- -n "$@" + fi + if [ "$VERBOSE" = false ]; then + set -- -Q "$@" + else + set -x + fi + exec ccl "$@" + ;; + cmucl) + exit 4 + ;; + ecl) + exit 4 + ;; + jscl) + exit 4 + ;; + mkcl) + exit 4 + ;; + sbcl) + set -- --load "$MAIN" "$@" + if [ -n "$IMAGE" ]; then + # The '--core' "C runtime option" must appear before the + # other "Lisp options", such as '--load'. + set -- --core "$IMAGE" "$@" + fi + if [ "$NO_RC" = true ]; then + set -- --no-sysinit --no-userinit "$@" + fi + if [ "$VERBOSE" = false ]; then + set -- --noinform "$@" + else + set -x + fi + exec sbcl "$@" + ;; + *) + printf 'Unsupported implementation: "%s".\n\n' "$IMPL" >&2 + usage >&2 + exit 2 + ;; +esac diff --git a/bin/clamp b/bin/clamp new file mode 100755 index 0000000..a673f72 --- /dev/null +++ b/bin/clamp @@ -0,0 +1,86 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + clamp NUMBER MIN MAX + clamp -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Clamp the NUMBER between MIN and MAX. + + + Examples: + + Assert the number is within the interval: + + $ clamp 5 3 9 + 5 + + When a number is below MIN it gets clamped: + + $ clamp 1 3 9 + 3 + + When a number is above MAX it gets clamped: + + $ clamp 15 3 9 + 9 + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +NUMBER="${1:-}" +MIN="${2:-}" +MAX="${3:-}" + +eval "$(assert-arg "$NUMBER" 'NUMBER')" +eval "$(assert-arg "$MIN" 'MIN')" +eval "$(assert-arg "$MAX" 'MAX')" + + +if [ "$MIN" -gt "$MAX" ]; then + printf 'MIN (%s) is greater then MAX (%s).\n' "$MIN" "$MAX" >&2 + exit 2 +fi + +min -- "$(max -- "$NUMBER" "$MIN")" "$MAX" diff --git a/bin/color b/bin/color new file mode 100755 index 0000000..dc610e9 --- /dev/null +++ b/bin/color @@ -0,0 +1,215 @@ +#!/bin/sh +# shellcheck disable=2059 +set -eu + +usage() { + cat <<-'EOF' + Usage: + color -c COLOR TEXT + color -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -c COLOR + -h, --help show this message + + + Print the given text with a color. + + The available colors are: + EOF + list_colors | sed 's/^/ /' + + cat <<-'EOF' + + Examples: + + Print "banana" in yellow: + + $ color -c yellow 'banana' + banana + + Print "grass" in green, with a newline: + + $ color -c green 'grass\n' + grass + EOF +} + + +END="\033[0m" + +black() { + BLACK="\033[0;30m" + printf "${BLACK}${1}${END}" +} + +blackb() { + BLACK_B="\033[1;30m" + printf "${BLACK_B}${1}${END}" +} + +blacki() { + BLACK_I="\033[0;90m" + printf "${BLACK_I}${1}${END}" +} + +white() { + WHITE="\033[0;37m" + printf "${WHITE}${1}${END}" +} + +whiteb() { + WHITE_B="\033[1;37m" + printf "${WHITE_B}${1}${END}" +} + +red() { + RED="\033[0;31m" + printf "${RED}${1}${END}" +} + +redb() { + RED_B="\033[1;31m" + printf "${RED_B}${1}${END}" +} + +green() { + GREEN="\033[0;32m" + printf "${GREEN}${1}${END}" +} + +greenb() { + GREEN_B="\033[1;32m" + printf "${GREEN_B}${1}${END}" +} + +yellow() { + YELLOW="\033[0;33m" + printf "${YELLOW}${1}${END}" +} + +yellowb() { + YELLOW_B="\033[1;33m" + printf "${YELLOW_B}${1}${END}" +} + +blue() { + BLUE="\033[0;34m" + printf "${BLUE}${1}${END}" +} + +blueb() { + BLUE_B="\033[1;34m" + printf "${BLUE_B}${1}${END}" +} + +bluei() { + BLUE_I="\033[0;94m" + printf "${BLUE_I}${1}${END}" +} + +purple() { + PURPLE="\033[0;35m" + printf "${PURPLE}${1}${END}" +} + + +purpleb() { + PURPLE_B="\033[1;35m" + printf "${PURPLE_B}${1}${END}" +} + +lightblue() { + LIGHT_BLUE="\033[0;36m" + printf "${LIGHT_BLUE}${1}${END}" +} + +lightblueb() { + LIGHT_BLUE_B="\033[1;36m" + printf "${LIGHT_BLUE_B}${1}${END}" +} + +COLOR_LIST=' +black +blackb +white +whiteb +red +redb +green +greenb +yellow +yellowb +blue +blueb +purple +purpleb +lightblue +lightblueb +blacki +bluei +' +list_colors() { + for c in $COLOR_LIST; do + printf '%s\n' "$("$c" "$c")" + done +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +COLOR_FN='' +while getopts 'c:h' flag; do + case "$flag" in + c) + EXISTS=false + for c in $COLOR_LIST; do + if [ "$OPTARG" = "$c" ]; then + EXISTS=true + break + fi + done + if [ "$EXISTS" = false ]; then + printf 'Invalid color: %s\n' "$OPTARG" >&2 + exit 2 + fi + COLOR_FN="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +TEXT="${1:-}" + +eval "$(assert-arg "$COLOR_FN" '-c COLOR')" +eval "$(assert-arg "$TEXT" 'TEXT')" + + +"$COLOR_FN" "$TEXT" diff --git a/bin/copy b/bin/copy new file mode 100755 index 0000000..64e1e32 --- /dev/null +++ b/bin/copy @@ -0,0 +1,67 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + copy [-n] < STDIN + copy -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -n remove newlines + -h, --help show this message + + Examples: + + Copy numbers to clipboard: + seq 10 | copy + + Copy string without newline: + echo 'with automatic newline' | copy -n + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +TRIM=false +while getopts 'nh' flag; do + case "$flag" in + n) + TRIM=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ "$TRIM" = true ]; then + cat - | tr -d '\n' | xclip -sel clip +else + cat - | xclip -sel clip +fi diff --git a/bin/dice b/bin/dice new file mode 100755 index 0000000..4a145d0 --- /dev/null +++ b/bin/dice @@ -0,0 +1,73 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + dice [SIZE] + dice -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + SIZE the size of the dice (default: 6) + + + Roll a dice of SIZE. Caveat: rolling a dice more than once in + the same second will give you the same number. + + + Examples: + + Roll a dice of size 6: + + $ dice + 3 + + Roll a D20: + + $ dice 20 + 15 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +SIZE="${1:-6}" +RAND="$(awk 'BEGIN { srand(); print int(rand()*32768) }' /dev/null)" +echo $(((RAND % SIZE) + 1)) @@ -0,0 +1,90 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + e [FILE] + e -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help + + + Flexibly run a text editor, either directly on in a pipe. + + Examples: + + Edit "file.txt": + + $ e file.txt + + Manipulate the content of a pipe midway: + + $ seq 10 | e | grep 5 + + The editor used is either $VISUAL or $EDITOR, with a fallback to + vi in case any of those variables aren't defined. + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +CMD="${VISUAL:-${EDITOR:-vi}}" +if [ "$CMD" = 'e' ]; then + CMD='vi' +fi + +if [ ! -t 0 ]; then + F="$(mkstemp)" + cat > "$F" + exec 0</dev/tty + exec 3>&1 + exec 1>/dev/tty + $CMD "$F" + cat "$F" >&3 +else + if [ $# -eq 0 ]; then + f="$(fzf --select-1 --exit-0 < "$XDG_DATA_HOME"/euandreh/e.list.txt)" + if [ -n "$f" ]; then + history -s e "$f" + sh -c "$CMD $f" + fi + else + $CMD "$@" + fi +fi diff --git a/bin/email b/bin/email new file mode 100755 index 0000000..87d1cec --- /dev/null +++ b/bin/email @@ -0,0 +1,73 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + email -s SUBJECT ADDRESS... < BODY + email -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -s SUBJECT the email subject + -h, --help show this message + + ADDRESS the email addresses to send the email to + BODY the text to be sent as the body + + + Send an email to ADDRESS using BODY. + + + Examples: + + Send 10 numbers to mail@example.com: + + $ seq 10 | email -s number mail@email.com + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 's:h' flag; do + case "$flag" in + s) + SUBJECT="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +eval "$(assert-arg "${SUBJECT:-}" '-s SUBJECT')" +eval "$(assert-arg "${1:-}" 'ADDRESS')" + +printf 'Content-Type: text/plain; charset=UTF-8\nSubject: %s\n\n%s' \ + "$(echo "$SUBJECT" | tr -d '\n')" \ + "$(cat)" | + msmtpq -a EuAndreh "$@" diff --git a/bin/forever b/bin/forever new file mode 100755 index 0000000..d4410e5 --- /dev/null +++ b/bin/forever @@ -0,0 +1,68 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + forever -- COMMAND... + forever -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Run COMMAND forever. + + + Examples: + + Print 123 forever: + + $ forever echo 123 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +eval "$(assert-arg "${1:-}" 'COMMAND')" + + +while true; do + STATUS=0 + "$@" || STATUS=$? + printf 'Exitted with code %s.\n' "$STATUS" >&2 +done @@ -0,0 +1,150 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + gc [TYPE] + gc -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + TYPE what to do GC on (default: all): + - guix + - nohup + - trash + - tmpdir + - docker + - email + - vcs + + + Free disk space system-wide. + + + Examples: + + Just run it, for all: + + $ gc + + + Cleanup tmpdir: + + $ gc tmpdir + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +disk() { + df -h . | + awk 'NR == 2 { printf "%s - %s/%s\n", $4, $3, $2 }' +} + + +gc_guix() { + pass show velhinho/0-andreh-password | head -n1 | sudo -iS guix system delete-generations + pass show velhinho/0-andreh-password | head -n1 | sudo -iS guix gc --optimize -d + guix home delete-generations + guix gc --optimize -d +} + +gc_nohup() { + find ~/ -type f -name 'nohup.out' -exec rm -vf "{}" \; +} + +gc_trash() { + yes | trash-empty +} + +gc_tmpdir() { + find "${TMPDIR:-/tmp}" -type f -atime +10 -exec rm -vf "{}" \; ||: +} + +gc_docker() { + if command -v docker; then + yes | docker system prune -a + docker rmi "$(docker images -a -q)" ||: + docker rm "$(docker ps -a -f status=exited -q)" ||: + docker stop "$(docker ps -a -q)" ||: + docker rm "$(docker ps -a -q)" ||: + yes | docker volume prune + yes | docker container prune + fi +} + +gc_email() { + notmuch search --output=files --exclude=false tag:killed | + xargs -I{} rm -vf "{}" +} + +gc_vcs() { + repos -e ~/dev/go/ -e ~/dev/quicklisp/ -e ~/dev/archive/ ~/dev/ | + xargs -I% -P4 x vcs -C% gc OR true +} + + +gc_all() { + set -x + gc_guix + gc_nohup + gc_trash + gc_tmpdir + gc_docker + gc_email + gc_vcs + set +x +} + +TYPE="${1:-all}" +CMD=gc_"$TYPE" +if ! command -v "$CMD" >/dev/null; then + printf 'Invalid TYPE: "%s".\n\n' "$TYPE" >&2 + usage >&2 + exit 2 +fi + + +BEFORE="$(disk)" +"$CMD" +AFTER="$(disk)" + +printf 'Disk space:\n' +printf ' before: %s\n' "$BEFORE" +printf ' after: %s\n' "$AFTER" diff --git a/bin/gen-password b/bin/gen-password new file mode 100755 index 0000000..327858f --- /dev/null +++ b/bin/gen-password @@ -0,0 +1,73 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + gen-password [LENGTH] + gen-password -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + LENGTH the length of the generated password + (default: 999) + + + Generate a random password, and emit it to STDOUT. + + + Examples: + + Create a file with a secret: + + $ gen-password > secret.txt + + + Generate an absurdly long secret: + + $ gen-password 9999 > giant-secret.txt + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +LENGTH="${1:-999}" + +tr -cd '[:alnum:]' < /dev/random | + fold -w "$LENGTH" | + head -n1 diff --git a/bin/git-cleanup b/bin/git-cleanup new file mode 100755 index 0000000..4196cff --- /dev/null +++ b/bin/git-cleanup @@ -0,0 +1,74 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + git cleanup [REMOTE] + git cleanup -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + REMOTE the remote to prune the remote tracking + branches from (default: origin) + + + Delete merged branches, both local and remote-tracking. + + + Examples: + + Cleanup branches from "origin": + + $ git cleanup + + + Delete branches from "upstream": + + $ git cleanup upstream + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +REMOTE="${1:-origin}" + +git branch --merged | + grep -v -e '^\*' -e '^. main$' | + xargs git branch -d + +git remote prune "$REMOTE" diff --git a/bin/grun b/bin/grun new file mode 100755 index 0000000..74d8819 --- /dev/null +++ b/bin/grun @@ -0,0 +1,96 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + grun [-r RECIPIENT] FILENAME -- COMMAND... + grun -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -r RECIPIENT the recipient to encrypt to. Can be provided + multiple times for multiple recipients. + -h, --help show this message + + COMMAND A command to be executed, that accepts input + in STDIN and emits result in STDOUT, and emits + errors as non-zero return codes. + FILENAME The GPG-encrypted file to be processed. If it + doesn't exist yet, it will be created. + + Examples: + + Edit "secrets.txt.gpg" using `vipe` and the default recipient: + + $ grun secrets.txt.gpg -- vipe + + Delete lines containing "TODO" in todos.gpg for specific keys: + + $ grun -r ABC123DEF321 todos.gpg -- sed '/TODO/d' + + If COMMAND emits a non-zero return code, the file is left + unmodified. + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'rh' flag; do + case "$flag" in + r) + RECIPIENTS_FLAG="${RECIPIENTS_FLAG:-} -r $OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +FILENAME="${1:-}" +eval "$(assert-arg "$FILENAME" 'FILENAME')" +shift + +if [ "${1:-}" != '--' ]; then + printf 'Missing "--" separator\n\n' >&2 + usage >&2 + exit 2 +fi +shift + +eval "$(assert-arg "${1:-}" 'COMMAND')" + + +if [ ! -e "$FILENAME" ]; then + OUT="$(printf '' | "$@")" +else + OUT="$(gpg -dq "$FILENAME" | "$@")" +fi + +# GPG recipients can't contain spaces: +# shellcheck disable=2086 +echo "$OUT" | gpg -e ${RECIPIENTS_FLAG:--r eu@euandre.org} | sponge "$FILENAME" diff --git a/bin/htmlesc b/bin/htmlesc new file mode 100755 index 0000000..d9c59bd --- /dev/null +++ b/bin/htmlesc @@ -0,0 +1,96 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + htmlesc [-e|d] + htmlesc -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -e escape the string (the default action) + -d unescape (de-escape?) the string + -h, --help show this message + + + Get a string from STDIN and convert it to/from HTML escaping. + + + Examples: + + oij + + $ printf 'a > 5 && !b' | htmlesc + a > 5 && !b + + + Unescape the content from a file: + + $ htmlesc -d < file.html + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +ENCODE=false +DECODE=false +while getopts 'edh' flag; do + case "$flag" in + e) + ENCODE=true + ;; + d) + DECODE=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ "$ENCODE" = true ] && [ "$DECODE" = true ]; then + printf 'Both -e and -d given. Pick one.\n' >&2 + usage >&2 + exit 2 +elif [ "$DECODE" = true ]; then + sed \ + -e 's|&|\&|g' \ + -e 's|<|<|g' \ + -e 's|>|>|g' \ + -e 's|"|"|g' \ + -e "s|'|'|g" +else + sed \ + -e 's|&|\&|g' \ + -e 's|<|\<|g' \ + -e 's|>|\>|g' \ + -e 's|"|\"|g' \ + -e "s|'|\'|g" +fi diff --git a/bin/httpno b/bin/httpno new file mode 100755 index 0000000..e64b872 --- /dev/null +++ b/bin/httpno @@ -0,0 +1,156 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + httpno [NUMBER|TEXT] + httpno -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + NUMBER the number of the HTTP status code + TEXT the text of the description of the status code + + + Print the given status code, or list them all when no arguments + are given. + + + Examples: + + Get the status code 404: + + $ httpno 404 + 404 Not Found + + + Get the status code for "created": + + $ httpno created + 201 Created + + + List all statuses: + + $ httpno + ... + EOF +} + +DATA() { + awk 'd == 1 { print; next } /^__DATA__$/ { d = 1 }' "$0" | + head -n -1 # trim ShellCheck quote +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +if [ $# -eq 0 ]; then + DATA +else + DATA | grep -i "$@" +fi + + +exit + +# Make ShellCheck happy. See https://github.com/koalaman/shellcheck/issues/1201 +# shellcheck disable=1112 +echo " +__DATA__ +100 Continue +101 Switching Protocols +102 Processing +200 OK +201 Created +202 Accepted +203 Non-Authoritative Information +204 No Content +205 Reset Content +206 Partial Content +207 Multi-Status +208 Already Reported +300 Multiple Choices +301 Moved Permanently +302 Found +303 See Other +304 Not Modified +305 Use Proxy +307 Temporary Redirect +400 Bad Request +401 Unauthorized +402 Payment Required +403 Forbidden +404 Not Found +405 Method Not Allowed +406 Not Acceptable +407 Proxy Authentication Required +408 Request Timeout +409 Conflict +410 Gone +411 Length Required +412 Precondition Failed +413 Request Entity Too Large +414 Request-URI Too Large +415 Unsupported Media Type +416 Request Range Not Satisfiable +417 Expectation Failed +418 I'm a teapot +420 Blaze it +422 Unprocessable Entity +423 Locked +424 Failed Dependency +425 No code +426 Upgrade Required +428 Precondition Required +429 Too Many Requests +431 Request Header Fields Too Large +449 Retry with +500 Internal Server Error +501 Not Implemented +502 Bad Gateway +503 Service Unavailable +504 Gateway Timeout +505 HTTP Version Not Supported +506 Variant Also Negotiates +507 Insufficient Storage +509 Bandwidth Limit Exceeded +510 Not Extended +511 Network Authentication Required +" @@ -0,0 +1,70 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + lc + lc -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Transforms text from STDIN from upper-case to lower-case. It is + the equivalent of running 'tr [:upper:] [:lower:]'. + + + Examples: + + Normalize to lower-case: + + $ echo EuAndreh | lc + euandreh + + + Keep the text as-is: + + $ echo andreh | lc + andreh + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +tr '[:upper:]' '[:lower:]' @@ -0,0 +1,104 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + li [-I IMPL ] [-v] [OPTIONS...] + li -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -I IMPL use the given implementation (default: $LISP_CLI_IMPL) + -v verbose mode + -h, --help show this message + + OPTIONS options to be forwarded to cl(1) (lisp-cli) + + + Run the cl(1) executable with OPTIONS, but make sure an up-to-date + image exists and load it. + + + Examples: + + Launch a REPL from an image: + + $ li + + + Give options to cl(1): + + $ li -I sbcl -e '(print 123)' + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +VERBOSE=false +IMPL="${LISP_CLI_IMPL:-clisp}" +while getopts ':I:vh' flag; do + case "$flag" in + I) + IMPL="$OPTARG" + ;; + v) + VERBOSE=true + ;; + h) + usage + help + exit + ;; + *) + ;; + esac +done +if [ "${1:-}" = '--' ]; then + shift +fi + + +IMAGE="${XDG_DATA_HOME:-$HOME/.local/share}/lisp-cli/$IMPL.image" +BIN="$(command -v cl)" +INIT="${XDG_CONFIG_HOME:-$HOME/.config}/lisp-cli/init.lisp" +if [ ! -e "$IMAGE" ]; then + printf 'Bootstrapping a new "%s" image...\n' "$IMPL" >&2 + cl \ + -I "$IMPL" \ + -v \ + -e '(ql:quickload :trivial-dump-core)' \ + -E "(trivial-dump-core:dump-image \"$IMAGE\")" +elif [ -n "$(find "$0" "$BIN" "$INIT" -newer "$IMAGE")" ]; then + printf 'Refresh existing "%s" image...\n' "$IMPL" >&2 + cl \ + -M "$IMAGE" \ + -I "$IMPL" \ + -v \ + -e '(ql:quickload :trivial-dump-core)' \ + -E "(trivial-dump-core:dump-image \"$IMAGE\")" +fi + +if [ "$VERBOSE" = true ]; then + set -x +fi +exec cl -M "$IMAGE" "$@" diff --git a/bin/lines b/bin/lines new file mode 100755 index 0000000..2f0bf46 --- /dev/null +++ b/bin/lines @@ -0,0 +1,81 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + lines START [END] + lines -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + START the nth line number to start showing + END the nth line number to stop + showing (default: START + 1) + + + Print the range START-END of lines of the content of STDIN. + + + Examples: + + Print 3rd line: + + $ seq 10 | lines 3 + 3 + + + Print lines 5~8: + + $ lines 5 8 < file.txt + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +START="${1:-}" + +if [ -z "${2:-}" ]; then + END=1 +else + END=$(($2 - START + 1)) +fi + +eval "$(assert-arg "$START" 'START')" + +tail -n +"$START" | head -n "$END" @@ -0,0 +1,66 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + m + m -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Fetch email via IMAP and update the notmuch index. + + + Examples: + + Just fetch email + + $ m + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +F="${XDG_DATA_HOME:-$HOME/.local/share}"/euandreh/mailcfg-accounts.txt + +notmuch new +xargs -I% -P "$(wc -l < "$F")" mbsync '%' < "$F" +notmuch new diff --git a/bin/mailcfg b/bin/mailcfg new file mode 100755 index 0000000..f77355e --- /dev/null +++ b/bin/mailcfg @@ -0,0 +1,297 @@ +#!/bin/sh +# shellcheck disable=1090,2153 +set -eu + +usage() { + cat <<-'EOF' + Usage: + mailcfg ACTION + mailcfg -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + ACTION one of: + - mbsync + - msmtp + - notmuchcfg + - notmuchhook + - alot + - list + + + Emit the generated configuration file for the chosen email + program. Get the configuration files from + $XDG_CONFIG_HOME/mailcfg/*.env, where every *.env file is a + shell script that defines the variables used in this program: + - $NAME + - $LABEL + - $IMAP + - $SMTP + - $ADDR + + One of the files also needs to define: + - $DEFAULT_NAME + - $DEFAULT_ADDR + - $DEFAULT_LABEL + + An example of such file could be "30-andre@work.com.env": + + #!/bin/sh + set -eu + + + NAME='André!' + LABEL='Work' + IMAP='imap.work.com' + SMTP='smtp.work.com' + ADDR='andre@work.com' + + + Examples: + + Get the alot configuration file: + + $ mailcfg alot + + + List the existing account labels: + + $ mailcfg list + EOF +} + + +for flag; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +ACTION="${1:-}" +eval "$(assert-arg "$ACTION" 'ACTION')" + +CFGDIR="${XDG_CONFIG_HOME:-$HOME/.config}/mailcfg" + + +mbsync() { + cat <<-'EOF' + SyncState * + Create Both + Expunge Both + Remove Both + Sync All + EOF + + for env in "$CFGDIR"/*.env; do + . "$env" + cat <<-EOF + + + ## $LABEL + + IMAPAccount $LABEL + Host $IMAP + User $ADDR + PassCmd "pass show $ADDR" + SSLType IMAPS + + IMAPStore ${LABEL}Remote + Account $LABEL + + MaildirStore ${LABEL}Local + Path ~/Maildir/$LABEL/ + Inbox ~/Maildir/$LABEL/INBOX + SubFolders Verbatim + + Channel ${LABEL}Folders + Far :${LABEL}Remote: + Near :${LABEL}Local: + Patterns * + + Group $LABEL + Channel ${LABEL}Folders + EOF + done +} + +msmtp() { + cat <<-EOF + defaults + auth on + tls on + port 587 + syslog on + logfile $XDG_LOG_HOME/msmtp.log + EOF + + for env in "$CFGDIR"/*.env; do + . "$env" + cat <<-EOF + + account $LABEL + host $SMTP + from $ADDR + user $ADDR + passwordeval pass show $ADDR + EOF + done + + cat <<-EOF + + account default : $DEFAULT_LABEL + EOF +} + +notmuchcfg() { + for env in "$CFGDIR"/*.env; do + . "$env" + done + + cat <<-EOF + [user] + name = $DEFAULT_NAME + primary_email = $DEFAULT_ADDR + EOF + + printf 'other_email = ' + for env in "$CFGDIR"/*.env; do + . "$env" + if [ "$ADDR" = "$DEFAULT_ADDR" ]; then + continue + fi + echo "$ADDR" + done | paste -sd';' + + cat <<-'EOF' + + [new] + tags = new; + ignore = .mbsyncstate;.uidvalidity + + [search] + exclude_tags = deleted;spam + + [maildir] + synchronize_flags = true + EOF +} + +notmuchhook() { + LABELS='' + for env in "$CFGDIR"/*.env; do + . "$env" + if [ -z "$LABELS" ]; then + LABELS="$LABEL" + else + LABELS="$LABELS $LABEL" + fi + done + sed \ + -e "s|@DIRS@|$LABELS|g" \ + -e "s|@DEFAULT_LABEL@|$DEFAULT_LABEL|g" \ + "$CFGDIR"/post-new +} + +alot() { + cat <<-'EOF' + attachment_prefix = "~/Downloads/" + + [bindings] + i = toggletags inbox + I = search folder:/INBOX/ AND NOT tag:killed AND NOT tag:archive + EOF + echo " + z archive + s spam + u unread + r keep + t track + " | while read -r l; do + if [ -z "$l" ]; then + continue + fi + LC="$( echo "$l" | cut -d' ' -f1)" + TAG="$(echo "$l" | cut -d' ' -f2)" + UC="$(echo "$LC" | tr '[:lower:]' '[:upper:]')" + cat <<-EOF + $LC = toggletags $TAG + $UC = search tag:$TAG AND NOT tag:killed + EOF + done + + cat <<-'EOF' + M = search folder:/lists/ AND NOT tag:killed + m = compose --tags inbox + [[thread]] + v = pipeto urlscan 2>/dev/null + V = pipeto 'gpg -d | less' + r = reply --all + R = reply + ' ' = fold; untag unread; move next unfolded + P = pipeto 'git am' + + [accounts] + EOF + + for env in "$CFGDIR"/*.env; do + . "$env" + cat <<-EOF + [[$LABEL]] + realname = $NAME + address = $ADDR + sendmail_command = msmtpq --account=$LABEL -t + sent_box = maildir://~/Maildir/$LABEL/Sent + draft_box = maildir://~/Maildir/$LABEL/Drafts + gpg_key = 5BDAE9B8B2F6C6BCBB0D6CE581F90EC3CD356060 + EOF + done +} + +list() { + for env in "$CFGDIR"/*.env; do + . "$env" + printf '%s\n' "$LABEL" + done +} + + +case "$ACTION" in + mbsync|msmtp|notmuchcfg|notmuchhook|alot|list) + "$1" + ;; + *) + printf 'Unsupported ACTION: "%s".\n\n' "$ACTION" >&2 + usage >&2 + exit 2 + ;; +esac @@ -0,0 +1,84 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + max NUMBER... + max -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + Get the maximum number from the given values. + + + Examples: + + Get the maximum number from a list: + + $ min 5 3 9 9 4 + 9 + + Get the maximum number when negative numbers are given + + $ max -- -3 -5 + -3 + + Get the maximum number given a single number + + $ max 8 + 8 + + The maximum default number: + + $ max + 0 + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ -z "${1:-}" ]; then + echo 0 + exit +fi + +N="$1" +for n in "$@"; do + N=$((N > n ? N : n)) +done +echo "$N" diff --git a/bin/menu b/bin/menu new file mode 100755 index 0000000..90af758 --- /dev/null +++ b/bin/menu @@ -0,0 +1,1587 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + menu ACTION + menu -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + ACTION one of: + - emoji + - username + - password + - bin + - clipboard + - yubikey + + + Lauch an interactive GUI menu for various desktop activities, to + be launched manually or via desktop keybindings. + + + Examples: + + Choose and copy the password to the clipboard: + + $ menu password + + + Execute a binary available in $PATH: + + $ menu bin + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) +ACTION="${1:-}" + +eval "$(assert-arg "$ACTION" 'ACTION')" + + +DATA() { + awk 'd == 1 { print; next } /^__DATA__$/ { d = 1 }' "$0" | + head -n -1 # trim ShellCheck quote +} + +show() { + dmenu -i -l 20 -fn Monospace-18 -p "$1:" +} + +pass_list() { + walk "$PASSWORD_STORE_DIR" | + grep '\.gpg$' | + sor 'test -f' | + sed -e "s|^$PASSWORD_STORE_DIR/||" \ + -e 's|\.gpg$||' | + LANG=POSIX.UTF-8 sort +} + +case "$ACTION" in + emoji) + DATA | show 'emoji' | awk '{print $(NF)}' | copy -n + ;; + username) + CHOICE="$(pass_list | show 'username')" + if [ -n "$CHOICE" ]; then + if pass show -c2 "$CHOICE"; then + notify-send -t 5000 -u normal -- \ + "$CHOICE" 'username copied to clipboard' + fi + fi + ;; + password) + CHOICE="$(pass_list | show 'password')" + if [ -n "$CHOICE" ]; then + if pass show -c1 "$CHOICE"; then + notify-send -t 5000 -u critical -- \ + "$CHOICE" 'password copied to clipboard' + fi + fi + ;; + bin) + CHOICE="$(bins | show 'bins')" + if [ -n "$CHOICE" ]; then + exec "$CHOICE" + fi + ;; + clipboard) + # For a potential improved version, see: + # https://github.com/cdown/clipmenu/pull/162 + clipmenu -i -l 20 -fn Monospace-18 -p "$1:" + notify-send -t 5000 -u low -- 'copied to clipboard' + ;; + yubikey) + CHOICE="$(ykman oath accounts list | show 'OTP')" + if [ -n "$CHOICE" ]; then + ykman oath accounts code "$CHOICE" | + awk '{ print $(NF) }' | + copy -n + notify-send -t 5000 -u normal -- \ + "$CHOICE" 'code copied to clipboard' + fi + ;; + *) + printf 'Bad ACTION: %s.\n\n' "$ACTION" >&2 + usage >&2 + exit 2 + ;; +esac + + + +exit + +# Make ShellCheck happy. See https://github.com/koalaman/shellcheck/issues/1201 +# shellcheck disable=1112 +echo ' +__DATA__ +grinning face 😀 +smiling face with open mouth 😃 +smiling face with open mouth & smiling eyes 😄 +grinning face with smiling eyes 😁 +smiling face with open mouth & closed eyes 😆 +smiling face with open mouth & cold sweat 😅 +face with tears of joy 😂 +rolling on the floor laughing 🤣 +smiling face ☺️ +smiling face with smiling eyes 😊 +smiling face with halo 😇 +slightly smiling face 🙂 +upside-down face 🙃 +winking face 😉 +relieved face 😌 +smiling face with heart-eyes 😍 +face blowing a kiss 😘 +kissing face 😗 +kissing face with smiling eyes 😙 +kissing face with closed eyes 😚 +face savouring delicious food 😋 +face with stuck-out tongue & winking eye 😜 +face with stuck-out tongue & closed eyes 😝 +face with stuck-out tongue 😛 +money-mouth face 🤑 +hugging face 🤗 +nerd face 🤓 +smiling face with sunglasses 😎 +clown face 🤡 +cowboy hat face 🤠 +smirking face 😏 +unamused face 😒 +disappointed face 😞 +pensive face 😔 +worried face 😟 +confused face 😕 +slightly frowning face 🙁 +frowning face ☹️ +persevering face 😣 +confounded face 😖 +tired face 😫 +weary face 😩 +face with steam from nose 😤 +angry face 😠 +pouting face 😡 +face without mouth 😶 +neutral face 😐 +expressionless face 😑 +hushed face 😯 +frowning face with open mouth 😦 +anguished face 😧 +face with open mouth 😮 +astonished face 😲 +dizzy face 😵 +flushed face 😳 +face screaming in fear 😱 +fearful face 😨 +face with open mouth & cold sweat 😰 +crying face 😢 +disappointed but relieved face 😥 +drooling face 🤤 +loudly crying face 😭 +face with cold sweat 😓 +sleepy face 😪 +sleeping face 😴 +face with rolling eyes 🙄 +thinking face 🤔 +lying face 🤥 +grimacing face 😬 +zipper-mouth face 🤐 +nauseated face 🤢 +sneezing face 🤧 +face with medical mask 😷 +face with thermometer 🤒 +face with head-bandage 🤕 +smiling face with horns 😈 +angry face with horns 👿 +ogre 👹 +goblin 👺 +pile of poo 💩 +ghost 👻 +skull 💀 +skull and crossbones ☠️ +alien 👽 +alien monster 👾 +robot face 🤖 +jack-o-lantern 🎃 +smiling cat face with open mouth 😺 +grinning cat face with smiling eyes 😸 +cat face with tears of joy 😹 +smiling cat face with heart-eyes 😻 +cat face with wry smile 😼 +kissing cat face with closed eyes 😽 +weary cat face 🙀 +crying cat face 😿 +pouting cat face 😾 +open hands 👐 +raising hands 🙌 +clapping hands 👏 +folded hands 🙏 +handshake 🤝 +thumbs up 👍 +thumbs down 👎 +oncoming fist 👊 +raised fist ✊ +left-facing fist 🤛 +right-facing fist 🤜 +crossed fingers 🤞 +victory hand ✌️ +sign of the horns 🤘 +OK hand 👌 +backhand index pointing left 👈 +backhand index pointing right 👉 +backhand index pointing up 👆 +backhand index pointing down 👇 +index pointing up ☝️ +raised hand ✋ +raised back of hand 🤚 +raised hand with fingers splayed 🖐 +vulcan salute 🖖 +waving hand 👋 +call me hand 🤙 +flexed biceps 💪 +middle finger 🖕 +writing hand ✍️ +selfie 🤳 +nail polish 💅 +ring 💍 +lipstick 💄 +kiss mark 💋 +mouth 👄 +tongue 👅 +ear 👂 +nose 👃 +footprints 👣 +eye 👁 +eyes 👀 +speaking head 🗣 +bust in silhouette 👤 +busts in silhouette 👥 +baby 👶 +boy 👦 +girl 👧 +man 👨 +woman 👩 +blond-haired woman 👱♀ +blond-haired person 👱 +old man 👴 +old woman 👵 +man with Chinese cap 👲 +woman wearing turban 👳♀ +person wearing turban 👳 +woman police officer 👮♀ +police officer 👮 +woman construction worker 👷♀ +construction worker 👷 +woman guard 💂♀ +guard 💂 +woman detective 🕵️♀️ +detective 🕵 +woman health worker 👩⚕ +man health worker 👨⚕ +woman farmer 👩🌾 +man farmer 👨🌾 +woman cook 👩🍳 +man cook 👨🍳 +woman student 👩🎓 +man student 👨🎓 +woman singer 👩🎤 +man singer 👨🎤 +woman teacher 👩🏫 +man teacher 👨🏫 +woman factory worker 👩🏭 +man factory worker 👨🏭 +woman technologist 👩💻 +man technologist 👨💻 +woman office worker 👩💼 +man office worker 👨💼 +woman mechanic 👩🔧 +man mechanic 👨🔧 +woman scientist 👩🔬 +man scientist 👨🔬 +woman artist 👩🎨 +man artist 👨🎨 +woman firefighter 👩🚒 +man firefighter 👨🚒 +woman pilot 👩✈ +man pilot 👨✈ +woman astronaut 👩🚀 +man astronaut 👨🚀 +woman judge 👩⚖ +man judge 👨⚖ +Mrs. Claus 🤶 +Santa Claus 🎅 +princess 👸 +prince 🤴 +bride with veil 👰 +man in tuxedo 🤵 +baby angel 👼 +pregnant woman 🤰 +woman bowing 🙇♀ +person bowing 🙇 +person tipping hand 💁 +man tipping hand 💁♂ +person gesturing NO 🙅 +man gesturing NO 🙅♂ +person gesturing OK 🙆 +man gesturing OK 🙆♂ +person raising hand 🙋 +man raising hand 🙋♂ +woman facepalming 🤦♀ +man facepalming 🤦♂ +woman shrugging 🤷♀ +man shrugging 🤷♂ +person pouting 🙎 +man pouting 🙎♂ +person frowning 🙍 +man frowning 🙍♂ +person getting haircut 💇 +man getting haircut 💇♂ +person getting massage 💆 +man getting massage 💆♂ +man in business suit levitating 🕴 +woman dancing 💃 +man dancing 🕺 +people with bunny ears partying 👯 +men with bunny ears partying 👯♂ +woman walking 🚶♀ +person walking 🚶 +woman running 🏃♀ +person running 🏃 +man and woman holding hands 👫 +two women holding hands 👭 +two men holding hands 👬 +couple with heart 💑 +couple with heart: woman woman 👩❤️👩 +couple with heart: man man 👨❤️👨 +kiss 💏 +kiss: woman woman 👩❤️💋👩 +kiss: man man 👨❤️💋👨 +family 👪 +family: man woman girl 👨👩👧 +family: man woman girl boy 👨👩👧👦 +family: man woman boy boy 👨👩👦👦 +family: man woman girl girl 👨👩👧👧 +family: woman woman boy 👩👩👦 +family: woman woman girl 👩👩👧 +family: woman woman girl boy 👩👩👧👦 +family: woman woman boy boy 👩👩👦👦 +family: woman woman girl girl 👩👩👧👧 +family: man man boy 👨👨👦 +family: man man girl 👨👨👧 +family: man man girl boy 👨👨👧👦 +family: man man boy boy 👨👨👦👦 +family: man man girl girl 👨👨👧👧 +family: woman boy 👩👦 +family: woman girl 👩👧 +family: woman girl boy 👩👧👦 +family: woman boy boy 👩👦👦 +family: woman girl girl 👩👧👧 +family: man boy 👨👦 +family: man girl 👨👧 +family: man girl boy 👨👧👦 +family: man boy boy 👨👦👦 +family: man girl girl 👨👧👧 +woman’s clothes 👚 +t-shirt 👕 +jeans 👖 +necktie 👔 +dress 👗 +bikini 👙 +kimono 👘 +high-heeled shoe 👠 +woman’s sandal 👡 +woman’s boot 👢 +man’s shoe 👞 +running shoe 👟 +woman’s hat 👒 +top hat 🎩 +graduation cap 🎓 +crown 👑 +rescue worker’s helmet ⛑ +school backpack 🎒 +clutch bag 👝 +purse 👛 +handbag 👜 +briefcase 💼 +glasses 👓 +sunglasses 🕶 +closed umbrella 🌂 +umbrella ☂️ +dog face 🐶 +cat face 🐱 +mouse face 🐭 +hamster face 🐹 +rabbit face 🐰 +fox face 🦊 +bear face 🐻 +panda face 🐼 +koala 🐨 +tiger face 🐯 +lion face 🦁 +cow face 🐮 +pig face 🐷 +pig nose 🐽 +frog face 🐸 +monkey face 🐵 +see-no-evil monkey 🙈 +hear-no-evil monkey 🙉 +speak-no-evil monkey 🙊 +monkey 🐒 +chicken 🐔 +penguin 🐧 +bird 🐦 +baby chick 🐤 +hatching chick 🐣 +front-facing baby chick 🐥 +duck 🦆 +eagle 🦅 +owl 🦉 +bat 🦇 +wolf face 🐺 +boar 🐗 +horse face 🐴 +unicorn face 🦄 +honeybee 🐝 +bug 🐛 +butterfly 🦋 +snail 🐌 +spiral shell 🐚 +lady beetle 🐞 +ant 🐜 +spider 🕷 +spider web 🕸 +turtle 🐢 +snake 🐍 +lizard 🦎 +scorpion 🦂 +crab 🦀 +squid 🦑 +octopus 🐙 +shrimp 🦐 +tropical fish 🐠 +fish 🐟 +blowfish 🐡 +dolphin 🐬 +shark 🦈 +spouting whale 🐳 +whale 🐋 +crocodile 🐊 +leopard 🐆 +tiger 🐅 +water buffalo 🐃 +ox 🐂 +cow 🐄 +deer 🦌 +camel 🐪 +two-hump camel 🐫 +elephant 🐘 +rhinoceros 🦏 +gorilla 🦍 +horse 🐎 +pig 🐖 +goat 🐐 +ram 🐏 +sheep 🐑 +dog 🐕 +poodle 🐩 +cat 🐈 +rooster 🐓 +turkey 🦃 +dove 🕊 +rabbit 🐇 +mouse 🐁 +rat 🐀 +chipmunk 🐿 +paw prints 🐾 +dragon 🐉 +dragon face 🐲 +cactus 🌵 +Christmas tree 🎄 +evergreen tree 🌲 +deciduous tree 🌳 +palm tree 🌴 +seedling 🌱 +herb 🌿 +shamrock ☘️ +four leaf clover 🍀 +pine decoration 🎍 +tanabata tree 🎋 +leaf fluttering in wind 🍃 +fallen leaf 🍂 +maple leaf 🍁 +mushroom 🍄 +sheaf of rice 🌾 +bouquet 💐 +tulip 🌷 +rose 🌹 +wilted flower 🥀 +sunflower 🌻 +blossom 🌼 +cherry blossom 🌸 +hibiscus 🌺 +globe showing Americas 🌎 +globe showing Europe-Africa 🌍 +globe showing Asia-Australia 🌏 +full moon 🌕 +waning gibbous moon 🌖 +last quarter moon 🌗 +waning crescent moon 🌘 +new moon 🌑 +waxing crescent moon 🌒 +first quarter moon 🌓 +waxing gibbous moon 🌔 +new moon face 🌚 +full moon with face 🌝 +sun with face 🌞 +first quarter moon with face 🌛 +last quarter moon with face 🌜 +crescent moon 🌙 +dizzy 💫 +white medium star ⭐️ +glowing star 🌟 +sparkles ✨ +high voltage ⚡️ +fire 🔥 +collision 💥 +comet ☄ +sun ☀️ +sun behind small cloud 🌤 +sun behind cloud ⛅️ +sun behind large cloud 🌥 +sun behind rain cloud 🌦 +rainbow 🌈 +cloud ☁️ +cloud with rain 🌧 +cloud with lightning and rain ⛈ +cloud with lightning 🌩 +cloud with snow 🌨 +snowman ☃️ +snowman without snow ⛄️ +snowflake ❄️ +wind face 🌬 +dashing away 💨 +tornado 🌪 +fog 🌫 +water wave 🌊 +droplet 💧 +sweat droplets 💦 +umbrella with rain drops ☔️ +green apple 🍏 +red apple 🍎 +pear 🍐 +tangerine 🍊 +lemon 🍋 +banana 🍌 +watermelon 🍉 +grapes 🍇 +strawberry 🍓 +melon 🍈 +cherries 🍒 +peach 🍑 +pineapple 🍍 +kiwi fruit 🥝 +avocado 🥑 +tomato 🍅 +eggplant 🍆 +cucumber 🥒 +carrot 🥕 +ear of corn 🌽 +hot pepper 🌶 +potato 🥔 +roasted sweet potato 🍠 +chestnut 🌰 +peanuts 🥜 +honey pot 🍯 +croissant 🥐 +bread 🍞 +baguette bread 🥖 +cheese wedge 🧀 +egg 🥚 +cooking 🍳 +bacon 🥓 +pancakes 🥞 +fried shrimp 🍤 +poultry leg 🍗 +meat on bone 🍖 +pizza 🍕 +hot dog 🌭 +hamburger 🍔 +french fries 🍟 +stuffed flatbread 🥙 +taco 🌮 +burrito 🌯 +green salad 🥗 +shallow pan of food 🥘 +spaghetti 🍝 +steaming bowl 🍜 +pot of food 🍲 +fish cake with swirl 🍥 +sushi 🍣 +bento box 🍱 +curry rice 🍛 +cooked rice 🍚 +rice ball 🍙 +rice cracker 🍘 +oden 🍢 +dango 🍡 +shaved ice 🍧 +ice cream 🍨 +soft ice cream 🍦 +shortcake 🍰 +birthday cake 🎂 +custard 🍮 +lollipop 🍭 +candy 🍬 +chocolate bar 🍫 +popcorn 🍿 +doughnut 🍩 +cookie 🍪 +glass of milk 🥛 +baby bottle 🍼 +hot beverage ☕️ +teacup without handle 🍵 +sake 🍶 +beer mug 🍺 +clinking beer mugs 🍻 +clinking glasses 🥂 +wine glass 🍷 +tumbler glass 🥃 +cocktail glass 🍸 +tropical drink 🍹 +bottle with popping cork 🍾 +spoon 🥄 +fork and knife 🍴 +fork and knife with plate 🍽 +soccer ball ⚽️ +basketball 🏀 +american football 🏈 +baseball ⚾️ +tennis 🎾 +volleyball 🏐 +rugby football 🏉 +pool 8 ball 🎱 +ping pong 🏓 +badminton 🏸 +goal net 🥅 +ice hockey 🏒 +field hockey 🏑 +cricket 🏏 +flag in hole ⛳️ +bow and arrow 🏹 +fishing pole 🎣 +boxing glove 🥊 +martial arts uniform 🥋 +ice skate ⛸ +skis 🎿 +skier ⛷ +snowboarder 🏂 +woman lifting weights 🏋️♀️ +person lifting weights 🏋 +person fencing 🤺 +women wrestling 🤼♀ +men wrestling 🤼♂ +woman cartwheeling 🤸♀ +man cartwheeling 🤸♂ +woman bouncing ball ⛹️♀️ +person bouncing ball ⛹ +woman playing handball 🤾♀ +man playing handball 🤾♂ +woman golfing 🏌️♀️ +person golfing 🏌 +woman surfing 🏄♀ +person surfing 🏄 +woman swimming 🏊♀ +person swimming 🏊 +woman playing water polo 🤽♀ +man playing water polo 🤽♂ +woman rowing boat 🚣♀ +person rowing boat 🚣 +horse racing 🏇 +woman biking 🚴♀ +person biking 🚴 +woman mountain biking 🚵♀ +person mountain biking 🚵 +running shirt 🎽 +sports medal 🏅 +military medal 🎖 +1st place medal 🥇 +2nd place medal 🥈 +3rd place medal 🥉 +trophy 🏆 +rosette 🏵 +reminder ribbon 🎗 +ticket 🎫 +admission tickets 🎟 +circus tent 🎪 +woman juggling 🤹♀ +man juggling 🤹♂ +performing arts 🎭 +artist palette 🎨 +clapper board 🎬 +microphone 🎤 +headphone 🎧 +musical score 🎼 +musical keyboard 🎹 +drum 🥁 +saxophone 🎷 +trumpet 🎺 +guitar 🎸 +violin 🎻 +game die 🎲 +direct hit 🎯 +bowling 🎳 +video game 🎮 +slot machine 🎰 +automobile 🚗 +taxi 🚕 +sport utility vehicle 🚙 +bus 🚌 +trolleybus 🚎 +racing car 🏎 +police car 🚓 +ambulance 🚑 +fire engine 🚒 +minibus 🚐 +delivery truck 🚚 +articulated lorry 🚛 +tractor 🚜 +kick scooter 🛴 +bicycle 🚲 +motor scooter 🛵 +motorcycle 🏍 +police car light 🚨 +oncoming police car 🚔 +oncoming bus 🚍 +oncoming automobile 🚘 +oncoming taxi 🚖 +aerial tramway 🚡 +mountain cableway 🚠 +suspension railway 🚟 +railway car 🚃 +tram car 🚋 +mountain railway 🚞 +monorail 🚝 +high-speed train 🚄 +high-speed train with bullet nose 🚅 +light rail 🚈 +locomotive 🚂 +train 🚆 +metro 🚇 +tram 🚊 +station 🚉 +helicopter 🚁 +small airplane 🛩 +airplane ✈️ +airplane departure 🛫 +airplane arrival 🛬 +rocket 🚀 +satellite 🛰 +seat 💺 +canoe 🛶 +sailboat ⛵️ +motor boat 🛥 +speedboat 🚤 +passenger ship 🛳 +ferry ⛴ +ship 🚢 +anchor ⚓️ +construction 🚧 +fuel pump ⛽️ +bus stop 🚏 +vertical traffic light 🚦 +horizontal traffic light 🚥 +world map 🗺 +moai 🗿 +Statue of Liberty 🗽 +fountain ⛲️ +Tokyo tower 🗼 +castle 🏰 +Japanese castle 🏯 +stadium 🏟 +ferris wheel 🎡 +roller coaster 🎢 +carousel horse 🎠 +umbrella on ground ⛱ +beach with umbrella 🏖 +desert island 🏝 +mountain ⛰ +snow-capped mountain 🏔 +mount fuji 🗻 +volcano 🌋 +desert 🏜 +camping 🏕 +tent ⛺️ +railway track 🛤 +motorway 🛣 +building construction 🏗 +factory 🏭 +house 🏠 +house with garden 🏡 +house 🏘 +derelict house 🏚 +office building 🏢 +department store 🏬 +Japanese post office 🏣 +post office 🏤 +hospital 🏥 +bank 🏦 +hotel 🏨 +convenience store 🏪 +school 🏫 +love hotel 🏩 +wedding 💒 +classical building 🏛 +church ⛪️ +mosque 🕌 +synagogue 🕍 +kaaba 🕋 +shinto shrine ⛩ +map of Japan 🗾 +moon viewing ceremony 🎑 +national park 🏞 +sunrise 🌅 +sunrise over mountains 🌄 +shooting star 🌠 +sparkler 🎇 +fireworks 🎆 +sunset 🌇 +cityscape at dusk 🌆 +cityscape 🏙 +night with stars 🌃 +milky way 🌌 +bridge at night 🌉 +foggy 🌁 +watch ⌚️ +mobile phone 📱 +mobile phone with arrow 📲 +laptop computer 💻 +keyboard ⌨️ +desktop computer 🖥 +printer 🖨 +computer mouse 🖱 +trackball 🖲 +joystick 🕹 +clamp 🗜 +computer disk 💽 +floppy disk 💾 +optical disk 💿 +dvd 📀 +videocassette 📼 +camera 📷 +camera with flash 📸 +video camera 📹 +movie camera 🎥 +film projector 📽 +film frames 🎞 +telephone receiver 📞 +telephone ☎️ +pager 📟 +fax machine 📠 +television 📺 +radio 📻 +studio microphone 🎙 +level slider 🎚 +control knobs 🎛 +stopwatch ⏱ +timer clock ⏲ +alarm clock ⏰ +mantelpiece clock 🕰 +hourglass ⌛️ +hourglass with flowing sand ⏳ +satellite antenna 📡 +battery 🔋 +electric plug 🔌 +light bulb 💡 +flashlight 🔦 +candle 🕯 +wastebasket 🗑 +oil drum 🛢 +money with wings 💸 +dollar banknote 💵 +yen banknote 💴 +euro banknote 💶 +pound banknote 💷 +money bag 💰 +credit card 💳 +gem stone 💎 +balance scale ⚖️ +wrench 🔧 +hammer 🔨 +hammer and pick ⚒ +hammer and wrench 🛠 +pick ⛏ +nut and bolt 🔩 +gear ⚙️ +chains ⛓ +pistol 🔫 +bomb 💣 +kitchen knife 🔪 +dagger 🗡 +crossed swords ⚔️ +shield 🛡 +cigarette 🚬 +coffin ⚰️ +funeral urn ⚱️ +amphora 🏺 +crystal ball 🔮 +prayer beads 📿 +barber pole 💈 +alembic ⚗️ +telescope 🔭 +microscope 🔬 +hole 🕳 +pill 💊 +syringe 💉 +thermometer 🌡 +toilet 🚽 +potable water 🚰 +shower 🚿 +bathtub 🛁 +person taking bath 🛀 +bellhop bell 🛎 +key 🔑 +old key 🗝 +door 🚪 +couch and lamp 🛋 +bed 🛏 +person in bed 🛌 +framed picture 🖼 +shopping bags 🛍 +shopping cart 🛒 +wrapped gift 🎁 +balloon 🎈 +carp streamer 🎏 +ribbon 🎀 +confetti ball 🎊 +party popper 🎉 +Japanese dolls 🎎 +red paper lantern 🏮 +wind chime 🎐 +envelope ✉️ +envelope with arrow 📩 +incoming envelope 📨 +e-mail 📧 +love letter 💌 +inbox tray 📥 +outbox tray 📤 +package 📦 +label 🏷 +closed mailbox with lowered flag 📪 +closed mailbox with raised flag 📫 +open mailbox with raised flag 📬 +open mailbox with lowered flag 📭 +postbox 📮 +postal horn 📯 +scroll 📜 +page with curl 📃 +page facing up 📄 +bookmark tabs 📑 +bar chart 📊 +chart increasing 📈 +chart decreasing 📉 +spiral notepad 🗒 +spiral calendar 🗓 +tear-off calendar 📆 +calendar 📅 +card index 📇 +card file box 🗃 +ballot box with ballot 🗳 +file cabinet 🗄 +clipboard 📋 +file folder 📁 +open file folder 📂 +card index dividers 🗂 +rolled-up newspaper 🗞 +newspaper 📰 +notebook 📓 +notebook with decorative cover 📔 +ledger 📒 +closed book 📕 +green book 📗 +blue book 📘 +orange book 📙 +books 📚 +open book 📖 +bookmark 🔖 +link 🔗 +paperclip 📎 +linked paperclips 🖇 +triangular ruler 📐 +straight ruler 📏 +pushpin 📌 +round pushpin 📍 +scissors ✂️ +pen 🖊 +fountain pen 🖋 +black nib ✒️ +paintbrush 🖌 +crayon 🖍 +memo 📝 +pencil ✏️ +left-pointing magnifying glass 🔍 +right-pointing magnifying glass 🔎 +locked with pen 🔏 +locked with key 🔐 +locked 🔒 +unlocked 🔓 +red heart ❤️ +yellow heart 💛 +green heart 💚 +blue heart 💙 +purple heart 💜 +black heart 🖤 +broken heart 💔 +heavy heart exclamation ❣️ +two hearts 💕 +revolving hearts 💞 +beating heart 💓 +growing heart 💗 +sparkling heart 💖 +heart with arrow 💘 +heart with ribbon 💝 +heart decoration 💟 +peace symbol ☮️ +latin cross ✝️ +star and crescent ☪️ +om 🕉 +wheel of dharma ☸️ +star of David ✡️ +dotted six-pointed star 🔯 +menorah 🕎 +yin yang ☯️ +orthodox cross ☦️ +place of worship 🛐 +Ophiuchus ⛎ +Aries ♈️ +Taurus ♉️ +Gemini ♊️ +Cancer ♋️ +Leo ♌️ +Virgo ♍️ +Libra ♎️ +Scorpius ♏️ +Sagittarius ♐️ +Capricorn ♑️ +Aquarius ♒️ +Pisces ♓️ +ID button 🆔 +atom symbol ⚛️ +Japanese “acceptable” button 🉑 +radioactive ☢️ +biohazard ☣️ +mobile phone off 📴 +vibration mode 📳 +Japanese “not free of charge” button 🈶 +Japanese “free of charge” button 🈚️ +Japanese “application” button 🈸 +Japanese “open for business” button 🈺 +Japanese “monthly amount” button 🈷️ +eight-pointed star ✴️ +VS button 🆚 +white flower 💮 +Japanese “bargain” button 🉐 +Japanese “secret” button ㊙️ +Japanese “congratulations” button ㊗️ +Japanese “passing grade” button 🈴 +Japanese “no vacancy” button 🈵 +Japanese “discount” button 🈹 +Japanese “prohibited” button 🈲 +A button (blood type) 🅰️ +B button (blood type) 🅱️ +AB button (blood type) 🆎 +CL button 🆑 +O button (blood type) 🅾️ +SOS button 🆘 +cross mark ❌ +heavy large circle ⭕️ +stop sign 🛑 +no entry ⛔️ +name badge 📛 +prohibited 🚫 +hundred points 💯 +anger symbol 💢 +hot springs ♨️ +no pedestrians 🚷 +no littering 🚯 +no bicycles 🚳 +non-potable water 🚱 +no one under eighteen 🔞 +no mobile phones 📵 +no smoking 🚭 +exclamation mark ❗️ +white exclamation mark ❕ +question mark ❓ +white question mark ❔ +double exclamation mark ‼️ +exclamation question mark ⁉️ +dim button 🔅 +bright button 🔆 +part alternation mark 〽️ +warning ⚠️ +children crossing 🚸 +trident emblem 🔱 +fleur-de-lis ⚜️ +Japanese symbol for beginner 🔰 +recycling symbol ♻️ +white heavy check mark ✅ +Japanese “reserved” button 🈯️ +chart increasing with yen 💹 +sparkle ❇️ +eight-spoked asterisk ✳️ +cross mark button ❎ +globe with meridians 🌐 +diamond with a dot 💠 +circled M Ⓜ️ +cyclone 🌀 +zzz 💤 +ATM sign 🏧 +water closet 🚾 +wheelchair symbol ♿️ +P button 🅿️ +Japanese “vacancy” button 🈳 +Japanese “service charge” button 🈂️ +passport control 🛂 +customs 🛃 +baggage claim 🛄 +left luggage 🛅 +men’s room 🚹 +women’s room 🚺 +baby symbol 🚼 +restroom 🚻 +litter in bin sign 🚮 +cinema 🎦 +antenna bars 📶 +Japanese “here” button 🈁 +input symbols 🔣 +information ℹ️ +input latin letters 🔤 +input latin lowercase 🔡 +input latin uppercase 🔠 +NG button 🆖 +OK button 🆗 +UP! button 🆙 +COOL button 🆒 +NEW button 🆕 +FREE button 🆓 +keycap: 0 0️⃣ +keycap: 1 1️⃣ +keycap: 2 2️⃣ +keycap: 3 3️⃣ +keycap: 4 4️⃣ +keycap: 5 5️⃣ +keycap: 6 6️⃣ +keycap: 7 7️⃣ +keycap: 8 8️⃣ +keycap: 9 9️⃣ +keycap 10 🔟 +input numbers 🔢 +keycap: # #️⃣ +keycap: * *️⃣ +play button ▶️ +pause button ⏸ +play or pause button ⏯ +stop button ⏹ +record button ⏺ +next track button ⏭ +last track button ⏮ +fast-forward button ⏩ +fast reverse button ⏪ +fast up button ⏫ +fast down button ⏬ +reverse button ◀️ +up button 🔼 +down button 🔽 +right arrow ➡️ +left arrow ⬅️ +up arrow ⬆️ +down arrow ⬇️ +up-right arrow ↗️ +down-right arrow ↘️ +down-left arrow ↙️ +up-left arrow ↖️ +up-down arrow ↕️ +left-right arrow ↔️ +left arrow curving right ↪️ +right arrow curving left ↩️ +right arrow curving up ⤴️ +right arrow curving down ⤵️ +shuffle tracks button 🔀 +repeat button 🔁 +repeat single button 🔂 +anticlockwise arrows button 🔄 +clockwise vertical arrows 🔃 +musical note 🎵 +musical notes 🎶 +heavy plus sign ➕ +heavy minus sign ➖ +heavy division sign ➗ +heavy multiplication x ✖️ +heavy dollar sign 💲 +currency exchange 💱 +trade mark ™️ +copyright ©️ +registered ®️ +wavy dash 〰️ +curly loop ➰ +double curly loop ➿ +END arrow 🔚 +BACK arrow 🔙 +ON! arrow 🔛 +TOP arrow 🔝 +SOON arrow 🔜 +heavy check mark ✔️ +ballot box with check ☑️ +radio button 🔘 +white circle ⚪️ +black circle ⚫️ +red circle 🔴 +blue circle 🔵 +red triangle pointed up 🔺 +red triangle pointed down 🔻 +small orange diamond 🔸 +small blue diamond 🔹 +large orange diamond 🔶 +large blue diamond 🔷 +white square button 🔳 +black square button 🔲 +black small square ▪️ +white small square ▫️ +black medium-small square ◾️ +white medium-small square ◽️ +black medium square ◼️ +white medium square ◻️ +black large square ⬛️ +white large square ⬜️ +speaker low volume 🔈 +muted speaker 🔇 +speaker medium volume 🔉 +speaker high volume 🔊 +bell 🔔 +bell with slash 🔕 +megaphone 📣 +loudspeaker 📢 +eye in speech bubble 👁🗨 +speech balloon 💬 +thought balloon 💭 +right anger bubble 🗯 +spade suit ♠️ +club suit ♣️ +heart suit ♥️ +diamond suit ♦️ +joker 🃏 +flower playing cards 🎴 +mahjong red dragon 🀄️ +one o’clock 🕐 +two o’clock 🕑 +three o’clock 🕒 +four o’clock 🕓 +five o’clock 🕔 +six o’clock 🕕 +seven o’clock 🕖 +eight o’clock 🕗 +nine o’clock 🕘 +ten o’clock 🕙 +eleven o’clock 🕚 +twelve o’clock 🕛 +one-thirty 🕜 +two-thirty 🕝 +three-thirty 🕞 +four-thirty 🕟 +five-thirty 🕠 +six-thirty 🕡 +seven-thirty 🕢 +eight-thirty 🕣 +nine-thirty 🕤 +ten-thirty 🕥 +eleven-thirty 🕦 +twelve-thirty 🕧 +white flag 🏳️ +black flag 🏴 +chequered flag 🏁 +triangular flag 🚩 +rainbow flag 🏳️🌈 +Afghanistan 🇦🇫 +Åland Islands 🇦🇽 +Albania 🇦🇱 +Algeria 🇩🇿 +American Samoa 🇦🇸 +Andorra 🇦🇩 +Angola 🇦🇴 +Anguilla 🇦🇮 +Antarctica 🇦🇶 +Antigua & Barbuda 🇦🇬 +Argentina 🇦🇷 +Armenia 🇦🇲 +Aruba 🇦🇼 +Australia 🇦🇺 +Austria 🇦🇹 +Azerbaijan 🇦🇿 +Bahamas 🇧🇸 +Bahrain 🇧🇭 +Bangladesh 🇧🇩 +Barbados 🇧🇧 +Belarus 🇧🇾 +Belgium 🇧🇪 +Belize 🇧🇿 +Benin 🇧🇯 +Bermuda 🇧🇲 +Bhutan 🇧🇹 +Bolivia 🇧🇴 +Caribbean Netherlands 🇧🇶 +Bosnia & Herzegovina 🇧🇦 +Botswana 🇧🇼 +Brazil 🇧🇷 +British Indian Ocean Territory 🇮🇴 +British Virgin Islands 🇻🇬 +Brunei 🇧🇳 +Bulgaria 🇧🇬 +Burkina Faso 🇧🇫 +Burundi 🇧🇮 +Cape Verde 🇨🇻 +Cambodia 🇰🇭 +Cameroon 🇨🇲 +Canada 🇨🇦 +Canary Islands 🇮🇨 +Cayman Islands 🇰🇾 +Central African Republic 🇨🇫 +Chad 🇹🇩 +Chile 🇨🇱 +China 🇨🇳 +Christmas Island 🇨🇽 +Cocos (Keeling) Islands 🇨🇨 +Colombia 🇨🇴 +Comoros 🇰🇲 +Congo - Brazzaville 🇨🇬 +Congo - Kinshasa 🇨🇩 +Cook Islands 🇨🇰 +Costa Rica 🇨🇷 +Côte d’Ivoire 🇨🇮 +Croatia 🇭🇷 +Cuba 🇨🇺 +Curaçao 🇨🇼 +Cyprus 🇨🇾 +Czech Republic 🇨🇿 +Denmark 🇩🇰 +Djibouti 🇩🇯 +Dominica 🇩🇲 +Dominican Republic 🇩🇴 +Ecuador 🇪🇨 +Egypt 🇪🇬 +El Salvador 🇸🇻 +Equatorial Guinea 🇬🇶 +Eritrea 🇪🇷 +Estonia 🇪🇪 +Ethiopia 🇪🇹 +European Union 🇪🇺 +Falkland Islands 🇫🇰 +Faroe Islands 🇫🇴 +Fiji 🇫🇯 +Finland 🇫🇮 +France 🇫🇷 +French Guiana 🇬🇫 +French Polynesia 🇵🇫 +French Southern Territories 🇹🇫 +Gabon 🇬🇦 +Gambia 🇬🇲 +Georgia 🇬🇪 +Germany 🇩🇪 +Ghana 🇬🇭 +Gibraltar 🇬🇮 +Greece 🇬🇷 +Greenland 🇬🇱 +Grenada 🇬🇩 +Guadeloupe 🇬🇵 +Guam 🇬🇺 +Guatemala 🇬🇹 +Guernsey 🇬🇬 +Guinea 🇬🇳 +Guinea-Bissau 🇬🇼 +Guyana 🇬🇾 +Haiti 🇭🇹 +Honduras 🇭🇳 +Hong Kong SAR China 🇭🇰 +Hungary 🇭🇺 +Iceland 🇮🇸 +India 🇮🇳 +Indonesia 🇮🇩 +Iran 🇮🇷 +Iraq 🇮🇶 +Ireland 🇮🇪 +Isle of Man 🇮🇲 +Israel 🇮🇱 +Italy 🇮🇹 +Jamaica 🇯🇲 +Japan 🇯🇵 +crossed flags 🎌 +Jersey 🇯🇪 +Jordan 🇯🇴 +Kazakhstan 🇰🇿 +Kenya 🇰🇪 +Kiribati 🇰🇮 +Kosovo 🇽🇰 +Kuwait 🇰🇼 +Kyrgyzstan 🇰🇬 +Laos 🇱🇦 +Latvia 🇱🇻 +Lebanon 🇱🇧 +Lesotho 🇱🇸 +Liberia 🇱🇷 +Libya 🇱🇾 +Liechtenstein 🇱🇮 +Lithuania 🇱🇹 +Luxembourg 🇱🇺 +Macau SAR China 🇲🇴 +Macedonia 🇲🇰 +Madagascar 🇲🇬 +Malawi 🇲🇼 +Malaysia 🇲🇾 +Maldives 🇲🇻 +Mali 🇲🇱 +Malta 🇲🇹 +Marshall Islands 🇲🇭 +Martinique 🇲🇶 +Mauritania 🇲🇷 +Mauritius 🇲🇺 +Mayotte 🇾🇹 +Mexico 🇲🇽 +Micronesia 🇫🇲 +Moldova 🇲🇩 +Monaco 🇲🇨 +Mongolia 🇲🇳 +Montenegro 🇲🇪 +Montserrat 🇲🇸 +Morocco 🇲🇦 +Mozambique 🇲🇿 +Myanmar (Burma) 🇲🇲 +Namibia 🇳🇦 +Nauru 🇳🇷 +Nepal 🇳🇵 +Netherlands 🇳🇱 +New Caledonia 🇳🇨 +New Zealand 🇳🇿 +Nicaragua 🇳🇮 +Niger 🇳🇪 +Nigeria 🇳🇬 +Niue 🇳🇺 +Norfolk Island 🇳🇫 +Northern Mariana Islands 🇲🇵 +North Korea 🇰🇵 +Norway 🇳🇴 +Oman 🇴🇲 +Pakistan 🇵🇰 +Palau 🇵🇼 +Palestinian Territories 🇵🇸 +Panama 🇵🇦 +Papua New Guinea 🇵🇬 +Paraguay 🇵🇾 +Peru 🇵🇪 +Philippines 🇵🇭 +Pitcairn Islands 🇵🇳 +Poland 🇵🇱 +Portugal 🇵🇹 +Puerto Rico 🇵🇷 +Qatar 🇶🇦 +Réunion 🇷🇪 +Romania 🇷🇴 +Russia 🇷🇺 +Rwanda 🇷🇼 +St. Barthélemy 🇧🇱 +St. Helena 🇸🇭 +St. Kitts & Nevis 🇰🇳 +St. Lucia 🇱🇨 +St. Pierre & Miquelon 🇵🇲 +St. Vincent & Grenadines 🇻🇨 +Samoa 🇼🇸 +San Marino 🇸🇲 +São Tomé & Príncipe 🇸🇹 +Saudi Arabia 🇸🇦 +Senegal 🇸🇳 +Serbia 🇷🇸 +Seychelles 🇸🇨 +Sierra Leone 🇸🇱 +Singapore 🇸🇬 +Sint Maarten 🇸🇽 +Slovakia 🇸🇰 +Slovenia 🇸🇮 +Solomon Islands 🇸🇧 +Somalia 🇸🇴 +South Africa 🇿🇦 +South Georgia & South Sandwich Islands 🇬🇸 +South Korea 🇰🇷 +South Sudan 🇸🇸 +Spain 🇪🇸 +Sri Lanka 🇱🇰 +Sudan 🇸🇩 +Suriname 🇸🇷 +Swaziland 🇸🇿 +Sweden 🇸🇪 +Switzerland 🇨🇭 +Syria 🇸🇾 +Taiwan 🇹🇼 +Tajikistan 🇹🇯 +Tanzania 🇹🇿 +Thailand 🇹🇭 +Timor-Leste 🇹🇱 +Togo 🇹🇬 +Tokelau 🇹🇰 +Tonga 🇹🇴 +Trinidad & Tobago 🇹🇹 +Tunisia 🇹🇳 +Turkey 🇹🇷 +Turkmenistan 🇹🇲 +Turks & Caicos Islands 🇹🇨 +Tuvalu 🇹🇻 +Uganda 🇺🇬 +Ukraine 🇺🇦 +United Arab Emirates 🇦🇪 +United Kingdom 🇬🇧 +United States 🇺🇸 +U.S. Virgin Islands 🇻🇮 +Uruguay 🇺🇾 +Uzbekistan 🇺🇿 +Vanuatu 🇻🇺 +Vatican City 🇻🇦 +Venezuela 🇻🇪 +Vietnam 🇻🇳 +Wallis & Futuna 🇼🇫 +Western Sahara 🇪🇭 +Yemen 🇾🇪 +Zambia 🇿🇲 +Zimbabwe 🇿🇼 +' @@ -0,0 +1,85 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + min NUMBER... + min -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Get the minimun number from the given values. + + + Examples: + + Get the minimum number from a list: + + $ min 5 3 9 9 4 + 3 + + Get the minimum number when negative numbers are given + + $ min -- -3 -5 + -5 + + Get the minimum number given a single number + + $ min 8 + 8 + + The minimum default number: + + $ min + 0 + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ -z "${1:-}" ]; then + echo 0 + exit +fi + +N="$1" +for n in "$@"; do + N=$((N < n ? N : n)) +done +echo "$N" diff --git a/bin/mkdtemp b/bin/mkdtemp new file mode 100755 index 0000000..3729dd4 --- /dev/null +++ b/bin/mkdtemp @@ -0,0 +1,64 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + mkdtemp + mkdtemp -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Create a new temporary file and echo its name back. + + + Examples: + + `cd` into temporary directory: + + $ cd "$(mkdtemp)" + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +name="$(tmpname)" +mkdir "$name" +echo "$name" diff --git a/bin/mkstemp b/bin/mkstemp new file mode 100755 index 0000000..4097e59 --- /dev/null +++ b/bin/mkstemp @@ -0,0 +1,64 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + mkstemp + mkstemp -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Create a new temporary file and echo its name back. + + + Examples: + + Capture output into temporary file: + + $ OUT="$(mkstemp)"; cmd > "$OUT" + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +name="$(tmpname)" +touch "$name" +echo "$name" @@ -0,0 +1,153 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + msg [-0|-1] [-X|-s|-S|-m|-D|-b] [MESSAGE] + msg -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -X send MESSAGE using the `xmpp` command + -s play $XDG_DATA_HOME/msg/{good,bad}.ogg sound + -S say MESSAGE using `speak` + -m send email with MESSAGE as subject and empty body + -D send desktop MESSAGE via `notify-send` + -b print terminal bell + -0 an OK message + -1 an error message + -h, --help show this message + + MESSAGE the text to be sent by the relevant channel + + Examples: + + Ring a terminal bell and play a sound, representing an error: + + $ msg -1sb + + Send an email and an XMPP message: + + $ msg -mX 'The message goes here' + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +sound() { + if [ "$OK" = true ]; then + play "$XDG_DATA_HOME"/msg/good.ogg 2>/dev/null + else + play "$XDG_DATA_HOME"/msg/bad.ogg 2>/dev/null + fi +} + +OK=true +XMPP=false +SOUND=false +SPEAK=false +MAIL=false +DESKTOP=false +BELL=false +ACTION_DONE=false +while getopts '01XsSmDbh' flag; do + case "$flag" in + 0) + OK=true + ;; + 1) + OK=false + ;; + X) + XMPP=true + ACTION_DONE=true + ;; + s) + SOUND=true + ACTION_DONE=true + ;; + S) + SPEAK=true + ACTION_DONE=true + ;; + m) + MAIL=true + ACTION_DONE=true + ;; + D) + DESKTOP=true + ACTION_DONE=true + ;; + b) + BELL=true + ACTION_DONE=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ "$ACTION_DONE" = false ]; then + sound + usage + help + exit +fi + + +MESSAGE="${1:-}" + +if [ "$XMPP" = true ]; then + eval "$(assert-arg "$MESSAGE" '-X MESSAGE')" + xmpp -m "$MESSAGE" eu@euandreh.xyz & +fi +if [ "$SOUND" = true ]; then + sound & +fi +if [ "$SPEAK" = true ]; then + eval "$(assert-arg "$MESSAGE" '-S MESSAGE')" + echo "$MESSAGE" | speak -v pt-BR & +fi +if [ "$MAIL" = true ]; then + eval "$(assert-arg "$MESSAGE" '-m MESSAGE')" + echo " " | email -s "$MESSAGE" eu@euandre.org & +fi +if [ "$DESKTOP" = true ]; then + eval "$(assert-arg "$MESSAGE" '-D MESSAGE')" + if [ "$OK" = true ]; then + notify-send -t 5000 "$MESSAGE" & + else + notify-send -t 5000 -u critical "$MESSAGE" & + fi +fi +if [ "$BELL" = true ]; then + printf '\a' & +fi + +wait diff --git a/bin/n-times b/bin/n-times new file mode 100755 index 0000000..4fa8b96 --- /dev/null +++ b/bin/n-times @@ -0,0 +1,73 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + n-times COUNT -- COMMAND... + n-times -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + COUNT the number of times for COMMAND to be executed + + + Examples: + + Print 123 5 times: + + $ n-times 5 echo 123 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +COUNT="${1:-}" +shift + +eval "$(assert-arg "$COUNT" 'COUNT')" + + +while true; do + if [ "$COUNT" = 0 ]; then + break + fi + COUNT=$((COUNT - 1)) + "$@" +done diff --git a/bin/nato b/bin/nato new file mode 100755 index 0000000..a9c21bf --- /dev/null +++ b/bin/nato @@ -0,0 +1,102 @@ +#!/usr/bin/env perl + +use v5.34; +use warnings; +use feature 'signatures'; +no warnings ('experimental::signatures'); +use Getopt::Std (); + +sub usage($fh) { + print $fh <<~'EOF' + Usage: + nato + nato -h + EOF +} + +sub help($fh) { + print $fh <<~'EOF' + + Options: + -h, --help show this message + + + Translate the given input to the NATO phonetic alphabet. + + + Examples: + + Spell 'EuAndreh': + + $ echo 'EuAndreh' | nato + Echo Uniform Alfa November Delta Romeo Echo Hotel + EOF +} + +for (@ARGV) { + last if $_ eq '--'; + if ($_ eq '--help') { + usage *STDOUT; + help *STDOUT; + exit + } +} + +my %opts; +if (!Getopt::Std::getopts('h', \%opts)) { + usage *STDERR; + exit 2; +} + +if ($opts{h}) { + usage *STDOUT; + help *STDOUT; + exit; +} + +my %DICT = ( + 'a' => 'Alfa', + 'b' => 'Bravo', + 'c' => 'Charlie', + 'd' => 'Delta', + 'e' => 'Echo', + 'f' => 'Foxtrot', + 'g' => 'Golf', + 'h' => 'Hotel', + 'i' => 'India', + 'j' => 'Juliett', + 'k' => 'Kilo', + 'l' => 'Lima', + 'm' => 'Mike', + 'n' => 'November', + 'o' => 'Oscar', + 'p' => 'Papa', + 'q' => 'Quebec', + 'r' => 'Romeo', + 's' => 'Sierra', + 't' => 'Tango', + 'u' => 'Uniform', + 'v' => 'Victor', + 'w' => 'Whiskey', + 'x' => 'X-ray', + 'y' => 'Yankee', + 'z' => 'Zulu', + '1' => 'One', + '2' => 'Two', + '3' => 'Three', + '4' => 'Four', + '5' => 'Five', + '6' => 'Six', + '7' => 'Seven', + '8' => 'Eight', + '9' => 'Nine', + '0' => 'Zero', +); + +while (<STDIN>) { + for my $c (split //, $_) { + my $char = $DICT{lc $c}; + print "$char " if defined $char; + } + print "\n"; +} diff --git a/bin/ootb b/bin/ootb new file mode 100755 index 0000000..3aa58db --- /dev/null +++ b/bin/ootb @@ -0,0 +1,103 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + ootb BUILD_DIRECTORY < FILE... + ootb -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + BUILD_DIRECTORY the path of the build directory + FILE the files to be linked + + + Create a directory out of symlinks of the given files. + + The goal is to enable parallel build directories to coexist, + so that one do *O*ut *O*f *T*ree *B*uilds without requiring the + build system or the project to explicitly support it. + + If a repository contains the files: + + .git/ + Makefile + README.md + src/ + file1.ext + file2.ext + + Running `git ls-files | ootb build-1/` would create the + 'build-1/' directory with: + + build-1/ + Makefile -> /absolute/path/to/Makefile + README.md -> /absolute/path/to/README.md + src/ + file1.ext -> /absolute/path/to/file1.ext + file2.ext -> /absolute/path/to/file2.ext + + With that one can `cd build-1/` and run builds there, without + the build artifacts littering the source tree. Also, one could + create a build-2/ directory, where different compiler flags or + build options are given, such as debug/release, while sharing the + underlying source code. + + + Examples: + + Create a 'build/' directory with the files from the Git repository: + + $ git ls-files | ootb build/ + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +BUILD_DIRECTORY="${1:-}" +eval "$(assert-arg "$BUILD_DIRECTORY" 'BUILD_DIRECTORY')" +mkdir -p "$BUILD_DIRECTORY" + + +while read -r f; do + mkdir -p "$BUILD_DIRECTORY"/"$(dirname "$f")" + ln -fs "$PWD"/"$f" "$BUILD_DIRECTORY"/"$f" +done diff --git a/bin/open b/bin/open new file mode 100755 index 0000000..d2eedd3 --- /dev/null +++ b/bin/open @@ -0,0 +1,101 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + open FILE... + open -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + Examples: + + Open an HTML file on the current $BROWSER: + $ open index.html + + Open multiple PDF files (with zathura): + $ open *.pdf + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ -z "${1:-}" ]; then + usage >&2 + exit 2 +fi + +for f in "$@"; do + case "$f" in + *.ico|*.jpg|*.jpeg|*.png) + feh "$f" + ;; + https://www.youtube.com/watch*) + nohup mpv "$f" 1>&2 2>/dev/null & + ;; + *.flac|*.ogg|*.mkv|*.avi|*.mp4) + nohup mpv "$f" 1>&2 2>/dev/null & + ;; + http*|*.svg|*.html) + "$BROWSER" "$f" + ;; + gopher://*) + amfora "$f" + ;; + gemini://*) + telescope "$f" + ;; + *.pdf|*.djvu|*.ps|*.epub) + nohup zathura "$f" 1>&2 2>/dev/null & + ;; + *.txt) + less "$f" + ;; + *.midi) + timidity "$f" + ;; + mailto:*) + alot compose "$f" + ;; + *) + DIR="$(cd -- "$(dirname -- "$0")"; pwd)" + CMD="$(without-env PATH "$DIR" -- command -v xdg-open)" + "$CMD" "$f" + ;; + esac +done diff --git a/bin/player b/bin/player new file mode 100755 index 0000000..b6e66d7 --- /dev/null +++ b/bin/player @@ -0,0 +1,136 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + player ACTION + player -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + ACTION one of: + - backward: go back 5 seconds + - forward: go forward 5 seconds + - previous: go to the previous track + - next: go to the next track + - play-pause: play/pause + - rotate: rotate across available MPRIS players + - current: show the current MPRIS player + + + Manipulate the MPRIS audio player. + + + Examples: + + Change the current MPRIS player: + + $ player current + + + Play/pause: + + $ player play-pause + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'P:h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) +ACTION="${1:-}" + +eval "$(assert-arg "$ACTION" 'ACTION')" + + + +CURRENT_PLAYER_PATH="$XDG_CACHE_HOME"/euandreh-mpris-player.txt +CURRENT_PLAYER="$(cat "$CURRENT_PLAYER_PATH" ||:)" +AVAILABLE_PLAYERS="$(playerctl --list-all | LANG=POSIX.UTF-8 sort)" + +pick_first() { + echo "$AVAILABLE_PLAYERS" | head -n1 +} + +next_player() { + if [ -z "$CURRENT_PLAYER" ]; then + pick_first + elif ! echo "$AVAILABLE_PLAYERS" | grep -q "$CURRENT_PLAYER"; then + # Unknown $CURRENT_PLAYER, pick anyone + pick_first + else + INDEX="$(echo "$AVAILABLE_PLAYERS" | grep -n "$CURRENT_PLAYER" | cut -d: -f1)" + LENGTH="$(echo "$AVAILABLE_PLAYERS" | wc -l)" + if [ "$INDEX" = "$LENGTH" ]; then + # Reached the end of the $AVAILABLE_PLAYERS list, wrapping + pick_first + else + # Get the next player instead + echo "$AVAILABLE_PLAYERS" | awk -v idx="$INDEX" 'NR == idx+1 {print}' + fi + fi +} + +case "$ACTION" in + backward) + playerctl --player="$CURRENT_PLAYER" position 5- + ;; + forward) + playerctl --player="$CURRENT_PLAYER" position 5+ + ;; + previous) + playerctl --player="$CURRENT_PLAYER" previous + ;; + next) + playerctl --player="$CURRENT_PLAYER" next + ;; + play-pause) + playerctl --player="$CURRENT_PLAYER" play-pause + ;; + rotate) + PLAYER="$(next_player)" + echo "$PLAYER" > "$CURRENT_PLAYER_PATH" + notify-send -t 1000 "$PLAYER" 'current MPRIS target' + ;; + current) + printf '%s\n' "$CURRENT_PLAYER" + ;; + *) + printf 'Bad ACTION: "%s".\n\n' "$ACTION" >&2 + usage >&2 + exit 2 + ;; +esac diff --git a/bin/playlist b/bin/playlist new file mode 100755 index 0000000..bd01d23 --- /dev/null +++ b/bin/playlist @@ -0,0 +1,103 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + playlist ACTION + playlist -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + ACTION one of: + - prompt + - run + + + Manage the playlist. + + + Examples: + + Enqueue a video: + + $ playlist prompt + + + Play the next video in the queue: + + $ playlist run + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) +ACTION="${1:-}" + +eval "$(assert-arg "$ACTION" 'ACTION')" + +F="$XDG_DATA_HOME"/euandreh/playlist.txt + +prompt() { + ENTRY="$(zenity --text 'URL of the video to enqueue:' --entry ||:)" + if [ -n "$ENTRY" ]; then + echo "$ENTRY" >> "$F" + fi +} + +run() { + next="$(head -n1 "$F")" + if [ -z "$next" ]; then + return + fi + mpv "$next" + echo "$next" >> "queue.$F" + tail -n+2 "$F" | sponge "$F" +} + +case "$ACTION" in + prompt) + prompt + ;; + run) + run + ;; + *) + printf 'Bad ACTION: %s.\n\n' "$ACTION" >&2 + usage >&2 + exit 2 + ;; +esac @@ -0,0 +1,78 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + pre [-c COLOR] PREFIX + pre -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -c COLOR ANSI color to be used on the prefix text + -h, --help show this message + + Prefix STDIN with PREFIX. + + + Examples: + + Prefix with 'database': + + $ ./run-db.sh | pre 'database' + + + Prefix with yellow 'numbers': + + $ seq 10 | pre -c yellow numbers + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +COLOR='' +while getopts 'c:h' flag; do + case "$flag" in + c) + COLOR="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +PREFIX="${1:-}" +eval "$(assert-arg "$PREFIX" 'PREFIX')" + +while read -r line; do + if [ -z "$COLOR" ]; then + printf '%s: %s\n' "$PREFIX" "$line" + else + printf '%s: %s\n' "$(color -c "$COLOR" "$PREFIX")" "$line" + fi +done diff --git a/bin/print b/bin/print new file mode 100755 index 0000000..e0d3d6e --- /dev/null +++ b/bin/print @@ -0,0 +1,151 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + print [-d] [-q QUALITY] [FILE...] + print -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -d print duplex/double-sided + -q QUALITY choose the print quality, either: + low, medium (default) or high. + -h, --help show this message + + Examples: + + Print the given PostScript file with default quality: + $ print f1.ps + + Print multiple PDF files with high quality: + $ print -dq high *.pdf + + Print the file from STDIN, double-sided: + $ print -d < f2.ps + + Print multiple source code files: + $ print src/*.{c,h} + EOF +} + +mkdtemp() { + name="$(echo 'mkstemp(template)' | + m4 -D template="${TMPDIR:-/tmp}/m4-tmpname.")" + rm -f "$name" + mkdir "$name" + echo "$name" +} + +n_pages() { + pdftk "$1" dump_data | awk '/NumberOfPages/ { print $2 }' +} + +main() { + if file -b "$FILE" | grep -q PostScript; then + ps2pdf "$FILE" "$NEWDIR"/in.pdf + elif file -b "$FILE" | grep -q PDF; then + cp "$FILE" "$NEWDIR"/in.pdf + else + enscript -o- "$FILE" | ps2pdf - "$NEWDIR"/in.pdf + fi + cd "$NEWDIR" + + if [ -z "$DUPLEX" ]; then + lp in.pdf + cd - > /dev/null + return + fi + + if [ "$(n_pages in.pdf)" = '1' ]; then + lp in.pdf + return + fi + + pdftk A=in.pdf cat Aodd output odd.pdf + pdftk A=in.pdf cat Aeven output even.pdf + + NODD="$(n_pages odd.pdf)" + NEVEN="$(n_pages even.pdf)" + + printf 'Printing odd pages...\n' >&2 + lp odd.pdf + printf 'Has printing finished yet? Once it does, reload the pages and hit it enter to continue. ' + read -r < /dev/tty + lp even.pdf + + if [ "$NODD" != "$NEVEN" ]; then + printf '\n' | lp + fi + + cd - > /dev/null +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +lpoptions -o PrintQuality=standard +DUPLEX= +while getopts 'dq:h' flag; do + case "$flag" in + d) + DUPLEX=1 + ;; + q) + case "$OPTARG" in + low) + lpoptions -o PrintQuality=draft + ;; + medium) + lpoptions -o PrintQuality=standard + ;; + high) + lpoptions -o PrintQuality=high + ;; + *) + echo "Bad QUALITY option: \"$OPTARG\"" >&2 + exit 2 + ;; + esac + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +NEWDIR="$(mkdtemp)" +if [ -z "${1:-}" ]; then + FILE="$NEWDIR"/STDIN + cat - > "$FILE" + main +else + for f in "$@"; do + FILE="$f" + main + done +fi diff --git a/bin/prompt b/bin/prompt new file mode 100755 index 0000000..247c81a --- /dev/null +++ b/bin/prompt @@ -0,0 +1,76 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + prompt STRING|- + prompt -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + STRING the text to be displayed in the prompt + + + Display a prompt and return a value corresponding to the + response. + + + Examples: + + Conditionally run download command + + if prompt 'Download files?'; then + run_download; + fi + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +STRING="${1:-}" +eval "$(assert-arg "$STRING" 'STRING')" + +printf '%s' "$STRING" +printf ' [Y/n]: ' +read -r yesno +if [ "$yesno" != 'n' ] && [ "$yesno" != 'N' ]; then + exit 0 +else + exit 1 +fi @@ -0,0 +1,71 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + qr [-s PIXEL_SIZE] + qr -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -s PIXEL_SIZE size of the pixel (default 10) + -h, --help show this help message + + + Read data from STDIN and present a QR image with said data. + + + Examples: + + Link to my homepage: + + $ printf 'https://euandre.org' | qr + + + Numbers with a smaller pixel size: + + $ seq 99 | qr -s 5 + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +PIXEL_SIZE=10 +while getopts 's:h' flag; do + case "$flag" in + s) + PIXEL_SIZE="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +cat | qrencode -s "$PIXEL_SIZE" -o- | feh - diff --git a/bin/repos b/bin/repos new file mode 100755 index 0000000..e45f4c8 --- /dev/null +++ b/bin/repos @@ -0,0 +1,175 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + repos [-e DIR...] [-v] [DIRECTORY] + repos -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -e DIR exclude the given directory from traversing + -v enable verbose mode + -h, --help show this message + + DIRECTORY the folder to traverse + + + Traverse DIRECTORY looking for VCS repositores. As soon as + one is found, stop recursing, and emit its name alongside its + type. + + On verbose mode, print the directories being visited. + + + Examples: + + Show all repositories under ~/dev/, excluding a few directories: + + $ repos -e ~/dev/go/ -e ~/dev/quicklisp/ ~/dev/ + # ... + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + + +# Similar to urlencode but only for % and \n (and not \0). +array_encode() { + sed 's/%/%25/g' | sed -e :a -e '$!N; s/\n/%0A/; ta' +} + +array_decode() { + sed -e 's/%0A/\ +/g' -e 's/%25/%/g' +} + + +# +# CAVEAT: +# To avoid needing to keep decoding the array elements on every call to +# `arr_includes`, assume directory names don't contain newlines. This makes the +# current code scanning ~/dev/ from 8 seconds go to 1 second. +# + +arr_push() { + ARR="$1" + ELT="$2" + if [ -n "$ARR" ]; then + echo "$ARR" + fi + echo "$ELT" # | array_encode (see CAVEAT) +} + +arr_includes() { + ARR="$1" + ELT="$2" + echo "$ARR" | while read -r el; do + # if [ "$(printf '%s\n' "$el" | array_decode)" = "$ELT" ]; then (see CAVEAT) + if [ "$el" = "$ELT" ]; then + return 2 + fi + done + if [ $? = 2 ]; then + return 0 + else + return 1 + fi +} + +EXCLUDE= +VERBOSE=false +while getopts 'e:vh' flag; do + case "$flag" in + e) + case "$OPTARG" in + */) + ARG="$OPTARG" + ;; + *) + ARG="$OPTARG/" + ;; + esac + EXCLUDE="$(arr_push "$EXCLUDE" "$ARG")" + ;; + v) + VERBOSE=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +is_repository() { + TYPE="$(vcs -C "$1" -t 2>/dev/null)" + if [ -n "$TYPE" ]; then + echo "$1" + else + return 1 + fi +} + + +traverse_directory() { + if [ "$VERBOSE" = true ]; then + printf 'cur: %s\n' "$1" >&2 + fi + if arr_includes "$EXCLUDE" "$1"; then + return + fi + if is_repository "$1"; then + return + fi + for d in "$1"/*; do + if [ "$VERBOSE" = true ]; then + printf 'cur: %s\n' "$d" >&2 + fi + if [ ! -d "$d" ]; then + continue + fi + if arr_includes "$EXCLUDE" "$d/"; then + continue + fi + if ! is_repository "$d"; then + traverse_directory "$d" + fi + done +} + +if [ -z "${1:-}" ]; then + set -- "$PWD" +fi + +for dir in "$@"; do + traverse_directory "${dir%%/}" +done @@ -0,0 +1,150 @@ +#!/bin/sh +set -eu + +D="${XDG_DATA_HOME:-$HOME/.local/share}/doc/rfc" +PROMPT_DB="$( + cat <<-EOF + RFC directory does not exist: + $D/ + + Do you want to download the files to create it? + EOF +)" + +usage() { + cat <<-'EOF' + Usage: + rfc [-w] RFC_NUMBER + rfc -u + rfc -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -w show the path to the RFC file instead of displaying + its contents + -u update the local RFC database + -h, --help show this message + + Lookup the given RFC + in $XDG_DATA_HOME/doc/rfc/ (defaults to ~/.local/share), + and feed it into the $PAGER, akin to doing: + + $ $PAGER $XDG_DATA_HOME/doc/rfc/rfc$RFC_NUMBER.txt + + If the $XDG_DATA_HOME/doc/rfc/ directory doesn't exist, it gets + created it by downloading the latest RFC files and placing all .txt + files there. + + + Examples: + + + Show RFC 1234 in $PAGER: + + $ rfc 1234 + + + Print path to RFC 2222: + + $ rfc 2222 + + + Download the latest RFCs: + + $ rfc -u + EOF +} + +view() { + if [ -t 1 ]; then + ${PAGER:-cat} + else + cat + fi +} + +update() { + rsync -avzP --delete ftp.rfc-editor.org::rfcs-text-only "$D" + STATUS=$? + if [ "$STATUS" != 0 ]; then + exit "$STATUS" + fi +} + +check_local_db() { + if [ ! -e "$D" ]; then + if prompt "$PROMPT_DB"; then + update + else + echo 'No local RFC database to operate on.' >&2 + exit 1 + fi + fi +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'wuh' flag; do + case "$flag" in + w) + WHERE=true + ;; + u) + UPDATE=true + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +RFC_NUMBER="${1:-}" +F="$D/rfc${RFC_NUMBER}.txt" + +check_local_db + +if [ "${UPDATE:-}" = true ]; then + update + exit +fi + +eval "$(assert-arg "$RFC_NUMBER" 'RFC_NUMBER')" + +if [ ! -e "$F" ]; then + printf 'Given RFC_NUMBER "%s" does not exist at:\n%s\n' \ + "$RFC_NUMBER" "$F" >&2 + exit 2 +fi + +if [ "${WHERE:-}" = true ]; then + printf '%s\n' "$F" + exit +else + view < "$F" + exit +fi diff --git a/bin/serve b/bin/serve new file mode 100755 index 0000000..4a06c50 --- /dev/null +++ b/bin/serve @@ -0,0 +1,78 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + serve [-d DIRECTORY] [-p PORT] + serve -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -d DIRECTORY the directory to serve (default: ".") + -p PORT the port to listen on (default: 8000) + -h, --help show this message + + + Serve DIRECTORY via HTTP as a static file server, and open the + URL on the $BROWSER. + + + Examples: + + Serve "." on the default PORT: + + $ serve + + + Serve "public/" on port 1234: + + $ serve -d public/ -p 1234 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +DIRECTORY='.' +PORT=8000 +while getopts 'd:p:h' flag; do + case "$flag" in + d) + DIRECTORY="$OPTARG" + ;; + p) + PORT="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done + +open "http://localhost:$PORT" +python3 -m http.server -d "$DIRECTORY" "$PORT" diff --git a/bin/slugify b/bin/slugify new file mode 100755 index 0000000..aa3b50a --- /dev/null +++ b/bin/slugify @@ -0,0 +1,69 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + slugify < STDIN + slugify -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + "slugify" the input string, removing diacritics and punctuation + from the string, on a best-effort basis. + + + Examples: + + Slugify the input string: + + $ echo 'Saçi-pererê, tomando açaí!!' | slugify + saci-perere-tomando-acai + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + esac +done +shift $((OPTIND - 1)) + +iconv -ct ASCII//TRANSLIT | + tr '[:upper:]' '[:lower:]' | + sed \ + -e 's/[^a-z0-9]/-/g' \ + -e 's/--*/-/g' \ + -e 's/^-//' \ + -e 's/-$//' diff --git a/bin/status-bar b/bin/status-bar new file mode 100755 index 0000000..bd55b05 --- /dev/null +++ b/bin/status-bar @@ -0,0 +1,104 @@ +#!/usr/bin/env perl + +# +# Derived from: +# https://github.com/i3/i3status/blob/28399bf84693a03eb772be647d5927011c1d2619/contrib/wrapper.pl +# + +use v5.34; +use warnings; +use feature 'signatures'; +no warnings ('experimental::signatures'); +use Getopt::Std (); +use JSON (); + +sub usage($fh) { + print $fh <<~'EOF' + Usage: + status-bar + status-bar -h + EOF +} + +sub help($fh) { + print $fh <<~'EOF' + + Options: + -h, --help show this message + + + Process the output of i3status and add custom information for + showing in the i3 bar. + + + Examples: + + Process i3status: + + $ i3status | status-bar + + + Configure i3 to use status-bar: + + # In $XDG_CONFIG_HOME/i3/config + bar { + status_command i3status | status-bar + } + EOF +} + +for (@ARGV) { + last if $_ eq '--'; + if ($_ eq '--help') { + usage *STDOUT; + help *STDOUT; + exit; + } +} + +my %opts; +if (!Getopt::Std::getopts('h', \%opts)) { + usage *STDERR; + exit 2; +} + +if ($opts{h}) { + usage *STDOUT; + help *STDOUT; + exit; +} + +# Don't buffer any output +$| = 1; + +# Skip the first line which contains the version header. +print scalar <STDIN>; + +# The second line contains the start of the infinite array. +print scalar <STDIN>; + +# Read lines forever, ignore a comma at the beginning if it exists. +while (my ($statusline) = (<STDIN> =~ /^,?(.*)/)) { + # Decode the JSON-encoded line. + my @blocks = @{JSON::decode_json($statusline)}; + + # Prefix our own information (you coud also suffix or insert in the + # middle). + + my $mpris = `player current`; + chomp $mpris; + + my $vms = `vm status | grep :up\$ | wc -l`; + chomp $vms; + + @blocks = ({ + full_text => $vms, + name => 'vms', + }, { + full_text => $mpris, + name => 'mpris', + }, @blocks); + + # Output the line as JSON. + print JSON::encode_json(\@blocks) . ",\n"; +} diff --git a/bin/stopwatch b/bin/stopwatch new file mode 100755 index 0000000..3d5cd07 --- /dev/null +++ b/bin/stopwatch @@ -0,0 +1,65 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + stopwatch + stopwatch -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Run a TUI stopwatch. + + + Examples: + + Just run it: + + $ stopwatch + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +date "+%l:%M:%S%p: stopwatch started (^D to stop)" +time cat +date "+%l:%M:%S%p: stopwatch stopped" @@ -0,0 +1,91 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + tmp FILE... + tmp -d + tmp -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -d delete the remote "tmp/" folder + -h, --help show this message + + + Copies a file to the public server. + + + Examples: + + Copy f.txt: + + $ tmp f.txt + + + Cleanup the $REMOTE: + + $ tmp -d + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +REMOTE='euandreh.xyz' +DIR='/opt/www/euandreh.xyz/static/tmp' +while getopts 'dh' flag; do + case "$flag" in + d) + printf 'Deleting %s:%s...\n' "$REMOTE" "$DIR/" >&2 + ssh "$REMOTE" rm -rf "$DIR" + exit + ;; + h) + usage + help + exit + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) +FILE="${1:-}" + +if [ -z "$FILE" ]; then + printf 'Missing FILE.\n\n' >&2 + usage >&2 + exit 2 +fi + +for f in "$@"; do + FILENAME="$(basename "$f")" + # shellcheck disable=2029 + ssh "$REMOTE" "mkdir -p '$DIR' && cat > '$DIR/$FILENAME'" < "$f" + + LINK="$(printf 'https://%s/tmp/%s' "$REMOTE" "$FILENAME")" + open "$LINK" + if [ $# = 1 ]; then + printf '%s' "$LINK" | copy + printf 'Copied %s to the clipboard!\n' "$LINK" >&2 + fi +done diff --git a/bin/tmpname b/bin/tmpname new file mode 100755 index 0000000..89d7e4d --- /dev/null +++ b/bin/tmpname @@ -0,0 +1,66 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + tmpname + tmpname -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Generate a temporary name. + + + Examples: + + Create a temporary file: + + $ OUT="$(tmpname)"; touch "$OUT"; cmd > "$OUT" + + + `cd` into a temporary directory: + + $ DIR="$(tmpname)"; mkdir -p "$DIR"; cd "$DIR" + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +echo "${TMPDIR:-/tmp}/uuid-tmpname with spaces.$(uuid)" diff --git a/bin/tuivid b/bin/tuivid new file mode 100755 index 0000000..54bddcf --- /dev/null +++ b/bin/tuivid @@ -0,0 +1,65 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + tuivid FILE + tuivid -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + FILE the name of the video file + + + Play a video in the terminal, withut X or a GUI. + + + Examples: + + Play "movie.mp4": + + $ tuivid movie.mp4 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +exec mpv --quiet --vo=tct --vo-tct-256=yes --vo-tct-algo=plain --framedrop=vo "$@" @@ -0,0 +1,70 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + uc + uc -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Transforms text from STDIN from upper-case to lower-case. It is + the equivalent of running 'tr [:lower:] [:upper:]'. + + + Examples: + + Normalize to lower-case: + + $ echo EuAndreh | uc + EUANDREH + + + Keep the text as-is: + + $ echo ANDREH | uc + ANDREH + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +tr '[:lower:]' '[:upper:]' diff --git a/bin/untill b/bin/untill new file mode 100755 index 0000000..030f921 --- /dev/null +++ b/bin/untill @@ -0,0 +1,83 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + until [-n SECONDS] -- COMMAND... + until -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -n SECONDS the amount of seconds to sleep between + attempts (default: 5) + -h, --help show this message + + + Runs COMMAND until it eventually succeeds. Sleep SECONDS + between attempts. + + + Examples: + + Try flaky build until it succeeds: + + $ until guix home build home.scm + + + Try to build until a new version is successfull, + waiting 30 seconds between attempts: + + $ until -n 30 -- x git pull AND make + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +_SECONDS=5 +while getopts 'n:h' flag; do + case "$flag" in + n) + _SECONDS="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +ATTEMPT=1 +while true; do + printf 'Attempt %s.\n' "$ATTEMPT" >&2 + ATTEMPT=$((ATTEMPT + 1)) + if "$@"; then + break + fi + sleep "$_SECONDS" +done diff --git a/bin/update b/bin/update new file mode 100755 index 0000000..cc2412e --- /dev/null +++ b/bin/update @@ -0,0 +1,73 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + update + update -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Updates miscellaneous things on the workstation: + - "guix pull" on the "andreh" and "root" accounts; + - get latest RFCs; + - updates RSS feeds; + - updates source code repositories. + + + Examples: + + Just use it: + + $ update + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +guix pull +rfc -u + +repos -e ~/dev/go/ -e ~/dev/quicklisp/ -e ~/dev/archive/ ~/dev/ | + xargs -I% -P4 x \ + echo 'Fetching on %.' AND \ + vcs -C% fetch OR \ + echo 'WARNING: Failed to fetch repository: %.' >&2 diff --git a/bin/upgrade b/bin/upgrade new file mode 100755 index 0000000..4447a3d --- /dev/null +++ b/bin/upgrade @@ -0,0 +1,66 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + upgrade + upgrade -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Upgrades the system: + - reconfigure the Guix "home" environment and "system". + + + Examples: + + Just use it: + + $ upgrade + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +pass show velhinho/0-andreh-password | + head -n1 | + sudo -ES guix system -v3 reconfigure /etc/guix/configuration.scm +guix home -v3 reconfigure "$XDG_CONFIG_HOME"/guix/home.scm @@ -0,0 +1,75 @@ +#!/usr/bin/env perl + +use v5.34; +use warnings; +use feature 'signatures'; +no warnings ('experimental::signatures'); +use Getopt::Std (); +use URI::Escape (); + +sub usage($fh) { + print $fh <<~'EOF' + Usage: + uri [-e|-d] + uri -h + EOF +} + +sub help($fh) { + print $fh <<~'EOF' + + Options: + -e encode the string (the default action) + -d decode the string + -h, --help show this message + + + Get a string from STDIN and convert it to/from URL encoding. + + + Examples: + + Encode the URL: + + $ echo "https://euandre.org/?q=$(printf '%s' 'a param' | uri)" + https://euandre.org/?q=a%20param + + + Decode the content from the file: + + $ uri -d < file + EOF +} + +for (@ARGV) { + last if $_ eq '--'; + if ($_ eq '--help') { + usage *STDOUT; + help *STDOUT; + exit; + } +} + +my %opts; +if (!Getopt::Std::getopts('edh', \%opts)) { + usage *STDERR; + exit 2; +} + +if ($opts{h}) { + usage *STDOUT; + help *STDOUT; + exit; +} elsif ($opts{e} and $opts{d}) { + say STDERR 'Both -e and -d given. Pick one.'; + say STDERR ''; + usage *STDERR; + exit 2; +} + + +if ($opts{d}) { + print URI::Escape::uri_unescape(<STDIN>); +} else { + print URI::Escape::uri_escape(<STDIN>); +} diff --git a/bin/uuid b/bin/uuid new file mode 100755 index 0000000..34b685f --- /dev/null +++ b/bin/uuid @@ -0,0 +1,62 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + uuid + uuid -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + + Generate UUID from /dev/random. + + + Examples: + + Generate a UUID: + + $ uuid + 755244c8-f955-16df-75cc-f25600c90422 + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +od -xN20 /dev/random | + awk 'NR == 1 { OFS="-"; print $2$3,$4,$5,$6,$7$8$9; exit }' @@ -0,0 +1,255 @@ +#!/bin/sh +set -eu + + +TYPES=' +git +mercurial +bitkeeper +darcs +cvs +fossil +' + + +guess_type() { + if [ -e "$1"/.git ]; then + echo git + elif [ -e "$1"/.hg ]; then + echo mercurial + elif [ -e "$1"/.bk ]; then + echo bitkeeper + elif [ -e "$1"/_darcs ]; then + echo darcs + elif [ -e "$1"/CVS/ ]; then + echo cvs + elif [ -e "$1"/.fslckout ]; then + echo fossil + else + return 1 + fi +} + + + + +git_fetch() { + git fetch "$@" +} + +darcs_fetch() { + darcs fetch "$@" +} + +mercurial_fetch() { + hg pull "$@" +} + +fossil_fetch() { + fossil pull "$@" +} + +cvs_fetch() { + timeout 300 cvs update "$@" +} + +git_status() { + git status "$@" +} + +fossil_status() { + fossil status "$@" +} + +git_diff() { + git diff "$@" +} + +git_ps1() { + BRANCH_NAME="$(git rev-parse --abbrev-ref HEAD)" + OUT="$(git status --short --branch --porcelain)" + BRANCH_LINE="$(echo "$OUT" | head -n 1)" + DIFF_LINES="$(echo "$OUT" | tail -n +2)" + + IS_AHEAD=false + IS_BEHIND=false + if echo "$BRANCH_LINE" | grep -q 'ahead'; then + IS_AHEAD=true + fi + if echo "$BRANCH_LINE" | grep -q 'behind'; then + IS_BEHIND=true + fi + + LINE='' + + if [ "$IS_AHEAD" = true ] && [ "$IS_BEHIND" = true ]; then + LINE="^^^ $BRANCH_NAME vvv" + elif [ "$IS_AHEAD" = true ]; then + LINE="^ $BRANCH_NAME ^" + elif [ "$IS_BEHIND" = true ]; then + LINE="v $BRANCH_NAME v" + else + LINE="$BRANCH_NAME" + fi + + HAS_DIFF=false + HAS_UNTRACKED=false + if echo "$DIFF_LINES" | grep -q '^[A|D|M| ][M|D| ]'; then + HAS_DIFF=true + fi + if echo "$DIFF_LINES" | grep -q '^[?][?]'; then + HAS_UNTRACKED=true + fi + + if [ "$HAS_DIFF" = true ]; then + COLOR_FN=redb + LINE="{$LINE}" + elif [ "$IS_AHEAD" = true ] || [ "$IS_BEHIND" = true ]; then + COLOR_FN=bluei + LINE="[$LINE]" + elif [ "$HAS_UNTRACKED" = true ]; then + COLOR_FN=lightblue + LINE="{$LINE}" + else + COLOR_FN=green + LINE="($LINE)" + fi + + color -c "$COLOR_FN" "$LINE" + + BRANCH_COUNT="$(git branch --list | wc -l)" + if [ "$BRANCH_COUNT" -gt 1 ]; then + color -c lightblue "<$BRANCH_COUNT>" + fi + + STASH_COUNT="$(git stash list | wc -l)" + if [ "$STASH_COUNT" != 0 ]; then + color -c red "*$STASH_COUNT" + fi + + color -c blacki " - git/$(git rev-parse HEAD)" +} + +fossil_ps1() { + BRANCH_NAME="$(fossil branch current)" + + if [ -n "$(fossil extras)" ]; then + HAS_UNTRACKED=1 + fi + + BRANCH_MARKER="$BRANCH_NAME" + + if [ -n "${HAS_UNTRACKED:-}" ]; then + COLOR_FN=lightblue + LINE="($BRANCH_MARKER)" + else + COLOR_FN=green + LINE="($BRANCH_MARKER)" + fi + + color -c "$COLOR_FN" "$LINE" + + color -c blacki " - fossil/$(fossil info | awk '/^checkout:/ { print $2 }')" +} + +mercurial_ps1() { + BRANCH_NAME="$(hg branch)" +} + +git_gc() { + git remote prune origin "$@" +} + + +usage() { + cat <<-'EOF' + Usage: + vcs [-C DIR] ACTION + vcs [-C DIR] -l|-t + vcs -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -C DIR change to DIR instead of PWD + -l list the supported VCS types + -t show the guesses VCS type + -h, --help show this message + + ACTION the action to be performed on the repository: + - fetch + - ps1 + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'C:lth' flag; do + case "$flag" in + C) + cd "$OPTARG" + ;; + l) + # shellcheck disable=2086 + echo $TYPES | tr ' ' '\n' + exit + ;; + t) + guess_type "$PWD" || { + printf 'Could not guess the type of the repository.\n' >&2 + exit 2 + } + exit + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + esac +done +shift $((OPTIND - 1)) + +ACTION="${1:-}" +eval "$(assert-arg "$ACTION" 'ACTION')" +shift + +if [ "${1:-}" = '--' ]; then + shift +fi + +TYPE="$(guess_type "$PWD")" +if [ -z "$TYPE" ]; then + printf 'Could not guess the type of the repository.\n' >&2 + exit 2 +fi + +CMD="$TYPE"_"$ACTION" +if ! command -v "$CMD" >/dev/null; then + printf 'Invalid ACTION: "%s".\n\n' "$ACTION" >&2 + usage >&2 + exit 2 +fi + +"$CMD" "$@" @@ -0,0 +1,176 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + vm ACTION [OS] + vm -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + ACTION one of: + - up + - down + - status + OS the name of the OS to be acted upon + + + Manage the state of known virtual machines. + + The VM QCOW2 images are stored under + $XDG_STATE_HOME/euandreh/qemu/, as "$OS.qcow2" files. The PIDs + of the running images are stored in individual files under + $XDG_RUNTIME_DIR/vm-pids/, as "$OS.pid" files. + + It also generates an SSH configuration file to bu `Included` + by the main $XDG_CONFIG_HOME/ssh/config file, which contains + one alias entry for each VM, so that one can do a combination + of `vm up alpine && ssh alpine`. + + + Examples: + + Start the VM for Alpine: + + $ vm up alpine + + + Stop the VM for Slackware, which was already down + + $ vm down slackware + The VM for "slackware" is not running, already. + + + List the available VMs, and their current state: + + $ vm status + alpine:up + slackware:down + freebsd:up + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +ACTION="${1:-}" +OS="${2:-}" + +eval "$(assert-arg "$ACTION" 'ACTION')" + + +VMS=' +alpine:60022 +' + +for vm in $VMS; do + NAME="$(echo "$vm" | cut -d: -f1)" + PORT="$(echo "$vm" | cut -d: -f2)" + cat <<-EOF + Host $NAME + HostName localhost + Port $PORT + + EOF +done > "$XDG_DATA_HOME"/euandreh/vm-ssh.conf + +PIDS_DIR="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/vm-pids" +PID_F="$PIDS_DIR/$OS.pid" +mkdir -p "$PIDS_DIR" + + +port_for_os() { + _OS="$1" + _PORT="$(echo "$VMS" | awk -F: -vOS="$_OS" '$1 == OS { print $2 }')" + if [ -z "$_PORT" ]; then + printf 'Unknown OS: "%s".\n\n' "$_OS" >&2 + usage >&2 + exit 2 + fi + printf '%s' "$_PORT" +} + +case "$ACTION" in + status) + echo "$VMS" | awk -F: '$0=$1' | while read -r vm; do + if [ -e "$PIDS_DIR/$vm.pid" ]; then + STATUS=up + else + STATUS=down + fi + printf '%s:%s\n' "$vm" "$STATUS" + done + ;; + up) + eval "$(assert-arg "$OS" 'OS')" + PORT="$(port_for_os "$OS")" + + if [ -e "$PID_F" ]; then + printf 'The VM for "%s" is already running with PID %s.\n' \ + "$OS" "$(cat "$PID_F")" >&2 + else + QCOW="$XDG_STATE_HOME"/euandreh/qemu/"$OS".qcow2 + qemu-system-x86_64 \ + -m 1G \ + -nic user,model=virtio,hostfwd=tcp::"$PORT"-:22 \ + -drive file="$QCOW",media=disk,if=virtio \ + -enable-kvm \ + -nographic 1>/dev/null 2>&1 & + printf '%s' $! > "$PID_F" + fi + ;; + down) + eval "$(assert-arg "$OS" 'OS')" + PORT="$(port_for_os "$OS")" + + if [ ! -e "$PID_F" ]; then + printf 'The VM for "%s" is not running, already.\n' "$OS" >&2 + else + # shellcheck disable=2064 + trap "rm -f $PID_F" EXIT + kill "$(cat "$PID_F")" + fi + ;; + *) + printf 'Unrecognized action: "%s".\n\n' "$ACTION" >&2 + usage >&2 + exit 2 +esac diff --git a/bin/volume b/bin/volume new file mode 100755 index 0000000..991fbc7 --- /dev/null +++ b/bin/volume @@ -0,0 +1,112 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + volume ACTION + volume -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + ACTION one of: + - up + - down + - toggle + - rotate + + + Manage the audio output. + + + Examples: + + Increase the volume: + + $ volume up + + + Toggle mute/unmute in the current audio output: + + $ volume toggle + + + Change the audio output: + + $ volume rotate + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) +ACTION="${1:-}" + +eval "$(assert-arg "$ACTION" 'ACTION')" + + +rotate() { + # This script assumes that at most 2 sinks exist at any time. + # When this premise is no longer true, it needs to be upgraded. + + CURRENT="$(pacmd list-sinks | grep '\* index' | cut -d: -f2 | tr -d ' ')" + OTHER="$(pacmd list-sinks | grep index | grep -v '\* index' | tail -n1 | cut -d: -f2 | tr -d ' ')" + + if [ "$CURRENT" = 0 ]; then + pacmd set-default-sink "$OTHER" + else + pacmd set-default-sink 0 + fi +} + +case "$ACTION" in + up) + pactl set-sink-volume @DEFAULT_SINK@ +10% + ;; + down) + pactl set-sink-volume @DEFAULT_SINK@ -10% + ;; + toggle) + pactl set-sink-mute @DEFAULT_SINK@ toggle + ;; + rotate) + rotate + ;; + *) + printf 'Bad ACTION: %s.\n\n' "$ACTION" >&2 + usage >&2 + exit 2 + ;; +esac diff --git a/bin/with-email b/bin/with-email new file mode 100755 index 0000000..7df101a --- /dev/null +++ b/bin/with-email @@ -0,0 +1,91 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + with-email [-s SUBJECT] COMMAND... + with-email -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -s SUBJECT set the subject of the email + -h, --help show this message + + COMMAND the command to be wrapped + + + Executes COMMAND and send all of its output via email + to eu@euandre.org. + + + Examples: + + Run a script and use the default subject: + + $ with-email -- ./script + + Run a command and use a custom subject: + + $ with-email -s 'CRONJOB' echo 123 + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +SUBJECT='NO SUBJECT' +while getopts 's:h' flag; do + case "$flag" in + s) + SUBJECT="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +eval "$(assert-arg "${1:-}" 'COMMAND...')" + +now() { + date '+%Y-%m-%dT%H:%M:%S%Z' +} + +OUT="$(mkstemp)" +{ + printf 'Running command: %s\n' "$*" + printf 'Starting at: %s\n' "$(now)" + printf '\n' + + STATUS=0 + "$@" || STATUS=$? + + printf '\n' + printf '\nFinished at: %s\n' "$(now)" +} 1>"$OUT" 2>&1 + +email -s "(exit status: $STATUS) - $SUBJECT" eu@euandre.org < "$OUT" diff --git a/bin/without-env b/bin/without-env new file mode 100755 index 0000000..fd9d1e8 --- /dev/null +++ b/bin/without-env @@ -0,0 +1,70 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + without-env ENVVAR PATH -- COMMAND... + without-env -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + Examples: + + Execute "command -V" filtering ~/bin, to get where "w3m" is + in $PATH, other than ~/bin: + $ without-env PATH ~/bin -- command -v w3m + + Compile foo.c, excluding ~/.local/include + from $C_INCLUDE_PATH: + $ without-env C_INCLUDE_PATH ~/.local/include -- cc -co foo.o foo.c + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +eval "$(assert-arg "${1:-}" 'ENVVAR')" +eval "$(assert-arg "${2:-}" 'PATH')" + +eval "export $1=\"\$(echo \"\$$1\" | sed \"s|\$2:||g\")\"" +shift # drop $1 +shift # drop $2 +if [ "${1:-}" = '--' ]; then + shift # drop -- +fi + +"$@" @@ -0,0 +1,95 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + wms ACTION + wms -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + ACTION one of: + - uuid + - date + - clear-notification + + + Helper script to launch CLI commands, without having complex + quoting, piping, flow control, etc. clutter the wm configuration + file. + + + Examples: + + Generate a new UUID, copy it to the clipboard and send a + desktop notification: + + $ wms uuid + EOF +} + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) +ACTION="${1:-}" + +eval "$(assert-arg "$ACTION" 'ACTION')" + + +copy_and_notify() { + STR="$1" + LABEL="$2" + printf '%s' "$STR" | copy -n + notify-send -t 5000 -u normal -- \ + "$STR" "$LABEL copied to clipboard" +} + +case "$ACTION" in + uuid) + copy_and_notify "$(uuid)" 'UUID' + ;; + date) + copy_and_notify "$(date '+%Y-%m-%d')" 'date' + ;; + clear-notification) + dunstctl close + ;; + *) + printf 'Bad ACTION: %s.\n\n' "$ACTION" >&2 + usage >&2 + exit 2 + ;; +esac @@ -0,0 +1,124 @@ +#!/usr/bin/env perl + +use v5.34; +use warnings; +use feature 'signatures'; +no warnings ('experimental::signatures', 'experimental::smartmatch'); +use Getopt::Std (); + +sub usage($fh) { + print $fh <<~'EOF' + Usage: + x COMMANDS... [ '&&' / '||' / '|' ] COMMANDS... + x COMMANDS... [ 'AND' / 'OR' / 'PIPE' ] COMMANDS... + x -h + EOF +} + +sub help($fh) { + print $fh <<~'EOF' + + Options: + -h, --help show this message + + COMMAND the command to be executed + '&&' / AND "AND" logical operator + '||' / OR "OR" logical operator + '|' / PIPE pipe operator + + + Run command chained together with operators. + + NOTE: Remember to quote '&&', 'OR' and '|' operators, otherwise + they'll get captured by the shell and not be passed to the 'x' + program! + + + Examples: + + Measure the time of two commands: + + $ time x sleep 1 '&&' sleep 2 + # equivalent to: + $ time sh -c 'sleep 1 && sleep 2' + + + Notify when either of the commands finish: + + $ boop x cmd-1 '||' cmd-2 + EOF +} + + +for (@ARGV) { + last if $_ eq '--'; + if ($_ eq '--help') { + usage *STDOUT; + help *STDOUT; + exit; + } +} + +my %opts; +if (!Getopt::Std::getopts('h', \%opts)) { + usage *STDERR; + exit 2; +} + +if ($opts{h}) { + usage *STDOUT; + help *STDOUT; + exit; +} + + +sub status_for($n) { + if ($n == -1) { + return 127; + } elsif ($n & 127) { + return $n & 127; + } else { + return $n >> 8; + } +} + +my @AND = ('&&', 'AND'); +my @OR = ('||', 'OR'); +my @PIPE = ('|', 'PIPE'); +my @OPS = (@AND, @OR, @PIPE); + +my @CMD; +for (@ARGV) { + if ($_ ~~ @OPS) { + system @CMD; + @CMD = (); + if ($_ ~~ @AND && $?) { + exit status_for($?); + } elsif ($_ ~~ @OR && !$?) { + exit 0; + } elsif ($_ ~~ @PIPE) { + ... + } + } else { + push @CMD, $_; + } +} + +exit status_for(system @CMD); + + +__END__ + +=head1 NAME + +x - chain shell commands without creating a subshell + +=head1 SYNOPSYS + +x COMMANDS... [ '&&' / '||' / '|' ] COMMANDS... + +x COMMANDS... [ 'AND' / 'OR' / 'PIPE' ] COMMANDS... + +x -h + +=cut diff --git a/bin/xdg-open b/bin/xdg-open new file mode 120000 index 0000000..ce4a72b --- /dev/null +++ b/bin/xdg-open @@ -0,0 +1 @@ +open
\ No newline at end of file diff --git a/bin/xmpp b/bin/xmpp new file mode 100755 index 0000000..b08a783 --- /dev/null +++ b/bin/xmpp @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +import os +import sys +import getopt +import logging +import slixmpp + +USAGE = """\ +Usage: + xmpp [-d] [-F FROM_JID] -m MESSAGE TO_JID... + xmpp -h""" + +HELP = """ +Options: + -d run in DEBUG mode + -m MESSAGE the text of the message to be sent + -h, --help show this message + + FROM_JID the address used to send the message from + TO_JID the addresses where to send the message to + + +Send a one-off XMPP message. + + +Examples: + + Send a message to eu@euandreh.xyz: + + $ xmpp -m 'Hello, XMPP!' eu@euandreh.xyz""" + +class SendMsgBot(slixmpp.ClientXMPP): + def __init__(self, jid, password, on_start): + slixmpp.ClientXMPP.__init__(self, jid, password) + + self.on_start = on_start + self.add_event_handler("session_start", self.start) + + def start(self, event): + self.on_start(self) + self.disconnect(wait=True) + +def main(): + logging.basicConfig(level=logging.INFO) + from_ = "bot@euandreh.xyz" + message = "" + + for s in sys.argv: + if s == "--": + break + elif s == "--help": + print(USAGE) + print(HELP) + sys.exit() + + try: + opts, args = getopt.getopt(sys.argv[1:], 'm:F:dh') + except getopt.GetoptError as err: + print(err, file=sys.stderr) + print(USAGE, file=sys.stderr) + sys.exit(2) + for o, a in opts: + if o == "-m": + message = a + elif o == "-F": + from_ = a + elif o == "-d": + logging.basicConfig(level=logging.DEBUG) + elif o == "-h": + print(USAGE) + print(HELP) + sys.exit() + else: + assert False, "unhandled option" + + if message == "": + print("Missing -m MESSAGE", file=sys.stderr) + print(USAGE, file=sys.stderr) + sys.exit(2) + + if args == []: + print("Missing TO_JID", file=sys.stderr) + print(USAGE, file=sys.stderr) + sys.exit(2) + + passcmd = "pass show VPS/kuvira/XMPP/" + from_ + " | head -n1 | tr -d '\\n'" + password = os.popen(passcmd).read() + + def on_start(self): + for to in args: + self.send_message(mto=to, mbody=message, mtype='chat') + + xmpp = SendMsgBot(from_, password, on_start) + xmpp.connect() + xmpp.process(forever=False) + +if __name__ == "__main__": + main() @@ -0,0 +1,113 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + yt [-f] [-n PLAYLIST_COUNT] [URL...|FILE...|-] + yt -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -n PLAYLIST_COUNT the number of videos to grab from a + playlist (default: 15) + -f force to download a video already in the + archive + -h, --help show this message + + URL an 'https://...' address + FILE a file with 'https://...' addresses, one + per line + + Download videos and store them locally. + + + Examples: + + Download the video from the given URL: + + $ yt https://www.youtube.com/watch?v=EihZv2XgJdU + + + Force re-download the latest 5 videos already downloaded: + + $ yt -nf5 https://www.youtube.com/watch?list=TLPQMTIwODIwMjLnL2XjyRRgSw + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +PLAYLIST_COUNT=15 +while getopts 'fn:h' flag; do + case "$flag" in + f) + FORCE=1 + ;; + n) + PLAYLIST_COUNT="$OPTARG" + ;; + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) +set -x + +if [ -z "${1:-}" ]; then + echo 'Missing URL|FILE argument' >&2 + usage >&2 + exit 2 +fi + + +if [ ! -e "$1" ]; then + F="$(mkstemp)" + printf '%s\n' "$1" > "$F" +else + F="$1" +fi + + +YT_TEMPLATE="$HOME/Downloads/yt-dl/%(uploader)s/%(upload_date)s %(title)s.%(ext)s" + +EXTRA_OPTIONS='' +if [ -z "${FORCE:-}" ]; then + EXTRA_OPTIONS="--download-archive $HOME/Documents/yt-dl-seen.txt" +fi + +# The value of $EXTRA_OPTIONS doesn't depend on user input, and can't contain +# spaces, unless $HOME contains spaces: +# shellcheck disable=2086 +youtube-dl \ + --batch-file "$F" \ + --format best \ + --prefer-free-formats \ + --playlist-end "$PLAYLIST_COUNT" \ + --write-description \ + --output "$YT_TEMPLATE" \ + $EXTRA_OPTIONS @@ -0,0 +1,153 @@ +#!/usr/bin/env perl + +use v5.34; +use warnings; +use feature 'signatures'; +no warnings qw(experimental::signatures); +use Getopt::Std (); +use File::Temp (); +use File::Basename (); +use List::Util qw(any); + + +sub usage($fh) { + print $fh <<~'EOF' + Usage: + z COMMANDS... + z -h + EOF +} + +sub help($fh) { + print $fh <<~'EOF' + + + Options: + -h, --help show this message + + + Wrapper that uncompresses file arguments to commands. + This enables having commands that operate on plain files to not + need to know if they're compressed or not. + + It doesn't depend on the file extension, but on what file(1) says + of it. + + + Examples: + + Replacement for zcat(1p): + + $ z cat a-file.gz + + + Transparent grep (where my-file.dat is xz compressed): + + $ z grep -E '[a-z]+' my-file.dat + EOF +} + +for (@ARGV) { + last if $_ eq '--'; + if ($_ eq '--help') { + usage *STDOUT; + help *STDOUT; + exit; + } +} + +my %opts; +if (!Getopt::Std::getopts('h', \%opts)) { + usage *STDERR; + exit 2; +} + +if ($opts{h}) { + usage *STDOUT; + help *STDOUT; + exit; +} + + +# Transform +# FILENAME: content/type; charset=some\n +# into +# content/type +sub trim($x) { + chomp $x; + $x =~ s/^.*?: //; + $x =~ s/;.*$//; + return $x; +} + +my %TYPES = ( + 'application/gzip' => [qw(gzip -dc)], + 'application/x-bzip2' => [qw(bzip2 -dc)], + 'application/x-xz' => [qw(xz -dc)], + 'application/x-lzma' => [qw(lzma -dc)], +); + +my @tmpfiles; +sub arg_for($arg) { + if (! -e $arg) { + return $arg; + } + + my $type = trim `file -i $_`; + if (any { $type eq $_ } keys %TYPES) { + my $template = File::Basename::basename $arg . '.XXXXXX'; + my ($fh, $tmpname) = File::Temp::tempfile(TEMPLATE => $template); + push @tmpfiles, $tmpname; + my @command = @{$TYPES{$type}}; + print $fh `@command $arg`; + die $! if $?; + close $fh; + return $tmpname; + } + + return $arg; +} + +sub status_for($n) { + if ($n == -1) { + return 127; + } elsif ($n & 127) { + return $n & 127; + } else { + return $n >> 8; + } +} + +my @CMD = map { arg_for $_ } @ARGV; +exit status_for(system @CMD); + +END { + unlink @tmpfiles; +} + + +__END__ + +=head1 NAME + +z - Wrapper that uncompresses file arguments to commands. + +=head1 SYNOPSIS + +z COMMAND FILE... + +=head1 DESCRIPTION + +Prefixing a shell command with "zrun" causes any compressed files that are +arguments of the command to be transparently uncompressed to temp files +(not pipes) and the uncompressed files fed to the command. + +This is a quick way to run a command that does not itself support +compressed files, without manually uncompressing the files. + +The following compression types are supported: gz bz2 Z xz lzma lzo + +If zrun is linked to some name beginning with z, like zprog, and the link is +executed, this is equivalent to executing "zrun prog". + +=cut diff --git a/etc/afew/config b/etc/afew/config new file mode 100644 index 0000000..94341e9 --- /dev/null +++ b/etc/afew/config @@ -0,0 +1,6 @@ +[SpamFilter] +[KillThreadsFilter] +[ListMailsFilter] +[ArchiveSentMailsFilter] +[MeFilter] +[InboxFilter] diff --git a/etc/bash/inputrc b/etc/bash/inputrc new file mode 100644 index 0000000..f5c7095 --- /dev/null +++ b/etc/bash/inputrc @@ -0,0 +1,2 @@ +"\e[B": history-search-forward +"\e[A": history-search-backward diff --git a/etc/git/config b/etc/git/config new file mode 100644 index 0000000..0ff1257 --- /dev/null +++ b/etc/git/config @@ -0,0 +1,25 @@ +[user] + email = eu@euandre.org + name = EuAndreh + signingkey = 81F90EC3CD356060 +[transfer] + fsckobjects = true +[push] + default = current +[commit] + gpgsign = true + verbose = true +[init] + defaultBranch = main +[sendemail] + assume8bitEncoding = UTF-8 + smtpserveroption = -a + smtpserveroption = EuAndreh + annotate = yes + confirm = never +[remote "origin"] + fetch = +refs/notes/*:refs/notes/* +[format] + useAutoBase = whenAble +[include] + path = config-extra diff --git a/etc/git/ignore b/etc/git/ignore new file mode 100644 index 0000000..9103c3d --- /dev/null +++ b/etc/git/ignore @@ -0,0 +1,3 @@ +/tmp/ +/FIXME +/vendor/ diff --git a/etc/gnupg/gpg-agent.conf.tmpl b/etc/gnupg/gpg-agent.conf.tmpl new file mode 100644 index 0000000..e772820 --- /dev/null +++ b/etc/gnupg/gpg-agent.conf.tmpl @@ -0,0 +1,6 @@ +# 2592000 = 60 * 60 * 24 * 30 +default-cache-ttl 172800 +default-cache-ttl-ssh 172800 +max-cache-ttl 2592000 +max-cache-ttl-ssh 2592000 +enable-ssh-support diff --git a/etc/gnupg/gpg.conf b/etc/gnupg/gpg.conf new file mode 100644 index 0000000..d4498fe --- /dev/null +++ b/etc/gnupg/gpg.conf @@ -0,0 +1 @@ +keyserver pool.sks-keyservers.net diff --git a/etc/gnupg/sshcontrol b/etc/gnupg/sshcontrol new file mode 100644 index 0000000..1ae03ed --- /dev/null +++ b/etc/gnupg/sshcontrol @@ -0,0 +1 @@ +750154E135FD7B11FDDF0107CC0904F92EBD2AE4 diff --git a/etc/guile/init.scm b/etc/guile/init.scm new file mode 100644 index 0000000..9e962e8 --- /dev/null +++ b/etc/guile/init.scm @@ -0,0 +1,6 @@ +(use-modules + (ice-9 colorized) + (ice-9 readline)) + +(activate-colorized) +(activate-readline) diff --git a/etc/guix/channels.scm b/etc/guix/channels.scm new file mode 100644 index 0000000..f2da084 --- /dev/null +++ b/etc/guix/channels.scm @@ -0,0 +1,20 @@ +(append + (list + (channel + (name 'xyz-euandreh) + (url "git://euandreh.xyz/package-repository") + (branch "main") + (introduction + (make-channel-introduction + "d749e053e6db365069cb9b2ef47a78b06f9e7361" + (openpgp-fingerprint + "5BDA E9B8 B2F6 C6BC BB0D 6CE5 81F9 0EC3 CD35 6060")))) + (channel + (name 'nonguix) + (url "https://gitlab.com/nonguix/nonguix") + (introduction + (make-channel-introduction + "897c1a470da759236cc11798f4e0a5f7d4d59fbc" + (openpgp-fingerprint + "2A39 3FFF 68F4 EF7A 3D29 12AF 6F51 20A0 22FB B2D5"))))) + %default-channels) diff --git a/etc/guix/home.scm b/etc/guix/home.scm new file mode 100644 index 0000000..fc9f2b5 --- /dev/null +++ b/etc/guix/home.scm @@ -0,0 +1,625 @@ +(use-modules + ((ice-9 textual-ports) #:prefix textual-ports:) + ((guix licenses) #:prefix licenses:) + ((xyz euandreh heredoc) #:prefix heredoc:) + (gnu home) + (gnu home services) + (gnu home services mcron) + (gnu home services shells) + (gnu home services shepherd) + (gnu home services xdg) + (gnu packages) + (gnu packages base) + (gnu packages dunst) + (gnu packages freedesktop) + (gnu packages gnupg) + (gnu packages gpodder) + (gnu packages libreoffice) + (gnu packages mail) + (gnu packages music) + (gnu packages wget) + (gnu packages lisp) + (gnu packages ssh) + (gnu packages texinfo) + (gnu packages tmux) + (gnu packages version-control) + (gnu packages video) + (gnu packages xdisorg) + (gnu services) + (guix build-system trivial) + (guix download) + (guix gexp) + (guix git-download) + (guix modules) + (guix packages) + (guix utils)) +(heredoc:enable-syntax) + +(define-public hunspell-iconv + (package + (inherit hunspell) + (name "hunspell-iconv") + (inputs + `(("libiconv" ,libiconv) + ,@(package-inputs hunspell))))) + +(define (hunspell-dictionary-utf8 dict-name) + (package + (name (string-append "hunspell-dict-" dict-name "-utf8")) + (version "630b34e6f8f3cbe7aa7b27b6d8ab118e27252fd1") + (source + (origin + (method git-fetch) + (uri + (git-reference + (url "https://github.com/wooorm/dictionaries") + (commit version))) + (file-name + (git-file-name "hunspell-dictionary-utf8" version)) + (sha256 + (base32 "1iknwzh5h04m067ddw9hlzc1qqj4mr9mdkcfapsnay304laaiyn5")))) + (build-system trivial-build-system) + (arguments + `(#:modules ((guix build utils)) + #:builder + (begin + (use-modules (guix build utils)) + (let ((source-prefix (string-append (assoc-ref %build-inputs "source") + "/dictionaries/" + ,dict-name + "/index")) + (install-prefix (string-append %output "/share/hunspell/"))) + (mkdir-p install-prefix) + (copy-file (string-append source-prefix ".aff") + (string-append install-prefix ,dict-name ".aff")) + (copy-file (string-append source-prefix ".dic") + (string-append install-prefix ,dict-name ".dic")))))) + (synopsis (string-append "Hunspell " dict-name " dictionary in UTF-8")) + (description (string-append "Hunspell " dict-name " dictionary in UTF-8")) + (license licenses:expat) + (home-page "https://github.com/wooorm/dictionaries"))) + +(define cmucl + (package + (name "cmucl-binary") + (version "21b") + (source + (origin + (method url-fetch) + (uri (string-append "https://common-lisp.net/project/cmucl/downloads/release/" + version + "/cmucl-" + version + "-x86-linux.tar.bz2")) + (sha256 + (base32 "13k3b5ygnbsq6n2i3r5i4ljw3r1qlskn2p5f4x9hrx6vfvbb3k7a")))) + (build-system trivial-build-system) + (arguments + `(#:modules ((guix build utils)) + #:builder + (begin + (use-modules (guix build utils)) + (let ((source (assoc-ref %build-inputs "source")) + (bin (string-append %output "/bin"))) + (mkdir-p bin) + (copy-file (string-append source "/bin/lisp") + (string-append bin "/lisp")))))) + (home-page "https://www.cons.org/cmucl/") + (synopsis "The CMU implementation of Common Lisp") + (description #"- + CMUCL is a free implementation of the Common Lisp programming language + which runs on most major Unix platforms. It mainly conforms to the + ANSI Common Lisp standard."#) + (license licenses:public-domain))) + +(define msmtp-non-hardcoded + (package + (inherit msmtp) + (name "msmtp-non-hardcoded") + (arguments + (substitute-keyword-arguments (package-arguments msmtp) + ((#:phases phases) + #~(modify-phases #$phases + (add-after 'install-additional-files 'patch-hardcoded-paths + (lambda* (#:key outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out"))) + (substitute* (string-append out "/bin/msmtpq") + (("LOG=.*$") "LOG=\"$XDG_LOG_HOME\"/msmtpq.log\n") + (("^Q=.*$") "Q=\"$XDG_DATA_HOME\"/msmtp/queue\n") + (("^MSMTPQ_Q=.*$") "MSMTPQ_Q=\"$Q\"\n") + (("mkdir -m 0700 \"\\$Q\"") "mkdir -p -m 0700 \"$Q\""))))))))))) + +(define gpodder-xdg + (package + (inherit gpodder) + (name "gpodder-xdg") + (arguments + (substitute-keyword-arguments (package-arguments gpodder) + ((#:phases phases '%standard-phases) + #~(modify-phases #$phases + (add-after 'install 'wrap-with-environment-variables + (lambda* (#:key outputs #:allow-other-keys) + (let ((out (assoc-ref outputs "out"))) + (for-each + (lambda (bin) + (wrap-program (string-append out "/bin/" bin) + '("GPODDER_HOME" = ("${XDG_DATA_HOME:-$HOME/.local/share}/gPodder")) + '("GPODDER_DOWNLOAD_DIR" = ("${XDG_DOWNLOAD_DIR:-$HOME/Downloads}/gPodder")))) + '("gpo" "gpodder"))))))))))) + +(define (with-options pkg bin opts) + (package + (inherit pkg) + (arguments + (substitute-keyword-arguments (package-arguments pkg) + ((#:phases phases '%standard-phases) + `(modify-phases ,phases + (add-after 'install 'wrap-with-flags + (lambda* (#:key outputs #:allow-other-keys) + (define (wrap-options prog options) + (let ((wrapped-file (string-append (dirname prog) "/." (basename prog) "-orig"))) + (rename-file prog wrapped-file) + (call-with-output-file prog + (lambda (port) + (format port + "#!/bin/sh~%~%exec \"~a\" ~a \"$@\"~%" + (canonicalize-path wrapped-file) + options))) + (chmod prog #o755))) + (wrap-options (string-append (assoc-ref outputs "out") + "/bin/" + ,bin) + ,opts))))))))) + +(define isync-with-options + (with-options isync "mbsync" "--config=\"$XDG_CONFIG_HOME\"/mbsync/config")) + +(define wget-with-options + (with-options wget "wget" "--hsts-file=\"$XDG_STATE_HOME\"/wget-hsts")) + +(define tmux-with-options + (with-options tmux "tmux" "-f \"$XDG_CONFIG_HOME\"/tmux/tmux.conf")) + +(define texinfo-with-options + (with-options texinfo "info" "--init-file \"$XDG_CONFIG_HOME\"/info/infokey")) + +(define mpv-with-options + (with-options mpv "mpv" (string-append "--script=" + (getenv "HOME") + "/.guix-home/profile/lib/mpris.so"))) + +(define openssh-with-options + (with-options openssh "ssh" "-F \"$XDG_CONFIG_HOME\"/ssh/config")) + +(define (xdg-config-home s) + (string-append (getenv "XDG_CONFIG_HOME") "/" s)) + + +(define (slurp name) + (string-trim-both + (call-with-input-file + name + textual-ports:get-string-all))) + +(define (script name content) + (package + (name name) + (version "latest") + (source #f) + (build-system trivial-build-system) + (arguments + `(#:modules ((guix build utils)) + #:builder + (begin + (use-modules (guix build utils)) + (let* ((bin (string-append %output "/bin")) + (prog (string-append bin "/" ,name))) + (mkdir-p bin) + (call-with-output-file prog + (lambda (port) + (format port "~a" ,content))) + (chmod prog #o755))))) + (home-page "") + (synopsis "") + (description "") + (license #f))) + +(define cronjobs + (list + #~(job "0 0 * * *" "cronjob msmtp-queue -r") + #~(job "0 0 * * *" "cronjob check") + #~(job "5 */6 * * *" "cronjob m") + #~(job "30 0 * * *" "cronjob x update AND upgrade") + #~(job "30 0 * * *" "cronjob backup -q cron"))) + +(define (home-service name package bin) + (service-type + (name name) + (extensions + (list + (service-extension home-shepherd-service-type + (lambda _ + (list + (shepherd-service + (provision (list name)) + (documentation + (format #f "Shepherd service that manages ~a." name)) + (start + #~(make-forkexec-constructor + (list #$(file-append package bin)))) + (stop #~(make-kill-destructor)))))) + (service-extension home-profile-service-type + (lambda _ (list package))))) + (default-value '()) + (description + (format #f + #"- + Service that runs ~a as a daemon under Shepherd. + + It has no configuration."# + name)))) + +(define xdg-prefix "$HOME/.usr") +(define (xdg path) + (string-append xdg-prefix "/" path)) + +(home-environment + (packages + (append + (map (compose list specification->package+output symbol->string) + '(nss-certs + bash + coreutils + findutils + diffutils + grep + sed + tar + gawk + bc + nvi + patchelf + + man-pages + man-pages-posix + + bash-completion + + git + git:send-email + git:gui + git-open + git-remote-gcrypt + git-lfs + mercurial + fossil + darcs + subversion + cvs + rcs + cssc + quilt + + gnupg + rsync + tree + diffoscope + mailutils + entr + pulseaudio + password-store + playerctl + pinentry-gtk2 + bmake + make + tup + autoconf + automake + libtool + pcre:out + pcre:bin + pcre:doc + pcre:static + pcre2 + avahi + libgcrypt + qbe + cproc + doxygen + gperf + readline + gmp + help2man + libtomcrypt + libtommath + lz4 + lokke + meson + ninja + sparse + ant + mpc + maven + pkg-config + fzf + ranger + blueman + pavucontrol + ledger + bind:utils + stunnel + netcat + siege + curl + curl:doc + xclip + cloc + strace + file@5.39 + urlscan + rlwrap + direnv + borg + khal + khard + libfaketime + qrencode + feh + sox + xset + graphviz + moreutils + shellcheck + gettext + lilypond + groff + groff:doc + grap + ghostscript + texlive + jq + recutils + units + ncurses + trash-cli + lsof + autojump + unzip + powertop + md4c + timidity++ + cmark + cmake + makefile2graph + po4a + mdpo + universal-ctags + gron + reptyr + xpdf + perf-tools + scdoc + rpm + cpio + + ;; for compiling ECL + libatomic-ops + libgc + libffi + ;; for compiling CLISP + libffcall + libsigsegv + + cryptsetup + btrfs-progs + + flatpak + xdg-desktop-portal + + sqlite + clojure + clojure-tools + leiningen + openjdk + perl + perl-dbi + perl-dbd-sqlite + perl-critic + perl-json + perl-mojolicious + perl-regexp-grammars + perl-commonmark + perl-aliased + perl-uri-escape + ruby + python + python-sphinx + python-slixmpp + python-unidecode + python-yubikey-manager + python-coverage + python-pytest + python-requests + python-beautifulsoup4 + python-docx + python-telegram-bot + ; python-docutils ; broken: conflicts with python-sphinx + python-mkdocs + valgrind + flex + bison + gcc-toolchain + clang + tcc + fuse + ; node ; broken: conflicts with archivebox + quickjs + m4 + go + xrandr + arandr + openssl + fswatch + ;; rust ; broken + ;; rust:cargo ; broken + ;; rust:rustfmt ; broken + vala + tcl + + sbcl + gcl + ecl + clisp + ccl + abcl + janet + kawa + chez-scheme + racket + chibi-scheme + ; chicken ; broken: conflicts with gcc-toolchain + gambit-c + gauche + + dash + fish + rc + es + tcsh + zsh + oksh + loksh + mksh + oil + gash + nushell + + gtk + gtk:bin + gtk:doc + glade + ; libglade ; broken: conflicts with zathura + cambalache + tk + qtbase + qtbase:debug + qtdeclarative + + st + i3status + xmessage + dmenu + httpd ;; for htpasswd + + weechat + alot + notmuch + w3m + afew + qtox + telescope + imagemagick + ffmpeg + pandoc + mktorrent + jekyll + flac + mediainfo + libnotify + espeak-ng + procps + htop + zenity + util-linux + lightning + lmdb + guile + guile-heredoc-latest + gzip + xz + bzip2 + lzip + lzop + which + libxml2 + psmisc + less + nano + patch + youtube-dl + tmux-plugin-resurrect + tmux-plugin-continuum + + ;; ArchiveBox and some of its optional dependencies + archivebox + ripgrep + + poezio + freetalk + mcabber + profanity + newsboat + mpv-mpris + vlc + hicolor-icon-theme + + keepassxc + + xbacklight + + gnote + telegram-desktop + zathura + zathura-djvu + zathura-pdf-poppler + zathura-ps + dino + poedit + transmission + transmission:gui + audacity + inkscape + libreoffice + quodlibet + ungoogled-chromium + icedove + firefox)) + (list msmtp-non-hardcoded + ;; cmucl + isync-with-options + wget-with-options + tmux-with-options + texinfo-with-options + mpv-with-options + openssh-with-options + hunspell-iconv + gpodder-xdg + (hunspell-dictionary-utf8 "en") + (hunspell-dictionary-utf8 "pt") + (hunspell-dictionary-utf8 "fr") + (hunspell-dictionary-utf8 "eo") + (script "cronjob" (slurp (string-append (getenv "XDG_CONFIG_HOME") + "/sh/cronjob.sh")))))) + (services + (list + (service (home-service 'clipmenu clipmenu "/bin/clipmenud")) + (service (home-service 'dunst dunst "/bin/dunst")) + (service (home-service 'poweralertd poweralertd "/bin/poweralertd")) + (service home-xdg-base-directories-service-type + (home-xdg-base-directories-configuration + (cache-home (xdg "var/cache")) + (config-home (xdg "etc")) + (data-home (xdg "share")) + (log-home (xdg "var/log")) + (state-home (xdg "var/state")))) + (simple-service 'my-shell-profile home-shell-profile-service-type + (list (plain-file + "my-profile" + (format #f + #"- + export XDG_PREFIX="~a" + . "$XDG_CONFIG_HOME"/sh/rc"# + xdg-prefix)))) + (service home-mcron-service-type + (home-mcron-configuration + (jobs cronjobs)))))) diff --git a/etc/guix/system.scm b/etc/guix/system.scm new file mode 100644 index 0000000..fddadca --- /dev/null +++ b/etc/guix/system.scm @@ -0,0 +1,209 @@ +(use-modules + ((xyz euandreh heredoc) #:prefix heredoc:) + (gnu bootloader) + (gnu bootloader grub) + (gnu packages) + (gnu services base) + (gnu services cups) + (gnu services desktop) + (gnu services docker) + (gnu services pm) + (gnu services security-token) + (gnu services sound) + (gnu services ssh) + (gnu services virtualization) + (gnu services vpn) + (gnu services xorg) + (gnu system keyboard) + (gnu system file-systems) + (gnu system locale) + (gnu system mapped-devices) + (guix gexp) + (guix packages) + (nongnu packages linux) + (nongnu system linux-initrd) + (srfi srfi-1) + (xyz euandreh queue)) +(heredoc:enable-syntax) + +(operating-system + (kernel linux) + (initrd microcode-initrd) + (firmware (list linux-firmware)) + (locale "fr_FR.UTF-8") + (locale-definitions + (append + (list + (locale-definition + (name "pt_BR.UTF-8") + (source "pt_BR"))) + %default-locale-definitions)) + (timezone "America/Sao_Paulo") + (keyboard-layout + (keyboard-layout "br" #:options '("caps:swapescape" "esperanto:qwerty"))) + (host-name "velhinho") + (hosts-file + (plain-file + "hosts" + (format #f + #"- + 127.0.0.1 localhost ~a + ::1 localhost ~a + + 10.0.2.1 kuvira.wg + 10.0.2.2 velhinho.wg + "# + host-name + host-name))) + (users + (append + (list + (user-account + (name "andreh") + (comment "EuAndreh") + (group "users") + (supplementary-groups '("netdev" "audio" "video" "wheel" "kvm" "docker")))) + %base-user-accounts)) + (packages + (append + (map (compose list specification->package+output symbol->string) + '(nss-certs + i3-wm + guile + guile-heredoc-latest)) + (list) + (remove (lambda (package) + (equal? "wget" (package-name package))) + %base-packages))) + (services + (append + (list + (service bluetooth-service-type) + (service tlp-service-type) + (service thermald-service-type) + (service pcscd-service-type) + (service docker-service-type) + (service libvirt-service-type) + (service virtlog-service-type) + (service xfce-desktop-service-type) + (service mate-desktop-service-type) + (service lxqt-desktop-service-type) + (service enlightenment-desktop-service-type) + (service gnome-desktop-service-type) + (service gnome-keyring-service-type) + (service wireguard-service-type + (wireguard-configuration + (addresses '("10.0.2.2/24")) + (peers + (list + (wireguard-peer + (name "kuvira") + (endpoint "euandreh.xyz:51820") + (public-key "FwXqY9wXO8jK/D7lyprI+cslVeb9AqOQBAbxKG6S5lE=") + (allowed-ips '("10.0.2.1/32")) + (keep-alive 25)))))) + (service qemu-binfmt-service-type + (qemu-binfmt-configuration + (platforms + (lookup-qemu-platforms "arm" "aarch64")))) + (service cups-service-type + (cups-configuration + (web-interface? #t) + (extensions + (list epson-L365)))) + (service openssh-service-type + (openssh-configuration + (password-authentication? #f) + (authorized-keys + `(("andreh" ,(local-file + (string-append (or (getenv "XDG_CONFIG_HOME") + (string-append (getenv "HOME") "/.ssh")) + "/ssh/id_rsa.pub"))))) + (extra-content #"- + ClientAliveInterval 30 + ClientAliveCountMax 20 + MaxSessions 20 + "#))) + #; + (udev-rules-service + 'backlight + (udev-rule + "backlight.rule" + (string-replace + #"- + ACTION=="add", SUBSYSTEM=="backlight", KERNEL=="@DEVICE@", GROUP="video", MODE="0664" + "# + "@DEVICE@" + (getenv "BACKLIGHT_DEVICE")))) + (set-xorg-configuration + (xorg-configuration + (keyboard-layout keyboard-layout) + (extra-config + (list + #"- + Section "InputClass" + Identifier "touchpad" + Driver "libinput" + MatchIsTouchpad "on" + Option "Tapping" "on" + EndSection + Section "Device" + Identifier "Intel Graphics" + Driver "intel" + Option "Backlight" "intel_backlight" + EndSection + "#))))) + (modify-services %desktop-services + (pulseaudio-service-type config => + (pulseaudio-configuration + (inherit config) + (extra-script-files + (list + (plain-file + "noise-cancelling.pa" + #"- + load-module module-echo-cancel + "#))))) + (guix-service-type config => + (guix-configuration + (inherit config) + (substitute-urls + (append + '("https://substitutes.nonguix.org") + %default-substitute-urls)) + (authorized-keys + (append + (list + (plain-file + "non-guix.pub" + #"- + (public-key + (ecc + (curve Ed25519) + (q #C1FD53E5D4CE971933EC50C9F307AE2171A2D3B52C804642A7A35F84F3A4EA98#))) + "#)) + %default-authorized-guix-keys))))))) + (bootloader + (bootloader-configuration + (bootloader grub-efi-bootloader) + (targets '("/boot/efi")) + (keyboard-layout keyboard-layout))) + (mapped-devices + (list + (mapped-device + (source (uuid "6b0d38a6-d93e-4f8e-a59a-7729f5adf892")) + (target "cryptroot") + (type luks-device-mapping)))) + (file-systems + (append + (list + (file-system + (mount-point "/boot/efi") + (device (uuid "1B26-9F4E" 'fat32)) + (type "vfat")) + (file-system + (mount-point "/") + (device "/dev/mapper/cryptroot") + (type "ext4") + (dependencies mapped-devices))) + %base-file-systems))) diff --git a/etc/hg/hgrc b/etc/hg/hgrc new file mode 100644 index 0000000..656f40b --- /dev/null +++ b/etc/hg/hgrc @@ -0,0 +1,2 @@ +[ui] +username = EuAndreh <eu@euandre.org> diff --git a/etc/i3/config b/etc/i3/config new file mode 100644 index 0000000..94648b2 --- /dev/null +++ b/etc/i3/config @@ -0,0 +1,185 @@ +set $mod Mod4 + + +# font pango:monospace 8 + +# This font is widely installed, provides lots of unicode glyphs, right-to-left +# text rendering and scalability on retina/hidpi displays (thanks to pango). +font pango:DejaVu Sans Mono 8 + +# Start XDG autostart .desktop files using dex. See also +# https://wiki.archlinux.org/index.php/XDG_Autostart +# exec --no-startup-id dex --autostart --environment i3 + +# The combination of xss-lock, nm-applet and pactl is a popular choice, so +# they are included here as an example. Modify as you see fit. + +# xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the +# screen before suspend. Use loginctl lock-session to lock your screen. +exec xss-lock --transfer-sleep-lock -- i3lock --nofork + +# NetworkManager is the most popular way to manage wireless networks on Linux, +# and nm-applet is a desktop environment-independent system tray GUI for it. +exec nm-applet +exec blueman-applet + +# Use pactl to adjust volume in PulseAudio. +set $refresh_i3status killall -SIGUSR1 i3status +bindsym F1 exec volume toggle && $refresh_i3status +bindsym F2 exec volume down && $refresh_i3status +bindsym F3 exec volume up && $refresh_i3status +bindsym $mod+Shift+s exec volume rotate && $refresh_i3status + +bindsym F4 exec player backward +bindsym Shift+F4 exec player previous +bindsym F5 exec player play-pause +Bindsym Shift+F5 exec player rotate +bindsym F6 exec player forward +bindsym Shift+F6 exec player next + +bindsym F7 exec brightness -1 +bindsym F8 exec brightness +1 + + + + + +# Use Mouse+$mod to drag floating windows to their wanted position +floating_modifier $mod + +bindsym $mod+Return exec st +bindsym $mod+Shift+q kill + + + + +bindsym $mod+u exec wms uuid +bindsym $mod+t exec wms date +bindsym $mod+m exec wms clear-notification +bindsym $mod+p exec menu bin +bindsym $mod+o exec menu emoji +bindsym $mod+v exec menu clipboard +bindsym $mod+i exec menu password +bindsym $mod+Control+i exec menu username +bindsym $mod+Control+Shift+i exec menu yubikey + + + + +# change focus +bindsym $mod+h focus left +bindsym $mod+j focus down +bindsym $mod+k focus up +bindsym $mod+l focus right + +# move focused window +bindsym $mod+Shift+h move left +bindsym $mod+Shift+j move down +bindsym $mod+Shift+k move up +bindsym $mod+Shift+l move right + +# bindsym $mod+h split h # what is this? + +# split in vertical orientation +bindsym $mod+Shift+v split v + +# enter fullscreen mode for the focused container +bindsym $mod+f fullscreen toggle + +# change container layout (stacked, tabbed, toggle split) +bindsym $mod+s layout stacking +bindsym $mod+w layout tabbed +bindsym $mod+e layout toggle split + +# toggle tiling / floating +bindsym $mod+Shift+space floating toggle + +# change focus between tiling / floating windows +bindsym $mod+space focus mode_toggle + +# focus the parent container +bindsym $mod+a focus parent + +# focus the child container +bindsym $mod+d focus child + +# Define names for default workspaces for which we configure key bindings later on. +# We use variables to avoid repeating the names in multiple places. +set $ws1 "1" +set $ws2 "2" +set $ws3 "3" +set $ws4 "4" +set $ws5 "5" +set $ws6 "6" +set $ws7 "7" +set $ws8 "8" +set $ws9 "9" +set $ws10 "10" + +# switch to workspace +bindsym $mod+1 workspace number $ws1 +bindsym $mod+2 workspace number $ws2 +bindsym $mod+3 workspace number $ws3 +bindsym $mod+4 workspace number $ws4 +bindsym $mod+5 workspace number $ws5 +bindsym $mod+6 workspace number $ws6 +bindsym $mod+7 workspace number $ws7 +bindsym $mod+8 workspace number $ws8 +bindsym $mod+9 workspace number $ws9 +bindsym $mod+0 workspace number $ws10 + +# move focused container to workspace +bindsym $mod+Shift+1 move container to workspace number $ws1 +bindsym $mod+Shift+2 move container to workspace number $ws2 +bindsym $mod+Shift+3 move container to workspace number $ws3 +bindsym $mod+Shift+4 move container to workspace number $ws4 +bindsym $mod+Shift+5 move container to workspace number $ws5 +bindsym $mod+Shift+6 move container to workspace number $ws6 +bindsym $mod+Shift+7 move container to workspace number $ws7 +bindsym $mod+Shift+8 move container to workspace number $ws8 +bindsym $mod+Shift+9 move container to workspace number $ws9 +bindsym $mod+Shift+0 move container to workspace number $ws10 + +# reload the configuration file +bindsym $mod+Shift+c reload +# restart i3 inplace (preserves your layout/session, can be used to upgrade i3) +bindsym $mod+Shift+r restart +# exit i3 (logs you out of your X session) +bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'" + +# resize window (you can also use the mouse for that) +mode "resize" { + # These bindings trigger as soon as you enter the resize mode + + # Pressing left will shrink the window’s width. + # Pressing right will grow the window’s width. + # Pressing up will shrink the window’s height. + # Pressing down will grow the window’s height. + bindsym h resize shrink width 10 px or 10 ppt + bindsym j resize grow height 10 px or 10 ppt + bindsym k resize shrink height 10 px or 10 ppt + bindsym l resize grow width 10 px or 10 ppt + + # same bindings, but for the arrow keys + # bindsym Left resize shrink width 10 px or 10 ppt + # bindsym Down resize grow height 10 px or 10 ppt + # bindsym Up resize shrink height 10 px or 10 ppt + # bindsym Right resize grow width 10 px or 10 ppt + + # back to normal: Enter or Escape or $mod+r + bindsym Return mode "default" + bindsym Caps_Lock mode "default" + bindsym $mod+r mode "default" +} + +bindsym $mod+r mode "resize" + +# Start i3bar to display a workspace bar (plus the system information i3status +# finds out, if available) +bar { + position top + status_command i3status | status-bar +} + +default_border pixel +default_floating_border pixel diff --git a/etc/i3status/config b/etc/i3status/config new file mode 100644 index 0000000..8099a2f --- /dev/null +++ b/etc/i3status/config @@ -0,0 +1,31 @@ +general { + output_format = "i3bar" + colors = true +} + +order += "wireless _first_" +order += "battery all" +order += "disk /" +order += "memory" +order += "tztime local" + +wireless _first_ { + format_up = "%essid ~%quality" +} + +battery all { + format = "bat: %status %percentage %remaining" +} + +disk "/" { + format = "disk: %avail" +} + +memory { + format = "mem: %used/%total" + threshold_degraded = "1G" +} + +tztime local { + format = "%A, %Y-%m-%d %H:%M:%S" +} diff --git a/etc/info/infokey b/etc/info/infokey new file mode 100644 index 0000000..90edc50 --- /dev/null +++ b/etc/info/infokey @@ -0,0 +1,7 @@ +^e down-line +^y up-line + +#var +link-style=cyan +active-link-style=blue,bold +match-style=underline,bold,bggreen diff --git a/etc/khal/config b/etc/khal/config new file mode 100644 index 0000000..f42a87b --- /dev/null +++ b/etc/khal/config @@ -0,0 +1,16 @@ +[calendars] + +[[private]] +path = $XDG_DATA_HOME/khal/calendars/private/ +type = calendar + +[locale] +timeformat = %H:%M +dateformat = %d/%m/%Y +longdateformat = %d/%m/%Y +datetimeformat = %d/%m/%Y %H:%M +longdatetimeformat = %d/%m/%Y %H:%M +default_timezone = America/Sao_Paulo + +[default] +default_calendar = private diff --git a/etc/khard/khard.conf b/etc/khard/khard.conf new file mode 100644 index 0000000..e6ef986 --- /dev/null +++ b/etc/khard/khard.conf @@ -0,0 +1,13 @@ +[addressbooks] + +[[private]] +path = $XDG_DATA_HOME/khard/contacts/private/ + +[general] +default_action = list + +[contact table] +show_nicknames = yes + +[vcard] +preferred_version = 4.0 diff --git a/etc/lisp-cli/init.lisp b/etc/lisp-cli/init.lisp new file mode 100644 index 0000000..69cb054 --- /dev/null +++ b/etc/lisp-cli/init.lisp @@ -0,0 +1,33 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "dev/quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(setf ql:*quickload-verbose* t) + +(defun load-once (p) + (let ((k (intern + (concatenate 'string + (string :ql/) + (string p)) + "KEYWORD"))) + (unless (member k *features*) + (ql:quickload p :verbose t) + (pushnew k *features*)) + k)) + +(mapcar #'load-once + (list + :cl-ppcre + :cffi + :trivial-dump-core + :named-readtables + :rstring)) + +(mapcar (lambda (p) + (pushnew (concatenate 'string p "/") cffi:*foreign-library-directories* + :test #'equal)) + (cl-ppcre:split ":" (uiop:getenv "LIBRARY_PATH"))) + +(load-once :cl-fswatch) diff --git a/etc/mailcaps/config b/etc/mailcaps/config new file mode 100644 index 0000000..60f7286 --- /dev/null +++ b/etc/mailcaps/config @@ -0,0 +1 @@ +text/html; env HOME=$XDG_DATA_HOME/w3m w3m -dump -o document_charset=%{charset} '%s'; nametemplate=%s.html; copiousoutput diff --git a/etc/newsboat/config b/etc/newsboat/config new file mode 100644 index 0000000..b138e1d --- /dev/null +++ b/etc/newsboat/config @@ -0,0 +1 @@ +text-width 80 diff --git a/etc/newsboat/urls b/etc/newsboat/urls new file mode 120000 index 0000000..c7c1b2d --- /dev/null +++ b/etc/newsboat/urls @@ -0,0 +1 @@ +../../var/lib/private/tilde/newsboat/urls
\ No newline at end of file diff --git a/etc/python/pythonrc.py b/etc/python/pythonrc.py new file mode 100644 index 0000000..1fc3bfe --- /dev/null +++ b/etc/python/pythonrc.py @@ -0,0 +1,15 @@ +import os +import atexit +import readline + +history = os.path.join(os.environ["XDG_STATE_HOME"], "python-history") + +try: + readline.read_history_file(history) +except OSError: + pass + +def write_history(): + readline.write_history_file(history) + +atexit.register(write_history) diff --git a/etc/ranger/rc.conf b/etc/ranger/rc.conf new file mode 100644 index 0000000..2f1d9c9 --- /dev/null +++ b/etc/ranger/rc.conf @@ -0,0 +1,4 @@ +map DD shell trash %s +map XX shell rm -rf %s +map <C-e> scroll_preview 1 +map <C-y> scroll_preview -1 diff --git a/etc/remhind/config.tmpl b/etc/remhind/config.tmpl new file mode 100644 index 0000000..4d74b20 --- /dev/null +++ b/etc/remhind/config.tmpl @@ -0,0 +1,4 @@ +[calendars] + [calendars.private_cal] + name = "private_cal" + path = "${XDG_DATA_HOME}/khal/calendars/private/" diff --git a/etc/sh/cronjob.sh b/etc/sh/cronjob.sh new file mode 100755 index 0000000..0569b44 --- /dev/null +++ b/etc/sh/cronjob.sh @@ -0,0 +1,74 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + cronjob COMMAND... + cronjob -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + COMMAND the command to be executed + + + Execute the given command, and send the output to email, with + special treatment to the status code. + + It loads the appropriate files, so that the actual cron + declaration is smaller. + + + Examples: + + Run a backup: + + $ cronjob backup -q cron + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + + +set +e +# shellcheck disable=1090 +. ~/.profile +set -e + +CMD="$*" +with-email -s "$CMD" -- "$@" 1>>"$XDG_LOG_HOME"/euandreh/mcron.log 2>&1 diff --git a/etc/sh/privrc.sh b/etc/sh/privrc.sh new file mode 120000 index 0000000..f946f34 --- /dev/null +++ b/etc/sh/privrc.sh @@ -0,0 +1 @@ +../../var/lib/private/tilde/privrc.sh
\ No newline at end of file diff --git a/etc/sh/rc b/etc/sh/rc new file mode 100644 index 0000000..ae552a6 --- /dev/null +++ b/etc/sh/rc @@ -0,0 +1,364 @@ +#!/bin/sh +# shellcheck disable=1090,1091 + +export ENV=~/.profile + +export XDG_DATA_DIRS="$XDG_DATA_HOME/flatpak/exports/share:/var/lib/flatpak/exports/share${XDG_DATA_DIRS:+:}${XDG_DATA_DIRS:-}" + + +mkdir -p \ + "$XDG_CONFIG_HOME" \ + "$XDG_CACHE_HOME/euandreh" \ + "$XDG_DATA_HOME/euandreh" \ + "$XDG_STATE_HOME/euandreh" \ + "$XDG_LOG_HOME/euandreh" + +GUIX_PROFILE="$XDG_CONFIG_HOME"/guix/current +if [ -r "$GUIX_PROFILE"/etc/profile ]; then + . "$GUIX_PROFILE"/etc/profile +fi + +idempotent_path_add() { + case "$(eval "echo \$$1")" in + *"$2"*) + ;; + *) + eval "export $1=$2\${$1:+:}\${$1:-}" + ;; + esac +} +export A="$HOME${A:+:}${A:-}" +# idempotent_path_add B "$HOME" +export XDG_DATA_DIRS="$XDG_DATA_HOME/flatpak/exports/share:/var/lib/flatpak/exports/share${XDG_DATA_DIRS:+:}${XDG_DATA_DIRS:-}" +# idempotent_path_add XDG_DATA_DIRS "$XDG_DATA_HOME/flatpak/exports/share" + + + +# +# +# + +export HISTSIZE=-1 +export HISTFILE="$XDG_STATE_HOME/sh-history" +export SRC=~/dev +export PRIV_CONFIG="$XDG_PREFIX"/var/lib/private/tilde +export EDITOR='e' +export VISUAL="$EDITOR" +export PAGER='less -R' +export BROWSER='firefox' +export MAILDIR=~/Maildir +export BACKLIGHT_DEVICE='acpi_video0' + +export GUILE_HISTORY="$XDG_STATE_HOME/guile-history" +export GNUPGHOME="$XDG_CONFIG_HOME/gnupg" +export RLWRAP_HOME="$XDG_CACHE_HOME/rlwrap" +export LESSHISTFILE="$XDG_STATE_HOME/lesshst" +export NOTMUCH_CONFIG="$XDG_CONFIG_HOME/notmuch/default/config" +export PASSWORD_STORE_DIR="$SRC/private/password-store" +export MAILCAPS="$XDG_CONFIG_HOME/mailcaps/config" +export PYTHONSTARTUP="$XDG_CONFIG_HOME/python/pythonrc.py" +export EXINIT=' + " set number + " set autoindent + set ruler + set showmode + set showmatch +' + +HOSTNAME="$(hostname)" +export BORG_PASSCOMMAND="pass show $HOSTNAME/borg/passphrase" +export BORG_REPO="suyin:borg/$HOSTNAME" +export BORG_REMOTE_PATH='borg1' + +N_PROCS=$(($(nproc) * 2 + 1)) +GUILE_EFFECTIVE_VERSION="$(guile -c '(display (effective-version))')" +export MAKEFLAGS="-j $N_PROCS" +export GOPATH="$SRC/go" +export CFLAGS='-std=c99 -Wall -Wextra -Wpedantic -g -flto -Werror' +export CC=gcc +export AR=gcc-ar +export LEX=flex +export LDFLAGS='-flto' +export LISP='sbcl --eval' +export N_PROCS GUILE_EFFECTIVE_VERSION +export LISP_CLI_IMPL=clisp + +add_prefix() { + export GUILE_LOAD_PATH="$1/share/guile/site/$GUILE_EFFECTIVE_VERSION${GUILE_LOAD_PATH:+:}${GUILE_LOAD_PATH:-:}" + export GUILE_LOAD_COMPILED_PATH="$1/lib/guile/$GUILE_EFFECTIVE_VERSION/site-ccache${GUILE_LOAD_COMPILED_PATH:+:}${GUILE_LOAD_COMPILED_PATH:-}" + export C_INCLUDE_PATH="$1/include${C_INCLUDE_PATH:+:}${C_INCLUDE_PATH:-}" + export LIBRARY_PATH="$1/lib${LIBRARY_PATH:+:}${LIBRARY_PATH:-}" + export INFOPATH="$1/share/info${INFOPATH:+:}${INFOPATH:-}" + export MANPATH="$1/share/man${MANPATH:+:}${MANPATH:-}" + export DICPATH="$1/share/hunspell${DICPATH:+:}${DICPATH:-}" + export PATH="$1/bin${PATH:+:}${PATH:-}" +} +export PREFIX="$XDG_PREFIX/var/mkg" +add_prefix "$PREFIX" +add_prefix "$XDG_PREFIX" + +for d in "$XDG_PREFIX"/opt/bin-dirs/*; do + PATH="$d:$PATH" +done + + + +# +# Aliases +# + +unalias -a + +alias p='ping euandre.org -c 3' +alias c='tmux send-keys -R \; clear-history' +alias o='open' +alias mm='msmtp-queue -r' +alias s='vcs status' +alias d='vcs diff' +alias ds='vcs diff -- --staged' +alias tpd='cd "$(mkdtemp)"' + +alias l='ls -lahF --color' +alias grep='grep --color=auto' +alias diff='diff --color=auto' +alias watch='watch --color ' +alias man='MANWIDTH=$((COLUMNS > 80 ? 80 : COLUMNS)) man' +alias less='less -R' +alias tree='tree -aC' +alias make='make -e' +alias mv='mv -i' +alias vi='echo "Use \"e\" instead."; false' +alias bc='bc -l' + +alias sqlite='rlwrap sqlite3' +alias guile='guile -l "$XDG_CONFIG_HOME"/guile/init.scm' + +alias flush='sync && echo 3 | sudo tee /proc/sys/vm/drop_caches > /dev/null' +alias kal='khal calendar today `LANG=en.UTF-8 date +%A`' + + + +# +# PS1 +# + +error_marker() { + STATUS=$? + if [ "$STATUS" != 0 ]; then + color -c redb " (!! $STATUS !!) " + fi +} + +shell_status_level() { + if [ -z "${SHLVL:-}" ]; then + return + fi + + if [ -n "${TMUX:-}" ]; then + LVL=$((SHLVL - 1)) + else + LVL="$SHLVL" + fi + + if [ "$LVL" = 1 ]; then + return + fi + + color -c white "$LVL" + if [ -n "${RANGER_LEVEL:-}" ]; then + color -c white '|' + color -c bluei 'r' + fi +} + +shell_status_jobs() { + JOBS="$(jobs | grep -cv autojump)" + if [ "$JOBS" != 0 ]; then + color -c red "$JOBS" + fi +} + +shell_status() { + LEVEL="$(shell_status_level)" + JOBS="$(shell_status_jobs)" + + if [ -z "$LEVEL" ] && [ -z "$JOBS" ]; then + return + fi + + color -c white '[' + printf '%s' "$LEVEL" + if [ -n "$LEVEL" ] && [ -n "$JOBS" ]; then + color -c white '|' + fi + printf '%s' "$JOBS" + color -c white ']' + printf ' ' +} + +timestamp() { + color -c blacki '\T' +} + +path() { + color -c yellowb '\w/' +} + +guix_env() { + if [ -z "${GUIX_ENVIRONMENT:-}" ]; then + return + fi + + printf '\n' + color -c blacki '~> ' + color -c purple 'guix environment ' + printf '(' + color -c blueb "$GUIX_ENVIRONMENT" + printf ')' +} + +in_nix_shell() { + if [ -z "${IN_NIX_SHELL:-}" ]; then + return + fi + + printf '\n' + color -c blacki '~> ' + color -c purpleb "$IN_NIX_SHELL " + color -c purple 'nix-shell ' + printf '(' + color -c blueb "${name:-}" + printf ')' +} + +PS1='`error_marker`'$(timestamp)' '$(path)' `shell_status``vcs ps1``guix_env``in_nix_shell` +$ ' + + + +# g '^\w.*json_destroy(' +g() { + # shellcheck disable=2086 + fn=$(git grep -n -- "$1" ${2:-} | \ + cut -d: -f -2 | \ + fzf --select-1 \ + --exit-0 \ + --preview "echo {} | \ + cut -d: -f1 | \ + xargs -I% awk -v bounds=25 -v pat=\"$1\" -v n=\$(echo {} | cut -d: -f2) ' + (n - bounds < NR) && (NR < n + bounds) && (NR != n) { print } + NR==n { gsub(pat, \"\033[1;33m&\033[1;000m\"); print } + ' %") + if [ -n "$fn" ]; then + f="$(echo "$fn" | cut -d: -f1)" + n="$(echo "$fn" | cut -d: -f2)" + # shellcheck disable=2068 + # history -s g "$@" + # history -s vi "+$n" "$f" + # vi "+$n" "$f" + history -s g "$@" + history -s e "+$n" "$f" + e "+$n" "$f" + fi +} + +f() { + # profile="f-shell-function$(pwd | sed -e 's_/_-_g')" + # file="$(git ls-files | grep ${2:-.} | remembering -p "$profile" -c "fzf --select-1 --exit-0 --preview 'cat {}'")" + # shellcheck disable=2086 + file="$(git ls-files | grep ${2:-.} | fzf --select-1 --exit-0 --preview 'cat {}')" + if [ -n "$file" ]; then + # shellcheck disable=2068 + history -s f "$@" + history -s "$1" "$file" + "$1" "$file" + fi +} + + + +r() { + . ~/.profile + xset r rate 225 100 +} + +_edit_without_executing() { + F="$(mkstemp)" + printf '%s\n' "$READLINE_LINE" > "$F" + e "$F" + READLINE_LINE="$(cat "$F")" + READLINE_POINT="${#READLINE_LINE}" + rm -f "$F" +} +if set -o | grep 'on' | grep -Eq '^(vi|emacs)'; then + bind -x '"\C-x\C-e":_edit_without_executing' +fi + + + + +# +# GPG and SSH agents configuration +# + +SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket) +GPG_TTY=$(tty ||:) +export GPG_TTY SSH_AUTH_SOCK +gpgconf --launch gpg-agent + + + +case $- in + *i*) + stty -ixon # Disable C-s/C-q mode + ;; + *) + ;; +esac + +F="$PRIV_CONFIG"/privrc.sh +if [ -r "$F" ]; then + # Extra rc code to be loaded, stored in private repository + . "$F" +fi + + + +# +# From here on, bash-specific things. +# + +if [ -z "$BASH_VERSION" ]; then + return +fi + +ln -fs .profile ~/.bashrc + +export INPUTRC="$XDG_CONFIG_HOME/bash/inputrc" +export HISTCONTROL=ignorespace:ignoredups + +eval "$(direnv hook bash)" + + +# +# From here on, interactive-only bash-specific things. +# + +case $- in + *i*) + ;; + *) + return + ;; +esac + +# IIRC, the Guix profile should do this +for f in "$HOME_ENVIRONMENT"/profile/etc/bash_completion.d/*; do + . "$f" +done + +# IIRC, the Guix package should do this +F="$HOME_ENVIRONMENT"/profile/share/autojump/autojump.bash +if [ -r "$F" ]; then + . "$F" +fi diff --git a/etc/sh/root-rc b/etc/sh/root-rc new file mode 100644 index 0000000..f78f293 --- /dev/null +++ b/etc/sh/root-rc @@ -0,0 +1,87 @@ +#!/bin/sh +# shellcheck disable=1090,1091 +export ENV=~/.profile + +XDG_PREFIX=~/.usr +export XDG_CACHE_HOME="$XDG_PREFIX/var/cache" +export XDG_CONFIG_HOME="$XDG_PREFIX/etc" +export XDG_DATA_HOME="$XDG_PREFIX/share" +export XDG_STATE_HOME="$XDG_PREFIX/state" +export XDG_LOG_HOME="$XDG_PREFIX/var/log" + +mkdir -p \ + "$XDG_CACHE_HOME" \ + "$XDG_CONFIG_HOME" \ + "$XDG_DATA_HOME" \ + "$XDG_STATE_HOME" \ + "$XDG_LOG_HOME" + +GUIX_PROFILE="$XDG_CONFIG_HOME"/guix/current +if [ -e "$GUIX_PROFILE"/etc/profile ]; then + . "$GUIX_PROFILE"/etc/profile +fi + +HISTSIZE=-1 +HISTFILE="$XDG_STATE_HOME"/bash-history +HISTCONTROL=ignorespace:ignoredups + +export EDITOR='vi' +export VISUAL="$EDITOR" +export PAGER='less -R' +export GUILE_HISTORY="$XDG_STATE_HOME"/guile-history +export RLWRAP_HOME="$XDG_CACHE_HOME"/rlwrap +export LESSHISTFILE="$XDG_STATE_HOME"/lesshst +export EXINIT=' + " set number + set autoindent + set ruler + set showmode + set showmatch +' + +alias l='ls -lahF --color=auto' +alias grep='grep --color=auto' +alias diff='diff --color' +alias less='less -R' +alias mv='mv -i' +alias rm='rm -i' +alias r='reload' + +reload() { + . ~/.profile +} + + + +# +# PS1 +# + +error_marker() { + STATUS=$? + if [ "$STATUS" != 0 ]; then + printf ' (!! %s !!) ' "$STATUS" + fi +} + +shell_level() { + # shellcheck disable=3028 + L="${SHLVL:-1}" + if [ -n "${TMUX:-}" ]; then + LVL=$((L - 1)) + else + LVL="$L" + fi + if [ "$LVL" != 1 ]; then + printf '[%s] ' "$LVL" + fi +} + +guix_env() { + if [ -n "${GUIX_ENVIRONMENT:-}" ]; then + printf '\n~> guix environment (%s)' "$GUIX_ENVIRONMENT" + fi +} + +PS1='`error_marker`\T \w/ `shell_level``guix_env` +# ' diff --git a/etc/ssh/config.tmpl b/etc/ssh/config.tmpl new file mode 100644 index 0000000..fd4b8a3 --- /dev/null +++ b/etc/ssh/config.tmpl @@ -0,0 +1,6 @@ +Host * + UserKnownHostsFile ${XDG_CONFIG_HOME}/ssh/known_hosts + +Include ~/dev/libre/servers/src/infrastructure/ssh.conf +Include ~/dev/others/lawtech/src/infrastructure/ssh.conf +Include ${XDG_DATA_HOME}/euandreh/vm-ssh.conf diff --git a/etc/ssh/known_hosts b/etc/ssh/known_hosts new file mode 100644 index 0000000..fa1b43a --- /dev/null +++ b/etc/ssh/known_hosts @@ -0,0 +1,84 @@ +|1|G2vfdmQ84glwobYXZZ0d+cCMVRE=|DZJYmWjbeP52J4K7+Bsz2e0dgBA= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= +|1|yosAhKlbZt51FfD2VvQJiVijSBA=|BFEig1gqq4EwCHEHagEASQZQmNI= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= +|1|5yKbUaAB5AFz4MINtTVhVAPwMtU=|LwcI5Z8hXwwKxtkk4KDpeZduyPQ= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +|1|sfEM3JnFec93XEzMf9A6TkthgdI=|oZX7oe9eBKYMrXLcoFydh70my5A= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +|1|F/E3nafzrpMZEzyN8iA++okJ7Q4=|T00Gk7F90YmbaTMSOGjP8yhls94= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMylg2eib0byVT2R7dVFkxdhXO5pvgllnszKhlHiEP15ee8IjMYNPvz2A605hUdIxXtsBgjf+u7jlubh6mbx/YA= +|1|jSpDKjDomux2z3O/ok/UPGGQ8xs=|ZP60naGKQnpK6yHGD+B/+ykB05c= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMylg2eib0byVT2R7dVFkxdhXO5pvgllnszKhlHiEP15ee8IjMYNPvz2A605hUdIxXtsBgjf+u7jlubh6mbx/YA= +|1|PJybLcZRkpN9IyDsqaNjGO6lE5Y=|K0nchvCA7XV91J2X6l0h2DwOmyI= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +|1|zzdSJf8JIMfi5sKbc/mNcBy/RB8=|0GxC/CesxJHcRdt8MuPVjfab06k= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBxDZv64oRMzRkywjmRRrml2pr0XFSZhlL46nUSmM60 +|1|X5OmtKdXZh2kC//XCXEEOim7tgE=|Rh5ro2oEB4MN8MP6PRbG3QBR0Kk= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBxDZv64oRMzRkywjmRRrml2pr0XFSZhlL46nUSmM60 +46.101.43.82 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLIGIc5X12Y61eVcKJnNzCvrPeSKgyKgElqGl7QDeGeynE33sRVoNAg9aqkgXdc2MkN+nFhEWELkjPuBKYsyp2I= +hinarioespirita.org,167.99.34.30 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEnGFkXNEF6RHihT3szgVEBAhRKXlI6YKSUOQFzhuNVkMOBgEfr+OaXpFV3zQ3/Dp875skdTOZaA9DjQ0EeUS+M= +pt.hinarioespirita.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEnGFkXNEF6RHihT3szgVEBAhRKXlI6YKSUOQFzhuNVkMOBgEfr+OaXpFV3zQ3/Dp875skdTOZaA9DjQ0EeUS+M= +mtm.hinarioespirita.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEnGFkXNEF6RHihT3szgVEBAhRKXlI6YKSUOQFzhuNVkMOBgEfr+OaXpFV3zQ3/Dp875skdTOZaA9DjQ0EeUS+M= +77.109.148.18 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBxDZv64oRMzRkywjmRRrml2pr0XFSZhlL46nUSmM60 +35.231.145.151 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= +git.sr.ht,173.195.146.142 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4= +192.168.33.10 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCL+hEiW/Va2L6y5Y4MyxRBXHJw/9bIC02M3wGVe1zaD3DT8wsAUcJ2QO1lJILudvMInx+SaPyJmwBvUn58YR2c= +173.195.146.152 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4= +2001:1620:2019::218 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKBxDZv64oRMzRkywjmRRrml2pr0XFSZhlL46nUSmM60 +140.82.114.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +140.82.113.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +140.82.113.4 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +140.82.114.4 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +2604:bf00:710:0:5054:ff:fe7d:8fa8 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4= +azusa.runners.sr.ht,2604:bf00:710:0:ae1f:6bff:fead:55a ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPzxK5AM1FdMI9gZVDpw2O5iiS/49QokWpzANFntVt0Qig4qdBt4K7B0O6MrwggLh3A+zBlsXCMoWtvFtPQgLxA= +18.228.52.138 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +18.231.5.6 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +2604:bf00:710:0:5054:ff:fe36:ebc6 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4= +18.228.67.229 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +hg.sr.ht,2604:bf00:710:0:5054:ff:fe25:1aa6 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL4aNGa+KnvMA0QoWrIVuI2QBU0Q/xX48sMBl3VtP/zPOGMvS50zGVMaA00RSzfcI2X0v/aUTsVm2vBNo/V1gTg= +euandre.org,2a03:b0c0:3:d0::387:b001 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBF6wlJqwd7KWLzLovwnwbTmNfO4E4yVDyqxAAlUkn9eDsTtzV1RYNDsaLPWv4mweJqP4crZPFxg40sFVeMDbkC0= +2606:4700:90:0:f22e:fbec:5bed:a9b9 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= +46.101.160.115 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBF6wlJqwd7KWLzLovwnwbTmNfO4E4yVDyqxAAlUkn9eDsTtzV1RYNDsaLPWv4mweJqP4crZPFxg40sFVeMDbkC0= +173.195.146.249 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPzxK5AM1FdMI9gZVDpw2O5iiS/49QokWpzANFntVt0Qig4qdBt4K7B0O6MrwggLh3A+zBlsXCMoWtvFtPQgLxA= +2604:a880:800:14::32:4000 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK8YTumqLFAL5MJ0AcDtFG9dWfbkJKU7FUDlH0xEgAZvHGU57TBr9DIQy2OHrxCxuhk9bZEUX8+vJiRXE05+Rzs= +2001:19f0:7001:5cec:5400:2ff:feec:9940 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPYRWlGutc+bRJ+N0sXHxhgnDsRvRoauQ92yM1U7N+8a +2001:19f0:6c01:2cf0:5400:2ff:feec:99c5 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMGiiEXB+koPS1vgGkwdExk2Q5fGv3Yc5rf8jVHB2FB7 +45.32.155.96 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfPxSXxvq3MHuMvthg+q69ooniSeqXbit1UiW0gbLZN +45.77.65.204 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfPxSXxvq3MHuMvthg+q69ooniSeqXbit1UiW0gbLZN +136.244.85.68 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfPxSXxvq3MHuMvthg+q69ooniSeqXbit1UiW0gbLZN +199.247.0.136 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +euandreh.xyz ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +136.244.80.130 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +209.250.232.122 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +git.euandreh.xyz ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +80.240.24.148 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +95.179.246.150 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +45.77.52.185 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +199.247.2.245 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +140.82.112.4 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +104.238.176.223 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +172.65.251.78 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= +140.82.112.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +45.32.158.17 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +95.179.253.243 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +95.179.163.103 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +217.69.2.177 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhpYZqFXy4CkOrSg5naR5Any47jqgZUIwbiTvsl2Yhm5EiUtCZTIVHui7q262M5qlDY6syQ5lVMtSsLJuTce+I= +104.238.176.81 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhpYZqFXy4CkOrSg5naR5Any47jqgZUIwbiTvsl2Yhm5EiUtCZTIVHui7q262M5qlDY6syQ5lVMtSsLJuTce+I= +173.199.70.52 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhpYZqFXy4CkOrSg5naR5Any47jqgZUIwbiTvsl2Yhm5EiUtCZTIVHui7q262M5qlDY6syQ5lVMtSsLJuTce+I= +[remembering.euandreh.xyz]:23841 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +167.71.86.194 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCXg/l/grol/OAY95VphKbVn4yXUStSWpxQjnka7PWXzWyForfX+fpmY+p72r1Uimx5oXIFISt8uSZW/tvD61iQ= +[localhost]:10022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLHACdQJXUj5e4Znyh7KdTjB1RLznSfStRw6cuHZlu8rhANVfkjt300Fum9Jv6yLra6W4v2FLALWfcYpOlUzt9c= +107.191.63.70 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMONpsqwH79f/MsjtsOPitT5C+3hPPJqVh42oHMKOen6 +217.69.11.49 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPKbzWSlFwmlrC+k6XU9eUJaejREGxTF03OX/mT7KhSr +anoncvs.netbsd.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA3QiBl8leG9fqIJpKeNov0PKq5YryFFiroMWOPUv4hDFn8R0jC07YVaR/OSBrr37CTmGX5AFceXPzoFnLlwCqWR7rXg4NR75FTlTp9CG9EBAEtU8mee27KDrUFBTZdfVl2+aRYoAI5fTXA+0vpIO68Cq843vRWUZCcwinS4cNLUU= +git.2f30.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJSLZ4G5w4NysBUmAHmr6/V9om42IHSUCtqrNdhWoYQ0 +[199.247.13.53]:38123 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoz1gFl6chY91vQ5SrZXSP5yHqRI3TdYy2ccEDpS7Z4 +[2001:19f0:6801:988:5400:3ff:fea1:b566]:38123 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoz1gFl6chY91vQ5SrZXSP5yHqRI3TdYy2ccEDpS7Z4 +[gerrit.wikimedia.org]:29418 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCF8pwFLehzCXhbF1jfHWtd9d1LFq2NirplEBQYs7AOrGwQ/6ZZI0gvZFYiEiaw1o+F1CMfoHdny1VfWOJF3mJ1y9QMKAacc8/Z3tG39jBKRQCuxmYLO1SWymv7/Uvx9WQlkNRoTdTTa9OJFy6UqvLQEXKYaokfMIUHZ+oVFf1CgQ== +2001:19f0:5:1d65:5400:3ff:fee3:7463 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF2EgU3IgCwv1ynnWfxFm0SHTSoE0AYG3MJT/TpN3pBz +[camarada.site]:38123 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoz1gFl6chY91vQ5SrZXSP5yHqRI3TdYy2ccEDpS7Z4 +149.28.21.56 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2cITa9TuC3lzEihIfFYb0KhyJJ5Vg2vnGW1SMMwxhc +camarada.site ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoz1gFl6chY91vQ5SrZXSP5yHqRI3TdYy2ccEDpS7Z4 +[127.0.0.1]:60022 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuoUWqrJ8WLBDWwWG7zCyYVYz1upMlg1mSXMGMHIVzY +[localhost]:60022 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuoUWqrJ8WLBDWwWG7zCyYVYz1upMlg1mSXMGMHIVzY +[10.0.2.1]:23841 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +[kuvira.wg]:23841 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +kuvira.wg ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIvhhXQBEY9GhWOtBoLpDJRjqHAu7ci2A9l1xoet1Cz/ +216.238.68.100 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKF5klRzMiaDhfFv7vj6nIT2BdjbcgpsmnNT3y/X9oUu +arrobaponto.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKF5klRzMiaDhfFv7vj6nIT2BdjbcgpsmnNT3y/X9oUu +10.0.2.2 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII9jxIXM2FSYeZmY2uLWkQUJQLNIQQJyJdc7P4eEPhEU +velhinho ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII9jxIXM2FSYeZmY2uLWkQUJQLNIQQJyJdc7P4eEPhEU +velhinho.wg ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII9jxIXM2FSYeZmY2uLWkQUJQLNIQQJyJdc7P4eEPhEU +localhost ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII9jxIXM2FSYeZmY2uLWkQUJQLNIQQJyJdc7P4eEPhEU diff --git a/etc/tmux/tmux.conf b/etc/tmux/tmux.conf new file mode 100644 index 0000000..93efdf9 --- /dev/null +++ b/etc/tmux/tmux.conf @@ -0,0 +1,96 @@ +# Use personal prefix over "C-b" +unbind C-b +set -g prefix C-v + +# Turn on mouse mode +# https://groups.google.com/forum/#!msg/tmux-users/TRwPgEOVqho/Ck_oth_SDgAJ +# https://github.com/tmux/tmux/blob/310f0a960ca64fa3809545badc629c0c166c6cd2/CHANGES#L12 +set -g mouse on + +# Bind "C-x r" to reload the configuration file +bind-key r source-file $XDG_CONFIG_HOME/tmux/tmux.conf \; display-message "$XDG_CONFIG_HOME/tmux.conf reloaded" + +# Holy answer that properly implements copying from tmux! +# https://unix.stackexchange.com/a/349020/276661 +bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe "xclip -selection clipboard -i" \; +bind-key y send-keys -X copy-pipe "xclip -selection clipboard -i" \; display-message "Copied system clipboard! Hooray! Long-live tmux!" + +# Moving around windows +bind-key -n M-[ previous-window +bind-key -n M-] next-window + +# Moving around panes +bind-key -n M-h select-pane -L +bind-key -n M-j select-pane -D +bind-key -n M-k select-pane -U +bind-key -n M-l select-pane -R + +# Resizing panes +bind-key -n M-H resize-pane -L 5 +bind-key -n M-J resize-pane -D 5 +bind-key -n M-K resize-pane -U 5 +bind-key -n M-L resize-pane -R 5 + +# Reorder windows +bind-key -n C-S-Left swap-window -t -1\; select-window -t -1 +bind-key -n C-S-Right swap-window -t +1\; select-window -t +1 +# +# To change the number of a window, use: PREFIX-., and pick a new unused number +# + +# Join windows +bind-key -n C-S-M-Left join-pane -s :-0 -t :-1 +bind-key -n C-S-M-Right join-pane -s :-0 -t :+1 + +# "M m" to actually clear the pane history +bind -n M-m send-keys -R \; clear-history + +# Keybinding to activate pane typing sync +# https://stackoverflow.com/questions/25909964/tmux-how-to-toggle-on-and-off-options-with-the-same-key +bind-key b setw synchronize-panes \; display-message "synchronize-panes toggle" + +setw -g mode-keys vi # Move around with vi keys +set-option -g status-key "vi" # Use vi mode for status bar command (like after typing "C-x [" one can search with "/") +set-option -g status-bg "#333333" # Status bar background color +set-option -g status-fg "#ffffff" # Status bar foreground color +set-option -g status-left-length 50 # session name in status bar length =[annex]= part +set-option -g history-limit 150000 # How many lines of history to keep +set-option -g status-right "" + +# Set the panes initial index value to 1 instead of 0 +# 0 is too far from ` ;) +set -g base-index 1 +set-window-option -g pane-base-index 1 + +# Automatically set window title +set-window-option -g automatic-rename on +set-option -g set-titles on + +# Set "correct term" +# https://wiki.archlinux.org/index.php/Tmux +set -g default-terminal screen-256color + +# No delay for escape key press +# https://mutelight.org/practical-tmux#faster-command-sequences +set -sg escape-time 0 + +# Display pane numbers for longer +# https://unix.stackexchange.com/questions/307696/how-to-increase-tmux-pane-numbers-display-time-ctrl-b-q +set -g display-panes-time 2500 + +bind-key t resize-pane -x 80 + +bind -n M-r attach-session -t . -c '#{pane_current_path}' \; display-message "CWD for session updated to #{pane_current_path}!" + +bind-key u capture-pane \; save-buffer $XDG_RUNTIME_DIR/tmux-urlscan-buffer \; new-window -n "urlscan" '$SHELL -c "urlscan < $XDG_RUNTIME_DIR/tmux-urlscan-buffer"' +bind-key - last + + +# $XDG_DATA_HOME variable isn't allowed by tmux-resurrect +set -g @resurrect-dir '~/.usr/share/tmux/resurrect' +set -g @continuum-restore 'on' +set -g @continuum-save-interval '60' +set -g @resurrect-capture-pane-contents 'on' +set -g @resurrect-processes '~make ~ssh ~e "~alot->alot" "~ranger->ranger" "~newsboat->newsboat" "~entr->entr" "~git->git" "~info->info"' +run-shell ~/.guix-home/profile/share/tmux-plugins/resurrect/resurrect.tmux +run-shell ~/.guix-home/profile/share/tmux-plugins/continuum/continuum.tmux diff --git a/etc/weechat/irc.conf b/etc/weechat/irc.conf new file mode 120000 index 0000000..d42fdad --- /dev/null +++ b/etc/weechat/irc.conf @@ -0,0 +1 @@ +../../var/lib/private/tilde/weechat/irc.conf
\ No newline at end of file diff --git a/opt/aux/gen-e-list.sh b/opt/aux/gen-e-list.sh new file mode 100755 index 0000000..3d5ee4b --- /dev/null +++ b/opt/aux/gen-e-list.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu + +{ + cat <<-EOF + ~/Documents/txt/TODOs.md + ~/Documents/txt/scratch.txt + $XDG_CONFIG_HOME/sh/rc + $XDG_CONFIG_HOME/guix/home.scm + $XDG_CONFIG_HOME/guix/system.scm + EOF + + find ~/Documents/txt/*.txt ~/Documents/txt/*.md -not -name TODOs.md | + sed "s|^$HOME|~|" | + LANG=POSIX.UTF-8 sort + + cat <<-EOF + ~/dev/libre/package-repository/dependencies.dot + ~/dev/private/armário/src/dinheiros/dinheiros.ledger + ~/dev/libre/dotfiles/sh/fake-symlinks.sh + $XDG_CONFIG_HOME/guix/channels.scm + $XDG_CONFIG_HOME/lisp-cli/init.lisp + EOF +} | sed "s|$HOME|~|" diff --git a/opt/bin-dirs/clisp b/opt/bin-dirs/clisp new file mode 120000 index 0000000..56fe4cc --- /dev/null +++ b/opt/bin-dirs/clisp @@ -0,0 +1 @@ +/home/andreh/dev/misc/common-lisp/clisp/src
\ No newline at end of file diff --git a/opt/bin-dirs/euandre.org b/opt/bin-dirs/euandre.org new file mode 120000 index 0000000..db5a280 --- /dev/null +++ b/opt/bin-dirs/euandre.org @@ -0,0 +1 @@ +/home/andreh/dev/published/euandre.org/src/bin
\ No newline at end of file diff --git a/opt/tests/assert-gpg-expiration.sh b/opt/tests/assert-gpg-expiration.sh new file mode 100755 index 0000000..d17486e --- /dev/null +++ b/opt/tests/assert-gpg-expiration.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu + + +SECRET_KEY='81F90EC3CD356060' +NEXT_6_MONTHS="$(echo "$(date '+%s') + (60 * 60 * 24 * 30 * 6)" | bc)" + +gpg --with-colons --fixed-list-mode --list-keys "$SECRET_KEY" | + grep -e ^pub -e ^sub | + while read -r subkey; do + EXPIRY="$(echo "$subkey" | cut -d: -f7)" + if [ -z "$EXPIRY" ]; then + continue + fi + + if [ "$EXPIRY" -gt "$(date '+%s')" ] && + [ "$EXPIRY" -lt "$NEXT_6_MONTHS" ]; then + printf 'Key %s to expire soon!.\n' \ + "$(echo "$subkey" | cut -d: -f5)" >&2 + exit 1 + fi + done diff --git a/share/msg/bad.ogg b/share/msg/bad.ogg Binary files differnew file mode 100644 index 0000000..0e91525 --- /dev/null +++ b/share/msg/bad.ogg diff --git a/share/msg/good.ogg b/share/msg/good.ogg Binary files differnew file mode 100644 index 0000000..22cb563 --- /dev/null +++ b/share/msg/good.ogg |