diff options
author | Romain Gonçalves <me@rgoncalves.se> | 2021-12-23 18:28:03 +0000 |
---|---|---|
committer | Romain Gonçalves <me@rgoncalves.se> | 2021-12-23 18:28:03 +0000 |
commit | 0f08d04698c814955116b6bae50752e64b774d8f (patch) | |
tree | 8cf9a33557093eebfd25aab2872e97639c7e2f62 /.bin | |
download | dots-0f08d04698c814955116b6bae50752e64b774d8f.tar.gz |
Thu Dec 23 06:28:03 PM UTC 2021
Diffstat (limited to '.bin')
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 @@ -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 |