refactor: nixify / clean remaining dots scripts

This commit is contained in:
hektor committed 2026-11-06 21:00:20 +01:00
1 parent 9030f33842
commit 9110789527
53 files changed
+265 -655

No files matched your search

-26
View File
@@ -1,26 +0,0 @@
# Scripts
Mostly tiny helper scripts \& experiments, some more useful than others.
## Setup
Make sure the scripts are in your `$PATH` and executable.
To add the complete directory of scripts to your `$PATH`:
```bash
export PATH=~/.bin:$PATH
```
To make a any script `<script>` executable:
```bash
chmod +x <script>
```
## Notes
Statusbar scripts are prefixed with `sb` under the assumption you're using
`polybar` (scripts my need changes to be used with different statusbars).
Some scripts have dependencies (e.g. `fzf`, `jq`, `xclip`, ...), make sure you
have them installed.
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/env bash
# Tiny AUR clone helper
# aurpac <package-name>
git clone "https://aur.archlinux.org/$1.git"
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env bash
# back up a file by copying it to a new file with a .bak extension
# reference: https://askubuntu.com/questions/962489/is-there-any-way-to-create-backup-copy-of-a-file-without-type-its-name-twice
usage() {
echo "Usage: bak [-t|--timestamp] <file to back up>"
exit 1
}
timestamp=false
while [[ $# -gt 0 ]]; do
case $1 in
-t|--timestamp)
timestamp=true
shift
;;
*)
break
;;
esac
done
[ $# -eq 1 ] || usage
if $timestamp; then
date="$(date +%Y-%m-%d_%H-%M-%S)"
cp -vpn "$1"{,."$date".bak}
else
cp -vpn "$1"{,.bak}
fi
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
ipython -i /home/h/.bin/calc.py
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env bash
# Open webcam window
ffplay -f v4l2 -x 640 -y 480 -i /dev/video0 >/dev/null 2>&1 & disown
-7
View File
@@ -1,7 +0,0 @@
#!/usr/bin/env node
const fs = require('fs')
const objLength = obj => Object.keys(obj).length
fs.readFile(process.argv[2], (err, data) => (err ? console.error(err) : console.log(objLength(JSON.parse(data)))))
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
xdg-open https://devdocs.io/offline
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/env bash
options="Mouse\nHeadphones"
selected="$(echo -e "$options" | fzf)"
case "$selected" in
"Mouse") toggle-bt-device E4:19:21:56:C8:70;;
"Headphones") toggle-bt-device 38:18:4C:D4:74:42;;
esac
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Generate menu of book filenames and save paths
# Preview window contains metadata
function get_book_paths {
find /home/h/doc/books/ -regex '.*\.\(pdf\|epub\|djvu\)' -type f | sort
}
function select_file {
get_book_paths | fzf --delimiter=/ --with-nth=-1
}
function open {
if [ -n "$1" ]; then
echo "Opening \"$1\""
zathura "$1" --fork
else
echo "No file selected"
exit 1
fi
}
case "$1" in
--open) open "$(select_file)" ;;
--help) printf "open \n" >&2 ;;
*) open "$(select_file)" ;;
esac
[[ -n "$selected" ]] && xdg-open "$selected" &> /dev/null & disown
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
fn="/tmp/fontnames.txt"
contains_dash() {
[[ "$1" =~ - ]]
}
update() {
echo "" > "$fn"
font_list=$(fc-list -f "%{fullname}\n")
echo "$font_list" | while read line ; do
first="$(echo "$line" | cut -d',' -f1)"
last="$(echo "$line" | cut -d',' -f2)"
if $(contains_dash "$first"); then
echo "$last" >> "$fn"
else
echo "$first" >> "$fn"
fi
done
}
case "$1" in
--update) update;;
*) cat "$fn" | sort | uniq | awk 'NF' | fzf;;
esac
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
ssh "$(ssh-hosts | fzf)"
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
curl 127.0.0.1:8765 -X POST -d '{"action": "deckNames", "version": 6}' | jq '.result[]' -r
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env bash
# Get Anki review status from exit code
num_reviews="$(cat /tmp/anki-reviews)"
if [ "$num_reviews" -ge "400" ]
then
exit 0
else
exit 1
fi
-2
View File
@@ -1,2 +0,0 @@
pactl load-module module-jack-source connect=0
pactl load-module module-loopback source=jack_in
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env python
import yaml, json, sys
print(yaml.dump(json.load(open(sys.argv[1])), allow_unicode=True), end='')
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env bash
# Pipe into notify-send
#
# e.g. `echo "Hello world" | notify`
read -r msg
notify-send "$msg" "$@"
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
sudo pacman -Qtdq | sudo pacman -Rns -
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
pdfinfo "$1" | head -n 1 | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env python3
# vim: set filetype=python:
"""
Pomodoro timer
- Writes pomodoro timer to temporary file so statusbar can read it
- Notification on session finish
- Notification on break finish
"""
import atexit
import os
from argparse import ArgumentParser
from time import sleep
from plyer import notification
POMO_PATH = os.path.join(
os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")), "pomo"
)
@atexit.register
def clear():
if os.path.exists(POMO_PATH):
os.remove(POMO_PATH)
def format_mins_secs(mins, secs):
return f"{mins:02d}:{secs:02d}"
def make_countdown():
def countdown(duration):
while duration != 0:
mins = duration // 60
secs = duration % 60
time_str = format_mins_secs(mins, secs)
os.system(f'echo -n "{time_str}" > {POMO_PATH}')
sleep(1)
duration -= 1
return countdown
def main(args):
work_duration = args.work_duration * 60
break_duration = args.break_duration * 60
repeats = args.repeats
work_countdown = make_countdown()
break_countdown = make_countdown()
while repeats != 0:
notification.notify(title="Get started")
work_countdown(work_duration)
if break_duration != 0:
notification.notify(title="Time for a break")
break_countdown(break_duration)
notification.notify(title="Break is over, back to work")
repeats -= 1
def handle_signal(signal, frame):
# Wait for clear to finish
clear()
print("Exiting")
exit(0)
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument(
"-w", "--work-duration", type=int, help="Session duration", default=25
)
parser.add_argument(
"-b", "--break-duration", type=int, help="Break duration", default=5
)
parser.add_argument(
"-r", "--repeats", type=int, help="Numer of sessions", default=1
)
parser.add_argument("-c", "--clear", action="store_true", help="Clear timer")
args = parser.parse_args()
main(args)
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/env bash
session="r5rs"
tmux attach-session -t "$session" || tmux new-session -s "$session" \; \
split-window -h -t $session \; \
send-keys -t 1 "nvim -c \"set ft=scheme\"" C-m \; \
send-keys -t 2 "plt-r5rs --no-prim" C-m \; \
select-pane -t 1
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
sed -i -r 's/\[*\]\(\.\/([A-z-]*.md)/\]\(\1/g' *.md
-23
View File
@@ -1,23 +0,0 @@
#!/usr/bin/env bash
function start() {
adb start-server
nohup gnirehtet autorun &> /dev/null &
printf "Started reverse tethering \n"
}
function stop() {
adb kill-server
gnirehtet stop
pkill gnirehtet
printf "Stopped reverse tethering \n"
}
case "$1" in
start) start ;;
stop) stop ;;
restart) stop; start ;;
*) printf "start | stop | restart \n" >&2
exit 1
;;
esac
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env bash
selected_hosts="$(ssh-hosts | fzf -m)"
for host in $selected_hosts; do
echo "Saving $host"
directories="$(ssh "$host" ls | fzf -m)"
for directory in $directories; do
echo "Saving $host:$directory"
ssh "$host" "(tar cvzf - ~/$directory)" > "${host}_${directory}.tar.gz"
done
done
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env bash
# Anki review percentage for statusbar
num_to_review=200
# Get current card count from Anki
count=$(curl -s 127.0.0.1:8765 -X POST -d '{"action": "getNumCardsReviewedToday", "version": 6}' | jq '.result')
if [ -z "$count" ]
then
if [ -s /tmp/anki-reviews ]; then
true
else
echo "-1" > /tmp/anki-reviews
fi
else
echo "$count" > /tmp/anki-reviews
fi
num_reviews=$(cat /tmp/anki-reviews)
echo -n -e " Reviews: $(python3 -c "print('{:.2%}'.format($num_reviews/$num_to_review))") "
-13
View File
@@ -1,13 +0,0 @@
#!/usr/bin/env bash
cap="$(cat /sys/class/power_supply/BAT0/capacity)"
if [ "$cap" -ge 33 ];then
color="\x01"
elif [ "$cap" -ge 10 ]; then
color="\x03"
else
color="\x04"
fi
echo -n -e "$color $cap% \x01"
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
echo -n -e "Week $(date '+%V') $(date '+%a %d %b %H:%M') "
-7
View File
@@ -1,7 +0,0 @@
#!/bin/sh
if grep -xq 'up' /sys/class/net/w*/operstate 2>/dev/null ; then
wifiicon="$(awk '/^\s*w/ { print "WiFi", int($3 * 100 / 70) "% " }' /proc/net/wireless)"
fi
printf " %s%s%s" "$wifiicon" "$(sed "s/down//;s/up/Ethernet/" /sys/class/net/e*/operstate 2>/dev/null)"
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
[ -f "/home/h/.local/share/pomo" ] && cat /home/h/.local/share/pomo || echo ""
-4
View File
@@ -1,4 +0,0 @@
#!/usr/bin/env bash
# Render contents of $HOME/.project if present
[ -f "$HOME/.project" ] && cat "$HOME/.project" || echo ""
-7
View File
@@ -1,7 +0,0 @@
#!/usr/bin/env bash
num_done="$(task end.after:today status:completed count)"
num_pending="$(($(task count status:pending) + $num_done))"
num_waiting="$(($(task count status:waiting)))"
echo -e "Tasks: $num_done/$num_pending+$num_waiting"
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env sh
# Reference: https://github.com/mil-ad/polybar-wireguard
connected_interfaces=$(networkctl | grep -P "\d+ .* wireguard routable" -o | cut -d" " -f2)
if [ -n "$connected_interfaces" ];
then
for interface in $connected_interfaces; do echo "$interface"; done
else
exit 1
fi
-25
View File
@@ -1,25 +0,0 @@
#!/usr/bin/env bash
# Script to create script
if [ -z "$1" ]; then
echo "Usage: script <scriptname>"
exit
fi
path="$HOME/.bin/$1"
# Prevent overwriting existing script
set -o noclobber
# Create script
cat > "$path" << EOF
#!/usr/bin/env bash
EOF
chmod +x "$path"
# Open script in editor on line 3
"$EDITOR" +3 "$path"
-208
View File
@@ -1,208 +0,0 @@
#!/bin/bash
pac_list=(
at
automake
autopep8
base
base-devel
bash-completion
bash-language-server
bc
brightnessctl
chromium
dmidecode
entr
eslint_d
feh
firefox-developer-edition
firefox-tridactyl
firefox-tridactyl-native
firefox-tridactyl-native-debug
fzf
gcc
git
haskell-language-server
haskell-ormolu
hsetroot
htop
httpie
jq
keepassxc
kitty
lua-language-server
make
man-db
man-pages
neovim
nmap
nodejs-lts-jod
pacman-contrib
pandoc-cli
pandoc-crossref
parallel
pass
pkgbuild-language-server
pnpm
ripgrep
sshfs
stylelint
svelte-language-server
tailwindcss-language-server
task
tldr
tmux
tmuxp
tree
tree-sitter-cli
ts-node
typescript-language-server
unzip
vim-language-server
wget
xclip
yaml-language-server
)
aurpac_list=(
hadolint-bin
nvm
nvimpager
paru
tmux-bash-completion-git
ttf-iosevka-term-ss08
vim-plug
vtsls
xbanish
)
install() {
local package="$1"
if pacman -Qi "$package" &> /dev/null; then
echo "$package is already installed"
else
echo "Installing " "$package"
sudo pacman -S --noconfirm --needed "$package"
fi
}
aurpac() {
git clone "https://aur.archlinux.org/$1.git" "$HOME/.build/$1"
}
install_packages() {
announce "Installing packages"
local count
for pac in "${pac_list[@]}" ; do
count=$((count+1))
install "$pac";
done
echo "$count packages installed"
}
install_dotfiles() {
announce "Installing dotfiles"
origin="https://git.hektormisplon.xyz/hektor/dots"
git clone "$origin" "$HOME/dots"
cp -r "$HOME/dots/.git" "$HOME/.git"
git --git-dir="$HOME/.git" config --local status.showUntrackedFiles no
git --git-dir="$HOME/.git" stash -m "[dots]"
git --git-dir="$HOME/.git" stash apply
git --git-dir="$HOME/.git" restore "$HOME"
}
install_aur_packages() {
announce "Installing AUR packages"
local count
for package in "${aurpac_list[@]}" ; do
if pacman -Qi "$1" &> /dev/null; then
echo "$1 is already installed"
else
count=$((count+1))
aurpac "$package" && makepkg -si -D "$HOME/.build/$package"
fi
done
echo "$count AUR packages installed"
}
setup_neovim() {
announce "Setting up NeoVim"
git clone --depth=1 https://github.com/savq/paq-nvim.git \
"${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/paqs/start/paq-nvim
}
setup_keyboard() {
announce "Setting up keyboard"
install "interception-tools"
install "interception-caps2esc"
udevmon_config_contents="\
- JOB: intercept -g \$DEVNODE | caps2esc -m 1 | uinput -d \$DEVNODE
DEVICE:
EVENTS:
EV_KEY: [KEY_CAPSLOCK]"
if [ -f /etc/interception/udevmon.yaml ] && diff -q <(echo "$udevmon_config_contents") /etc/interception/udevmon.yaml; then
echo "udevmon config already exists"
echo "$udevmon_config_contents"
elif [ -f /etc/interception/udevmon.yaml ]; then
echo "interception udevmon.yaml already exists"
cat /etc/interception/udevmon.yaml
echo "verify if this config matches the one below"
echo "$udevmon_config_contents"
else
echo "interception udevmon.yaml does not exist, creating one"
sudo bash -c "echo '$udevmon_config_contents' > /etc/interception/udevmon.yaml"
fi
sudo systemctl enable --now udevmon.service
if pgrep -x caps2esc > /dev/null; then
echo "caps2esc is already running"
else
caps2esc -m 1
fi
}
configure_gnome() {
announce "Configuring Gnome"; setup-gnome
}
setup_firewall() {
announce "Configuring firewalld"
install firewalld
sudo systemctl enable --now firewalld
}
setup_docker() {
announce "Setting up Docker"
install docker
install docker-compose
sudo systemctl enable --now docker.socket
sudo usermod -aG docker "$USER"
echo "User added to docker group, please restart your session"
}
announce() {
local message="$1"
echo " "
echo "[dots] $message"
echo " "
}
confirm() {
local question="$1"
read -r -p "[dots] $question? [y/N]" -n 1
case "$REPLY" in y|Y ) "$2";; * ) echo "Skipping"; esac
}
printf '%s\n' "${pac_list[@]}"
confirm "Install these packages? " install_packages
printf '%s\n' "${aurpac_list[@]}"
confirm "Install these AUR packages? " install_aur_packages
confirm "Setup NeoVim? " setup_neovim
confirm "Install dotfiles? " install_dotfiles
confirm "Setup keyboard? " setup_keyboard
if pacman -Qi "gdm" &> /dev/null; then
confirm "Configure Gnome? " configure_gnome
fi
confirm "Setup firewall? " setup_firewall
confirm "Setup Docker? " setup_docker
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env bash
ssh_hosts="$(grep -E 'Host [a-z0-9\-]*$' ~/.ssh/config | awk '{print $2}')"
echo "$ssh_hosts"
-144
View File
@@ -1,144 +0,0 @@
#!/usr/bin/env python3
import argparse
import json
import subprocess
from collections import defaultdict
def get_task_data():
command = (
"task +PENDING or +WAITING -COMPLETED -DELETED export | "
"jq '[.[] | {uuid: .uuid, id, depends: .depends, description: .description, status: .status }]'"
)
output = subprocess.check_output(command, shell=True)
return json.loads(output)
def parse_task_data(data):
dependency_graph = defaultdict(list)
task_details = {}
dependent_tasks = set()
for task in data:
task_id = task["uuid"]
task_details[task_id] = {
"id": task.get("id", "?"),
"description": task.get("description", "No description"),
"status": task.get("status", "Unknown status"),
}
if task["depends"]:
for dependency in task["depends"]:
dependency_graph[dependency].append(task_id)
dependent_tasks.add(task_id)
root_tasks = set(task_details.keys()) - dependent_tasks
return task_details, dependency_graph, root_tasks
def get_all_parents(task_id, dependency_graph):
return [
parent for parent, children in dependency_graph.items() if task_id in children
]
def build_ascii_dag(
task_id,
task_details,
dependency_graph,
prefix="",
is_last=True,
show_id=True,
visited=None,
):
if visited is None:
visited = set()
if task_id in visited:
return [f"{prefix}{'└── ' if is_last else '├── '}... (cycle detected)"]
visited.add(task_id)
task_info = task_details[task_id]
task_line = f"{prefix}{'└── ' if is_last else '├── '}{task_info['id'] + ': ' if show_id else ''}{task_info['description']} ({task_info['status']})"
lines = [task_line]
children = dependency_graph.get(task_id, [])
for idx, child in enumerate(children):
child_is_last = idx == len(children) - 1
child_prefix = prefix + (" " if is_last else "│ ")
lines.extend(
build_ascii_dag(
child,
task_details,
dependency_graph,
child_prefix,
child_is_last,
show_id,
visited.copy(),
)
)
return lines
def render_dependency_dag(task_details, dependency_graph, root_tasks, show_id):
dag_lines = []
global_visited = set()
def dfs(task_id, prefix="", is_last=True, visited=None):
if visited is None:
visited = set()
if task_id in visited:
return
visited.add(task_id)
global_visited.add(task_id)
task_info = task_details[task_id]
task_line = f"{prefix}{'└── ' if is_last else '├── '}{str(task_info['id']) + ': ' if show_id else ''}{task_info['description']} ({task_info['status']})"
dag_lines.append(task_line)
children = dependency_graph.get(task_id, [])
for idx, child in enumerate(children):
child_is_last = idx == len(children) - 1
child_prefix = prefix + (" " if is_last else "│ ")
dfs(child, child_prefix, child_is_last, visited.copy())
root_tasks_with_children = [
root for root in root_tasks if dependency_graph.get(root, [])
]
for root in sorted(
root_tasks_with_children,
key=lambda x: len(dependency_graph.get(x, [])),
reverse=True,
):
if root not in global_visited:
dfs(root)
dag_lines.append("")
return "\n".join(dag_lines).rstrip()
def main(args):
data = get_task_data()
task_details, dependency_graph, root_tasks = parse_task_data(data)
ascii_dag = render_dependency_dag(
task_details, dependency_graph, root_tasks, show_id=args.show_id
)
print(ascii_dag)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generates a task dependency DAG for Taskwarrior tasks."
)
parser.add_argument(
"--show-id",
action="store_true",
default=False,
help="Include task IDs in the output.",
)
args = parser.parse_args()
main(args)
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -euf -o pipefail
VIM=${VIM:-"vim"}
TMUX=${TMUX:-"tmux"}
FILE=${FILE:-"$(date +%F).tidal"}
SESSION=${SESSION:-"tidal"}
TIDAL_BOOT_PATH=${TIDAL_BOOT_PATH:-""}
GHCI=${GHCI:-""}
args=${*:-$FILE}
# attach if session else create
$TMUX attach-session -t "$SESSION" || $TMUX new-session -s "$SESSION" \; \
split-window -h -t "$SESSION" \; \
send-keys -t 0 "$VIM $args" C-m \; \
send-keys -t 1 "TIDAL_BOOT_PATH=$TIDAL_BOOT_PATH GHCI=$GHCI tidal" C-m \; \
new-window -t "$SESSION":2 -n SuperDirt \; \
send-keys -t 0 "jack_control start && sclang ~/dev/live/scripts/start.scd" C-m \; \
select-window -t 1 \; \
resize-pane -t 1 -x 100 \; \
resize-pane -t 0 -x 125 \; \
select-pane -t 0
-13
View File
@@ -1,13 +0,0 @@
#!/usr/bin/env bash
session="main"
tmux attach-session -t $session || tmux new-session -s $session \; \
rename-window task \; \
send-keys -t 1 "task" C-m \; \
new-window -n zk \; \
send-keys -t 2 "nvim $ZK_PATH/index.md" C-m \; \
new-window -n term \; \
new-window -n music \; \
send-keys -t 4 "echo 'TODO: open music player'" C-m \; \
select-window -t 1 \;
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
status="$(bluetoothctl info "$1" | grep Connected | cut -f 2 -d ':' | cut -f 2 -d ' ')"
if [ "$status" == "yes" ]
then
bluetoothctl disconnect "$1"
else
trust "$1"
bluetoothctl connect "$1"
fi
-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
sudo pacman -Syu
-13
View File
@@ -1,13 +0,0 @@
# My dotfiles
To get started, run the following:
```
bash <(curl -s https://git.hektormisplon.xyz/hektor/dots/raw/branch/master/.bin/setup)
```
Note: will not set up *everything* (e.g. `/etc` configs, dependencies...), but
should get you up and running.
2025-10-04: I am migrating from Arch Linux to NixOS. For this transition, these
dotfiles are currently included as a subtree in my [NixOS repository](https://git.hektormisplon.xyz/hektor/nix).