summaryrefslogtreecommitdiffstats
path: root/.bin
diff options
context:
space:
mode:
Diffstat (limited to '.bin')
-rwxr-xr-x.bin/.requirements.txt7
-rwxr-xr-x.bin/ag-audio55
-rwxr-xr-x.bin/ag-autorandr14
-rwxr-xr-x.bin/ag-light14
-rwxr-xr-x.bin/ag-lock21
-rwxr-xr-x.bin/ag-status56
-rwxr-xr-x.bin/ag-term9
-rwxr-xr-x.bin/cheat3
-rwxr-xr-x.bin/dot-bootstrap17
-rwxr-xr-x.bin/dot-clean7
-rwxr-xr-x.bin/dot-pkgs76
-rwxr-xr-x.bin/dot-sync25
-rwxr-xr-x.bin/draw-logo23
-rwxr-xr-x.bin/dwm-start17
-rwxr-xr-x.bin/get-mailbox-imap77
-rwxr-xr-x.bin/get-pass11
-rwxr-xr-x.bin/music70
-rwxr-xr-x.bin/nmutt19
-rwxr-xr-x.bin/nwsflux192
-rwxr-xr-x.bin/oak180
-rwxr-xr-x.bin/password-gen28
-rwxr-xr-x.bin/pinentry-common3
-rwxr-xr-x.bin/pipx-sync12
-rwxr-xr-x.bin/qb130
-rwxr-xr-x.bin/resume-to-pdf17
-rwxr-xr-x.bin/screen-copy22
-rwxr-xr-x.bin/show-pass22
-rwxr-xr-x.bin/start-org25
-rwxr-xr-x.bin/sync-public-dotfiles52
-rwxr-xr-x.bin/synchronize-ereader21
-rwxr-xr-x.bin/tenv87
-rwxr-xr-x.bin/term-color36
-rwxr-xr-x.bin/unlock-key5
-rwxr-xr-x.bin/wchat4
-rwxr-xr-x.bin/wttr3
-rwxr-xr-x.bin/x11-config39
-rwxr-xr-x.bin/x11-screen27
-rwxr-xr-x.bin/yubikey-reset20
38 files changed, 1446 insertions, 0 deletions
diff --git a/.bin/.requirements.txt b/.bin/.requirements.txt
new file mode 100755
index 0000000..3e69fba
--- /dev/null
+++ b/.bin/.requirements.txt
@@ -0,0 +1,7 @@
+
+# ~/.bin/.requirements.txt
+# dotfiles requirements for python packages
+
+requests
+miniflux
+ansible_runner
diff --git a/.bin/ag-audio b/.bin/ag-audio
new file mode 100755
index 0000000..f39522f
--- /dev/null
+++ b/.bin/ag-audio
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+__increase() {
+ pamixer -i "${1:-10}"
+}
+
+__decrease() {
+ pamixer -d "${1:-10}"
+}
+
+__mic_toggle() {
+ pamixer --default-source -t
+}
+
+__toggle() {
+ pamixer -t
+}
+
+__next() {
+ cmus-remote -n
+}
+
+__prev() {
+ cmus-remote -r
+}
+
+__play() {
+ cmus-remote -u
+}
+
+case "${1}" in
+ i*)
+ __increase "${2}"
+ ;;
+ d*)
+ __decrease "${2}"
+ ;;
+ mic-t*)
+ __mic_toggle
+ ;;
+ t*)
+ __toggle
+ ;;
+ n*)
+ __next
+ ;;
+ pr*)
+ __prev
+ ;;
+ pl*)
+ __play
+ ;;
+esac
+
+ag-status &>/dev/null
diff --git a/.bin/ag-autorandr b/.bin/ag-autorandr
new file mode 100755
index 0000000..0a75819
--- /dev/null
+++ b/.bin/ag-autorandr
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+screens=$(xrandr --listmonitors | tail -n +2 | rev | cut -d " " -f 1 | rev)
+screen_master=$(echo "${screens}" | cut -d " " -f 1)
+
+logger -s reset xrandr size
+xrandr -s 0
+xrandr --output "${screen_master}" --auto
+
+for screen in $(echo ${screens} | cut -d " " -f 2); do
+ xrandr --output "${screen}" --auto --right-of "${screen_master}"
+ echo --output "${screen}" --auto --right-of "${screen_master}"
+ screen_master=${screen}
+done
diff --git a/.bin/ag-light b/.bin/ag-light
new file mode 100755
index 0000000..0d15722
--- /dev/null
+++ b/.bin/ag-light
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+light -N 1
+
+case "${1}" in
+ i*)
+ light -A 10
+ ;;
+ d*)
+ light -U 10
+ ;;
+esac
+
+ag-status &>/dev/null
diff --git a/.bin/ag-lock b/.bin/ag-lock
new file mode 100755
index 0000000..e340245
--- /dev/null
+++ b/.bin/ag-lock
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -xe
+
+if pgrep xidle >/dev/null; then
+ pkill -USR1 xidle &
+elif command -v xsecurelock >/dev/null; then
+ xsecurelock &
+fi
+
+if [ ! "${1}" = "-s" ]; then
+ exit 0
+fi
+
+sleep 1
+
+if command -v systemctl; then
+ systemctl suspend
+elif command -v zzz; then
+ ! doas -n zzz && zzz
+fi
diff --git a/.bin/ag-status b/.bin/ag-status
new file mode 100755
index 0000000..150b9db
--- /dev/null
+++ b/.bin/ag-status
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+__cleanup_value() {
+ cat /dev/stdin | sed 's/%//g'
+}
+
+uname=$(uname)
+
+battery=""
+battery_status=""
+time=""
+volume=""
+
+while true; do
+ case "${uname}" in
+ OpenBSD)
+ battery=$(apm -l)
+ battery_status=$(apm |
+ grep "A/C" |
+ cut -d ":" -f 2 |
+ tr -d " ")
+ volume=$(sndioctl -n output.level |
+ cut -c 3-4)
+ ;;
+ Linux)
+ battery=$(acpi -b |
+ cut -d " " -f 4 |
+ __cleanup_value)
+ battery_status=$(acpi -a |
+ tr -s " " |
+ cut -d " " -f 3)
+ volume=$(pamixer --get-volume |
+ __cleanup_value)
+
+ if $(pamixer --get-mute); then
+ volume_status="__mute__"
+ fi
+ ;;
+ esac
+
+ time=$(date +%Y-%m-%dT%H:%M:%S)
+ status="VOL: ${volume}%"
+
+ if [ -n "${battery}" ]; then
+ status="${status} | BATTERY ${battery}%"
+ fi
+
+ status="${status} | DATE: ${time}"
+
+ echo "${status}"
+ xsetroot -name " ${status}"
+
+ [ "${1}" != "-l" ] && exit 0
+
+ sleep 5
+done
diff --git a/.bin/ag-term b/.bin/ag-term
new file mode 100755
index 0000000..2d0c6b1
--- /dev/null
+++ b/.bin/ag-term
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -xe
+
+terms="alacritty st xterm"
+
+for term in ${terms}; do
+ command -v "${term}" && exec "${term}"
+done
diff --git a/.bin/cheat b/.bin/cheat
new file mode 100755
index 0000000..cb6f4aa
--- /dev/null
+++ b/.bin/cheat
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+curl -s "cheat.sh/${1}"
diff --git a/.bin/dot-bootstrap b/.bin/dot-bootstrap
new file mode 100755
index 0000000..56840dc
--- /dev/null
+++ b/.bin/dot-bootstrap
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+set -x -e
+
+directories="git \
+ downloads \
+ .cache/dot \
+ .cache/neomutt \
+ .local/bin \
+ .local/share/dot"
+
+remote=$(yadm remote | head -n 1)
+yadm branch --set-upstream-to="${remote}/trunk" trunk
+
+for directory in ${directories}; do
+ [ ! -d "${HOME}/${directory}" ] && mkdir -p "${HOME}/${directory}"
+done
diff --git a/.bin/dot-clean b/.bin/dot-clean
new file mode 100755
index 0000000..5b326ea
--- /dev/null
+++ b/.bin/dot-clean
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+clean_files="*.core"
+
+for file in ${clean_files}; do
+ find "${HOME}" -maxdepth 2 -iname "*.core" -exec rm {} \;
+done
diff --git a/.bin/dot-pkgs b/.bin/dot-pkgs
new file mode 100755
index 0000000..f5c80ee
--- /dev/null
+++ b/.bin/dot-pkgs
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+
+import subprocess
+import os
+
+ENV = {
+ 'path': f'{os.environ["HOME"]}/.cache/dot-pkgs',
+ 'bin_path': f'{os.environ["HOME"]}/.local/bin'
+}
+
+PKGS = [
+ {
+ 'src': 'git://git.suckless.org/dwm',
+ 'path': f'{ENV["path"]}/dwm',
+ 'bin': 'dwm',
+ 'type': 'git'
+ },
+ {
+ 'src': 'git://git.suckless.org/st',
+ 'path': f'{ENV["path"]}/st',
+ 'bin': 'st',
+ 'type': 'git'
+ },
+ {
+ 'src': 'git@st0dev1:_suckless/dwm',
+ 'path': f'{os.environ["HOME"]}/git.rgoncalves.se/_suckless/dwm',
+ 'bin': 'st',
+ 'type': 'git'
+ },
+ {
+ 'src': '',
+ 'type': ''
+ }
+]
+
+
+def copy_executable_file(src, dst):
+ pass
+
+
+def merge_mappings(pkgs, env):
+ """
+ Merge two dict in the first one.
+ """
+ for index, pkg in enumerate(pkgs):
+ pkgs[index] = {**env, **pkg}
+
+
+def handle_git(pkg):
+ """
+ Clone and update git repo.
+ """
+ commands = [
+ ['git', 'clone', pkg['src'], pkg['path']],
+ ['git', 'pull']
+ ]
+
+ for command in commands:
+ subprocess.run(command, cwd=pkg['path'])
+
+
+def handle_make(pkg):
+ subprocess.run(['make'], cwd=pkg['path'])
+
+
+def main():
+ merge_mappings(PKGS, ENV)
+
+ for pkg in [pkg for pkg in PKGS if pkg['type'] == 'git']:
+ os.makedirs(pkg['path'], exist_ok=True)
+ handle_git(pkg)
+ handle_make(pkg)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/.bin/dot-sync b/.bin/dot-sync
new file mode 100755
index 0000000..7f4d490
--- /dev/null
+++ b/.bin/dot-sync
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+set -x -e
+
+export GIT_SSH_COMMAND="ssh -o ConnectTimeout=1 -o ConnectionAttempts=1"
+
+yadm pull
+
+yadm add -u
+yadm add \
+ $HOME/.bin \
+ $HOME/.config/i3 \
+ $HOME/.config/neomutt \
+ $HOME/.config/newsboat \
+ $HOME/.config/nvim \
+ $HOME/.config/qutebrowser/{*.py,greasemonkey} \
+ $HOME/.config/sway \
+ $HOME/.config/yadm \
+ $HOME/.config/waybar \
+ $HOME/.public-keys
+
+yadm push
+
+yadm commit -m "$(date +%Y-%m-%dT%H:%M:%S)"
+yadm push
diff --git a/.bin/draw-logo b/.bin/draw-logo
new file mode 100755
index 0000000..d65bf8f
--- /dev/null
+++ b/.bin/draw-logo
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -xe
+
+circle_coordinates="256,256,256"
+background_color="white"
+foreground_color="black"
+
+convert -size 512x512 \
+ "xc:${backgrond_color}" \
+ -fill "${foreground_color}" \
+ -draw "circle ${circle_coordinates},32" \
+ -fill "${background_color}" \
+ -draw "circle ${circle_coordinates},96" \
+ "${1}.png"
+
+convert "${1}.png" -resize "50%" "${1}-medium.png"
+convert "${1}.png" -resize "25%" "${1}-small.png"
+
+convert "${1}-small.png" \
+ -level -10%,12% \
+ -ordered-dither o8x8 \
+ "${1}-dithered.png"
diff --git a/.bin/dwm-start b/.bin/dwm-start
new file mode 100755
index 0000000..e411db5
--- /dev/null
+++ b/.bin/dwm-start
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+kill -9 xidle
+xidle &
+
+while true; do
+ # status
+ pkill -9 -f "ag-status"
+ ag-status -l >/dev/null &
+
+ # x11 configuration
+ x11-config
+ . ~/.bin/x11-screen
+
+ # dwm
+ dwm 2> ~/.dwm.log
+done
diff --git a/.bin/get-mailbox-imap b/.bin/get-mailbox-imap
new file mode 100755
index 0000000..fad7e21
--- /dev/null
+++ b/.bin/get-mailbox-imap
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+
+import imaplib
+import sys
+
+
+def get_boxes(list):
+ """
+ Retrieve and decode all mailboxes.
+ """
+ return [box.decode('utf-8').rsplit(' ')[-1] for box in list]
+
+
+def flatten_output(list):
+ """
+ Print all boxes with a flattened output,
+ primarily for neomutt usage.
+ """
+ print(''.join([f"+'{box}' " for box in list]), end=' ')
+
+
+def sort_boxes(bxs):
+ """
+ Sort boxes list,
+ according to a predefined order
+ """
+
+ order = [
+ "INBOX",
+ "Unread",
+ "Drafts",
+ "Sent",
+ "Spam",
+ "Trash",
+ "Junk",
+ "Archive"
+ ]
+
+ bxs_orig = sorted(bxs)
+
+ # sort based on predefined order
+ bxs = []
+ for exp in order:
+ matching = [s for s in bxs_orig if exp in s]
+ bxs.extend(matching)
+
+ # ensure all retrieved boxes are present
+ for bx in bxs_orig:
+ if bx not in bxs:
+ bxs.append(bx)
+
+ return bxs
+
+
+def main():
+ """
+ Retrieve, sort, and pretty print for neomutt
+ """
+
+ # user information
+ remote = sys.argv[1]
+ username = sys.argv[2]
+ password = sys.argv[3]
+
+ # connection
+ mail = imaplib.IMAP4_SSL(remote)
+ mail.login(username, password)
+
+ # parse folders output
+ bxs = sort_boxes(get_boxes(mail.list()[1]))
+
+ # oneline pretty-print for neomutt
+ flatten_output(bxs)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.bin/get-pass b/.bin/get-pass
new file mode 100755
index 0000000..4cdeafd
--- /dev/null
+++ b/.bin/get-pass
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -e
+
+[ ${#} -ne 0 ]
+
+password_file=$(gopass ls --flat | grep "${@}" | head -n 1)
+[ "${password_file}" ]
+
+echo "+ password: ${password_file}" >&2
+exec gopass show -o "${password_file}"
diff --git a/.bin/music b/.bin/music
new file mode 100755
index 0000000..5498dc0
--- /dev/null
+++ b/.bin/music
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+
+MUSIC_DIR="${HOME}/music"
+MUSIC_LIST=
+MUSIC_FILE=
+MUSIC_YT_OPTIONS=
+
+log() {
+ echo "[${0} ] ${@}"
+}
+
+main() {
+ # arguments
+ while getopts "c:" arg; do
+ case "${arg}" in
+ c)
+ MUSIC_FILE="${OPTARG}"
+ ;;
+ h)
+ exit 0
+ ;;
+ esac
+ done
+
+ # ensure parameters are correct
+ [ ! -f "${MUSIC_FILE}" ] && exit 1
+
+
+ while read -r line; do
+
+ # skip comments
+ line=$(echo ${line} | grep -v -e "^$" -e "^#")
+ [ -z "${line}" ] && continue
+
+ # retrieve playlist params
+ url=$(echo "${line}" | cut -d " " -f 1)
+ dir=$(echo "${line}" | cut -d " " -f 2)
+
+ dir="${MUSIC_DIR}/${dir}"
+
+ [ -d "${dir}" ] &&
+ log "${dir}: directory already exists" &&
+ continue
+
+ mkdir "${dir}"
+ log "${dir} ${url}: download"
+
+ youtube-dl --rm-cache-dir >/dev/null
+ youtube-dl \
+ --extract-audio \
+ --audio-format mp3 \
+ --prefer-ffmpeg \
+ --audio-quality 0 \
+ --embed-thumbnail \
+ --metadata-from-title "%(artist)s - %(title)s" \
+ --no-warnings \
+ --ignore-errors \
+ --no-overwrites \
+ --continue \
+ --add-metadata \
+ --user-agent "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
+ --output "${dir}/'%(title)s.%(ext)s'" \
+ "${url}"
+
+
+ done < "${MUSIC_FILE}"
+}
+
+main ${@}
diff --git a/.bin/nmutt b/.bin/nmutt
new file mode 100755
index 0000000..9a2bb28
--- /dev/null
+++ b/.bin/nmutt
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+NMUTT_CONFIGURATION_FILE="${HOME}/.config/neomutt/personal"
+
+get_param() {
+ grep "${2}" "${1}" |
+ head -n 1 |
+ rev |
+ cut -d " " -f 1 |
+ rev |
+ tr -d '"'
+}
+
+# export server/username for get-mailbox-imap script
+export MAIL_SERVER=$(get_param "${NMUTT_CONFIGURATION_FILE}" "my_server")
+export MAIL_USERNAME=$(get_param "${NMUTT_CONFIGURATION_FILE}" "my_user")
+
+export MAIL_PASSWORD=$(get-pass "mailbox")
+exec neomutt ${@}
diff --git a/.bin/nwsflux b/.bin/nwsflux
new file mode 100755
index 0000000..3e725b0
--- /dev/null
+++ b/.bin/nwsflux
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+#
+# nwsflux
+
+import miniflux
+import urllib3
+import logging
+import os
+import re
+import shlex
+import argparse
+
+
+def get_local_feed(line: str) -> dict:
+ """
+ Parse a line and return a constructed dict like miniflux's API.
+ """
+ feed_url = shlex.split(line)[0]
+ title = shlex.split(line)[-1]
+ tag = shlex.split(line)[1]
+ if tag == title:
+ tag = 'all'
+
+ return {
+ 'feed_url': feed_url,
+ 'title': title,
+ 'category': {
+ 'title': tag
+ }
+ }
+
+
+def get_local_feeds(filename: str) -> list:
+ """
+ Read feeds from a newsboat url file.
+ """
+
+ with open(filename, 'r') as f:
+ content = f.readlines()
+
+ r = re.compile('^http.*://')
+ content = list(filter(r.match, content))
+
+ return [get_local_feed(line) for line in content]
+
+
+def delete_categories(client: miniflux.Client, local_cats: dict, remote_cats: dict):
+ """
+ Remove remote categories that are absent in local file.
+
+ . Useless for now, miniflux's API return non-empty categories only!
+ """
+ for remote in remote_cats:
+ if any(local['title'] == remote['title'] for local in local_cats):
+ continue
+ try:
+ logging.info(f'remove category: {remote["title"]}')
+ client.delete_category(remote['id'])
+ # ignores categories that are empty on remote
+ except miniflux.ClientError as e:
+ logging.error('can not remove non-empty category:'
+ f'{remote["title"]} {e}')
+
+
+def create_categories(client: miniflux.Client, local_cats: dict, remote_cats: dict):
+ """
+ Create categories present in local file and absent on remote.
+ """
+ for local in local_cats:
+ if any(remote['title'] == local['title'] for remote in remote_cats):
+ continue
+ try:
+ logging.info(f'create category: {local["title"]}')
+ client.create_category(local['title'])
+ # ignores categories that are empty on remote
+ except miniflux.ClientError as e:
+ logging.error(f'remote category empty: {local["title"]} {e}')
+
+
+def sync_categories(client: miniflux.Client, local_feeds: dict, remote_feeds: dict) -> dict:
+ local_cats = get_uniq_categories(local_feeds)
+ remote_cats = get_uniq_categories(remote_feeds)
+
+ delete_categories(client, local_cats, remote_cats)
+ create_categories(client, local_cats, remote_cats)
+ return client.get_categories()
+
+
+def get_uniq_categories(buffer: list) -> list:
+ return list(map(dict, frozenset(
+ frozenset(x['category'].items()) for x in buffer
+ )))
+
+
+def delete_feeds(client: miniflux.Client, local_feeds: dict, remote_feeds: dict):
+ """
+ Remove remote feeds that are absent in local file.
+ """
+ for remote in remote_feeds:
+ if any(local['feed_url'] == remote['feed_url'] for local in local_feeds):
+ continue
+ logging.info(f'remove feed: {remote["feed_url"]}')
+ client.delete_feed(remote['id'])
+
+
+def create_feeds(client: miniflux.Client, local_feeds: dict, remote_feeds: dict):
+ """
+ Create feeds that are present in local file and absent on remote.
+ """
+ categories = client.get_categories()
+
+ for local in local_feeds:
+ if any(remote['feed_url'] == local['feed_url'] for remote in remote_feeds):
+ continue
+ logging.info(f'create feed: {local}')
+ category_id = next((x['id'] for x in categories if x['title'] == local['category']['title']), None)
+ try:
+ client.create_feed(local['feed_url'], category_id)
+ except miniflux.ClientError as e:
+ logging.error(e)
+
+
+def sync_feeds(client: miniflux.Client, local_feeds: dict, remote_feeds: dict) -> dict:
+ """
+ Synchronize all given feeds.
+ """
+ delete_feeds(client, local_feeds, remote_feeds)
+ create_feeds(client, local_feeds, remote_feeds)
+ return client.get_feeds()
+
+
+def parse():
+ """
+ Parse command-line arguments.
+ """
+ parser = argparse.ArgumentParser(description='Synchronize RSS feeds from'
+ 'newsboat to an miniflux instance.')
+
+ parser.add_argument('-c', dest='config', type=str, required=True,
+ help='Newsboat url file')
+ parser.add_argument('-k', dest='key', type=str, required=True,
+ help='Miniflux API key')
+ parser.add_argument('-u', dest='url', type=str, required=True,
+ help='Miniflux url')
+ parser.add_argument('-d', dest='debug', action='store_true',
+ help='Enable debugging output')
+ parser.add_argument('-e', dest='export', action='store_true',
+ help='Export Miniflux OPML config to stdout')
+
+ return parser.parse_args()
+
+
+def main():
+
+ """
+ SSL verify.
+ """
+ os.environ["CURL_CA_BUNDLE"] = ""
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+ """
+ Arguments.
+ """
+ args = parse()
+
+ """
+ Debugging.
+ """
+ logging.basicConfig(level=logging.INFO)
+ if not args.debug:
+ logging.level = logging.NOTSET
+
+ """
+ Synchronization.
+ """
+ client = miniflux.Client(args.url, api_key=args.key)
+
+ local_feeds = get_local_feeds(args.config)
+ remote_feeds = client.get_feeds()
+
+ sync_categories(client, local_feeds, remote_feeds)
+ sync_feeds(client, local_feeds, remote_feeds)
+
+ """
+ Export Miniflux to OPML.
+ """
+ if args.export:
+ print(client.export_feeds())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/.bin/oak b/.bin/oak
new file mode 100755
index 0000000..4ea560e
--- /dev/null
+++ b/.bin/oak
@@ -0,0 +1,180 @@
+#!/bin/sh
+# _
+# | |
+# __ __, | |
+# / \_/ | |/_)
+# \__/ \_/|_/| \_/
+#
+# ~/.bin/oak
+#
+# manage multiple dotfiles repo in same work tree
+#
+# Example:
+# dotfiles.d/
+# |_ config
+# |_ secret
+#
+# ~ rgoncalves.se
+
+usage() {
+ cat <<- EOF
+ USAGE ${0} : git_dirname git_action [arguments...]
+ -b: enable bootstraping for absent repository
+ -d: oak directory to work on
+ works in batch mode if no directory is provided
+ -f: force all operations
+ use with caution
+ -m: commit message when using -s option
+ commits_msg_have_to_be_written_like_this
+ -r: git remote with full path to parent directory
+ -h: show usage of ${0}
+ EOF
+}
+
+log() {
+ echo "[OAK ] ${@}"
+}
+
+git_chroot() {
+ log ">> ${1}"
+ git --git-dir="${OAK_ROOT}/${1}" --work-tree="${HOME}" ${2}
+}
+
+git_bootstrap() {
+ # directory url remote
+ git_chroot "${1}" "clone --bare git@${2}/${1} ${OAK_ROOT}/${1}"
+ [ "${?}" -ne 0 ] && log "Failed to bootstrap ${1}" && exit 1
+ git_chroot "${1}" "checkout trunk" >/dev/null 2>&1
+ git_chroot "${1}" "branch --set-upstream-to trunk origin/trunk" >/dev/null 2>&1
+ git_chroot "${1}" "push -u origin trunk" >/dev/null 2>&1
+}
+
+git_sync() {
+ # actions
+ git_sane_all
+ pull_out=$(git_chroot "${1}" "pull")
+ if [ "${?}" -ne 0 ]; then
+ log "error while doing git pull"
+ exit 1
+ fi
+ echo "${pull_out}" 2>&1 | grep " ."
+ git_chroot "${1}" "add -u" >/dev/null
+ git_chroot "${1}" "commit -m ${OAK_MSG}"
+ git_chroot "${1}" "push --set-upstream origin trunk"
+ # cleanup
+ unset pull_out push_out
+}
+
+git_list() {
+ log "Available repositories : "
+ for dir in ${OAK_DIRS}; do
+ log " - ${dir}"
+ done
+}
+
+git_sane() {
+ if [ "${1}" = "${2}" ]; then
+ return 1
+ fi
+ git_chroot "${1}" "ls-files" > ~/.cache/oak_a
+ git_chroot "${2}" "ls-files" > ~/.cache/oak_b
+ out=$(grep -F -x -f ~/.cache/oak_a ~/.cache/oak_b)
+ if [ -n "${out}" ]; then
+ log "detected similar files for directories: ${repo_a}, ${repo_b}"
+ echo "${out}"
+ return 1
+ fi
+ return 0
+}
+
+git_sane_all() {
+ for repo_a in ${DOT_DIRS}; do
+ for repo_b in ${DOT_DIRS}; do
+ if [ "${repo_a}" = "${repo_b}" ]; then
+ continue
+ fi
+ git_sane "${DOT_ROOT}/${repo_a}" "${DOT_ROOT}/${repo_b}"
+ if [ "${?}" -ne 0 ]; then
+ log "aborting"
+ exit 1
+ fi
+ done
+ done
+}
+
+main() {
+
+ OAK_ROOT="${HOME}/.dotfiles.d"
+ OAK_DIRS=$(ls "${OAK_ROOT}")
+ OAK_DIR=""
+ OAK_REMOTE=""
+ OAK_CMD=""
+ OAK_MSG=""
+
+ # Retrieve oak params
+ while getopts "bd:hlm:r:s" arg; do
+ case "${arg}" in
+ b)
+ OAK_BOOTSTRAP=1
+ ;;
+ d)
+ OAK_DIR="${OPTARG}"
+ ;;
+ f)
+ OAK_FORCE=1
+ ;;
+ h)
+ usage && exit 0
+ ;;
+ m)
+ OAK_MSG="$(echo ${OPTARG} | tr -s '_' ' ')"
+ ;;
+ l)
+ git_list
+ exit 0
+ ;;
+ r)
+ OAK_REMOTE="${OPTARG}"
+ ;;
+ s)
+ OAK_SYNC=1
+ ;;
+ esac
+ done
+
+ [ -z "${OAK_MSG}" ] && OAK_MSG=$(date +%j_%H_%M_%S)
+
+ # Bootstrap non-present repositories
+ if [ -n "${OAK_DIR}" ] && [ ! -d "${OAK_ROOT}/${OAK_DIR}" ]; then
+ git_bootstrap "${OAK_DIR}" "${OAK_REMOTE}"
+ if [ "${?}" -ne 0 ]; then
+ log "Git repository does not exist. Error"
+ exit 1
+ fi
+ log "Successfully bootstraped git repository"
+ fi
+
+ # Retrieve oak command for git
+ shift $(expr "${OPTIND}" - 1)
+ for cmd in "${@}"; do
+ OAK_CMD="${OAK_CMD}${cmd} "
+ done
+
+ # Git synchronization in each repository
+ if [ -n "${OAK_SYNC}" ]; then
+ for dir in ${OAK_DIR:-$OAK_DIRS}; do
+ log "synchronize ${dir}"
+ git_sync "${dir}"
+ done
+ fi
+
+ # Raw command in each git repository
+ if [ -n "${OAK_CMD}" ]; then
+ for dir in ${OAK_DIR:-$OAK_DIRS}; do
+ git_chroot "${dir}" "${OAK_CMD}"
+ done
+ fi
+
+}
+
+main $@
diff --git a/.bin/password-gen b/.bin/password-gen
new file mode 100755
index 0000000..e29dcb5
--- /dev/null
+++ b/.bin/password-gen
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+pass_length="24"
+
+usage() {
+ cat <<-EOF
+ usage: pagen [-l pass_length]
+ EOF
+}
+
+main() {
+ while getopts "l:" arg; do
+ case "${arg}" in
+ l)
+ pass_length="${OPTARG-$pass_length}"
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+ esac
+ done
+
+ # generate and display password
+ openssl rand -base64 "${pass_length}"
+}
+
+main $@
diff --git a/.bin/pinentry-common b/.bin/pinentry-common
new file mode 100755
index 0000000..c97af72
--- /dev/null
+++ b/.bin/pinentry-common
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec pinentry-curses
diff --git a/.bin/pipx-sync b/.bin/pipx-sync
new file mode 100755
index 0000000..181768e
--- /dev/null
+++ b/.bin/pipx-sync
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+set -x -e
+
+packages="pywal
+ ipython"
+
+pipx upgrade-all
+
+for package in ${packages}; do
+ pipx install "${package}" || true
+done
diff --git a/.bin/qb b/.bin/qb
new file mode 100755
index 0000000..3caf57e
--- /dev/null
+++ b/.bin/qb
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import yaml
+
+
+def bypass_file_exists(func, *args):
+ """
+ Exceptions for existing files are ignored.
+ """
+ try:
+ func(*args)
+ except FileExistsError as e:
+ print(e)
+
+
+def start_qutebrowser(args):
+ """
+ Replace the current process with qutebrowser.
+ """
+ os.execvp('qutebrowser', args)
+
+
+def generate_tab(url):
+ full_url = f'https://{url}'
+
+ return {
+ 'active': True,
+ 'history': [{
+ 'active': True,
+ 'pinned': True,
+ 'last_visited': '2021-09-16T12:22:57',
+ 'scroll-pos': {'x': 0, 'y': 0},
+ 'zoom': 1.0,
+ 'title': url,
+ 'original-url': full_url,
+ 'url': full_url
+ }]
+ }
+
+
+def restore_default_tabs(profile, path=None):
+ """
+ Declarative way to enforce opened/favorite tabs.
+ """
+ mapping = {
+ 'default': [
+ 'mailbox.org',
+ 'news.ycombinator.com',
+ 'miniflux.rgoncalves.se',
+ 'status.rgoncalves.se'
+ ],
+ 'work': [
+ 'mail.zoho.eu',
+ 'cliq.zoho.eu',
+ 'gitlab.viperdev.io',
+ 'projects.zoho.eu',
+ 'kb.viperdev.io',
+ ]
+ }
+
+ assert mapping[profile]
+
+ # Open file an cleanup
+ with open(path, 'r') as file:
+ data = yaml.safe_load(file)
+
+ # Scrap existing tabs,
+ # then filter pinned tabs
+ original_tabs = data['windows'][0]['tabs']
+ tabs = [tab['history'][-1] for tab in original_tabs
+ if len(tab['history']) != 0]
+ tabs = [tab for tab in tabs if tab['pinned']]
+
+ missing_urls = [url for url in mapping[profile]
+ if not any(url in tab['url'] for tab in tabs)]
+
+ for url in missing_urls:
+ original_tabs.append(generate_tab(url))
+
+ original_tabs.sort(reverse=True, key=lambda k: k['history'][-1]['pinned'])
+
+ # Persist tabs in yaml file
+ with open(path, 'w') as file:
+ yaml.dump(data, file)
+
+
+def main():
+ try:
+ QB_PROFILE = f'{sys.argv[1]}'
+ except IndexError:
+ QB_PROFILE = 'default'
+
+ """
+ restore_default_tabs(
+ QB_PROFILE,
+ path=f'{os.environ["HOME"]}/' +
+ '.local/share/qutebrowser/sessions/default.yml'
+ )
+ """
+ start_qutebrowser([' '])
+ sys.exit(0)
+
+ QB_DIR = f'{os.environ["HOME"]}/.config/qutebrowser'
+ QB_PROFILES_DIR = f'{QB_DIR}/_profiles'
+ QB_PROFILE_DIR = f'{QB_PROFILES_DIR}/{QB_PROFILE}'
+ QB_PROFILE_DIR_CONFIG = f'{QB_PROFILE_DIR}/config'
+ QB_PROFILE_TAG = QB_PROFILE[0].lower()
+
+ # Ensure directory existence
+ for dir in [QB_PROFILES_DIR, QB_PROFILE_DIR, QB_PROFILE_DIR_CONFIG]:
+ bypass_file_exists(os.mkdir, dir)
+
+ # Ensure common files are shared with other profiles
+ for file in ['config.py', 'bookmarks', 'greasemonkey']:
+ bypass_file_exists(os.symlink,
+ f'{QB_DIR}/{file}',
+ f'{QB_PROFILE_DIR_CONFIG}/{file}')
+
+ start_qutebrowser([
+ ' ', '--basedir', QB_PROFILE_DIR,
+ '--set', 'window.title_format', f'[{QB_PROFILE_TAG}]',
+ '--set', 'tabs.title.format',
+ f'[{QB_PROFILE_TAG}] {{audio}}{{index}}: {{current_title}}'
+ ])
+
+
+if __name__ == '__main__':
+ main()
diff --git a/.bin/resume-to-pdf b/.bin/resume-to-pdf
new file mode 100755
index 0000000..04dfba8
--- /dev/null
+++ b/.bin/resume-to-pdf
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+src="https://rgoncalves.se/a/resume.html"
+out="resume.pdf"
+margin="2cm"
+
+wkhtmltopdf --page-size A4 \
+ --orientation portrait \
+ --dpi 300 \
+ --image-dpi 300 \
+ --image-quality 100 \
+ --margin-top "${margin}" \
+ --margin-right "${margin}" \
+ --margin-bottom "${margin}" \
+ --margin-left "${margin}" \
+ "${src}" \
+ "${out}"
diff --git a/.bin/screen-copy b/.bin/screen-copy
new file mode 100755
index 0000000..9078dfb
--- /dev/null
+++ b/.bin/screen-copy
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copy screen area to clipboard
+
+. ~/.bin/tenv
+
+main() {
+ filename="/tmp/screenshot-$(date +%F_%T).png"
+
+ case "${_DISPLAY_SERVER}" in
+ xorg)
+ scrot -s "${filename}"
+ xclip -selection clip -t image/png "${filename}"
+ ;;
+ *)
+ echo " display server not supported" >&2
+ ;;
+ esac
+
+}
+
+main $@
diff --git a/.bin/show-pass b/.bin/show-pass
new file mode 100755
index 0000000..568570e
--- /dev/null
+++ b/.bin/show-pass
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+usage () {
+ cat >&2 <<-EOF
+ usage: ${0} password_regexp
+ EOF
+}
+
+log () {
+ echo ["${0}"] $@ >&2
+}
+
+main() {
+ # verify arguments
+ [ "${#}" -ne 1 ] && usage && exit 1
+
+ # retrieve password
+ log "retrieving password for : ${1}"
+ gopass show --password $(gopass ls --flat | grep "${1}" | head -n 1)
+}
+
+main $@
diff --git a/.bin/start-org b/.bin/start-org
new file mode 100755
index 0000000..c32b99e
--- /dev/null
+++ b/.bin/start-org
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+set -xe
+
+session_name="org"
+windows="wchat:weechat
+ nmutt:neomutt
+ calcurse:calcurse"
+
+if [ "${1}" = "-f" ]; then
+ tmux kill-session -t "${session_name}"
+fi
+
+tmux new-session -s "${session_name}" -d
+
+for window in ${windows}; do
+ window_cmd=$(echo "${window}" | cut -d ":" -f 1)
+ window_name=$(echo "${window}" | cut -d ":" -f 2)
+
+ tmux new-window -n "${window_name}"
+ tmux send-keys -t "${session_name}:${window_name}" \
+ "${window_cmd} "
+done
+
+tmux a -t "${session_name}"
diff --git a/.bin/sync-public-dotfiles b/.bin/sync-public-dotfiles
new file mode 100755
index 0000000..7bff64f
--- /dev/null
+++ b/.bin/sync-public-dotfiles
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+set -e
+
+command -v yadm git
+
+allowed_patterns="-e ^.bin
+ -e ^.config/alacritty/
+ -e ^.config/calcurse/
+ -e ^.config/cmus/classic.theme
+ -e ^.config/cmus/rc
+ -e ^.config/dot/term-color-*
+ -e ^.config/gopass/config.yml
+ -e ^.config/i3*/config
+ -e ^.config/tmux/
+ -e ^.config/mimeapps.list
+ -e ^.config/neomutt/neomuttrc
+ -e ^.config/newsboat/config
+ -e ^.config/nvim/
+ -e ^.config/qutebrowser/config.py
+ -e ^.config/qutebrowser/greasemonkey/
+ -e ^.config/sway/config
+ -e ^.config/systemd/*.service
+ -e ^.config/user-dirs.dir
+ -e ^.config/user-dirs.locale
+ -e ^.gnupg/gpg-agent.conf
+ -e ^.public-keys/
+ -e ^.kshrc
+ -e ^.weechat/buflist.conf
+ -e ^.weechat/fset.conf
+ -e ^.weechat/logger.conf
+ -e ^.weechat/weechat.conf
+ -e ^.xinitrc
+ -e ^.zshrc
+ "
+
+[ "$(yadm rev-parse --show-toplevel)" != "$(git rev-parse --show-toplevel)" ]
+
+# retrieve existing files
+upstream_files=$(cd "${HOME}" && yadm ls-files)
+local_files=$(find . -not -path "./.git*" -not -path ".")
+
+echo "${local_files}" | xargs rm -rf
+
+allowed_files=$(echo "${upstream_files}" | grep ${allowed_patterns})
+for allowed_file in ${allowed_files}; do
+ echo "${allowed_file}"
+ install -D "${HOME}/${allowed_file}" "${allowed_file}"
+done
+
+git add .
+git commit -m "$(date)"
diff --git a/.bin/synchronize-ereader b/.bin/synchronize-ereader
new file mode 100755
index 0000000..482a846
--- /dev/null
+++ b/.bin/synchronize-ereader
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -xe
+
+src_dir=$(readlink -f "${1}")
+out_dir=$(readlink -f "${2}")
+
+[ -d "${src_dir}" ]
+[ -d "${out_dir}" ]
+
+rsync -hvrPt \
+ --delete-after \
+ --fuzzy \
+ --prune-empty-dirs \
+ --include "*.pdf" \
+ --include "*.epub" \
+ --include "*.png" \
+ --include "*.jpg" \
+ --include "*/" \
+ --exclude "*" \
+ "${src_dir}/" "${out_dir}/"
diff --git a/.bin/tenv b/.bin/tenv
new file mode 100755
index 0000000..7718d89
--- /dev/null
+++ b/.bin/tenv
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# o
+# _|_ _ _ _ _ _
+# | | / |/ | | | |/ / |/ | | |_
+# |_/|_/ | |_/ \_/|/ |__/ | |_/ \/
+# /|
+# \|
+#
+# tiny env
+# scraps system and user's configuration,
+# providing unified env. variables accross exotic setup.
+#
+# ~ rgoncalves.se
+
+log() {
+ echo [] "${@}"
+}
+
+usage() {
+ cat <<-EOF
+ usage: tinyenv [-d]
+ EOF
+}
+
+get_os_distribution() {
+ # tmp
+ os=$(uname | tr "[:upper:]" "[:lower:]")
+ distribution=$(uname -r)
+ # logic
+ case "${os}" in
+ linux)
+ distribution=$(uname -r)
+ ;;
+ esac
+ # return
+ export _OS_DISTRIBUTION="${os}_${distribution}"
+ unset -v os distribution
+}
+
+get_display() {
+ # tmp
+ list="wayland Xorg"
+ # logic
+ for display in ${list}; do
+ # search display and skip export if not found
+ pgrep "${display}" >/dev/null
+ [ "${?}" -ne 0 ] && display="none" && continue
+ # found display server
+ break
+ done
+ # return
+ export _DISPLAY_SERVER=$(echo "${display}" | tr "[:upper:]" "[:lower:]")
+ unset -v list display
+}
+
+get_screens() {
+ # tmp
+ screens="1"
+ # logic
+ [ "${_DISPLAY_SERVER}" = "xorg" ] && \
+ screens=$(xrandr | grep " connected" | wc -l | tr -d " ")
+ # return
+ export _SCREENS="${screens}"
+ unset screens
+}
+
+show_env() {
+ list=$(env | grep "^_.*" | sort -n)
+ for el in ${list}; do
+ log "${el}"
+ done
+ unset -v list el
+}
+
+main() {
+
+ # must be first
+ get_os_distribution
+ # alpha/numeric ordered
+ get_display
+ get_screens
+
+ [ -n "${DEBUG}" ] && show_env
+}
+
+main $@
diff --git a/.bin/term-color b/.bin/term-color
new file mode 100755
index 0000000..45a2dcc
--- /dev/null
+++ b/.bin/term-color
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+set -xe
+
+sequences="${HOME}/.config/dot/term-color"
+cache_file="${HOME}/.cache/dot/sequences"
+
+if [ "${1}" = "-l" ]; then
+ sequences="${sequences}-light"
+else
+ sequences="${sequences}-dark"
+fi
+
+[ -f "${sequences}" ]
+
+case $(uname) in
+ OpenBSD)
+ ttys=$(ps |
+ tail -n +2 |
+ tr -s " " |
+ sed 's/^ //g' |
+ cut -d " " -f 2 |
+ sed 's/-$//g' |
+ uniq |
+ sed 's/^/\/dev\/tty/g')
+ ;;
+ Linux)
+ ttys=$(find /dev/pts -iname "[0-9]*")
+ ;;
+esac
+
+for tty in $ttys; do
+ [ -c "${tty}" ] && cat "${sequences}" > $tty
+done
+
+cp "${sequences}" "${cache_file}"
diff --git a/.bin/unlock-key b/.bin/unlock-key
new file mode 100755
index 0000000..d0bebbf
--- /dev/null
+++ b/.bin/unlock-key
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -xe
+
+gopass show -c .buffer 2>/dev/null
diff --git a/.bin/wchat b/.bin/wchat
new file mode 100755
index 0000000..83f13a3
--- /dev/null
+++ b/.bin/wchat
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export WEECHAT_PASSPHRASE="$(get-pass weechat)"
+exec weechat $@
diff --git a/.bin/wttr b/.bin/wttr
new file mode 100755
index 0000000..c06c825
--- /dev/null
+++ b/.bin/wttr
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+curl -s "wttr.in/${1}?m"
diff --git a/.bin/x11-config b/.bin/x11-config
new file mode 100755
index 0000000..5230c13
--- /dev/null
+++ b/.bin/x11-config
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+set -xe
+
+wallpaper_file="${HOME}/.local/share/dot/wallpaper"
+synclient_options="TapButton1=1 \
+ TapButton2=3 \
+ TapButton3=2 \
+ PalmDetect=1 \
+ TouchpadOff=0"
+
+# synaptic
+if command -v syndaemon; then
+ pkill syndaemon && syndaemon -RKd -i 0.2
+ synclient ${synclient_options}
+fi
+
+# keyboard
+xset r rate 250 75
+setxkbmap -option compose:ralt
+
+# screen saving
+xset s off
+xset s noblank
+xset -dpms
+
+if [ $(uname -s) = "OpenBSD" ]; then
+ xinput set-prop "/dev/wsmouse" "WS Pointer Wheel Emulation" 1
+ xinput set-prop "/dev/wsmouse" "WS Pointer Wheel Emulation Button" 2
+ xinput set-prop "/dev/wsmouse" "WS Pointer Wheel Emulation Axes" 6 7 4 5
+fi
+
+# background
+xsetroot -mod 2 2 -fg white -bg black
+xsetroot -grey
+
+if [ -f "${wallpaper_file}" ]; then
+ feh --bg-scale "${wallpaper_file}"
+fi
diff --git a/.bin/x11-screen b/.bin/x11-screen
new file mode 100755
index 0000000..ccccda7
--- /dev/null
+++ b/.bin/x11-screen
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+__hidpi() {
+ xrdb -merge ~/.Xresources.hidpi
+
+ export GDK_SCALE=2
+ export GDK_DPI_SCALE=0.5
+ export GDK_SCALE=2
+ export QT_SCREEN_SCALE_FACTORS=2
+ export QT_AUTO_SCREEN_SCALE_FACTOR=1
+ export QT_QPA_PLATFORMTHEME=qt5ct
+}
+
+hostname=$(uname -n)
+
+xrandr -s 0
+xrdb ~/.Xresources
+
+if [ "${hostname}" = "ws-bare01" ]; then
+ xrandr --output HDMI-0 --left-of DVI-D-0
+fi
+
+if [ "${hostname}" = "ws-xps01" ]; then
+ xrandr --output eDP1 --mode 3200x1800
+ echo a
+ __hidpi
+fi
diff --git a/.bin/yubikey-reset b/.bin/yubikey-reset
new file mode 100755
index 0000000..4824bc8
--- /dev/null
+++ b/.bin/yubikey-reset
@@ -0,0 +1,20 @@
+echo "DESTRUCTIVE !!!"
+
+sleep 60
+
+gpg-connect-agent <<EOF
+/hex
+scd serialno
+scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+scd apdu 00 e6 00 00
+scd apdu 00 44 00 00
+/echo Yubikey has been successfully reset.
+/echo The factory default PINs are 123456 (user) and 12345678 (admin).
+EOF
remember that computers suck.