initialise: dotfiles
This commit is contained in:
commit
66a4a699b0
106 changed files with 6092 additions and 0 deletions
35
.config/README.md
Normal file
35
.config/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# .config
|
||||
|
||||
This directory contains user-specific configuration files conforming to the XDG Base Directory Specification. The configurations map out a modular, Wayland-centric desktop environment alongside core terminal and system utilities.
|
||||
|
||||
## COMPOSITORS AND SESSION MANAGEMENT
|
||||
* **niri:** Scrollable-tiling Wayland compositor.
|
||||
* **sway:** Tiling Wayland compositor and X11/i3 replacement.
|
||||
* **swaylock:** Screen locking utility for Wayland sessions.
|
||||
* **nwg-displays:** Output and display management.
|
||||
|
||||
## TERMINALS AND SHELL ENVIRONMENT
|
||||
* **kitty:** GPU-accelerated terminal emulator.
|
||||
* **foot:** Lightweight Wayland terminal emulator.
|
||||
* **fish:** Interactive shell configuration.
|
||||
* **tmux:** Terminal multiplexer for persistent workspace sessions.
|
||||
* **starship.toml:** Cross-shell prompt configuration.
|
||||
|
||||
## SYSTEM UI AND THEMING
|
||||
* **waybar:** Customisable Wayland status bar.
|
||||
* **fuzzel:** Application launcher for wlroots-based compositors.
|
||||
* **mako:** Lightweight notification daemon.
|
||||
* **nwg-look:** GTK settings editor for maintaining visual consistency.
|
||||
* **qt5ct / qt6ct:** Qt configuration utilities for uniform theming across toolkits.
|
||||
|
||||
## MEDIA AND DOCUMENTS
|
||||
* **mpd:** Music Player Daemon for backend audio management.
|
||||
* **ncmpcpp:** Feature-rich ncurses client for MPD.
|
||||
* **mpv:** Hardware-accelerated command-line media player.
|
||||
* **zathura:** Keyboard-driven document and PDF viewer.
|
||||
|
||||
## SYSTEM UTILITIES AND DEVELOPMENT
|
||||
* **nvim:** Neovim configuration for development and text editing.
|
||||
* **git:** Global version control parameters.
|
||||
* **btop:** Resource monitor for system usage and hardware polling.
|
||||
* **incus:** Local configuration for the container and virtual machine daemon.
|
||||
63
.config/alacritty/alacritty.toml
Normal file
63
.config/alacritty/alacritty.toml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# --- CORE CAPABILITIES ---
|
||||
[env]
|
||||
TERM = "alacritty"
|
||||
|
||||
[window]
|
||||
padding = { x = 8, y = 8 }
|
||||
dynamic_padding = true
|
||||
# sway manages window borders and aesthetics
|
||||
decorations = "none"
|
||||
opacity = 1.0
|
||||
|
||||
[scrolling]
|
||||
history = 10000
|
||||
multiplier = 3
|
||||
|
||||
# --- TYPOGRAPHY ---
|
||||
[font]
|
||||
size = 9.0
|
||||
|
||||
[font.normal]
|
||||
family = "JetBrainsMono Nerd Font"
|
||||
style = "Regular"
|
||||
|
||||
[font.bold]
|
||||
family = "JetBrainsMono Nerd Font"
|
||||
style = "Bold"
|
||||
|
||||
[font.italic]
|
||||
family = "JetBrainsMono Nerd Font"
|
||||
style = "Italic"
|
||||
|
||||
# --- blackK ---
|
||||
[colors.primary]
|
||||
background = "#030303"
|
||||
foreground = "#eeeeee"
|
||||
|
||||
[colors.cursor]
|
||||
text = "#100f0f"
|
||||
cursor = "#cecdc3"
|
||||
|
||||
[colors.normal]
|
||||
black = "#100f0f"
|
||||
red = "#af3029"
|
||||
green = "#66800b"
|
||||
yellow = "#ad8301"
|
||||
blue = "#205ea6"
|
||||
magenta = "#5e409d"
|
||||
cyan = "#24837b"
|
||||
white = "#cecdc3"
|
||||
|
||||
[colors.bright]
|
||||
black = "#282726"
|
||||
red = "#d14d41"
|
||||
green = "#879a39"
|
||||
yellow = "#d0a215"
|
||||
blue = "#4385be"
|
||||
magenta = "#8b7ec8"
|
||||
cyan = "#3aa99f"
|
||||
white = "#e6e4d9"
|
||||
|
||||
# --- KEYBOARD AND MOUSE ---
|
||||
[mouse]
|
||||
hide_when_typing = true
|
||||
286
.config/btop/btop.conf
Normal file
286
.config/btop/btop.conf
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
#? Config file for btop v.1.4.7
|
||||
|
||||
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
|
||||
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
|
||||
color_theme = "/home/dil/.config/btop/themes/flexoki-dark.theme"
|
||||
|
||||
#* If the theme set background should be shown, set to False if you want terminal background transparency.
|
||||
theme_background = true
|
||||
|
||||
#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.
|
||||
truecolor = true
|
||||
|
||||
#* Set to true to force tty mode regardless if a real tty has been detected or not.
|
||||
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
|
||||
force_tty = false
|
||||
|
||||
#* Option to disable presets. Either the default preset, custom presets, or all presets.
|
||||
#* "Off" All presets are enabled.
|
||||
#* "Default" preset is disabled.#* "Custom" presets are disabled.#* "All" presets are disabled.
|
||||
disable_presets = "Off"
|
||||
|
||||
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
|
||||
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
|
||||
#* Use whitespace " " as separator between different presets.
|
||||
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
|
||||
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
|
||||
|
||||
#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists.
|
||||
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
|
||||
vim_keys = false
|
||||
|
||||
#* Disable all mouse events.
|
||||
disable_mouse = false
|
||||
|
||||
#* Rounded corners on boxes, is ignored if TTY mode is ON.
|
||||
rounded_corners = true
|
||||
|
||||
#* Use terminal synchronized output sequences to reduce flickering on supported terminals.
|
||||
terminal_sync = true
|
||||
|
||||
#* Default symbols to use for graph creation, "braille", "block" or "tty".
|
||||
#* "braille" offers the highest resolution but might not be included in all fonts.
|
||||
#* "block" has half the resolution of braille but uses more common characters.
|
||||
#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY.
|
||||
#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view.
|
||||
graph_symbol = "braille"
|
||||
|
||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
||||
graph_symbol_cpu = "default"
|
||||
|
||||
# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty".
|
||||
graph_symbol_gpu = "default"
|
||||
|
||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
||||
graph_symbol_mem = "default"
|
||||
|
||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
||||
graph_symbol_net = "default"
|
||||
|
||||
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
|
||||
graph_symbol_proc = "default"
|
||||
|
||||
#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
|
||||
shown_boxes = "cpu proc"
|
||||
|
||||
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
|
||||
update_ms = 2000
|
||||
|
||||
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
|
||||
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
|
||||
proc_sorting = "cpu lazy"
|
||||
|
||||
#* Reverse sorting order, True or False.
|
||||
proc_reversed = false
|
||||
|
||||
#* Show processes as a tree.
|
||||
proc_tree = false
|
||||
|
||||
#* Use the cpu graph colors in the process list.
|
||||
proc_colors = true
|
||||
|
||||
#* Use a darkening gradient in the process list.
|
||||
proc_gradient = true
|
||||
|
||||
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
|
||||
proc_per_core = false
|
||||
|
||||
#* Show process memory as bytes instead of percent.
|
||||
proc_mem_bytes = true
|
||||
|
||||
#* Show cpu graph for each process.
|
||||
proc_cpu_graphs = true
|
||||
|
||||
#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)
|
||||
proc_info_smaps = false
|
||||
|
||||
#* Show proc box on left side of screen instead of right.
|
||||
proc_left = false
|
||||
|
||||
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
|
||||
proc_filter_kernel = false
|
||||
|
||||
#* Should the process list follow the selected process when detailed view is open.
|
||||
proc_follow_detailed = true
|
||||
|
||||
#* In tree-view, always accumulate child process resources in the parent process.
|
||||
proc_aggregate = false
|
||||
|
||||
#* Should cpu and memory usage display be preserved for dead processes when paused.
|
||||
keep_dead_proc_usage = false
|
||||
|
||||
#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available.
|
||||
#* Select from a list of detected attributes from the options menu.
|
||||
cpu_graph_upper = "Auto"
|
||||
|
||||
#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available.
|
||||
#* Select from a list of detected attributes from the options menu.
|
||||
cpu_graph_lower = "Auto"
|
||||
|
||||
#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off".
|
||||
show_gpu_info = "Auto"
|
||||
|
||||
#* Toggles if the lower CPU graph should be inverted.
|
||||
cpu_invert_lower = true
|
||||
|
||||
#* Set to True to completely disable the lower CPU graph.
|
||||
cpu_single_graph = false
|
||||
|
||||
#* Show cpu box at bottom of screen instead of top.
|
||||
cpu_bottom = false
|
||||
|
||||
#* Shows the system uptime in the CPU box.
|
||||
show_uptime = true
|
||||
|
||||
#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo.
|
||||
show_cpu_watts = true
|
||||
|
||||
#* Show cpu temperature.
|
||||
check_temp = true
|
||||
|
||||
#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.
|
||||
cpu_sensor = "Auto"
|
||||
|
||||
#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.
|
||||
show_coretemp = true
|
||||
|
||||
#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.
|
||||
#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.
|
||||
#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.
|
||||
#* Example: "4:0 5:1 6:3"
|
||||
cpu_core_map = ""
|
||||
|
||||
#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine".
|
||||
temp_scale = "celsius"
|
||||
|
||||
#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.
|
||||
base_10_sizes = false
|
||||
|
||||
#* Show CPU frequency.
|
||||
show_cpu_freq = true
|
||||
|
||||
#* How to calculate CPU frequency, available values: "first", "range", "lowest", "highest" and "average".
|
||||
freq_mode = "first"
|
||||
|
||||
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
|
||||
#* Special formatting: /host = hostname | /user = username | /uptime = system uptime
|
||||
clock_format = "%X"
|
||||
|
||||
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
|
||||
background_update = true
|
||||
|
||||
#* Custom cpu model name, empty string to disable.
|
||||
custom_cpu_name = ""
|
||||
|
||||
#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ".
|
||||
#* Only disks matching the filter will be shown. Prepend exclude= to only show disks not matching the filter. Examples: disk_filter="/boot /home/user", disks_filter="exclude=/boot /home/user"
|
||||
disks_filter = ""
|
||||
|
||||
#* Show graphs instead of meters for memory values.
|
||||
mem_graphs = true
|
||||
|
||||
#* Show mem box below net box instead of above.
|
||||
mem_below_net = false
|
||||
|
||||
#* Count ZFS ARC in cached and available memory.
|
||||
zfs_arc_cached = true
|
||||
|
||||
#* If swap memory should be shown in memory box.
|
||||
show_swap = true
|
||||
|
||||
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
|
||||
swap_disk = true
|
||||
|
||||
#* If mem box should be split to also show disks info.
|
||||
show_disks = true
|
||||
|
||||
#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.
|
||||
only_physical = true
|
||||
|
||||
#* Read disks list from /etc/fstab. This also disables only_physical.
|
||||
use_fstab = true
|
||||
|
||||
#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)
|
||||
zfs_hide_datasets = false
|
||||
|
||||
#* Set to true to show available disk space for privileged users.
|
||||
disk_free_priv = false
|
||||
|
||||
#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.
|
||||
show_io_stat = true
|
||||
|
||||
#* Toggles io mode for disks, showing big graphs for disk read/write speeds.
|
||||
io_mode = false
|
||||
|
||||
#* Set to True to show combined read/write io graphs in io mode.
|
||||
io_graph_combined = false
|
||||
|
||||
#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ".
|
||||
#* Example: "/mnt/media:100 /:20 /boot:1".
|
||||
io_graph_speeds = ""
|
||||
|
||||
#* Swap the positions of the upload and download speed graphs. When true, upload will be on top.
|
||||
swap_upload_download = false
|
||||
|
||||
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
|
||||
net_download = 100
|
||||
|
||||
net_upload = 100
|
||||
|
||||
#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
|
||||
net_auto = true
|
||||
|
||||
#* Sync the auto scaling for download and upload to whichever currently has the highest scale.
|
||||
net_sync = true
|
||||
|
||||
#* Starts with the Network Interface specified here.
|
||||
net_iface = ""
|
||||
|
||||
#* "True" shows bitrates in base 10 (Kbps, Mbps). "False" shows bitrates in binary sizes (Kibps, Mibps, etc.). "Auto" uses base_10_sizes.
|
||||
base_10_bitrate = "Auto"
|
||||
|
||||
#* Show battery stats in top right if battery is present.
|
||||
show_battery = true
|
||||
|
||||
#* Which battery to use if multiple are present. "Auto" for auto detection.
|
||||
selected_battery = "Auto"
|
||||
|
||||
#* Show power stats of battery next to charge indicator.
|
||||
show_battery_watts = true
|
||||
|
||||
#* Set loglevel for "~/.local/state/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
|
||||
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
|
||||
log_level = "WARNING"
|
||||
|
||||
#* Automatically save current settings to config file on exit.
|
||||
save_config_on_exit = true
|
||||
|
||||
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
|
||||
nvml_measure_pcie_speeds = true
|
||||
|
||||
#* Measure PCIe throughput on AMD cards, may impact performance on certain cards.
|
||||
rsmi_measure_pcie_speeds = true
|
||||
|
||||
#* Horizontally mirror the GPU graph.
|
||||
gpu_mirror_graph = true
|
||||
|
||||
#* Set which GPU vendors to show. Available values are "nvidia amd intel apple"
|
||||
shown_gpus = "nvidia amd intel"
|
||||
|
||||
#* Custom gpu0 model name, empty string to disable.
|
||||
custom_gpu_name0 = ""
|
||||
|
||||
#* Custom gpu1 model name, empty string to disable.
|
||||
custom_gpu_name1 = ""
|
||||
|
||||
#* Custom gpu2 model name, empty string to disable.
|
||||
custom_gpu_name2 = ""
|
||||
|
||||
#* Custom gpu3 model name, empty string to disable.
|
||||
custom_gpu_name3 = ""
|
||||
|
||||
#* Custom gpu4 model name, empty string to disable.
|
||||
custom_gpu_name4 = ""
|
||||
|
||||
#* Custom gpu5 model name, empty string to disable.
|
||||
custom_gpu_name5 = ""
|
||||
27
.config/fuzzel/fuzzel.ini
Normal file
27
.config/fuzzel/fuzzel.ini
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# THE NOIR THEME
|
||||
|
||||
[main]
|
||||
# fuzzel requires wrapper scripts for dynamic per-mode prompts
|
||||
prompt="APP: "
|
||||
font=JetBrainsMono Nerd Font:size=8,Noto Color Emoji:size=8
|
||||
# character width dictates the horizontal size
|
||||
width=50
|
||||
lines=8
|
||||
icons-enabled=yes
|
||||
terminal=footclient
|
||||
# disable the generic exit button to maintain the sharp aesthetic
|
||||
exit-on-keyboard-focus-loss=yes
|
||||
|
||||
[colors]
|
||||
# flexoki ink translations
|
||||
background=100f0fff
|
||||
text=cecdc3ff
|
||||
match=8b7ec8ff
|
||||
selection=282726ff
|
||||
selection-text=cecdc3ff
|
||||
selection-match=8b7ec8ff
|
||||
border=343331ff
|
||||
|
||||
[border]
|
||||
width=1
|
||||
radius=0
|
||||
60
.config/git/base.gitconfig
Normal file
60
.config/git/base.gitconfig
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
[gpg]
|
||||
format = ssh
|
||||
|
||||
[core]
|
||||
editor = nvim
|
||||
autocrlf = input
|
||||
pager = delta
|
||||
|
||||
[init]
|
||||
defaultBranch = main
|
||||
|
||||
[color]
|
||||
ui = auto
|
||||
|
||||
[pull]
|
||||
rebase = true
|
||||
|
||||
[push]
|
||||
autoSetupRemote = true
|
||||
|
||||
[merge]
|
||||
tool = nvimdiff
|
||||
|
||||
[mergetool]
|
||||
prompt = false
|
||||
keepBackup = false
|
||||
|
||||
[interactive]
|
||||
diffFilter = delta --color-only
|
||||
|
||||
[delta]
|
||||
navigate = true
|
||||
line-numbers = true
|
||||
side-by-side = true
|
||||
|
||||
[commit]
|
||||
gpgsign = true
|
||||
|
||||
[tag]
|
||||
gpgsign = true
|
||||
|
||||
[alias]
|
||||
s = status
|
||||
st = status -sb
|
||||
lg = log --oneline --graph --decorate --all
|
||||
|
||||
aa = add -A
|
||||
cm = commit -m
|
||||
am = commit -am
|
||||
|
||||
ps = push
|
||||
pl = pull
|
||||
|
||||
lo = log --oneline --graph --decorate
|
||||
|
||||
unstage = reset HEAD --
|
||||
last = log -1 HEAD
|
||||
oops = commit --amend --no-edit
|
||||
|
||||
dot = "!git --git-dir=$HOME/.dotfiles --work-tree=$HOME"
|
||||
41
.config/kitty/kitty.conf
Normal file
41
.config/kitty/kitty.conf
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# WINDOW
|
||||
window_padding_width 8
|
||||
hide_window_decorations yes
|
||||
background_opacity 1.0
|
||||
|
||||
# SCROLLBACK
|
||||
scrollback_lines 10000
|
||||
mouse_hide_wait -1
|
||||
|
||||
# FONT
|
||||
font_size 9
|
||||
font_family JetBrainsMono Nerd Font
|
||||
bold_font JetBrainsMono Nerd Font Bold
|
||||
italic_font JetBrainsMono Nerd Font Italic
|
||||
bold_italic_font JetBrainsMono Nerd Font Bold Italic
|
||||
|
||||
# -COLOURS
|
||||
foreground #cecdc3
|
||||
background #000000
|
||||
cursor_text_color #100f0f
|
||||
|
||||
color0 #100f0f
|
||||
color1 #af3029
|
||||
color2 #66800b
|
||||
color3 #ad8301
|
||||
color4 #205ea6
|
||||
color5 #5e409d
|
||||
color6 #24837b
|
||||
color7 #cecdc3
|
||||
|
||||
color8 #282726
|
||||
color9 #d14d41
|
||||
color10 #879a39
|
||||
color11 #d0a215
|
||||
color12 #4385be
|
||||
color13 #8b7ec8
|
||||
color14 #3aa99f
|
||||
color15 #e6e4d9
|
||||
|
||||
# SHELL
|
||||
#shell /home/dil/.local/bin/tmux-wrapper
|
||||
0
.config/lazygit/config.yml
Normal file
0
.config/lazygit/config.yml
Normal file
41
.config/mako/config
Normal file
41
.config/mako/config
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# --- THE VOID: MAKO EDITION ---
|
||||
|
||||
# Font & Text
|
||||
font=JetBrainsMono Nerd Font 12
|
||||
text-color=#ffffff
|
||||
width=350
|
||||
height=150
|
||||
margin=10
|
||||
padding=15
|
||||
|
||||
# The Box (Hard edges, glass background)
|
||||
background-color=#000000cc
|
||||
border-size=1
|
||||
border-color=#333333
|
||||
border-radius=0
|
||||
|
||||
# Icons
|
||||
icons=1
|
||||
max-icon-size=48
|
||||
icon-location=left
|
||||
|
||||
# Behavior
|
||||
default-timeout=5000
|
||||
ignore-timeout=1
|
||||
layer=overlay
|
||||
anchor=top-right
|
||||
|
||||
# --- URGENCY OVERRIDES ---
|
||||
|
||||
# Critical alerts (Inverted: White bg, Black text, just like your Critical Battery)
|
||||
[urgency=critical]
|
||||
background-color=#ffffff
|
||||
text-color=#000000
|
||||
border-color=#ffffff
|
||||
|
||||
# Low priority (Dimmer)
|
||||
[urgency=low]
|
||||
default-timeout=1500
|
||||
background-color=#1a1a1a
|
||||
text-color=#888888
|
||||
border-color=#333333
|
||||
BIN
.config/mpd/database
Normal file
BIN
.config/mpd/database
Normal file
Binary file not shown.
30
.config/mpd/mpd.conf
Normal file
30
.config/mpd/mpd.conf
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# FILES
|
||||
music_directory "~/music"
|
||||
playlist_directory "~/.config/mpd/playlists"
|
||||
db_file "~/.config/mpd/database"
|
||||
log_file "syslog"
|
||||
pid_file "/tmp/mpd.pid"
|
||||
state_file "~/.config/mpd/state"
|
||||
|
||||
# NETWORK
|
||||
bind_to_address "127.0.0.1"
|
||||
port "6600"
|
||||
|
||||
# AUDIO OUTPUT (PipeWire/Pulse)
|
||||
audio_output {
|
||||
type "pulse"
|
||||
name "PulseAudio/PipeWire"
|
||||
}
|
||||
|
||||
# VISUALISER FEED
|
||||
audio_output {
|
||||
type "fifo"
|
||||
name "visualiser"
|
||||
path "/tmp/mpd.fifo"
|
||||
format "44100:16:2"
|
||||
enabled "yes"
|
||||
}
|
||||
|
||||
# TWEAKS
|
||||
auto_update "yes"
|
||||
restore_paused "yes"
|
||||
43
.config/mpv/input.conf
Normal file
43
.config/mpv/input.conf
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# --- VIM NAVIGATION (Time) ---
|
||||
h seek -5 # Left: Back 5s
|
||||
l seek 5 # Right: Forward 5s
|
||||
Shift+h seek -60 # H: Back 1 min
|
||||
Shift+l seek 60 # L: Forward 1 min
|
||||
|
||||
# --- VIM AUDIO (Volume) ---
|
||||
k add volume 5 # Up: Volume Up
|
||||
j add volume -5 # Down: Volume Down
|
||||
m cycle mute # Mute
|
||||
|
||||
# --- TRACK SELECTION (The Missing Part) ---
|
||||
a cycle audio # Next Audio Track
|
||||
Shift+a cycle audio down # Previous Audio Track
|
||||
c cycle sub # Next Subtitle (Captions)
|
||||
Shift+c cycle sub down # Previous Subtitle
|
||||
v cycle sub-visibility # Hide/Show Subtitles
|
||||
|
||||
# --- SYNC CORRECTION (Because rips aren't perfect) ---
|
||||
z add sub-delay -0.1 # Shift subs earlier
|
||||
Shift+z add sub-delay +0.1 # Shift subs later
|
||||
- add audio-delay -0.1 # Shift audio earlier
|
||||
= add audio-delay +0.1 # Shift audio later
|
||||
|
||||
# --- PLAYBACK CONTROL ---
|
||||
SPACE cycle pause # Play/Pause
|
||||
. frame-step # Next Frame
|
||||
, frame-back-step # Previous Frame
|
||||
> add speed 0.1 # Speed Up
|
||||
< add speed -0.1 # Slow Down
|
||||
BS set speed 1.0 # Reset Speed (Backspace)
|
||||
|
||||
# --- POSITION & VIEW ---
|
||||
0 seek 0 absolute # Go to Start
|
||||
$ seek 100 absolute-percent # Go to End
|
||||
f cycle fullscreen # Toggle Fullscreen
|
||||
s screenshot # Take Screenshot
|
||||
S screenshot video # Screenshot (No Subs)
|
||||
|
||||
# --- UTILS ---
|
||||
q quit-watch-later # Quit (Save position)
|
||||
Shift+q quit # Quit (Discard position)
|
||||
i script-binding stats/display-stats-toggle # Show Info
|
||||
25
.config/mpv/mpv.conf
Normal file
25
.config/mpv/mpv.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# VIDEO AND PERFORMANCE
|
||||
# use gpu for video output
|
||||
vo=gpu
|
||||
# enable safe hardware decoding to reduce cpu usage
|
||||
hwdec=auto-safe
|
||||
# keep the player window open after the file finishes
|
||||
keep-open=yes
|
||||
|
||||
# AUDIO BEHAVIOUR
|
||||
# default startup volume
|
||||
volume=100
|
||||
# maximum volume boost
|
||||
volume-max=150
|
||||
# maintain original audio pitch when changing playback speed
|
||||
audio-pitch-correction=yes
|
||||
|
||||
# SUBTITLES AND LANGUAGES
|
||||
# prefer english audio
|
||||
alang=en,eng
|
||||
# prefer english subtitles
|
||||
slang=en,eng
|
||||
# set subtitle text colour to white
|
||||
sub-color="#FFFFFFFF"
|
||||
# scale subtitles relative to window size
|
||||
sub-scale-with-window=yes
|
||||
30
.config/ncmpcpp/config
Normal file
30
.config/ncmpcpp/config
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# --- CONNECTION ---
|
||||
mpd_host = "127.0.0.1"
|
||||
mpd_port = "6600"
|
||||
|
||||
# --- VISUALIZER (The Cool Part) ---
|
||||
visualizer_data_source = "/tmp/mpd.fifo"
|
||||
visualizer_output_name = "my_fifo"
|
||||
visualizer_in_stereo = "yes"
|
||||
visualizer_type = "spectrum" # Options: spectrum, wave, ellipse
|
||||
visualizer_look = "●●" # The characters for the graph
|
||||
|
||||
# --- UI TWEAKS ---
|
||||
playlist_display_mode = "columns" # Classic table view
|
||||
browser_display_mode = "columns"
|
||||
search_engine_display_mode = "columns"
|
||||
playlist_editor_display_mode = "columns"
|
||||
autocenter_mode = "yes"
|
||||
centered_cursor = "yes"
|
||||
user_interface = "alternative" # Cleaner look
|
||||
|
||||
# --- COLORS (Arch-Droid Style) ---
|
||||
colors_enabled = "yes"
|
||||
main_window_color = "default"
|
||||
header_window_color = "cyan"
|
||||
volume_color = "green"
|
||||
progressbar_color = "black"
|
||||
progressbar_elapsed_color = "green"
|
||||
|
||||
# --- SEEKING ---
|
||||
seek_time = "5"
|
||||
22
.config/niri/autostart.kdl
Normal file
22
.config/niri/autostart.kdl
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// STARTUP DAEMONS
|
||||
//spawn-at-startup "/usr/lib/polkit-kde-authentication-agent-1"
|
||||
spawn-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
|
||||
spawn-at-startup "swaybg" "-i" "/home/sgbv/Pictures/turtle.jpg" "-m" "fill"
|
||||
spawn-at-startup "waybar"
|
||||
spawn-at-startup "mako"
|
||||
spawn-at-startup "nm-applet"
|
||||
spawn-at-startup "blueman-applet"
|
||||
spawn-at-startup "wireplumber"
|
||||
spawn-at-startup "pipewire"
|
||||
spawn-at-startup "pipewire-pulse"
|
||||
spawn-at-startup "sh" "-c" "mkdir -p /run/user/1000/keyring && gnome-keyring-daemon --start --components=secrets"
|
||||
|
||||
// CLIPBOARD TRACKING
|
||||
spawn-at-startup "wl-paste" "--type" "text" "--watch" "cliphist" "store"
|
||||
spawn-at-startup "wl-paste" "--type" "image" "--watch" "cliphist" "store"
|
||||
|
||||
// IDLE MANAGEMENT
|
||||
spawn-at-startup "bash" "-c" "swayidle -w timeout 150 'brightnessctl -s set 10' resume 'brightnessctl -r' timeout 200 'swaylock -f -c 000000' timeout 250 'niri msg action power-off-monitors' resume 'niri msg action power-on-monitors' timeout 1800 'systemctl suspend' before-sleep 'swaylock -f -c 000000'"
|
||||
|
||||
// BRIGHTNESS
|
||||
spawn-at-startup "~/.local/bin/restore-brightness"
|
||||
95
.config/niri/binds.kdl
Normal file
95
.config/niri/binds.kdl
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// KEYBINDINGS
|
||||
binds {
|
||||
Mod+Return cooldown-ms=500 { spawn "alacritty"; }
|
||||
Mod+Shift+Return cooldown-ms=500 { spawn "code"; }
|
||||
Alt+Return cooldown-ms=500 {spawn "virt-manager"; }
|
||||
Mod+Space { spawn "fuzzel"; }
|
||||
Mod+Shift+Space { spawn "bash" "-c" "$HOME/.local/bin/fuzzel-run"; }
|
||||
|
||||
// APPLICATION LAUNCHERS
|
||||
Mod+B cooldown-ms=500 { spawn "librewolf"; }
|
||||
Mod+Shift+B cooldown-ms=500 { spawn "firefox"; }
|
||||
Mod+Alt+B cooldown-ms=500 { spawn "tor-browser"; }
|
||||
Mod+O cooldown-ms=500 { spawn "obsidian"; }
|
||||
Mod+A cooldown-ms=500 { spawn "enteauth"; }
|
||||
Mod+Shift+A cooldown-ms=500 { spawn "keepassxc"; }
|
||||
Mod+E cooldown-ms=500 { spawn "alacritty" "-e" "yazi"; }
|
||||
Mod+Shift+E cooldown-ms=500 { spawn "nautilus"; }
|
||||
Mod+G cooldown-ms=500 { spawn "azahar"; }
|
||||
Mod+T cooldown-ms=500 { spawn "telegram-desktop"; }
|
||||
|
||||
// TUI SYSTEM WORKPLACES
|
||||
Mod+D cooldown-ms=500 { spawn "alacritty" "-e" "btop"; }
|
||||
Mod+Shift+Y cooldown-ms=500 { spawn "alacritty" "-e" "lazygit"; }
|
||||
Mod+M cooldown-ms=500 { spawn "alacritty" "-e" "ncmpcpp"; }
|
||||
Mod+Shift+T cooldown-ms=500 { spawn "alacritty" "-e" "tut"; }
|
||||
|
||||
// HARDWARE AND OVERLAYS
|
||||
Mod+Shift+C { spawn "/home/sgbv/.local/bin/pick"; }
|
||||
Mod+W { spawn-sh "killall -SIGUSR1 waybar"; }
|
||||
|
||||
// WINDOW MECHANICS
|
||||
Mod+Q repeat=false { close-window; }
|
||||
Mod+Shift+Q { quit skip-confirmation=true; }
|
||||
|
||||
// SPATIAL NAVIGATION
|
||||
Mod+H { focus-column-left; }
|
||||
Mod+L { focus-column-right; }
|
||||
Mod+J { focus-window-down; }
|
||||
Mod+K { focus-window-up; }
|
||||
|
||||
Mod+Shift+H { move-column-left; }
|
||||
Mod+Shift+L { move-column-right; }
|
||||
Mod+Shift+J { move-window-down; }
|
||||
Mod+Shift+K { move-window-up; }
|
||||
|
||||
Mod+U { focus-workspace-down; }
|
||||
Mod+I { focus-workspace-up; }
|
||||
Mod+Shift+U { move-column-to-workspace-down; }
|
||||
Mod+Shift+I { move-column-to-workspace-up; }
|
||||
|
||||
// INTERFACE GEOMETRY
|
||||
Mod+Minus { set-column-width "-10%"; }
|
||||
Mod+Equal { set-column-width "+10%"; }
|
||||
Mod+Shift+Minus { set-window-height "-10%"; }
|
||||
Mod+Shift+Equal { set-window-height "+10%"; }
|
||||
|
||||
Mod+Control+L { consume-window-into-column; }
|
||||
Mod+Control+H { expel-window-from-column; }
|
||||
|
||||
Mod+F { maximize-column; }
|
||||
Mod+Shift+F { fullscreen-window; }
|
||||
Mod+V { toggle-window-floating; }
|
||||
|
||||
// NEWLY BOUND ACTIONS
|
||||
Mod+Slash { show-hotkey-overlay; }
|
||||
Mod+Tab { switch-preset-column-width; }
|
||||
Mod+Shift+V { switch-focus-between-floating-and-tiling; }
|
||||
Mod+R { open-overview; }
|
||||
|
||||
// HARDWARE MEDIA ROUTING
|
||||
Mod+Alt+K { spawn "~/.local/bin/volume-notify" "+10%"; }
|
||||
Mod+Alt+J { spawn "~/.local/bin/volume-notify" "-10%"; }
|
||||
XF86AudioRaiseVolume { spawn "~/.local/bin/volume-notify" "+5%"; }
|
||||
XF86AudioLowerVolume { spawn "~/.local/bin/volume-notify" "-5%"; }
|
||||
XF86AudioMute { spawn "~/.local/bin/volume-notify" "toggle"; }
|
||||
XF86AudioMicMute { spawn "pamixer" "--default-source" "-t"; }
|
||||
XF86MonBrightnessUp { spawn "~/.local/bin/brightness-notify" "+5%"; }
|
||||
XF86MonBrightnessDown { spawn "~/.local/bin/brightness-notify" "5%-"; }
|
||||
Mod+Alt+I { spawn "~/.local/bin/brightness-notify" "+10%"; }
|
||||
Mod+Alt+U { spawn "~/.local/bin/brightness-notify" "10%-"; }
|
||||
|
||||
|
||||
// MPC BINDS
|
||||
Mod+Alt+Space { spawn "mpc" "toggle"; }
|
||||
Mod+Alt+L { spawn "mpc" "next"; }
|
||||
Mod+Alt+H { spawn "mpc" "prev"; }
|
||||
Mod+Alt+Shift+K { spawn "mpc" "stop"; }
|
||||
|
||||
Mod+Print { spawn "bash" "-c" "grim -g \"$(slurp)\" - | wl-copy"; }
|
||||
Print { spawn "bash" "-c" "grim - | wl-copy"; }
|
||||
Alt+Print { screenshot-window; }
|
||||
Mod+C { spawn "bash" "-c" "cliphist list | fuzzel -d | cliphist decode | wl-copy"; }
|
||||
Mod+Period { spawn "bash" "-c" "rofimoji --selector fuzzel --action type --clipboarder wl-copy"; }
|
||||
Mod+Escape { spawn "~/.local/bin/power"; }
|
||||
}
|
||||
52
.config/niri/config.kdl
Normal file
52
.config/niri/config.kdl
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// HARDWARE OUTPUT
|
||||
output "eDP-1" {
|
||||
scale 1.0
|
||||
}
|
||||
|
||||
// INPUT CONFIGURATION
|
||||
input {
|
||||
keyboard {
|
||||
xkb {
|
||||
//layout "de"
|
||||
layout "gb"
|
||||
}
|
||||
}
|
||||
touchpad {
|
||||
tap
|
||||
natural-scroll
|
||||
}
|
||||
|
||||
focus-follows-mouse max-scroll-amount="0%"
|
||||
}
|
||||
|
||||
// APPEARANCE AND SPATIAL LAYOUT
|
||||
layout {
|
||||
gaps 2
|
||||
center-focused-column "never"
|
||||
always-center-single-column
|
||||
default-column-width { proportion 0.5; }
|
||||
|
||||
border {
|
||||
width 0.5
|
||||
active-color "rgb(61, 76, 7)"
|
||||
inactive-color "rgb(26, 30, 12)"
|
||||
}
|
||||
|
||||
shadow {
|
||||
off
|
||||
}
|
||||
|
||||
focus-ring {
|
||||
off
|
||||
}
|
||||
}
|
||||
|
||||
// MODULE INCLUSIONS
|
||||
// merge external logical blocks
|
||||
include "autostart.kdl"
|
||||
include "rules.kdl"
|
||||
include "binds.kdl"
|
||||
|
||||
// MISCELLANEOUS CONFIGURATION
|
||||
// disable saving screenshots to disk
|
||||
screenshot-path null
|
||||
154
.config/niri/rules.kdl
Normal file
154
.config/niri/rules.kdl
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// Floating windows
|
||||
window-rule {
|
||||
match app-id="blueman-manager"
|
||||
match app-id="nm-connection-editor"
|
||||
|
||||
open-floating true
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="firefox" title="Picture-in-Picture"
|
||||
match app-id="librewolf" title="Picture-in-Picture"
|
||||
|
||||
open-floating true
|
||||
default-floating-position x=1466 y=44
|
||||
|
||||
min-width 439
|
||||
min-height 248
|
||||
max-width 439
|
||||
max-height 248
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="io.ente.auth"
|
||||
|
||||
open-floating true
|
||||
default-floating-position x=1533 y=332
|
||||
|
||||
min-width 352
|
||||
min-height 447
|
||||
max-width 352
|
||||
max-height 447
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="org.keepassxc.KeePassXC"
|
||||
|
||||
open-floating true
|
||||
default-floating-position x=1092 y=43
|
||||
|
||||
min-width 808
|
||||
min-height 535
|
||||
max-width 808
|
||||
max-height 535
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="blueman-manager"
|
||||
open-on-workspace "main"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="nm-connection-editor"
|
||||
open-on-workspace "main"
|
||||
}
|
||||
|
||||
// Maximized windows
|
||||
window-rule {
|
||||
match app-id="org.azahar_emu.Azahar"
|
||||
open-maximized-to-edges true
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="Tor Browser"
|
||||
open-maximized-to-edges true
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="Tor Browser"
|
||||
open-maximized-to-edges true
|
||||
}
|
||||
|
||||
// Column widths
|
||||
window-rule {
|
||||
match app-id="Alacritty"
|
||||
default-column-width { proportion 0.60; }
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="org.gnome.Nautilus"
|
||||
default-column-width { proportion 0.60; }
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="code"
|
||||
default-column-width { proportion 0.60; }
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="firefox"
|
||||
default-column-width { proportion 0.40; }
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="librewolf"
|
||||
default-column-width { proportion 0.40; }
|
||||
}
|
||||
|
||||
// Privacy
|
||||
window-rule {
|
||||
match app-id="Tor Browser"
|
||||
|
||||
block-out-from "screencast"
|
||||
}
|
||||
|
||||
// Workspace placement
|
||||
window-rule {
|
||||
match app-id="Alacritty"
|
||||
open-on-workspace "main"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="org.gnome.Nautilus"
|
||||
open-on-workspace "main"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="code"
|
||||
open-on-workspace "main"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="firefox"
|
||||
open-on-workspace "main"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="librewolf"
|
||||
open-on-workspace "main"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="md.Obsidian"
|
||||
open-on-workspace "gaming"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="org.azahar_emu.Azahar"
|
||||
open-on-workspace "gaming"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="org.telegram.desktop"
|
||||
open-on-workspace "private"
|
||||
}
|
||||
|
||||
window-rule {
|
||||
match app-id="Tor Browser"
|
||||
open-on-workspace "private"
|
||||
}
|
||||
|
||||
// Workspaces
|
||||
workspace "main"
|
||||
workspace "gaming"
|
||||
workspace "private"
|
||||
157
.config/nvim/init.lua
Normal file
157
.config/nvim/init.lua
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
-- CORE SETTINGS
|
||||
-- define leader key
|
||||
vim.g.mapleader = " "
|
||||
vim.g.maplocalleader = " "
|
||||
|
||||
-- editor behaviour
|
||||
vim.opt.number = true
|
||||
vim.opt.relativenumber = true
|
||||
vim.opt.mouse = "a"
|
||||
vim.opt.ignorecase = true
|
||||
vim.opt.smartcase = true
|
||||
vim.opt.updatetime = 250
|
||||
vim.opt.termguicolors = true
|
||||
|
||||
-- wayland clipboard integration
|
||||
vim.opt.clipboard = "unnamedplus"
|
||||
|
||||
-- regional standardisation
|
||||
vim.opt.spelllang = "en_gb"
|
||||
|
||||
-- PACKAGE MANAGER
|
||||
-- bootstrap lazy nvim
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not vim.loop.fs_stat(lazypath) then
|
||||
vim.fn.system({
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"https://github.com/folke/lazy.nvim.git",
|
||||
"--branch=stable",
|
||||
lazypath,
|
||||
})
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
|
||||
-- PLUGINS
|
||||
-- load flexoki and core utilities
|
||||
require("lazy").setup({
|
||||
{
|
||||
"kepano/flexoki-neovim",
|
||||
name = "flexoki",
|
||||
priority = 1000,
|
||||
config = function()
|
||||
vim.cmd("colorscheme flexoki-dark")
|
||||
-- override specific highlights to match your sway environment
|
||||
vim.api.nvim_set_hl(0, "Normal", { bg = "#100F0F" })
|
||||
vim.api.nvim_set_hl(0, "NormalFloat", { bg = "#100F0F" })
|
||||
end,
|
||||
},
|
||||
{
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
build = ":TSUpdate",
|
||||
config = function()
|
||||
-- INSTALLATION
|
||||
-- trigger parser downloads
|
||||
require("nvim-treesitter").install({ "lua", "bash", "markdown", "markdown_inline" })
|
||||
|
||||
-- HIGHLIGHTING
|
||||
-- activate native treesitter engine per filetype
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = { "lua", "sh", "markdown" },
|
||||
callback = function()
|
||||
vim.treesitter.start()
|
||||
end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
"hrsh7th/nvim-cmp",
|
||||
dependencies = {
|
||||
"hrsh7th/cmp-path",
|
||||
"hrsh7th/cmp-buffer",
|
||||
},
|
||||
config = function()
|
||||
local cmp = require("cmp")
|
||||
|
||||
-- COMPLETION ENGINE SETUP
|
||||
-- initialise strictly local sources for paths and buffer text
|
||||
cmp.setup({
|
||||
mapping = cmp.mapping.preset.insert({
|
||||
["<C-b>"] = cmp.mapping.scroll_docs(-4),
|
||||
["<C-f>"] = cmp.mapping.scroll_docs(4),
|
||||
["<C-Space>"] = cmp.mapping.complete(),
|
||||
["<C-e>"] = cmp.mapping.abort(),
|
||||
["<CR>"] = cmp.mapping.confirm({ select = true }),
|
||||
}),
|
||||
sources = cmp.config.sources({
|
||||
{ name = "path" },
|
||||
{ name = "buffer" },
|
||||
})
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
"akinsho/bufferline.nvim",
|
||||
dependencies = { "nvim-tree/nvim-web-devicons" },
|
||||
config = function()
|
||||
-- BUFFER VISUALISATION
|
||||
-- render background buffers as a top navigational bar
|
||||
require("bufferline").setup({
|
||||
options = {
|
||||
mode = "buffers",
|
||||
numbers = "ordinal",
|
||||
show_buffer_close_icons = false,
|
||||
show_close_icon = false,
|
||||
separator_style = "thin",
|
||||
enforce_regular_tabs = true,
|
||||
always_show_bufferline = true,
|
||||
}
|
||||
})
|
||||
|
||||
-- KEY MAPS
|
||||
-- bind shift-h and shift-l to cycle through the open buffers
|
||||
vim.keymap.set("n", "<S-h>", "<cmd>BufferLineCyclePrev<CR>", { desc = "previous buffer" })
|
||||
vim.keymap.set("n", "<S-l>", "<cmd>BufferLineCycleNext<CR>", { desc = "next buffer" })
|
||||
|
||||
-- buffer termination
|
||||
-- bind leader-x to safely unload the current buffer from memory
|
||||
vim.keymap.set("n", "<leader>x", "<cmd>bdelete<CR>", { desc = "close current buffer" })
|
||||
end,
|
||||
},
|
||||
{
|
||||
"MeanderingProgrammer/render-markdown.nvim",
|
||||
dependencies = {
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
"nvim-tree/nvim-web-devicons"
|
||||
},
|
||||
opts = {
|
||||
heading = {
|
||||
sign = false,
|
||||
icons = { ' ', ' ', ' ', ' ', ' ', ' ' },
|
||||
},
|
||||
code = {
|
||||
sign = false,
|
||||
width = "block",
|
||||
right_pad = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"folke/zen-mode.nvim",
|
||||
config = function()
|
||||
require("zen-mode").setup({
|
||||
window = {
|
||||
width = 80,
|
||||
options = {
|
||||
number = false,
|
||||
relativenumber = false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
-- map leader-z to toggle the writing mode
|
||||
vim.keymap.set("n", "<leader>z", "<cmd>ZenMode<CR>", { desc = "toggle zen mode" })
|
||||
end
|
||||
}
|
||||
})
|
||||
12
.config/nvim/lazy-lock.json
Normal file
12
.config/nvim/lazy-lock.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
|
||||
"cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" },
|
||||
"flexoki": { "branch": "main", "commit": "c3e2251e813d29d885a7cbbe9808a7af234d845d" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||
"nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" },
|
||||
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "7d8dad2c837fcdf2e65997e68657c1a21c568074" },
|
||||
"render-markdown.nvim": { "branch": "main", "commit": "629eb9533ec989d9d5c6cab8f3ad5372422c24e0" },
|
||||
"zen-mode.nvim": { "branch": "main", "commit": "8564ce6d29ec7554eb9df578efa882d33b3c23a7" }
|
||||
}
|
||||
9
.config/nwg-displays/config
Normal file
9
.config/nwg-displays/config
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"view-scale": 0.15,
|
||||
"snap-threshold": 10,
|
||||
"indicator-timeout": 500,
|
||||
"custom-mode": [],
|
||||
"use-desc": false,
|
||||
"confirm-timeout": 10,
|
||||
"profile-bound-wallpapers": true
|
||||
}
|
||||
10
.config/nwg-look/config
Normal file
10
.config/nwg-look/config
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"export-settings-ini": true,
|
||||
"export-gtkrc-20": true,
|
||||
"export-index-theme": true,
|
||||
"export-xsettingsd": true,
|
||||
"export-gtk4-symlinks": true,
|
||||
"flatpak-export-gtk-theme-override": false,
|
||||
"flatpak-export-icon-theme-override": false,
|
||||
"flatpak-install-current-gtk-theme": false
|
||||
}
|
||||
2
.config/qt5ct/qt5ct.conf
Normal file
2
.config/qt5ct/qt5ct.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[SettingsWindow]
|
||||
geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\xff\xff\xff\xfd\xff\xff\xff\xe2\0\0\x3\xb2\0\0\x4\r\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n\0\0\0\0\0\0\0\0\a\x80\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n)
|
||||
2
.config/qt6ct/qt6ct.conf
Normal file
2
.config/qt6ct/qt6ct.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[SettingsWindow]
|
||||
geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\xff\xff\xff\xfd\xff\xff\xff\xe2\0\0\x3\xb2\0\0\x4\r\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n\0\0\0\0\0\0\0\0\a\x80\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n)
|
||||
55
.config/starship.toml
Normal file
55
.config/starship.toml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# PROMPT LAYOUT
|
||||
# define the left and right structural order
|
||||
format = "$python$directory$git_branch$git_status$hostname$character"
|
||||
right_format = """$jobs$time"""
|
||||
|
||||
# PYTHON ENVIRONMENT
|
||||
# display the active virtual environment name without the python version
|
||||
[python]
|
||||
style = "green"
|
||||
format = "[$virtualenv]($style) "
|
||||
|
||||
# DIRECTORY PATH
|
||||
# display the truncated current working path
|
||||
[directory]
|
||||
style = "cyan"
|
||||
truncate_to_repo = true
|
||||
truncation_length = 3
|
||||
format = "[$path]($style) "
|
||||
|
||||
# GIT BRANCH
|
||||
# display the current repository branch
|
||||
[git_branch]
|
||||
format = "[$branch]($style) "
|
||||
style = "yellow"
|
||||
|
||||
# GIT STATUS
|
||||
# display modification indicators
|
||||
[git_status]
|
||||
style = "red"
|
||||
format = "[$all_status]($style) "
|
||||
|
||||
# HOSTNAME
|
||||
# display the machine name only during ssh sessions
|
||||
[hostname]
|
||||
ssh_only = true
|
||||
format = "[$hostname](blue) "
|
||||
|
||||
# PROMPT CHARACTER
|
||||
# display stateful execution symbols
|
||||
[character]
|
||||
success_symbol = "[>](bold green)"
|
||||
error_symbol = "[x](bold red)"
|
||||
vicmd_symbol = "[v](bold green)"
|
||||
|
||||
# BACKGROUND JOBS
|
||||
# indicate processes suspended in the background
|
||||
[jobs]
|
||||
symbol = "✦ "
|
||||
|
||||
# SYSTEM TIME
|
||||
# display the current time on the right axis
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = "%T"
|
||||
format = "[$time]($style)"
|
||||
43
.config/swaylock/config
Normal file
43
.config/swaylock/config
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# CORE AESTHETICS
|
||||
# base background colour (flexoki ink dark)
|
||||
color=100f0f
|
||||
font=JetBrainsMono Nerd Font
|
||||
font-size=24
|
||||
|
||||
# INDICATOR GEOMETRY
|
||||
indicator-radius=120
|
||||
indicator-thickness=12
|
||||
indicator-idle-visible
|
||||
|
||||
# INTERNAL RING COLOURS
|
||||
inside-color=100f0f
|
||||
inside-clear-color=100f0f
|
||||
inside-ver-color=100f0f
|
||||
inside-wrong-color=100f0f
|
||||
|
||||
# EXTERNAL RING COLOURS
|
||||
# normal state (grey)
|
||||
ring-color=cecdc3
|
||||
# clearing state (yellow)
|
||||
ring-clear-color=d0a215
|
||||
# verifying state (purple)
|
||||
ring-ver-color=8b7ec8
|
||||
# incorrect state (red)
|
||||
ring-wrong-color=af3029
|
||||
|
||||
# KEYPRESS HIGHLIGHTS
|
||||
# highlight colour when typing (green)
|
||||
key-hl-color=879a39
|
||||
# highlight colour when backspacing (yellow)
|
||||
bshl-color=d0a215
|
||||
|
||||
# TEXT COLOURS
|
||||
text-color=cecdc3
|
||||
text-clear-color=100f0f
|
||||
text-ver-color=cecdc3
|
||||
text-wrong-color=cecdc3
|
||||
|
||||
# BEHAVIOUR
|
||||
# disable the caps lock warning to maintain minimalism
|
||||
ignore-empty-password
|
||||
show-failed-attempts
|
||||
28
.config/tmux/tmux.conf
Normal file
28
.config/tmux/tmux.conf
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# CORE ENVIRONMENT
|
||||
set -g prefix C-b
|
||||
bind C-b send-prefix
|
||||
|
||||
set -g base-index 1
|
||||
set -g pane-base-index 1
|
||||
set -g renumber-windows on
|
||||
|
||||
set -g default-terminal "tmux-256color"
|
||||
|
||||
set -g mouse on
|
||||
set -g mode-keys vi
|
||||
|
||||
# STATUS BAR
|
||||
set -g status-position top
|
||||
set -g status-justify right
|
||||
|
||||
set -g status-style bg="#000000",fg="#cecdc3"
|
||||
|
||||
set -g window-status-format " #I:#W "
|
||||
set -g window-status-current-format " #I:#W "
|
||||
set -g window-status-current-style bg="#282726",fg="#8b7ec8",bold
|
||||
|
||||
set -g status-left " [#S] "
|
||||
set -g status-left-length 40
|
||||
|
||||
set -g status-right ""
|
||||
set -g status-right-length 80
|
||||
68
.config/waybar/config
Normal file
68
.config/waybar/config
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"layer": "top",
|
||||
"position": "top",
|
||||
"height": 28,
|
||||
"spacing": 8,
|
||||
|
||||
"modules-left": ["niri/workspaces", "niri/window", "mpd"],
|
||||
"modules-center": ["clock"],
|
||||
"modules-right": ["custom/bandwidth", "battery", "tray"],
|
||||
|
||||
"niri/window": {
|
||||
"format": "{}",
|
||||
"rewrite": {
|
||||
"(.*) — Mozilla Firefox": "Firefox",
|
||||
"(.*) — LibreWolf": "Librewolf",
|
||||
"(.*) - Obsidian v(.*)": "Obsidian"
|
||||
},
|
||||
"max-length": 50
|
||||
},
|
||||
|
||||
"niri/workspaces": {
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"focused": "●",
|
||||
"default": "○"
|
||||
}
|
||||
},
|
||||
|
||||
"clock": {
|
||||
"format": "{:%H:%M | %d}",
|
||||
"tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>"
|
||||
},
|
||||
|
||||
"mpd": {
|
||||
"format": "{stateIcon} {artist} - {title}",
|
||||
"format-disconnected": " offline",
|
||||
"format-stopped": " stopped",
|
||||
"state-icons": {
|
||||
"paused": "",
|
||||
"playing": ""
|
||||
},
|
||||
"max-length": 40,
|
||||
"tooltip-format": "MPD (connected)",
|
||||
"tooltip-format-disconnected": "MPD (disconnected)",
|
||||
"on-click": "mpc toggle",
|
||||
"on-scroll-up": "mpc next",
|
||||
"on-scroll-down": "mpc prev",
|
||||
"on-click-right": "alacritty -e ncmpcpp"
|
||||
},
|
||||
|
||||
"custom/bandwidth": {
|
||||
"exec": "~/.local/bin/waybar-net",
|
||||
"return-type": "json",
|
||||
"interval": 60,
|
||||
"format": "{}"
|
||||
},
|
||||
|
||||
"battery": {
|
||||
"states": { "warning": 30, "critical": 15 },
|
||||
"format": "{icon} {capacity}%",
|
||||
"format-icons": ["", "", "", "", "", "", ""]
|
||||
},
|
||||
|
||||
"tray": {
|
||||
"icon-size": 14,
|
||||
"spacing": 10
|
||||
}
|
||||
}
|
||||
104
.config/waybar/style.css
Normal file
104
.config/waybar/style.css
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
* {
|
||||
font-family: "JetBrainsMono Nerd Font", "Roboto", sans-serif;
|
||||
font-size: 13px;
|
||||
min-height: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* BASE WINDOW */
|
||||
/* edge-to-edge dark translucent bar */
|
||||
window#waybar {
|
||||
background-color: rgba(15, 15, 15, 0.85);
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
/* WORKSPACES */
|
||||
#workspaces {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#workspaces button {
|
||||
padding: 0 8px;
|
||||
color: #555555;
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* flexoki yellow for active workspaces */
|
||||
#workspaces button.focused,
|
||||
#workspaces button.active {
|
||||
color: #d0a215;
|
||||
}
|
||||
|
||||
#workspaces button:hover {
|
||||
box-shadow: inherit;
|
||||
text-shadow: inherit;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
|
||||
/* MODULE SPACING */
|
||||
#clock,
|
||||
#custom,
|
||||
#network,
|
||||
#bluetooth,
|
||||
#pulseaudio,
|
||||
#backlight,
|
||||
#battery,
|
||||
#tray,
|
||||
#window {
|
||||
padding: 0 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* HARDWARE COLOURS */
|
||||
/* assigning distinct muted hues to logical blocks */
|
||||
#clock {
|
||||
color: #babbf1;
|
||||
}
|
||||
|
||||
#network {
|
||||
color: #8caaee;
|
||||
}
|
||||
|
||||
#bluetooth {
|
||||
color: #ca9ee6;
|
||||
}
|
||||
|
||||
#pulseaudio {
|
||||
color: #81c8be;
|
||||
}
|
||||
|
||||
#backlight {
|
||||
color: #e5c890;
|
||||
}
|
||||
|
||||
#battery {
|
||||
color: #a6d189;
|
||||
}
|
||||
|
||||
#battery.charging {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
|
||||
#battery.critical:not(.charging) {
|
||||
color: #e78284;
|
||||
}
|
||||
|
||||
/* MPD CONTROLS */
|
||||
#mpd {
|
||||
padding: 0 10px;
|
||||
font-family: monospace;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
/* dim the text when inactive */
|
||||
#mpd.disconnected,
|
||||
#mpd.stopped {
|
||||
color: #6c7086;
|
||||
}
|
||||
|
||||
/* green hue when active */
|
||||
#mpd.playing {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
20
.config/zathura/zathurarc
Normal file
20
.config/zathura/zathurarc
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# ZATHURA CONFIGURATION
|
||||
# disable automatic recolouring on startup
|
||||
set recolor "false"
|
||||
|
||||
# COLOUR MAPPINGS
|
||||
# preserve image hues when recolouring is activated
|
||||
set recolor-keephue "true"
|
||||
|
||||
# recolour variables applied only when toggled
|
||||
set recolor-lightcolor "#1e1e2e"
|
||||
set recolor-darkcolor "#cdd6f4"
|
||||
|
||||
# USER INTERFACE
|
||||
# static dark theme for the application chrome
|
||||
set default-bg "#1e1e2e"
|
||||
set default-fg "#cdd6f4"
|
||||
set statusbar-bg "#1e1e2e"
|
||||
set statusbar-fg "#cdd6f4"
|
||||
|
||||
unmap d
|
||||
29
.local/bin/README.md
Executable file
29
.local/bin/README.md
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
# UTILS
|
||||
|
||||
a collection of personal utility scripts for arch linux system management, automation, and infrastructure reconnaissance.
|
||||
|
||||
## DEPENDENCIES
|
||||
|
||||
most scripts rely on the following packages available in the arch repositories or aur:
|
||||
|
||||
`bash`, `python3`, `jq`, `curl`, `bc`, `awk`, `bind`, `whois`, `openssl`, `aria2`, `yt-dlp`, `ffmpeg`, `fzf`, `lm_sensors`, `pacman-contrib`, `rsync`, `python-watchdog`, `waybar`
|
||||
|
||||
## UTILITIES ARCHITECTURE
|
||||
|
||||
the repository is structured around singular, verb-oriented tools designed for autonomous infrastructure management:
|
||||
|
||||
* `archive`: handles universal file compression and extraction routing.
|
||||
* `fetch`: executes autonomous media liberation via aria2c and yt-dlp.
|
||||
* `rate`: provides terminal-native fiat and crypto currency evaluation.
|
||||
* `recon`: executes domain reconnaissance and infrastructure security auditing.
|
||||
* `status`: audits arch linux system telemetry and environment health.
|
||||
* `pick`: provides modular element selection functionality.
|
||||
* `power`: handles system state and operational power management.
|
||||
* `scratch`: controls the window manager scratchpad buffer.
|
||||
* `audit-pkg`: scans local package environments for vulnerabilities.
|
||||
* `audit-users`: maps system permissions and active user accounts.
|
||||
* `backup-obsd`: automates synchronisation for local knowledge vaults.
|
||||
* `obsd`: launches the local knowledge base environment.
|
||||
* `adbx`: wraps android debug bridge operations.
|
||||
* `fuzzel-run`: configures the autonomous application launcher.
|
||||
* `waybar-net`: feeds network state data to the status bar.
|
||||
120
.local/bin/adbx
Executable file
120
.local/bin/adbx
Executable file
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# COLOURS
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
yellow='\033[1;33m'
|
||||
blue='\033[0;34m'
|
||||
cyan='\033[0;36m'
|
||||
nc='\033[0m'
|
||||
|
||||
# LOGGING
|
||||
log_info() { printf "${blue}[info]${nc} %s\n" "$1"; }
|
||||
log_success() { printf "${green}[ok]${nc} %s\n" "$1"; }
|
||||
log_warn() { printf "${yellow}[warn]${nc} %s\n" "$1"; }
|
||||
log_err() { printf "${red}[err]${nc} %s\n" "$1"; }
|
||||
|
||||
# ENVIRONMENT
|
||||
check_deps() {
|
||||
local tools=(adb fastboot apktool uber-apk-signer fzf)
|
||||
for t in "${tools[@]}"; do
|
||||
if ! command -v "$t" &>/dev/null; then
|
||||
log_warn "$t missing"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# DEVICE MANAGEMENT
|
||||
cmd_info() {
|
||||
printf "${cyan}DEVICE INFORMATION${nc}\n"
|
||||
printf "model: %s\n" "$(adb shell getprop ro.product.model)"
|
||||
printf "android: %s\n" "$(adb shell getprop ro.build.version.release)"
|
||||
|
||||
local bat=$(adb shell dumpsys battery | grep level | cut -d: -f2 | tr -d ' ')
|
||||
local stat=$(adb shell dumpsys battery | grep 'AC powered' | cut -d: -f2 | tr -d ' ')
|
||||
[[ "$stat" == "true" ]] && charge="charging" || charge="discharging"
|
||||
|
||||
printf "battery: %s%% (%s)\n" "$bat" "$charge"
|
||||
|
||||
if adb shell su -c "echo 'rooted'" &>/dev/null; then
|
||||
printf "root: ${green}yes${nc}\n"
|
||||
else
|
||||
printf "root: ${red}no${nc}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_wifi() {
|
||||
local port=${1:-5555}
|
||||
local ip=$(adb shell ip addr show wlan0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
|
||||
if [ -z "$ip" ]; then
|
||||
log_err "wifi ip not detected"
|
||||
return 1
|
||||
fi
|
||||
adb tcpip $port
|
||||
sleep 2
|
||||
adb connect "$ip:$port"
|
||||
log_success "connected to $ip:$port"
|
||||
}
|
||||
|
||||
# PACKAGE MANAGEMENT
|
||||
cmd_nuke() {
|
||||
local pkg=$(adb shell pm list packages | cut -d: -f2 | fzf --prompt="nuke > " --height=40%)
|
||||
if [ -n "$pkg" ]; then
|
||||
read -p "confirm nuke $pkg? (y/n): " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
adb shell pm uninstall -k --user 0 "$pkg"
|
||||
log_success "nuked $pkg"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# APK ENGINEERING
|
||||
cmd_pull() {
|
||||
local pkg=${1:-$(adb shell pm list packages | cut -d: -f2 | fzf --prompt="pull > ")}
|
||||
[ -z "$pkg" ] && return
|
||||
local path=$(adb shell pm path "$pkg" | head -n 1 | cut -d: -f2 | tr -d '\r')
|
||||
log_info "pulling $pkg..."
|
||||
adb pull "$path" "./$pkg.apk"
|
||||
}
|
||||
|
||||
cmd_scrot() {
|
||||
local file="scrot_$(date +%Y%m%d_%H%M%S).png"
|
||||
adb shell screencap -p /sdcard/temp.png
|
||||
adb pull /sdcard/temp.png "./$file" >/dev/null
|
||||
adb shell rm /sdcard/temp.png
|
||||
log_success "saved: $file"
|
||||
}
|
||||
|
||||
# MAIN ROUTER
|
||||
usage() {
|
||||
printf "${cyan}ARCH-DROID TOOL (ADBX)${nc}\n"
|
||||
printf "usage: adbx <command> [args]\n\n"
|
||||
printf "DEVICE\n"
|
||||
printf " check verify tools\n"
|
||||
printf " list list devices\n"
|
||||
printf " info show battery/root\n"
|
||||
printf " wifi wireless adb\n\n"
|
||||
printf "PACKAGES\n"
|
||||
printf " nuke interactive uninstall\n"
|
||||
printf " freeze disable app\n"
|
||||
printf " melt enable app\n\n"
|
||||
printf "FILES\n"
|
||||
printf " pull extract apk\n"
|
||||
printf " sign sign apk\n"
|
||||
printf " scrot screenshot\n"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
check) check_deps ;;
|
||||
list)
|
||||
adb devices -l
|
||||
fastboot devices
|
||||
;;
|
||||
info) cmd_info ;;
|
||||
wifi) cmd_wifi "$2" ;;
|
||||
nuke) cmd_nuke ;;
|
||||
pull) cmd_pull "$2" ;;
|
||||
scrot) cmd_scrot ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
117
.local/bin/archive
Executable file
117
.local/bin/archive
Executable file
|
|
@ -0,0 +1,117 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# COLOURS
|
||||
red='\033[1;31m'
|
||||
green='\033[1;32m'
|
||||
blue='\033[1;34m'
|
||||
yellow='\033[1;33m'
|
||||
nc='\033[0m'
|
||||
|
||||
# USAGE GUIDE
|
||||
# displays the help menu if arguments are missing or incorrect
|
||||
show_help() {
|
||||
printf "${blue}usage:${nc} archivectl <command> <arguments>\n\n"
|
||||
printf "commands:\n"
|
||||
printf " compress <source> [output] compresses a file or directory\n"
|
||||
printf " extract <file> extracts a supported archive\n\n"
|
||||
printf "examples:\n"
|
||||
printf " archivectl compress myfolder\n"
|
||||
printf " archivectl compress myfolder data.zip\n"
|
||||
printf " archivectl extract data.tar.gz\n"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# COMPRESSION ROUTINE
|
||||
# handles the creation of new archives
|
||||
do_compress() {
|
||||
if [ -z "$1" ]; then
|
||||
printf "${red}error: target missing for compression.${nc}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local source_path="${1%/}"
|
||||
if [ ! -e "$source_path" ]; then
|
||||
printf "${red}error: source '%s' not found.${nc}\n" "$source_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local output
|
||||
if [ -n "$2" ]; then
|
||||
output="$2"
|
||||
else
|
||||
output="${source_path}.tar.gz"
|
||||
fi
|
||||
|
||||
printf "${blue}compressing ${yellow}%s${nc} into ${yellow}%s${nc}...\n" "$source_path" "$output"
|
||||
|
||||
case "$output" in
|
||||
*.tar.gz | *.tgz) tar -czf "$output" "$source_path" ;;
|
||||
*.tar.xz) tar -cJf "$output" "$source_path" ;;
|
||||
*.tar.bz2) tar -cjf "$output" "$source_path" ;;
|
||||
*.tar) tar -cf "$output" "$source_path" ;;
|
||||
*.zip) zip -r "$output" "$source_path" ;;
|
||||
*.7z) 7z a "$output" "$source_path" ;;
|
||||
*.rar) rar a "$output" "$source_path" ;;
|
||||
*)
|
||||
printf "${red}error: format not recognised.${nc}\n"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
printf "${green}compression complete.${nc}\n"
|
||||
du -h "$output" | awk '{print $1 " " $2}'
|
||||
}
|
||||
|
||||
# EXTRACTION ROUTINE
|
||||
# handles the unpacking of existing archives
|
||||
do_extract() {
|
||||
if [ -z "$1" ]; then
|
||||
printf "${red}error: target missing for extraction.${nc}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local file="$1"
|
||||
if [ ! -f "$file" ]; then
|
||||
printf "${red}error: file '%s' not found.${nc}\n" "$file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "${blue}extracting: ${yellow}%s${nc}...\n" "$file"
|
||||
|
||||
case "$file" in
|
||||
*.tar.bz2 | *.tbz2) tar xjf "$file" ;;
|
||||
*.tar.gz | *.tgz) tar xzf "$file" ;;
|
||||
*.tar.xz) tar xJf "$file" ;;
|
||||
*.tar) tar xf "$file" ;;
|
||||
*.lzma) unlzma "$file" ;;
|
||||
*.bz2) bunzip2 "$file" ;;
|
||||
*.rar) unrar x "$file" ;;
|
||||
*.gz) gunzip "$file" ;;
|
||||
*.zip) unzip "$file" ;;
|
||||
*.Z) uncompress "$file" ;;
|
||||
*.7z) 7z x "$file" ;;
|
||||
*.xz) unxz "$file" ;;
|
||||
*.exe) cabextract "$file" ;;
|
||||
*)
|
||||
printf "${red}error: format not recognised.${nc}\n"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
printf "${green}extraction complete.${nc}\n"
|
||||
}
|
||||
|
||||
# COMMAND ROUTER
|
||||
# parses the first argument to determine execution path
|
||||
case "$1" in
|
||||
compress)
|
||||
do_compress "$2" "$3"
|
||||
;;
|
||||
extract)
|
||||
do_extract "$2"
|
||||
;;
|
||||
*)
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
144
.local/bin/audit-pkg
Executable file
144
.local/bin/audit-pkg
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# GUARD
|
||||
if ! command -v pacman &>/dev/null; then
|
||||
echo "pacman not found. this script is for arch linux." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# COLOURS
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
yellow='\033[1;33m'
|
||||
blue='\033[0;34m'
|
||||
cyan='\033[0;36m'
|
||||
bold='\033[1m'
|
||||
dim='\033[2m'
|
||||
nc='\033[0m'
|
||||
|
||||
# COUNTERS
|
||||
_covered=0
|
||||
_partial=0
|
||||
_missing=0
|
||||
|
||||
# HELPERS
|
||||
is_installed() {
|
||||
pacman -Qq "$1" &>/dev/null
|
||||
}
|
||||
|
||||
print_section() {
|
||||
printf "\n${blue}${bold}%s${nc}\n" "$1"
|
||||
printf "${dim}--------------------------------------------------${nc}\n"
|
||||
}
|
||||
|
||||
check_any() {
|
||||
local category="$1"
|
||||
local description="$2"
|
||||
shift 2
|
||||
local -a found=()
|
||||
for pkg in "$@"; do
|
||||
is_installed "$pkg" && found+=("$pkg")
|
||||
done
|
||||
|
||||
if [[ ${#found[@]} -gt 0 ]]; then
|
||||
printf " ${green}[ ok ]${nc} ${bold}%-35s${nc} ${dim}%s${nc}\n" "$category" "${found[0]}"
|
||||
_covered=$((_covered + 1))
|
||||
else
|
||||
printf " ${red}[ !! ]${nc} ${bold}%-35s${nc}\n" "$category"
|
||||
printf " ${dim}%s${nc}\n" "$description"
|
||||
_missing=$((_missing + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
check_all() {
|
||||
local category="$1"
|
||||
local description="$2"
|
||||
shift 2
|
||||
local -a found=()
|
||||
local -a absent=()
|
||||
for pkg in "$@"; do
|
||||
if is_installed "$pkg"; then found+=("$pkg"); else absent+=("$pkg"); fi
|
||||
done
|
||||
|
||||
if [[ ${#absent[@]} -eq 0 ]]; then
|
||||
printf " ${green}[ ok ]${nc} ${bold}%-35s${nc}\n" "$category"
|
||||
_covered=$((_covered + 1))
|
||||
elif [[ ${#found[@]} -gt 0 ]]; then
|
||||
printf " ${yellow}[ / ]${nc} ${bold}%-35s${nc}\n" "$category"
|
||||
printf " ${dim}missing: %s${nc}\n" "${absent[*]}"
|
||||
_partial=$((_partial + 1))
|
||||
else
|
||||
printf " ${red}[ !! ]${nc} ${bold}%-35s${nc}\n" "$category"
|
||||
printf " ${dim}need all of: %s${nc}\n" "$*"
|
||||
_missing=$((_missing + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# HEADER
|
||||
clear
|
||||
printf "${cyan}${bold}ARCH LINUX + HYPRLAND AUDIT${nc}\n"
|
||||
printf "${dim}checking package categories in local database${nc}\n\n"
|
||||
|
||||
# CORE SYSTEM
|
||||
print_section "CORE SYSTEM"
|
||||
check_all "base system" "essential os foundation" base base-devel
|
||||
check_any "kernel" "linux kernel flavour" linux linux-lts linux-zen linux-hardened
|
||||
check_any "ucode" "processor microcode patches" intel-ucode amd-ucode
|
||||
check_any "bootloader" "system startup manager" grub refind
|
||||
check_any "filesystems" "fat/ntfs/ext4 utilities" dosfstools ntfs-3g e2fsprogs
|
||||
|
||||
# DISPLAY AND COMPOSITOR
|
||||
print_section "DISPLAY AND COMPOSITOR"
|
||||
check_any "compositor" "hyprland or alternatives" hyprland sway niri
|
||||
check_any "session manager" "uwsm or greetd" uwsm ly greetd
|
||||
check_all "xdg portals" "screen sharing and file pickers" xdg-desktop-portal-hyprland xdg-desktop-portal-gtk
|
||||
check_any "gpu drivers" "mesa or nvidia proprietary" mesa nvidia nvidia-open
|
||||
|
||||
# DESKTOP COMPONENTS
|
||||
print_section "DESKTOP COMPONENTS"
|
||||
check_any "status bar" "waybar or eww" waybar eww ags
|
||||
check_any "launcher" "app and command fuzzy search" rofi-wayland wofi fuzzel
|
||||
check_any "notifications" "notification daemon" mako swaync dunst
|
||||
check_any "locker" "screen locking utility" hyprlock swaylock
|
||||
check_any "wallpaper" "wayland native wallpaper setter" hyprpaper swww
|
||||
check_all "clipboard" "manager and history stack" wl-clipboard cliphist
|
||||
|
||||
# AUDIO AND NETWORK
|
||||
print_section "AUDIO AND NETWORK"
|
||||
check_all "pipewire" "full audio stack" pipewire pipewire-pulse pipewire-alsa wireplumber
|
||||
check_any "network" "networkmanager or iwd" networkmanager iwd
|
||||
check_any "firewall" "traffic control" ufw nftables
|
||||
|
||||
# SHELL AND EDITORS
|
||||
print_section "SHELL AND EDITORS"
|
||||
check_any "terminal" "wayland native terminal" kitty alacritty foot ghostty
|
||||
check_any "shell" "bash or zsh" bash zsh fish
|
||||
check_any "prompt" "shell prompt customiser" starship oh-my-posh
|
||||
check_any "editor" "modal or simple text editor" nvim vim micro nano
|
||||
|
||||
# INPUT AND POWER
|
||||
print_section "INPUT AND POWER"
|
||||
check_any "brightness" "backlight control" brightnessctl light
|
||||
check_any "power" "power management daemon" tlp auto-cpufreq
|
||||
check_all "bluetooth" "bluetooth stack" bluez bluez-utils
|
||||
check_any "bt-manager" "bluetooth frontend" blueman bluetui
|
||||
|
||||
# THEMING AND FONTS
|
||||
print_section "THEMING AND FONTS"
|
||||
check_any "nerd-font" "glyphs for terminal icons" ttf-jetbrains-mono-nerd ttf-hack-nerd
|
||||
check_any "emoji" "emoji rendering support" noto-fonts-emoji
|
||||
check_any "gtk-theme" "gtk appearance manager" nwg-look lxappearance
|
||||
|
||||
# SECURITY AND SECRETS
|
||||
print_section "SECURITY AND SECRETS"
|
||||
check_any "polkit" "auth agent for elevated tasks" polkit-kde-agent lxqt-policykit
|
||||
check_any "keyring" "password and secret storage" gnome-keyring
|
||||
|
||||
# SUMMARY
|
||||
_total=$((_covered + _partial + _missing))
|
||||
printf "\n${blue}${bold}SUMMARY${nc}\n"
|
||||
printf "${dim}--------------------------------------------------${nc}\n"
|
||||
printf " ${green}ok: %d${nc}\n" "$_covered"
|
||||
printf " ${yellow}partial: %d${nc}\n" "$_partial"
|
||||
printf " ${red}missing: %d${nc}\n" "$_missing"
|
||||
printf " total: %d\n\n" "$_total"
|
||||
22
.local/bin/audit-users
Executable file
22
.local/bin/audit-users
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# COLOURS
|
||||
cyan='\033[0;36m'
|
||||
nc='\033[0m'
|
||||
|
||||
# USER AUDIT
|
||||
printf "${cyan}AUTHORISED HUMAN USERS${nc}\n"
|
||||
printf "%-12s %-6s %-15s %-30s %s\n" "user" "uid" "shell" "groups" "last login"
|
||||
|
||||
# filter for uids between 1000 and 60000
|
||||
awk -F: '$3 >= 1000 && $3 < 60000 {print $1}' /etc/passwd | while read -r user; do
|
||||
uid=$(id -u "$user")
|
||||
groups=$(groups "$user" | cut -d: -f2 | xargs)
|
||||
shell=$(getent passwd "$user" | cut -d: -f7)
|
||||
|
||||
# parse lastlog for the second line and strip the username to get the timestamp
|
||||
last_raw=$(lastlog -u "$user" | awk 'NR==2 { $1=""; print $0 }' | xargs)
|
||||
|
||||
printf "%-12s %-6s %-15s %-30s %s\n" \
|
||||
"$user" "$uid" "$shell" "$groups" "${last_raw:-never logged in}"
|
||||
done
|
||||
20
.local/bin/backup-obsd
Executable file
20
.local/bin/backup-obsd
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-07-28 16:26 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "creating tarball"
|
||||
echo ""
|
||||
tar --zstd -cf - -C ~ obsidian | age -r age1y5fnkaugdmqgegurgq6jdqrm5lz3ehyp9ve7rhf69pmr9xy9veyqtnappe -o ~/vault-$(date "+%FT%T").tar.zst.age
|
||||
echo ""
|
||||
echo "pushing tarball to filen"
|
||||
echo ""
|
||||
filen upload ~/vault-*.tar.zst.age /obsidian
|
||||
echo ""
|
||||
echo "deleting tarbal"
|
||||
rm -rf ~/vault-*.tar.zst.age
|
||||
echo ""
|
||||
echo "obsidian vault backup complete"
|
||||
echo ""
|
||||
filen ls -l /obsidian
|
||||
13
.local/bin/brightness-notify
Executable file
13
.local/bin/brightness-notify
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-08-04 05:02 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ADJUST BACKLIGHT
|
||||
brightnessctl set "$@" > /dev/null
|
||||
|
||||
# FETCH CURRENT PERCENTAGE
|
||||
brightness=$(brightnessctl -m | awk -F, '{print $4}' | tr -d '%')
|
||||
|
||||
notify-send -h string:x-canonical-private-synchronous:brightness -u low "Brightness: ${brightness}%"
|
||||
10
.local/bin/bw
Executable file
10
.local/bin/bw
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
SESSION=$(secret-tool lookup service bitwarden 2>/dev/null)
|
||||
|
||||
if [[ -z "$SESSION" ]]; then
|
||||
SESSION=$(bw unlock --raw)
|
||||
secret-tool store --label="bw-session" service bitwarden <<< "$SESSION"
|
||||
fi
|
||||
|
||||
exec /usr/bin/bw "$@" --session "$SESSION"
|
||||
250
.local/bin/fetch
Executable file
250
.local/bin/fetch
Executable file
|
|
@ -0,0 +1,250 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# COLOURS
|
||||
red='\033[1;31m'
|
||||
green='\033[1;32m'
|
||||
blue='\033[1;34m'
|
||||
cyan='\033[1;36m'
|
||||
yellow='\033[1;33m'
|
||||
nc='\033[0m'
|
||||
|
||||
# CONFIGURATION
|
||||
session_file="$HOME/.aria2_session"
|
||||
xdg_videos="${XDG_VIDEOS_DIR:-$HOME/Videos}"
|
||||
xdg_music="${XDG_MUSIC_DIR:-$HOME/Music}"
|
||||
hist_video="$HOME/.config/archive_history_video.txt"
|
||||
hist_music="$HOME/.config/archive_history_music.txt"
|
||||
|
||||
# establish configuration directory
|
||||
mkdir -p "$HOME/.config"
|
||||
touch "$session_file"
|
||||
|
||||
# UTILITIES
|
||||
log() {
|
||||
local col="$1"
|
||||
local msg="$2"
|
||||
printf "${col}%s${nc}\n" "$msg"
|
||||
}
|
||||
|
||||
# verify core binaries and clipboard providers
|
||||
check_dependencies() {
|
||||
local deps=("aria2c" "yt-dlp" "spotdl")
|
||||
for dep in "${deps[@]}"; do
|
||||
if ! command -v "$dep" >/dev/null 2>&1; then
|
||||
log "$red" "missing core dependency: $dep"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v wl-paste >/dev/null 2>&1 && ! command -v xclip >/dev/null 2>&1; then
|
||||
log "$yellow" "warning: missing wl-clipboard or xclip. clipboard polling disabled."
|
||||
fi
|
||||
}
|
||||
|
||||
# extract link from clipboard
|
||||
get_clipboard_url() {
|
||||
local content=""
|
||||
|
||||
if command -v wl-paste >/dev/null 2>&1; then
|
||||
content=$(wl-paste)
|
||||
elif command -v xclip >/dev/null 2>&1; then
|
||||
content=$(xclip -o -selection clipboard)
|
||||
fi
|
||||
|
||||
content="$(echo "$content" | xargs)"
|
||||
|
||||
[[ "$content" == http* ]] && echo "$content"
|
||||
}
|
||||
|
||||
# ARIA2C CORE
|
||||
run_aria2c() {
|
||||
aria2c \
|
||||
--continue=true \
|
||||
--max-connection-per-server=16 \
|
||||
--split=16 \
|
||||
--min-split-size=1M \
|
||||
--user-agent="Mozilla/5.0" \
|
||||
--save-session="$session_file" \
|
||||
--save-session-interval=60 \
|
||||
--seed-time=0 \
|
||||
"$@" || log "$red" "aria2c failed"
|
||||
}
|
||||
|
||||
# SPOTIFY CORE
|
||||
run_spotdl() {
|
||||
local url="$1"
|
||||
local target_dir="$2"
|
||||
|
||||
log "$cyan" "spotify routing to $target_dir"
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
cd "$target_dir" || return 1
|
||||
|
||||
local cmd=(
|
||||
spotdl download "$url"
|
||||
--format opus
|
||||
--output "{artists} - {title}.{ext}"
|
||||
)
|
||||
|
||||
local attempt
|
||||
for attempt in 1 2 3; do
|
||||
log "$cyan" "spotdl execution attempt $attempt"
|
||||
|
||||
if timeout 300 "${cmd[@]}"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "$yellow" "spotdl network or api failure on attempt $attempt"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
log "$red" "spotdl failed permanently due to api restrictions"
|
||||
return 1
|
||||
}
|
||||
|
||||
# YT-DLP CORE
|
||||
run_ytdlp() {
|
||||
local url="$1"
|
||||
local target_dir="$2"
|
||||
local hist_file="$3"
|
||||
local out_tmpl="$4"
|
||||
shift 4
|
||||
local extra_args=("$@")
|
||||
|
||||
log "$cyan" "media routing to $target_dir"
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
yt-dlp \
|
||||
--progress \
|
||||
--ignore-errors \
|
||||
--add-metadata \
|
||||
--embed-thumbnail \
|
||||
--embed-subs \
|
||||
--embed-chapters \
|
||||
--downloader aria2c \
|
||||
--downloader-args "aria2c:-x 4 -k 1M" \
|
||||
--download-archive "$hist_file" \
|
||||
-o "${target_dir}/${out_tmpl}" \
|
||||
"${extra_args[@]}" \
|
||||
"$url" \
|
||||
|| log "$yellow" "yt-dlp finished with errors"
|
||||
}
|
||||
|
||||
# MODES
|
||||
mode_media() {
|
||||
local url="$1"
|
||||
local media_type="$2"
|
||||
local format_arg="$3"
|
||||
local playlist_flag="$4"
|
||||
|
||||
local audio_tmpl="%(playlist_index|)s%(playlist_index& - |)s%(artist,uploader|Unknown)s - %(title)s [%(id)s].%(ext)s"
|
||||
local video_tmpl="%(playlist_index|)s%(playlist_index& - |)s%(uploader,channel|Unknown)s - %(title)s [%(id)s].%(ext)s"
|
||||
|
||||
local extra_args=()
|
||||
[[ -n "$playlist_flag" ]] && extra_args+=("$playlist_flag")
|
||||
|
||||
if [[ "$media_type" == "audio" ]]; then
|
||||
run_ytdlp "$url" "$xdg_music" "$hist_music" "$audio_tmpl" \
|
||||
"${extra_args[@]}" \
|
||||
--extract-audio --audio-format opus --audio-quality 0
|
||||
else
|
||||
if [[ "$format_arg" == "best" ]]; then
|
||||
run_ytdlp "$url" "$xdg_videos" "$hist_video" "$video_tmpl" \
|
||||
"${extra_args[@]}" -f "bv*+ba/b" --merge-output-format mkv
|
||||
|
||||
elif [[ "$format_arg" == "ask" ]]; then
|
||||
log "$cyan" "querying upstream for format manifests"
|
||||
yt-dlp -F "$url"
|
||||
printf "\n${cyan}enter desired format code:${nc} "
|
||||
read -r selected_format
|
||||
|
||||
if [[ -n "$selected_format" ]]; then
|
||||
run_ytdlp "$url" "$xdg_videos" "$hist_video" "$video_tmpl" \
|
||||
"${extra_args[@]}" -f "$selected_format" --merge-output-format mkv
|
||||
else
|
||||
log "$red" "aborted: no format code provided"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
run_ytdlp "$url" "$xdg_videos" "$hist_video" "$video_tmpl" \
|
||||
"${extra_args[@]}" -f "$format_arg" --merge-output-format mkv
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# BATCH MODE
|
||||
mode_batch() {
|
||||
local filepath="$1"
|
||||
|
||||
[[ ! -f "$filepath" ]] && {
|
||||
log "$red" "batch file missing: $filepath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
log "$blue" "batch mode execution for $filepath"
|
||||
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
|
||||
# strip comments and empty lines
|
||||
grep -vE '^\s*#|^\s*$' "$filepath" > "$tmp"
|
||||
|
||||
run_aria2c --input-file="$tmp" --max-concurrent-downloads=4
|
||||
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
# ARG PARSING
|
||||
target=""
|
||||
media_type="video"
|
||||
format_arg="best"
|
||||
playlist_flag=""
|
||||
resume=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-t|--type)
|
||||
media_type="$2"; shift 2 ;;
|
||||
-f|--format)
|
||||
format_arg="$2"; shift 2 ;;
|
||||
-p|--playlist)
|
||||
playlist_flag="--yes-playlist"; shift ;;
|
||||
-r|--resume)
|
||||
resume=1; shift ;;
|
||||
*)
|
||||
[[ -z "$target" ]] && target="$1"
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ROUTER
|
||||
check_dependencies
|
||||
|
||||
[[ -z "$target" ]] && target=$(get_clipboard_url)
|
||||
|
||||
if [[ -z "$target" && $resume -eq 0 ]]; then
|
||||
log "$yellow" "usage: fetch [url|file] [-t audio|video] [-f best|ask|code] [-p]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $resume -eq 1 ]]; then
|
||||
log "$yellow" "resuming global aria2 session"
|
||||
run_aria2c --input-file="$session_file"
|
||||
|
||||
elif [[ -f "$target" ]]; then
|
||||
mode_batch "$target"
|
||||
|
||||
elif [[ "$target" =~ \.(iso|zip|rar|tar|gz|bz2|7z|torrent|pdf|png|jpg|mp4|mkv)$ ]]; then
|
||||
log "$blue" "direct file payload detected"
|
||||
run_aria2c "$target"
|
||||
|
||||
elif [[ "$target" =~ spotify\.com ]]; then
|
||||
run_spotdl "$target" "$xdg_music" || {
|
||||
log "$red" "spotdl failed. yt-dlp cannot natively parse walled-garden spotify urls. manual extraction required."
|
||||
exit 1
|
||||
}
|
||||
|
||||
else
|
||||
mode_media "$target" "$media_type" "$format_arg" "$playlist_flag"
|
||||
fi
|
||||
7
.local/bin/fuzzel-run
Executable file
7
.local/bin/fuzzel-run
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Generate a list of all commands, remove duplicates, and pipe to Fuzzel
|
||||
selected=$(compgen -c | sort -u | fuzzel -d --prompt="RUN: ")
|
||||
|
||||
# Execute the selected binary through the compositor
|
||||
[ -n "$selected" ] && swaymsg exec -- "$selected"
|
||||
669
.local/bin/hledger-charts
Executable file
669
.local/bin/hledger-charts
Executable file
|
|
@ -0,0 +1,669 @@
|
|||
#!/usr/bin/env python3
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-07-12 09:40 +0100
|
||||
# UPDATED: 2026-07-27 10:53 +0100
|
||||
|
||||
"""
|
||||
hledger-charts
|
||||
|
||||
terminal and image charts for hledger csv output. reads csv from standard
|
||||
input, piped directly from hledger, and renders an ascii chart to the
|
||||
terminal, a matplotlib png, or both.
|
||||
|
||||
ARCH DIFFERENCE
|
||||
matplotlib is only required for --png output, ascii charts use the standard
|
||||
library alone. install matplotlib via pacman rather than pip where possible:
|
||||
|
||||
pacman -S python-matplotlib
|
||||
|
||||
pip installs into an externally managed environment fail under PEP 668
|
||||
unless a virtualenv is used or --break-system-packages is passed.
|
||||
|
||||
INPUT
|
||||
pipe hledger csv output in directly, or pass --journal so the script
|
||||
calls hledger itself. pass the -O csv flag when piping directly from
|
||||
hledger. the --format flag specifies a custom line format string and
|
||||
produces incompatible text:
|
||||
|
||||
hledger balance -O csv | hledger-charts pie --png pie.png
|
||||
hledger-charts pie --journal .journal/2026.journal --png pie.png
|
||||
|
||||
histogram and pie read a single period balance report. trend, stacked, and
|
||||
grouped read a multi period balance report (hledger balance -M), which the
|
||||
script requests automatically when --journal is given. pass an account pattern
|
||||
or date range as trailing query arguments to focus the chart. all trailing
|
||||
arguments and flags after the chart options are passed straight through to
|
||||
hledger balance:
|
||||
|
||||
hledger-charts stacked --journal .journal/2026.journal expenses
|
||||
hledger-charts trend --journal .journal/2026.journal expenses -b 2026-01 -e 2026-04
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
SPARK_CHARS = "▁▂▃▄▅▆▇█"
|
||||
ANSI_COLOURS = [31, 33, 32, 36, 34, 35, 91, 93, 92, 96, 94, 95]
|
||||
|
||||
|
||||
# AMOUNT PARSING
|
||||
# shared by both single period and multi period csv shapes
|
||||
|
||||
def clean_amount(raw, invert=False):
|
||||
"""EXTRACT THE FIRST NUMERIC VALUE FROM CURRENCY AND FORMATTED STRINGS
|
||||
accepts formats such as "1,200.00 NGN", "NGN -1,200.00", "- 450.00"
|
||||
"""
|
||||
cleaned = raw.replace(",", "")
|
||||
cleaned = re.sub(r"([-+])\s+", r"\1", cleaned)
|
||||
match = re.search(r"[-+]?\d+(?:\.\d+)?", cleaned)
|
||||
if not match:
|
||||
return 0.0
|
||||
val = float(match.group(0))
|
||||
return -val if invert else val
|
||||
|
||||
|
||||
# ACCOUNT HIERARCHY
|
||||
|
||||
def rollup_account(account, depth):
|
||||
"""TRUNCATE ACCOUNT NAME TO A SPECIFIED HIERARCHY DEPTH"""
|
||||
if not depth or depth <= 0:
|
||||
return account
|
||||
parts = account.split(":")
|
||||
return ":".join(parts[:depth])
|
||||
|
||||
|
||||
def aggregate_single_period(accounts, amounts, depth):
|
||||
"""GROUP SINGLE PERIOD AMOUNTS BY TRUNCATED ACCOUNT NAME"""
|
||||
if not depth or depth <= 0:
|
||||
return accounts, amounts
|
||||
grouped = {}
|
||||
for acc, amt in zip(accounts, amounts):
|
||||
parent = rollup_account(acc, depth)
|
||||
grouped[parent] = grouped.get(parent, 0.0) + amt
|
||||
return list(grouped.keys()), list(grouped.values())
|
||||
|
||||
|
||||
def aggregate_multi_period(accounts, periods, matrix, depth):
|
||||
"""GROUP MULTI PERIOD MATRICES BY TRUNCATED ACCOUNT NAME"""
|
||||
if not depth or depth <= 0:
|
||||
return accounts, periods, matrix
|
||||
grouped = {}
|
||||
n_periods = len(periods)
|
||||
for i, acc in enumerate(accounts):
|
||||
parent = rollup_account(acc, depth)
|
||||
if parent not in grouped:
|
||||
grouped[parent] = [0.0] * n_periods
|
||||
for j in range(n_periods):
|
||||
grouped[parent][j] += matrix[i][j]
|
||||
return list(grouped.keys()), periods, list(grouped.values())
|
||||
|
||||
|
||||
# CSV PARSING
|
||||
|
||||
def parse_balance_csv(rows, invert=False):
|
||||
"""PARSE A SINGLE PERIOD BALANCE REPORT
|
||||
expects rows shaped like [account, amount], skips headers and totals
|
||||
"""
|
||||
accounts = []
|
||||
amounts = []
|
||||
for row in rows:
|
||||
if len(row) < 2:
|
||||
continue
|
||||
account = row[0].strip()
|
||||
if account.lower().rstrip(" :") in ("account", "total", ""):
|
||||
continue
|
||||
try:
|
||||
amount = clean_amount(row[1], invert=invert)
|
||||
except ValueError:
|
||||
continue
|
||||
accounts.append(account)
|
||||
amounts.append(amount)
|
||||
return accounts, amounts
|
||||
|
||||
|
||||
def parse_period_csv(rows, invert=False):
|
||||
"""PARSE A MULTI PERIOD BALANCE REPORT
|
||||
expects a header of account,period1,... and drops any trailing total column
|
||||
"""
|
||||
if not rows:
|
||||
return [], [], []
|
||||
header = [c.strip() for c in rows[0]]
|
||||
if len(header) < 2:
|
||||
raise ValueError(
|
||||
"period csv needs an account column and at least one value column"
|
||||
)
|
||||
periods = header[1:]
|
||||
if len(periods) > 1 and periods[-1].lower().rstrip(" :") == "total":
|
||||
periods = periods[:-1]
|
||||
accounts = []
|
||||
matrix = []
|
||||
for row in rows[1:]:
|
||||
if len(row) < 2:
|
||||
continue
|
||||
account = row[0].strip()
|
||||
if account.lower().rstrip(" :") in ("account", "total", ""):
|
||||
continue
|
||||
values = row[1:1 + len(periods)]
|
||||
try:
|
||||
parsed = [clean_amount(v, invert=invert) for v in values]
|
||||
except ValueError:
|
||||
continue
|
||||
accounts.append(account)
|
||||
matrix.append(parsed)
|
||||
return accounts, periods, matrix
|
||||
|
||||
|
||||
# DATA LOADING
|
||||
|
||||
def load_csv_rows(args, period):
|
||||
"""LOAD BALANCE REPORT ROWS WITH TTY AND PIPELINE VALIDATION
|
||||
calls hledger directly when --journal is given, otherwise reads csv from stdin
|
||||
"""
|
||||
if not args.journal:
|
||||
if sys.stdin.isatty():
|
||||
print("error: no input piped to stdin and --journal not specified", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if args.query:
|
||||
print("warning: query arguments are ignored when reading pre-computed csv from stdin", file=sys.stderr)
|
||||
return list(csv.reader(sys.stdin))
|
||||
cmd = ["hledger", "-f", args.journal, "balance", "-O", "csv"]
|
||||
if period:
|
||||
cmd.append("-M")
|
||||
cmd.extend(args.query)
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
except FileNotFoundError:
|
||||
print("error: hledger not found on PATH", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if result.returncode != 0:
|
||||
print(f"error: hledger exited with status {result.returncode}", file=sys.stderr)
|
||||
if result.stderr:
|
||||
print(result.stderr.strip(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return list(csv.reader(io.StringIO(result.stdout)))
|
||||
|
||||
|
||||
# RANKING HELPERS
|
||||
# keep charts readable when the account list is long
|
||||
|
||||
def top_n_with_other(labels, values, n):
|
||||
"""COLLAPSE LOW RANKED ENTRIES INTO A SINGLE OTHER CATEGORY"""
|
||||
paired = sorted(zip(labels, values), key=lambda p: abs(p[1]), reverse=True)
|
||||
if n <= 0 or len(paired) <= n:
|
||||
return [p[0] for p in paired], [p[1] for p in paired]
|
||||
top = paired[:n]
|
||||
rest = paired[n:]
|
||||
other_total = sum(v for _, v in rest)
|
||||
out_labels = [p[0] for p in top]
|
||||
out_values = [p[1] for p in top]
|
||||
if other_total != 0:
|
||||
out_labels.append("other")
|
||||
out_values.append(other_total)
|
||||
return out_labels, out_values
|
||||
|
||||
|
||||
def top_n_matrix_with_other(accounts, matrix, n):
|
||||
"""COLLAPSE LOW RANKED ACCOUNTS INTO AN OTHER ROW ACROSS ALL PERIODS"""
|
||||
totals = [sum(abs(v) for v in row) for row in matrix]
|
||||
order = sorted(range(len(accounts)), key=lambda i: totals[i], reverse=True)
|
||||
if n <= 0 or len(order) <= n:
|
||||
idx = order
|
||||
return [accounts[i] for i in idx], [matrix[i] for i in idx]
|
||||
top_idx = order[:n]
|
||||
rest_idx = order[n:]
|
||||
top_accounts = [accounts[i] for i in top_idx]
|
||||
top_matrix = [matrix[i] for i in top_idx]
|
||||
n_periods = len(matrix[0]) if matrix else 0
|
||||
other_row = [sum(matrix[i][j] for i in rest_idx) for j in range(n_periods)]
|
||||
if any(other_row):
|
||||
top_accounts.append("other")
|
||||
top_matrix.append(other_row)
|
||||
return top_accounts, top_matrix
|
||||
|
||||
|
||||
# ASCII RENDERING PRIMITIVES
|
||||
|
||||
def use_colour(args):
|
||||
if args.no_colour:
|
||||
return False
|
||||
if os.environ.get("NO_COLOR"):
|
||||
return False
|
||||
return sys.stdout.isatty()
|
||||
|
||||
|
||||
def sparkline(values):
|
||||
"""RENDER A COMPACT UNICODE SPARKLINE FOR ONE SERIES"""
|
||||
if not values:
|
||||
return ""
|
||||
lo = min(values)
|
||||
hi = max(values)
|
||||
span = hi - lo
|
||||
if span == 0:
|
||||
return SPARK_CHARS[0] * len(values)
|
||||
out = []
|
||||
for v in values:
|
||||
idx = int(((v - lo) / span) * (len(SPARK_CHARS) - 1))
|
||||
out.append(SPARK_CHARS[idx])
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def multiline_sparkline(values, rows=2):
|
||||
"""RENDER A MULTI-LINE ASCII SPARKLINE CANVAS"""
|
||||
if not values or rows <= 1:
|
||||
return [sparkline(values)]
|
||||
lo = min(values)
|
||||
hi = max(values)
|
||||
span = hi - lo if hi != lo else 1.0
|
||||
canvas = [[" " for _ in values] for _ in range(rows)]
|
||||
for col, v in enumerate(values):
|
||||
norm = (v - lo) / span
|
||||
scaled = norm * rows
|
||||
full_blocks = int(scaled)
|
||||
remainder = scaled - full_blocks
|
||||
for r in range(full_blocks):
|
||||
if r < rows:
|
||||
canvas[rows - 1 - r][col] = "█"
|
||||
if full_blocks < rows:
|
||||
idx = int(remainder * len(SPARK_CHARS))
|
||||
if idx > 0 or full_blocks == 0:
|
||||
canvas[rows - 1 - full_blocks][col] = SPARK_CHARS[min(idx, len(SPARK_CHARS) - 1)]
|
||||
return ["".join(row) for row in canvas]
|
||||
|
||||
|
||||
def segment_glyph(i, colour):
|
||||
"""PICK A GLYPH FOR SEGMENT I
|
||||
coloured terminals reuse a plain block since colour carries the
|
||||
distinction, plain terminals fall back to a letter per segment so
|
||||
adjacent segments of the same rendered width stay distinguishable
|
||||
"""
|
||||
if colour:
|
||||
return "█"
|
||||
return chr(ord("A") + (i % 26))
|
||||
|
||||
|
||||
def render_stacked_bar(labels, values, width, colour):
|
||||
"""RENDER ONE PROPORTIONAL STACKED BAR ACROSS THE GIVEN WIDTH"""
|
||||
abs_values = [abs(v) for v in values]
|
||||
total = sum(abs_values)
|
||||
if total <= 0:
|
||||
return " " * width
|
||||
segments = []
|
||||
allocated = 0
|
||||
for i, v in enumerate(abs_values):
|
||||
seg_width = int(round((v / total) * width))
|
||||
if i == len(abs_values) - 1:
|
||||
seg_width = max(width - allocated, 0)
|
||||
allocated += seg_width
|
||||
glyph = segment_glyph(i, colour)
|
||||
char = glyph * seg_width
|
||||
if colour:
|
||||
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
|
||||
char = f"\033[{code}m{char}\033[0m"
|
||||
segments.append(char)
|
||||
return "".join(segments)
|
||||
|
||||
|
||||
def render_legend(labels, values, colour):
|
||||
"""RENDER A LABEL AND PERCENTAGE LEGEND"""
|
||||
abs_values = [abs(v) for v in values]
|
||||
total = sum(abs_values)
|
||||
max_label = max((len(l) for l in labels), default=0)
|
||||
lines = []
|
||||
for i, (label, value) in enumerate(zip(labels, values)):
|
||||
pct = (abs(value) / total * 100) if total else 0.0
|
||||
glyph = segment_glyph(i, colour)
|
||||
swatch = glyph
|
||||
if colour:
|
||||
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
|
||||
swatch = f"\033[{code}m{glyph}\033[0m"
|
||||
lines.append(f"{swatch} {label:<{max_label}} {value:>14.2f} {pct:>5.1f}%")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# MATPLOTLIB HELPERS
|
||||
# imported lazily, ascii output never needs this dependency
|
||||
|
||||
def _matplotlib_pyplot(theme="default"):
|
||||
try:
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
if theme and theme != "default":
|
||||
try:
|
||||
plt.style.use(theme)
|
||||
except OSError:
|
||||
print(f"warning: matplotlib theme '{theme}' not found, falling back to default", file=sys.stderr)
|
||||
return plt
|
||||
except ImportError:
|
||||
print(
|
||||
"error: matplotlib not installed, install with "
|
||||
"'pacman -S python-matplotlib' or "
|
||||
"'pip install matplotlib --break-system-packages'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def render_histogram_png(accounts, amounts, path, currency, theme):
|
||||
plt = _matplotlib_pyplot(theme)
|
||||
data = sorted(zip(accounts, amounts), key=lambda p: abs(p[1]), reverse=True)
|
||||
labels = [d[0] for d in data]
|
||||
values = [d[1] for d in data]
|
||||
fig, ax = plt.subplots(figsize=(10, max(4, len(labels) * 0.4)))
|
||||
ax.barh(labels, values, color="#4C72B0")
|
||||
ax.invert_yaxis()
|
||||
ax.set_xlabel(f"amount ({currency})" if currency else "amount")
|
||||
ax.set_title("account balances")
|
||||
fig.tight_layout()
|
||||
fig.savefig(path, dpi=150)
|
||||
fig.clf()
|
||||
plt.close(fig)
|
||||
print(f"saved histogram to {path}", file=sys.stderr)
|
||||
|
||||
|
||||
# MATPLOTLIB HELPERS
|
||||
|
||||
def render_pie_png(labels, values, path, currency, theme):
|
||||
plt = _matplotlib_pyplot(theme)
|
||||
fig, ax = plt.subplots(figsize=(10, 7))
|
||||
abs_values = [abs(v) for v in values]
|
||||
total = sum(abs_values)
|
||||
|
||||
# format legend labels with percentages
|
||||
legend_labels = [
|
||||
f"{label} ({(abs(val) / total * 100):.1f}%)" if total else label
|
||||
for label, val in zip(labels, values)
|
||||
]
|
||||
|
||||
# suppress autopct on small slices to prevent overlap
|
||||
def autopct_fmt(pct):
|
||||
return f"{pct:.1f}%" if pct >= 3.0 else ""
|
||||
|
||||
wedges, _, autotexts = ax.pie(
|
||||
abs_values,
|
||||
autopct=autopct_fmt,
|
||||
startangle=90,
|
||||
pctdistance=0.75,
|
||||
)
|
||||
|
||||
# adjust text contrast based on slice luminance
|
||||
for wedge, autotext in zip(wedges, autotexts):
|
||||
if autotext.get_text():
|
||||
r, g, b = wedge.get_facecolor()[:3]
|
||||
luminance = 0.299 * r + 0.587 * g + 0.114 * b
|
||||
autotext.set_color("black" if luminance > 0.6 else "white")
|
||||
autotext.set_fontsize(9)
|
||||
autotext.set_weight("bold")
|
||||
|
||||
ax.legend(
|
||||
wedges,
|
||||
legend_labels,
|
||||
title="accounts",
|
||||
loc="center left",
|
||||
bbox_to_anchor=(1, 0, 0.5, 1),
|
||||
frameon=False,
|
||||
)
|
||||
|
||||
ax.set_title(
|
||||
f"account distribution ({currency})"
|
||||
if currency
|
||||
else "account distribution"
|
||||
)
|
||||
ax.axis("equal")
|
||||
fig.tight_layout()
|
||||
fig.savefig(path, dpi=150, bbox_inches="tight")
|
||||
fig.clf()
|
||||
plt.close(fig)
|
||||
print(f"saved pie chart to {path}", file=sys.stderr)
|
||||
|
||||
|
||||
def render_trend_png(accounts, periods, matrix, path, currency, theme):
|
||||
plt = _matplotlib_pyplot(theme)
|
||||
fig, ax = plt.subplots(figsize=(10, 6))
|
||||
for i, account in enumerate(accounts):
|
||||
ax.plot(periods, matrix[i], marker="o", label=account)
|
||||
ax.set_xlabel("period")
|
||||
ax.set_ylabel(f"amount ({currency})" if currency else "amount")
|
||||
ax.set_title("balance trend")
|
||||
ax.legend()
|
||||
plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
|
||||
fig.tight_layout()
|
||||
fig.savefig(path, dpi=150)
|
||||
fig.clf()
|
||||
plt.close(fig)
|
||||
print(f"saved trend chart to {path}", file=sys.stderr)
|
||||
|
||||
|
||||
def render_stacked_png(accounts, periods, matrix, path, currency, theme):
|
||||
plt = _matplotlib_pyplot(theme)
|
||||
fig, ax = plt.subplots(figsize=(10, 6))
|
||||
bottom = [0.0] * len(periods)
|
||||
for i, account in enumerate(accounts):
|
||||
values = [abs(v) for v in matrix[i]]
|
||||
ax.bar(periods, values, bottom=bottom, label=account)
|
||||
bottom = [b + v for b, v in zip(bottom, values)]
|
||||
ax.set_xlabel("period")
|
||||
ax.set_ylabel(f"amount ({currency})" if currency else "amount")
|
||||
ax.set_title("spending by category per period")
|
||||
ax.legend()
|
||||
plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
|
||||
fig.tight_layout()
|
||||
fig.savefig(path, dpi=150)
|
||||
fig.clf()
|
||||
plt.close(fig)
|
||||
print(f"saved stacked chart to {path}", file=sys.stderr)
|
||||
|
||||
|
||||
def render_grouped_png(accounts, periods, matrix, path, currency, theme):
|
||||
plt = _matplotlib_pyplot(theme)
|
||||
x = list(range(len(periods)))
|
||||
n = len(accounts)
|
||||
width = 0.8 / max(n, 1)
|
||||
fig, ax = plt.subplots(figsize=(10, 6))
|
||||
for i, account in enumerate(accounts):
|
||||
values = [abs(v) for v in matrix[i]]
|
||||
offset = (i - (n - 1) / 2) * width
|
||||
positions = [xi + offset for xi in x]
|
||||
ax.bar(positions, values, width=width, label=account)
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(periods, rotation=45, ha="right")
|
||||
ax.set_xlabel("period")
|
||||
ax.set_ylabel(f"amount ({currency})" if currency else "amount")
|
||||
ax.set_title("monthly comparison by account")
|
||||
ax.legend()
|
||||
fig.tight_layout()
|
||||
fig.savefig(path, dpi=150)
|
||||
fig.clf()
|
||||
plt.close(fig)
|
||||
print(f"saved grouped chart to {path}", file=sys.stderr)
|
||||
|
||||
|
||||
# SUBCOMMANDS
|
||||
|
||||
def cmd_histogram(args):
|
||||
rows = load_csv_rows(args, period=False)
|
||||
accounts, amounts = parse_balance_csv(rows, invert=args.invert)
|
||||
if not accounts:
|
||||
print("error: no account data found in input", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
accounts, amounts = aggregate_single_period(accounts, amounts, args.depth)
|
||||
total = sum(amounts)
|
||||
abs_total = sum(abs(a) for a in amounts)
|
||||
data = sorted(zip(accounts, amounts), key=lambda p: abs(p[1]), reverse=True)
|
||||
max_label = max(len(a) for a, _ in data)
|
||||
bar_width = 35
|
||||
print(f"\n{'ACCOUNT':<{max_label}} | {'AMOUNT':>14} | {'%':>5} | HISTOGRAM")
|
||||
print("-" * (max_label + 62))
|
||||
for account, amount in data:
|
||||
pct = (abs(amount) / abs_total * 100) if abs_total else 0.0
|
||||
filled = int((pct / 100) * bar_width)
|
||||
print(f"{account:<{max_label}} | {amount:>14.2f} | {pct:>4.1f}% | {'█' * filled}")
|
||||
print("-" * (max_label + 62))
|
||||
print(f"{'TOTAL':<{max_label}} | {total:>14.2f} | 100.0% |\n")
|
||||
if args.png:
|
||||
render_histogram_png(accounts, amounts, args.png, args.currency, args.theme)
|
||||
|
||||
|
||||
def cmd_pie(args):
|
||||
rows = load_csv_rows(args, period=False)
|
||||
accounts, amounts = parse_balance_csv(rows, invert=args.invert)
|
||||
if not accounts:
|
||||
print("error: no account data found in input", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
accounts, amounts = aggregate_single_period(accounts, amounts, args.depth)
|
||||
labels, values = top_n_with_other(accounts, amounts, args.top)
|
||||
colour = use_colour(args)
|
||||
print()
|
||||
print(render_stacked_bar(labels, values, args.width, colour))
|
||||
print()
|
||||
print(render_legend(labels, values, colour))
|
||||
print()
|
||||
if args.png:
|
||||
render_pie_png(labels, values, args.png, args.currency, args.theme)
|
||||
|
||||
|
||||
def cmd_trend(args):
|
||||
rows = load_csv_rows(args, period=True)
|
||||
accounts, periods, matrix = parse_period_csv(rows, invert=args.invert)
|
||||
if not accounts:
|
||||
print("error: no account data found in input", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
accounts, periods, matrix = aggregate_multi_period(accounts, periods, matrix, args.depth)
|
||||
if args.top > 0:
|
||||
accounts, matrix = top_n_matrix_with_other(accounts, matrix, args.top)
|
||||
else:
|
||||
# DEFAULT TO A SINGLE AGGREGATE SERIES
|
||||
# sums every account per period unless --top requests individual lines
|
||||
agg = [sum(row[j] for row in matrix) for j in range(len(periods))]
|
||||
accounts, matrix = ["total"], [agg]
|
||||
colour = use_colour(args)
|
||||
max_label = max(len(a) for a in accounts)
|
||||
print()
|
||||
for i, account in enumerate(accounts):
|
||||
series = matrix[i]
|
||||
spark_lines = multiline_sparkline(series, rows=args.rows)
|
||||
if colour:
|
||||
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
|
||||
spark_lines = [f"\033[{code}m{sl}\033[0m" for sl in spark_lines]
|
||||
if args.rows > 1:
|
||||
print(f"{account}: {series[0]:>12.2f} -> {series[-1]:>12.2f}")
|
||||
for sl in spark_lines:
|
||||
print(f" {sl}")
|
||||
print()
|
||||
else:
|
||||
print(f"{account:<{max_label}} {spark_lines[0]} {series[0]:>12.2f} -> {series[-1]:>12.2f}")
|
||||
if args.rows <= 1:
|
||||
print()
|
||||
print("periods: " + ", ".join(periods))
|
||||
print()
|
||||
if args.png:
|
||||
render_trend_png(accounts, periods, matrix, args.png, args.currency, args.theme)
|
||||
|
||||
|
||||
def cmd_stacked(args):
|
||||
rows = load_csv_rows(args, period=True)
|
||||
accounts, periods, matrix = parse_period_csv(rows, invert=args.invert)
|
||||
if not accounts:
|
||||
print("error: no account data found in input", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
accounts, periods, matrix = aggregate_multi_period(accounts, periods, matrix, args.depth)
|
||||
accounts, matrix = top_n_matrix_with_other(accounts, matrix, args.top)
|
||||
colour = use_colour(args)
|
||||
max_period = max(len(p) for p in periods)
|
||||
print()
|
||||
for j, period in enumerate(periods):
|
||||
values = [abs(matrix[i][j]) for i in range(len(accounts))]
|
||||
bar = render_stacked_bar(accounts, values, args.width, colour)
|
||||
print(f"{period:<{max_period}} {bar}")
|
||||
print()
|
||||
totals = [sum(abs(matrix[i][j]) for j in range(len(periods))) for i in range(len(accounts))]
|
||||
print(render_legend(accounts, totals, colour))
|
||||
print()
|
||||
if args.png:
|
||||
render_stacked_png(accounts, periods, matrix, args.png, args.currency, args.theme)
|
||||
|
||||
|
||||
def cmd_grouped(args):
|
||||
rows = load_csv_rows(args, period=True)
|
||||
accounts, periods, matrix = parse_period_csv(rows, invert=args.invert)
|
||||
if not accounts:
|
||||
print("error: no account data found in input", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
accounts, periods, matrix = aggregate_multi_period(accounts, periods, matrix, args.depth)
|
||||
accounts, matrix = top_n_matrix_with_other(accounts, matrix, args.top)
|
||||
colour = use_colour(args)
|
||||
bar_width = 20
|
||||
print()
|
||||
for i, account in enumerate(accounts):
|
||||
row = [abs(v) for v in matrix[i]]
|
||||
row_max = max(row) if row else 0
|
||||
print(account)
|
||||
for j, period in enumerate(periods):
|
||||
filled = int((row[j] / row_max) * bar_width) if row_max else 0
|
||||
char = "█" * filled
|
||||
if colour:
|
||||
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
|
||||
char = f"\033[{code}m{char}\033[0m"
|
||||
padded = char + " " * (bar_width - filled)
|
||||
print(f" {period:<10} {padded} {row[j]:>12.2f}")
|
||||
print()
|
||||
if args.png:
|
||||
render_grouped_png(accounts, periods, matrix, args.png, args.currency, args.theme)
|
||||
|
||||
|
||||
# ARGUMENT PARSING
|
||||
|
||||
def build_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="terminal and image charts for hledger csv output, reads csv from standard input"
|
||||
)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
common = argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument("--png", metavar="PATH", help="also render a matplotlib png to this path")
|
||||
common.add_argument("-t", "--theme", default="default", help="matplotlib stylesheet theme (e.g., dark_background, ggplot)")
|
||||
common.add_argument("--currency", default="", help="currency label for chart titles")
|
||||
common.add_argument("--no-colour", action="store_true", help="disable ansi colour output")
|
||||
common.add_argument("-i", "--invert", action="store_true", help="invert mathematical signs (multiply all amounts by -1)")
|
||||
common.add_argument("-d", "--depth", type=int, default=0, help="summarise accounts to this hierarchy depth (0 for full names)")
|
||||
common.add_argument("--journal", metavar="PATH", help="hledger journal file, calls hledger directly instead of reading csv from stdin")
|
||||
common.add_argument("query", nargs=argparse.REMAINDER, help="optional hledger query passed through to hledger balance, e.g. an account pattern or -b/-e dates, only used with --journal")
|
||||
|
||||
p_hist = sub.add_parser("histogram", parents=[common], help="horizontal bar histogram of account balances")
|
||||
p_hist.set_defaults(func=cmd_histogram)
|
||||
|
||||
p_pie = sub.add_parser("pie", parents=[common], help="proportional bar and pie chart of account distribution")
|
||||
p_pie.add_argument("--top", type=int, default=8, help="accounts to show before collapsing into other, 0 for no limit")
|
||||
p_pie.add_argument("--width", type=int, default=60, help="width of the ascii bar in characters")
|
||||
p_pie.set_defaults(func=cmd_pie)
|
||||
|
||||
p_trend = sub.add_parser("trend", parents=[common], help="balance trend over time as sparklines and a line chart")
|
||||
p_trend.add_argument("--top", type=int, default=0, help="individual account lines to show, 0 shows only the total")
|
||||
p_trend.add_argument("-r", "--rows", type=int, default=1, help="height of the ascii sparkline canvas in rows (default 1)")
|
||||
p_trend.set_defaults(func=cmd_trend)
|
||||
|
||||
p_stack = sub.add_parser("stacked", parents=[common], help="stacked bar of spending by category per period")
|
||||
p_stack.add_argument("--top", type=int, default=8, help="accounts to show before collapsing into other, 0 for no limit")
|
||||
p_stack.add_argument("--width", type=int, default=50, help="width of each ascii bar in characters")
|
||||
p_stack.set_defaults(func=cmd_stacked)
|
||||
|
||||
p_group = sub.add_parser("grouped", parents=[common], help="grouped bar comparison of accounts across periods")
|
||||
p_group.add_argument("--top", type=int, default=6, help="accounts to show before collapsing into other, 0 for no limit")
|
||||
p_group.set_defaults(func=cmd_grouped)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
parser = build_parser()
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
35
.local/bin/md2csv
Executable file
35
.local/bin/md2csv
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python3
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-07-28 11:14 +0100
|
||||
|
||||
import csv
|
||||
import sys
|
||||
|
||||
# INPUT VALIDATION
|
||||
# ensure an input file argument is provided
|
||||
if len(sys.argv) < 2:
|
||||
print("usage: md2csv <input.md>")
|
||||
sys.exit(1)
|
||||
|
||||
input_file = sys.argv[1]
|
||||
|
||||
# TABLE CONVERSION
|
||||
# open input markdown and initialize standard output csv writer
|
||||
with open(input_file, "r", encoding="utf-8") as infile:
|
||||
writer = csv.writer(sys.stdout)
|
||||
|
||||
for line in infile:
|
||||
line = line.strip()
|
||||
|
||||
# ROW FILTERING
|
||||
# skip empty lines and markdown layout separator lines
|
||||
if not line or "---" in line:
|
||||
continue
|
||||
|
||||
# CELL EXTRACTION
|
||||
# check if line represents a markdown table row
|
||||
if line.startswith("|") and line.endswith("|"):
|
||||
# strip leading and trailing pipes then split by delimiter
|
||||
raw_cells = line[1:-1].split("|")
|
||||
cleaned_cells = [cell.strip() for cell in raw_cells]
|
||||
writer.writerow(cleaned_cells)
|
||||
198
.local/bin/md2hledger
Executable file
198
.local/bin/md2hledger
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
#!/usr/bin/env python3
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-07-27
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
# CONFIGURATION
|
||||
# set default accounting buckets, fallback currency, and journal vault path
|
||||
DEFAULT_EXPENSE_ACCOUNT = "Expenses:Uncategorised"
|
||||
DEFAULT_INCOME_ACCOUNT = "Income:Uncategorised"
|
||||
DEFAULT_PAYMENT_ACCOUNT = "Assets:Banks:OPay"
|
||||
DEFAULT_CURRENCY = "NGN"
|
||||
NOTES_DIR = Path("~/obsidian/finances").expanduser()
|
||||
|
||||
# ACCOUNT NORMALISATION
|
||||
# intercept legacy account strings and fix misrouted categories at compile time
|
||||
def normalize_account(account_str, currency, name):
|
||||
clean_account = account_str.strip()
|
||||
clean_name = name.lower()
|
||||
|
||||
# auto resolve generic raenest root postings to multi currency leaf accounts
|
||||
if clean_account == "Assets:Banks:Raenest":
|
||||
if currency == "USD":
|
||||
return "Assets:Banks:Raenest:USD"
|
||||
if currency == "GBP":
|
||||
return "Assets:Banks:Raenest:GBP"
|
||||
if currency == "EUR":
|
||||
return "Assets:Banks:Raenest:EUR"
|
||||
return "Assets:Banks:Raenest:NGN"
|
||||
|
||||
# intercept fee and charge keywords before portfolio redirects
|
||||
if any(w in clean_name for w in ("charge", "fee", "loss", "tax", "commission")):
|
||||
if "uncategorised" in clean_account.lower():
|
||||
return "Expenses:Financial:BankLoss"
|
||||
return clean_account
|
||||
|
||||
# redirect uncategorised investment transfers to asset portfolios
|
||||
if "uncategorised" in clean_account.lower():
|
||||
if "cowrywise" in clean_name:
|
||||
return "Assets:Investments:Cowrywise"
|
||||
if "bybit" in clean_name:
|
||||
return "Assets:Investments:Bybit"
|
||||
if "octafx" in clean_name:
|
||||
return "Assets:Investments:OctaFX"
|
||||
|
||||
return clean_account
|
||||
|
||||
# FRONTMATTER PARSER
|
||||
# extract yaml between the opening and closing dashes without external libraries
|
||||
def parse_frontmatter(file_path):
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
except OSError as e:
|
||||
print(f"warning: unable to read {file_path} ({e})", file=sys.stderr)
|
||||
return None
|
||||
|
||||
if not content.startswith("---"):
|
||||
return None
|
||||
|
||||
parts = content.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
return None
|
||||
|
||||
yaml_text = parts[1]
|
||||
data = {}
|
||||
for line in yaml_text.strip().split("\n"):
|
||||
# ignore empty lines and full line comments
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
if ":" in line:
|
||||
key, val = line.split(":", 1)
|
||||
# strip inline yaml comments and surrounding quotation marks
|
||||
clean_val = val.split("#", 1)[0].strip().strip('\'"')
|
||||
data[key.strip().lower()] = clean_val
|
||||
|
||||
return data
|
||||
|
||||
# JOURNAL GENERATOR
|
||||
# convert parsed dictionary into an hledger double entry transaction string
|
||||
def generate_entry(data, file_path):
|
||||
try:
|
||||
date = data["date"]
|
||||
name = data.get("name", "untitled transaction")
|
||||
tx_type = data.get("type", "expense").lower()
|
||||
|
||||
# read dynamic accounts or fallback to defaults
|
||||
currency = data.get("currency", DEFAULT_CURRENCY).strip().upper()
|
||||
raw_payment = data.get("payment_account", DEFAULT_PAYMENT_ACCOUNT)
|
||||
raw_target = data.get("account", DEFAULT_EXPENSE_ACCOUNT if tx_type not in {"income", "liability", "borrowing", "inflow"} else DEFAULT_INCOME_ACCOUNT)
|
||||
|
||||
# normalise accounts against currency and description keywords
|
||||
payment_account = normalize_account(raw_payment, currency, name)
|
||||
target_account = normalize_account(raw_target, currency, name)
|
||||
|
||||
# use decimal for precise financial arithmetic
|
||||
unit = Decimal(data.get("unit", "1"))
|
||||
amount = Decimal(data["amount"])
|
||||
total = unit * amount
|
||||
|
||||
# CHARGE RESOLUTION
|
||||
# extract bank transfer fees and pos agent charges without multiplying by unit
|
||||
transfer_charges = Decimal(data.get("transfer_charges", "0"))
|
||||
pos_charges = Decimal(data.get("pos_charges", "0"))
|
||||
|
||||
# FOREX CONVERSIONS
|
||||
# handle dual currency exchanges and correct backwards conversion frontmatter
|
||||
if tx_type == "conversion":
|
||||
source_currency = data.get("source_currency", "USD").strip().upper()
|
||||
source_amount = Decimal(data.get("source_amount", "0")) * unit
|
||||
total_source_outflow = source_amount
|
||||
|
||||
charge_postings = ""
|
||||
if pos_charges > 0:
|
||||
charge_postings += f" Expenses:Financial:POSFees {pos_charges:.2f} {source_currency}\n"
|
||||
total_source_outflow += pos_charges
|
||||
if transfer_charges > 0:
|
||||
charge_postings += f" Expenses:Financial:BankLoss {transfer_charges:.2f} {source_currency}\n"
|
||||
total_source_outflow += transfer_charges
|
||||
|
||||
# fix reversed raenest conversion where foreign currency was logged as spent instead of local currency
|
||||
if ("raenest" in name.lower() or "raenest" in payment_account.lower()) and source_currency != DEFAULT_CURRENCY:
|
||||
entry = (
|
||||
f"{date} * {name}\n"
|
||||
f" {payment_account} {source_amount:.2f} {source_currency}\n"
|
||||
f" {target_account} -{total:.2f} {currency} @@ {source_amount:.2f} {source_currency}\n"
|
||||
f"{charge_postings}\n"
|
||||
)
|
||||
return entry
|
||||
|
||||
entry = (
|
||||
f"{date} * {name}\n"
|
||||
f" {target_account} {total:.2f} {currency}\n"
|
||||
f"{charge_postings}"
|
||||
f" {payment_account} -{total_source_outflow:.2f} {source_currency} @@ {total:.2f} {currency}\n\n"
|
||||
)
|
||||
return entry
|
||||
|
||||
# CASH FLOW ROUTING
|
||||
# route transactions based on whether value enters or leaves the payment account
|
||||
inflow_types = {"income", "liability", "borrowing"}
|
||||
|
||||
# handle loans collected and standard inflows unless routed from equity windfalls
|
||||
if tx_type == "inflow" and not payment_account.lower().startswith("equity"):
|
||||
inflow_types.add("inflow")
|
||||
|
||||
if tx_type in inflow_types:
|
||||
entry = (
|
||||
f"{date} * {name}\n"
|
||||
f" {payment_account} {currency} {total:.2f}\n"
|
||||
f" {target_account} -{currency} {total:.2f}\n\n"
|
||||
)
|
||||
else:
|
||||
charge_postings = ""
|
||||
total_outflow = total
|
||||
|
||||
if pos_charges > 0:
|
||||
charge_postings += f" Expenses:Financial:POSFees {currency} {pos_charges:.2f}\n"
|
||||
total_outflow += pos_charges
|
||||
|
||||
if transfer_charges > 0:
|
||||
charge_postings += f" Expenses:Financial:BankLoss {currency} {transfer_charges:.2f}\n"
|
||||
total_outflow += transfer_charges
|
||||
|
||||
entry = (
|
||||
f"{date} * {name}\n"
|
||||
f" {target_account} {currency} {total:.2f}\n"
|
||||
f"{charge_postings}"
|
||||
f" {payment_account} -{currency} {total_outflow:.2f}\n\n"
|
||||
)
|
||||
|
||||
return entry
|
||||
except (KeyError, InvalidOperation, TypeError) as e:
|
||||
# log malformed notes to standard error to prevent silent data loss
|
||||
print(f"warning: skipping malformed note '{file_path}' ({type(e).__name__})", file=sys.stderr)
|
||||
return ""
|
||||
|
||||
# MAIN EXECUTION
|
||||
# scan the journal directory recursively and print compiled journal entries
|
||||
if __name__ == "__main__":
|
||||
if not NOTES_DIR.is_dir():
|
||||
print(f"error: directory '{NOTES_DIR}' does not exist", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
entries = []
|
||||
# rglob enables recursive scanning through nested folder structures
|
||||
for file_path in sorted(NOTES_DIR.rglob("*.md")):
|
||||
data = parse_frontmatter(file_path)
|
||||
if data and "date" in data and "amount" in data:
|
||||
entry = generate_entry(data, file_path)
|
||||
if entry:
|
||||
entries.append(entry)
|
||||
|
||||
print("".join(entries), end="")
|
||||
158
.local/bin/obsd
Executable file
158
.local/bin/obsd
Executable file
|
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-08-08 16:59 +0100
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
# CONFIGURATION
|
||||
LOCAL_VAULT="${HOME}/obsidian"
|
||||
REMOTE_HOST="ish"
|
||||
REMOTE_VAULT="~/obsidian"
|
||||
|
||||
RSYNC_FLAGS=(
|
||||
-az
|
||||
--delete
|
||||
--human-readable
|
||||
--itemize-changes
|
||||
)
|
||||
|
||||
EXCLUDES=(
|
||||
--exclude=".DS_Store"
|
||||
--exclude=".Trash-*"
|
||||
|
||||
# Obsidian cache
|
||||
--exclude=".obsidian/cache/"
|
||||
--exclude=".obsidian/workspace*.json"
|
||||
--exclude=".obsidian/workspace-mobile.json"
|
||||
|
||||
# Vim
|
||||
--exclude="*.swp"
|
||||
--exclude="*.swo"
|
||||
|
||||
# Temporary files
|
||||
--exclude="*~"
|
||||
)
|
||||
|
||||
# HELP
|
||||
usage() {
|
||||
cat <<EOF
|
||||
obsd - Obsidian Vault Synchroniser
|
||||
|
||||
Usage:
|
||||
obsd push
|
||||
obsd pull
|
||||
obsd status
|
||||
obsd diff
|
||||
obsd backup
|
||||
obsd help
|
||||
|
||||
Commands
|
||||
|
||||
push Local -> iSH
|
||||
pull iSH -> Local
|
||||
status Dry-run using timestamps
|
||||
diff Dry-run using checksums
|
||||
backup Create compressed archive
|
||||
EOF
|
||||
}
|
||||
|
||||
# CHECKS
|
||||
check_requirements() {
|
||||
command -v rsync >/dev/null \
|
||||
|| { echo "rsync not installed."; exit 1; }
|
||||
|
||||
command -v ssh >/dev/null \
|
||||
|| { echo "ssh not installed."; exit 1; }
|
||||
|
||||
[[ -d "$LOCAL_VAULT" ]] \
|
||||
|| { echo "Vault not found: $LOCAL_VAULT"; exit 1; }
|
||||
}
|
||||
|
||||
# BACKUP
|
||||
backup() {
|
||||
local backup_dir="${HOME}/obsidian-backups"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
local stamp
|
||||
stamp="$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
local archive="${backup_dir}/obsidian-${stamp}.tar.zst"
|
||||
|
||||
echo "Creating backup..."
|
||||
|
||||
tar \
|
||||
--zstd \
|
||||
-cf "$archive" \
|
||||
-C "$HOME" \
|
||||
obsidian
|
||||
|
||||
echo
|
||||
echo "Backup created:"
|
||||
echo " $archive"
|
||||
}
|
||||
|
||||
# PUSH
|
||||
push() {
|
||||
rsync \
|
||||
"${RSYNC_FLAGS[@]}" \
|
||||
"${EXCLUDES[@]}" \
|
||||
"${LOCAL_VAULT}/" \
|
||||
"${REMOTE_HOST}:${REMOTE_VAULT}/"
|
||||
}
|
||||
|
||||
# PULL
|
||||
pull() {
|
||||
rsync \
|
||||
"${RSYNC_FLAGS[@]}" \
|
||||
"${EXCLUDES[@]}" \
|
||||
"${REMOTE_HOST}:${REMOTE_VAULT}/" \
|
||||
"${LOCAL_VAULT}/"
|
||||
}
|
||||
|
||||
# STATUS
|
||||
status() {
|
||||
rsync \
|
||||
-azin \
|
||||
"${EXCLUDES[@]}" \
|
||||
"${LOCAL_VAULT}/" \
|
||||
"${REMOTE_HOST}:${REMOTE_VAULT}/"
|
||||
}
|
||||
|
||||
# DIFF
|
||||
diff() {
|
||||
rsync \
|
||||
-azcin \
|
||||
"${EXCLUDES[@]}" \
|
||||
"${LOCAL_VAULT}/" \
|
||||
"${REMOTE_HOST}:${REMOTE_VAULT}/"
|
||||
}
|
||||
|
||||
# MAIN
|
||||
check_requirements
|
||||
|
||||
case "${1:-help}" in
|
||||
push)
|
||||
push
|
||||
;;
|
||||
pull)
|
||||
pull
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
diff)
|
||||
diff
|
||||
;;
|
||||
backup)
|
||||
backup
|
||||
;;
|
||||
help|-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: ${1}"
|
||||
echo
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
77
.local/bin/obsd-bash-2026-06-03
Executable file
77
.local/bin/obsd-bash-2026-06-03
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import time
|
||||
import subprocess
|
||||
from threading import Timer
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from pathlib import Path
|
||||
|
||||
# PRODUCTION CONFIGURATION
|
||||
local_dir = "//home/sgbv/obsidian/"
|
||||
remote_dest = "ish:/root/obsidian/"
|
||||
debounce_seconds = 2.0
|
||||
|
||||
# ignore workspace ui states to prevent infinite sync loops
|
||||
excludes = [
|
||||
"--exclude=.obsidian/workspace*",
|
||||
"--exclude=.trash/",
|
||||
"--exclude=.git/"
|
||||
]
|
||||
|
||||
def build_rsync_cmd(src, dest):
|
||||
return ["rsync", "-avz", "--delete"] + excludes + [src, dest]
|
||||
|
||||
def pull_sync():
|
||||
print(f"pulling changes from ish to arch...")
|
||||
subprocess.run(build_rsync_cmd(remote_dest, local_dir))
|
||||
print("pull complete.")
|
||||
|
||||
def push_sync():
|
||||
print(f"pushing changes from arch to ish...")
|
||||
subprocess.run(build_rsync_cmd(local_dir, remote_dest))
|
||||
print("push complete.")
|
||||
|
||||
class watchdog_push_handler(FileSystemEventHandler):
|
||||
def __init__(self):
|
||||
self.sync_timer = None
|
||||
|
||||
def on_any_event(self, event):
|
||||
# ignore directory changes and workspace/hidden cache updates
|
||||
if event.is_directory or any(x in event.src_path for x in [".obsidian", ".trash"]):
|
||||
return
|
||||
|
||||
print(f"local change detected: {event.event_type}")
|
||||
if self.sync_timer:
|
||||
self.sync_timer.cancel()
|
||||
|
||||
self.sync_timer = Timer(debounce_seconds, push_sync)
|
||||
self.sync_timer.start()
|
||||
|
||||
def watch_mode():
|
||||
print(f"arch watchdog listening on {local_dir}")
|
||||
handler = watchdog_push_handler()
|
||||
observer = Observer()
|
||||
observer.schedule(handler, local_dir, recursive=True)
|
||||
observer.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\nstopping watchdog...")
|
||||
observer.stop()
|
||||
observer.join()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="arch sync node")
|
||||
parser.add_argument("mode", nargs="?", default="watch", choices=["watch", "push", "pull"])
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.mode == "pull":
|
||||
pull_sync()
|
||||
elif args.mode == "push":
|
||||
push_sync()
|
||||
else:
|
||||
watch_mode()
|
||||
114
.local/bin/obsd-python-2026-07-26
Executable file
114
.local/bin/obsd-python-2026-07-26
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env python3
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-06-03 20:12 +0100
|
||||
# UPDATED: 2026-07-24 09:40 +0100
|
||||
"""
|
||||
The script was modified to replace the archive flag with explicit metadata-stripping and checksum evaluation flags (`-rltz`, `--checksum`, `--no-perms`, `--omit-dir-times`) to prevent inode timestamp updates during rsync pulls, while introducing a native post-pull sanitisation function that removes iOS-injected carriage returns without altering file modification times.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
from threading import Timer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
# PRODUCTION CONFIGURATION
|
||||
local_dir = "//home/sgbv/obsidian/"
|
||||
remote_dest = "ish:/root/obsidian/"
|
||||
debounce_seconds = 2.0
|
||||
|
||||
# ignore workspace ui states to prevent infinite sync loops
|
||||
excludes = [
|
||||
"--exclude=.obsidian/workspace*",
|
||||
"--exclude=.trash/",
|
||||
"--exclude=.git/"
|
||||
]
|
||||
|
||||
def build_rsync_cmd(src, dest):
|
||||
# use checksum and strip posix metadata mapping to prevent inode ctime destruction
|
||||
flags = [
|
||||
"rsync",
|
||||
"-rltz",
|
||||
"--checksum",
|
||||
"--delete",
|
||||
"--no-perms",
|
||||
"--no-owner",
|
||||
"--no-group",
|
||||
"--omit-dir-times"
|
||||
]
|
||||
return flags + excludes + [src, dest]
|
||||
|
||||
def sanitize_line_endings():
|
||||
# strip dos carriage returns natively while preserving file modification timestamps
|
||||
print("sanitising line endings...")
|
||||
vault_path = Path(local_dir)
|
||||
for file_path in vault_path.rglob("*.md"):
|
||||
try:
|
||||
content = file_path.read_bytes()
|
||||
if b"\r\n" in content:
|
||||
clean_content = content.replace(b"\r\n", b"\n")
|
||||
# record original timestamps before writing
|
||||
stat_data = file_path.stat()
|
||||
file_path.write_bytes(clean_content)
|
||||
# restore original access and modification timestamps
|
||||
os.utime(file_path, (stat_data.st_atime, stat_data.st_mtime))
|
||||
print(f"cleaned carriage returns: {file_path.name}")
|
||||
except Exception as error:
|
||||
print(f"sanitisation error on {file_path.name}: {error}")
|
||||
|
||||
def pull_sync():
|
||||
print("pulling changes from ish to arch...")
|
||||
subprocess.run(build_rsync_cmd(remote_dest, local_dir))
|
||||
sanitize_line_endings()
|
||||
print("pull complete.")
|
||||
|
||||
def push_sync():
|
||||
print("pushing changes from arch to ish...")
|
||||
subprocess.run(build_rsync_cmd(local_dir, remote_dest))
|
||||
print("push complete.")
|
||||
|
||||
class watchdog_push_handler(FileSystemEventHandler):
|
||||
def __init__(self):
|
||||
self.sync_timer = None
|
||||
|
||||
def on_any_event(self, event):
|
||||
# ignore directory changes and workspace or hidden cache updates
|
||||
if event.is_directory or any(x in event.src_path for x in [".obsidian", ".trash"]):
|
||||
return
|
||||
|
||||
print(f"local change detected: {event.event_type}")
|
||||
if self.sync_timer:
|
||||
self.sync_timer.cancel()
|
||||
|
||||
self.sync_timer = Timer(debounce_seconds, push_sync)
|
||||
self.sync_timer.start()
|
||||
|
||||
def watch_mode():
|
||||
print(f"arch watchdog listening on {local_dir}")
|
||||
handler = watchdog_push_handler()
|
||||
observer = Observer()
|
||||
observer.schedule(handler, local_dir, recursive=True)
|
||||
observer.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\nstopping watchdog...")
|
||||
observer.stop()
|
||||
observer.join()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="arch sync node")
|
||||
parser.add_argument("mode", nargs="?", default="watch", choices=["watch", "push", "pull"])
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.mode == "pull":
|
||||
pull_sync()
|
||||
elif args.mode == "push":
|
||||
push_sync()
|
||||
else:
|
||||
watch_mode()
|
||||
11
.local/bin/pac-size
Executable file
11
.local/bin/pac-size
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
pacman -Qi $(pactree -u "$1") | awk '/^Installed Size/ {
|
||||
size=$4
|
||||
if ($5 == "KiB") size /= 1024
|
||||
if ($5 == "GiB") size *= 1024
|
||||
if ($5 == "B") size /= 1048576
|
||||
total += size
|
||||
} END {
|
||||
printf "Total ecosystem size: %.2f MiB\n", total
|
||||
}'
|
||||
14
.local/bin/pick
Executable file
14
.local/bin/pick
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# DEPENDENCY CHECK
|
||||
# ensures required wayland utilities are present before execution
|
||||
if ! command -v hyprpicker >/dev/null 2>&1; then
|
||||
notify-send "error" "hyprpicker is not installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# EXECUTION AND NOTIFICATION
|
||||
# captures the hex value and triggers a system notification
|
||||
HEX_CODE=$(hyprpicker -a)
|
||||
notify-send "colour selected" "hex code $HEX_CODE copied to clipboard."
|
||||
40
.local/bin/power
Executable file
40
.local/bin/power
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# MENU ENTRIES
|
||||
# define the string values for the power options
|
||||
lock="lock"
|
||||
suspend="suspend"
|
||||
hibernate="hibernate"
|
||||
logout="log out"
|
||||
reboot="reboot"
|
||||
shutdown="shutdown"
|
||||
|
||||
# USER INPUT
|
||||
# pipe options into fuzzel and capture the string output
|
||||
selected=$(printf "%s\n%s\n%s\n%s\n%s\n%s" "$lock" "$suspend" "$hibernate" "$logout" "$reboot" "$shutdown" | fuzzel --dmenu --prompt "power: ")
|
||||
|
||||
# EXECUTION ROUTING
|
||||
# match the captured string to the system daemon command
|
||||
case "$selected" in
|
||||
"$lock")
|
||||
swaylock -f -c 000000
|
||||
;;
|
||||
"$suspend")
|
||||
loginctl suspend
|
||||
;;
|
||||
"$hibernate")
|
||||
loginctl hibernate
|
||||
;;
|
||||
"$logout")
|
||||
loginctl terminate-session "$XDG_SESSION_ID"
|
||||
;;
|
||||
"$reboot")
|
||||
loginctl reboot
|
||||
;;
|
||||
"$shutdown")
|
||||
loginctl shutdown
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
468
.local/bin/rate
Executable file
468
.local/bin/rate
Executable file
|
|
@ -0,0 +1,468 @@
|
|||
#!/usr/bin/env python3
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-05-26 12:45 +0100
|
||||
# UPDATED: 2026-07-12 15:13 +0100
|
||||
"""
|
||||
rate v0.6
|
||||
terminal multi-asset valuation engine. runs entirely on the python standard
|
||||
library with zero external dependencies. supports specialized workflows for
|
||||
forex, precious metals, energy futures, market indices, crypto, equities, and ETFs.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
# FORCE IPV4 RESOLUTION
|
||||
# prevent unreachable network errors on broken ipv6 internet gateways
|
||||
_old_getaddrinfo = socket.getaddrinfo
|
||||
|
||||
|
||||
def _ipv4_getaddrinfo(*args, **kwargs):
|
||||
responses = _old_getaddrinfo(*args, **kwargs)
|
||||
return [res for res in responses if res[0] == socket.AF_INET]
|
||||
|
||||
|
||||
socket.getaddrinfo = _ipv4_getaddrinfo
|
||||
|
||||
# TERMINAL COLOURS
|
||||
|
||||
|
||||
def get_colours():
|
||||
if not sys.stdout.isatty() or os.environ.get("NO_COLOR"):
|
||||
return {
|
||||
"red": "",
|
||||
"green": "",
|
||||
"yellow": "",
|
||||
"blue": "",
|
||||
"cyan": "",
|
||||
"nc": "",
|
||||
}
|
||||
return {
|
||||
"red": "\033[1;31m",
|
||||
"green": "\033[1;32m",
|
||||
"yellow": "\033[1;33m",
|
||||
"blue": "\033[1;34m",
|
||||
"cyan": "\033[1;36m",
|
||||
"nc": "\033[0m",
|
||||
}
|
||||
|
||||
|
||||
COL = get_colours()
|
||||
|
||||
# ASSET MAPS
|
||||
CRYPTO_MAP = {
|
||||
"btc": "bitcoin",
|
||||
"eth": "ethereum",
|
||||
"usdt": "tether",
|
||||
"bnb": "binancecoin",
|
||||
"sol": "solana",
|
||||
"xrp": "ripple",
|
||||
"usdc": "usd-coin",
|
||||
"ada": "cardano",
|
||||
"doge": "dogecoin",
|
||||
"ton": "the-open-network",
|
||||
"trx": "tron",
|
||||
"avax": "avalanche-2",
|
||||
"shib": "shiba-inu",
|
||||
"dot": "polkadot",
|
||||
"link": "chainlink",
|
||||
}
|
||||
|
||||
METAL_MAP = {
|
||||
"gold": "GC=F",
|
||||
"silver": "SI=F",
|
||||
"platinum": "PL=F",
|
||||
"palladium": "PA=F",
|
||||
"copper": "HG=F",
|
||||
}
|
||||
|
||||
ENERGY_MAP = {
|
||||
"oil": "CL=F",
|
||||
"wti": "CL=F",
|
||||
"brent": "BZ=F",
|
||||
"gas": "NG=F",
|
||||
"natgas": "NG=F",
|
||||
"heating": "HO=F",
|
||||
}
|
||||
|
||||
INDEX_MAP = {
|
||||
"sp500": "^GSPC",
|
||||
"s&p500": "^GSPC",
|
||||
"nasdaq": "^IXIC",
|
||||
"dow": "^DJI",
|
||||
"dji": "^DJI",
|
||||
"ftse": "^FTSE",
|
||||
"nikkei": "^N225",
|
||||
"dax": "^GDAXI",
|
||||
}
|
||||
|
||||
# FORMATTING UTILITIES
|
||||
|
||||
|
||||
def fmt_num(val, decimals=2):
|
||||
return f"{val:,.{decimals}f}"
|
||||
|
||||
|
||||
def fmt_rate(val):
|
||||
if val == 0:
|
||||
return "0.00"
|
||||
if abs(val) < 0.0001:
|
||||
return f"{val:,.8f}".rstrip("0").rstrip(".")
|
||||
if abs(val) < 1.0:
|
||||
return f"{val:,.4f}"
|
||||
return f"{val:,.2f}"
|
||||
|
||||
|
||||
# CORE HTTP ENGINE
|
||||
|
||||
|
||||
def fetch_json(url, params=None):
|
||||
if params:
|
||||
url = f"{url}?{urllib.parse.urlencode(params)}"
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
return json.loads(response.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as e:
|
||||
print(
|
||||
f"{COL['red']}error: api request failed with status {e.code}.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError:
|
||||
print(
|
||||
f"{COL['red']}error: network connection timed out or unreachable.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError:
|
||||
print(
|
||||
f"{COL['red']}error: failed to parse remote json payload.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_forex_rate(base, target):
|
||||
base, target = base.upper(), target.upper()
|
||||
if base == target:
|
||||
return 1.0
|
||||
data = fetch_json(f"https://open.er-api.com/v6/latest/{base}")
|
||||
rates = data.get("rates", {})
|
||||
if target not in rates:
|
||||
print(
|
||||
f"{COL['red']}error: currency target '{target}' unsupported.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
return float(rates[target])
|
||||
|
||||
|
||||
def fetch_yahoo_market_data(ticker):
|
||||
encoded = urllib.parse.quote(ticker)
|
||||
url = f"https://query2.finance.yahoo.com/v8/finance/chart/{encoded}"
|
||||
data = fetch_json(url, {"interval": "1d", "range": "1d"})
|
||||
result = data.get("chart", {}).get("result")
|
||||
if not result:
|
||||
print(
|
||||
f"{COL['red']}error: market asset '{ticker}' not found on yahoo finance.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
meta = result[0].get("meta", {})
|
||||
price = float(meta.get("regularMarketPrice", 0.0))
|
||||
prev_close = float(
|
||||
meta.get("chartPreviousClose")
|
||||
or meta.get("previousClose")
|
||||
or price
|
||||
)
|
||||
return {
|
||||
"symbol": meta.get("symbol", ticker.upper()),
|
||||
"name": (
|
||||
meta.get("shortName") or meta.get("longName") or ticker
|
||||
).strip(),
|
||||
"price": price,
|
||||
"currency": meta.get("currency", "USD").upper(),
|
||||
"change_pct": ((price - prev_close) / prev_close * 100.0) if prev_close else 0.0,
|
||||
}
|
||||
|
||||
|
||||
# UTILITY PARSER FOR MARKET INPUTS
|
||||
|
||||
|
||||
def parse_market_args(args, default_target="NGN"):
|
||||
amount = 1.0
|
||||
target = default_target
|
||||
if len(args) >= 2:
|
||||
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[1]):
|
||||
amount = float(args[1])
|
||||
if len(args) >= 3:
|
||||
target = args[2]
|
||||
else:
|
||||
target = args[1]
|
||||
if len(args) >= 3 and re.match(r"^[0-9]+(\.[0-9]+)?$", args[2]):
|
||||
amount = float(args[2])
|
||||
return amount, target.upper()
|
||||
|
||||
|
||||
# SUBCOMMAND EXECUTORS
|
||||
|
||||
|
||||
def cmd_forex(args):
|
||||
amount, base, target = 1.0, "USD", "NGN"
|
||||
if len(args) == 1:
|
||||
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[0]):
|
||||
amount = float(args[0])
|
||||
else:
|
||||
base = args[0]
|
||||
elif len(args) == 2:
|
||||
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[0]):
|
||||
amount, base = float(args[0]), args[1]
|
||||
else:
|
||||
base, target = args[0], args[1]
|
||||
elif len(args) >= 3:
|
||||
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[0]):
|
||||
amount, base, target = float(args[0]), args[1], args[2]
|
||||
else:
|
||||
base, target, amount = args[0], args[1], float(args[2])
|
||||
|
||||
base, target = base.upper(), target.upper()
|
||||
print(
|
||||
f"{COL['blue']}fetching exchange rate for {base}/{target}...{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
rate = get_forex_rate(base, target)
|
||||
val = amount * rate
|
||||
print("-----------------------------------")
|
||||
print(f"pair: {base} / {target}")
|
||||
print(f"rate: {COL['yellow']}1 {base} = {fmt_rate(rate)} {target}{COL['nc']}")
|
||||
print(
|
||||
f"value: {COL['green']}{target} {fmt_num(val, 2)}{COL['nc']} ({base} {fmt_num(amount, 2)})"
|
||||
)
|
||||
print("-----------------------------------")
|
||||
|
||||
|
||||
def cmd_yahoo_generic(ticker, amount, target_curr, class_label):
|
||||
print(
|
||||
f"{COL['blue']}fetching evaluation for {class_label} [{ticker}]...{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
market = fetch_yahoo_market_data(ticker)
|
||||
native_curr = market["currency"]
|
||||
native_price = market["price"]
|
||||
total_native = amount * native_price
|
||||
|
||||
if native_curr != target_curr:
|
||||
fx_rate = get_forex_rate(native_curr, target_curr)
|
||||
final_val = total_native * fx_rate
|
||||
rate_disp = f"{fmt_rate(native_price)} {native_curr} (~{fmt_num(native_price * fx_rate, 2)} {target_curr})"
|
||||
val_disp = f"{COL['green']}{fmt_num(final_val, 2)} {target_curr}{COL['nc']} ({fmt_num(total_native, 2)} {native_curr})"
|
||||
else:
|
||||
rate_disp = f"{fmt_rate(native_price)} {native_curr}"
|
||||
val_disp = f"{COL['green']}{fmt_num(total_native, 2)} {native_curr}{COL['nc']}"
|
||||
|
||||
chg = market["change_pct"]
|
||||
chg_col = COL["green"] if chg >= 0 else COL["red"]
|
||||
chg_sign = "+" if chg >= 0 else ""
|
||||
|
||||
print("-----------------------------------")
|
||||
print(f"asset: {market['name']} ({market['symbol']})")
|
||||
print(f"valuation: {COL['yellow']}{rate_disp}{COL['nc']}")
|
||||
print(f"24h move: {chg_col}{chg_sign}{chg:.2f}%{COL['nc']}")
|
||||
if amount != 1.0 or native_curr != target_curr:
|
||||
print(f"total sum: {val_disp}")
|
||||
print("-----------------------------------")
|
||||
|
||||
|
||||
def cmd_metal(args):
|
||||
if not args:
|
||||
print(
|
||||
f"{COL['red']}error: missing metal identifier.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
query = args[0].lower().strip()
|
||||
ticker = METAL_MAP.get(query, args[0].upper())
|
||||
amount, target = parse_market_args(args)
|
||||
cmd_yahoo_generic(ticker, amount, target, "Precious Metal")
|
||||
|
||||
|
||||
def cmd_energy(args):
|
||||
if not args:
|
||||
print(
|
||||
f"{COL['red']}error: missing energy identifier.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
query = args[0].lower().strip()
|
||||
ticker = ENERGY_MAP.get(query, args[0].upper())
|
||||
amount, target = parse_market_args(args)
|
||||
cmd_yahoo_generic(ticker, amount, target, "Energy Future")
|
||||
|
||||
|
||||
def cmd_indices(args):
|
||||
if not args:
|
||||
print(
|
||||
f"{COL['red']}error: missing index symbol.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
query = args[0].lower().strip()
|
||||
ticker = INDEX_MAP.get(query, args[0].upper())
|
||||
amount, target = parse_market_args(args)
|
||||
cmd_yahoo_generic(ticker, amount, target, "Market Index")
|
||||
|
||||
|
||||
def cmd_stock(args):
|
||||
if not args:
|
||||
print(
|
||||
f"{COL['red']}error: missing stock ticker.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
ticker = args[0].upper()
|
||||
amount, target = parse_market_args(args)
|
||||
cmd_yahoo_generic(ticker, amount, target, "Equity Asset")
|
||||
|
||||
|
||||
def cmd_etf(args):
|
||||
if not args:
|
||||
print(
|
||||
f"{COL['red']}error: missing ETF symbol.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
ticker = args[0].upper()
|
||||
amount, target = parse_market_args(args)
|
||||
cmd_yahoo_generic(ticker, amount, target, "Exchange Traded Fund")
|
||||
|
||||
|
||||
def cmd_crypto(args):
|
||||
if not args:
|
||||
print(
|
||||
f"{COL['red']}error: missing crypto token name or symbol.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
query = args[0].lower().strip()
|
||||
amount, vs_curr = parse_market_args(args, default_target="ngn")
|
||||
vs_target = vs_curr.lower()
|
||||
|
||||
if query in CRYPTO_MAP:
|
||||
coin_id, symbol = CRYPTO_MAP[query], query.upper()
|
||||
else:
|
||||
print(
|
||||
f"{COL['blue']}searching coingecko for '{query}'...{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
search = fetch_json(
|
||||
"https://api.coingecko.com/api/v3/search", {"query": query}
|
||||
)
|
||||
coins = search.get("coins", [])
|
||||
if not coins:
|
||||
print(
|
||||
f"{COL['red']}error: token '{query}' not found.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
coin_id, symbol = coins[0]["id"], coins[0]["symbol"].upper()
|
||||
|
||||
print(
|
||||
f"{COL['blue']}fetching {symbol} spot data in {vs_curr.upper()}...{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
data = fetch_json(
|
||||
"https://api.coingecko.com/api/v3/simple/price",
|
||||
{
|
||||
"ids": coin_id,
|
||||
"vs_currencies": vs_target,
|
||||
"include_24hr_change": "true",
|
||||
},
|
||||
)
|
||||
coin_data = data.get(coin_id, {})
|
||||
if vs_target not in coin_data:
|
||||
print(
|
||||
f"{COL['red']}error: target market currency '{vs_curr}' unsupported.{COL['nc']}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
rate = float(coin_data[vs_target])
|
||||
change = float(coin_data.get(f"{vs_target}_24h_change", 0.0))
|
||||
val = amount * rate
|
||||
chg_col = COL["green"] if change >= 0 else COL["red"]
|
||||
chg_sign = "+" if change >= 0 else ""
|
||||
|
||||
print("-----------------------------------")
|
||||
print(f"crypto: {symbol} ({coin_id})")
|
||||
print(f"spot rate: {COL['yellow']}{fmt_rate(rate)} {vs_curr.upper()}{COL['nc']}")
|
||||
print(f"24h move: {chg_col}{chg_sign}{change:.2f}%{COL['nc']}")
|
||||
if amount != 1.0:
|
||||
print(
|
||||
f"valuation: {COL['green']}{fmt_num(val, 2)} {vs_curr.upper()}{COL['nc']} ({symbol} {fmt_num(amount, 4)})"
|
||||
)
|
||||
print("-----------------------------------")
|
||||
|
||||
|
||||
# ROUTING MATRIX
|
||||
|
||||
|
||||
def show_help():
|
||||
print("rate - multi-asset evaluation utility")
|
||||
print("usage:")
|
||||
print(" rate forex [amt] <base> [target] fiat configurations (alias: fx)")
|
||||
print(" rate metal <gold|silver|ticker> precious spots contracts (alias: met)")
|
||||
print(" rate energy <oil|brent|gas|ticker> fuel commodity options (alias: nrg)")
|
||||
print(" rate indices <sp500|nasdaq|ticker> global market indices (alias: ind)")
|
||||
print(" rate crypto <coin> [amt] [curr] digital tokens valuations (alias: c)")
|
||||
print(" rate stock <ticker> [amt] [curr] corporate equities (alias: s)")
|
||||
print(" rate etf <ticker> [amt] [curr] exchange traded funds (alias: e)")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
if not args or args[0] in ("-h", "--help", "help"):
|
||||
show_help()
|
||||
|
||||
cmd = args[0].lower()
|
||||
sub_args = args[1:]
|
||||
|
||||
if cmd in ("forex", "fx", "to", "from"):
|
||||
if cmd == "to":
|
||||
cmd_forex(["1", "NGN", sub_args[0]] if sub_args else ["NGN"])
|
||||
elif cmd == "from":
|
||||
cmd_forex(["1", sub_args[0], "NGN"] if sub_args else ["USD", "NGN"])
|
||||
else:
|
||||
cmd_forex(sub_args)
|
||||
elif cmd in ("metal", "met"):
|
||||
cmd_metal(sub_args)
|
||||
elif cmd in ("energy", "nrg", "eng"):
|
||||
cmd_energy(sub_args)
|
||||
elif cmd in ("indices", "ind", "idx"):
|
||||
cmd_indices(sub_args)
|
||||
elif cmd in ("crypto", "c"):
|
||||
cmd_crypto(sub_args)
|
||||
elif cmd in ("stock", "stocks", "s"):
|
||||
cmd_stock(sub_args)
|
||||
elif cmd in ("etf", "e"):
|
||||
cmd_etf(sub_args)
|
||||
else:
|
||||
# direct default fallback to legacy raw strings evaluation
|
||||
cmd_forex(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
189
.local/bin/recon
Executable file
189
.local/bin/recon
Executable file
|
|
@ -0,0 +1,189 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o pipefail
|
||||
export LC_ALL=C
|
||||
|
||||
# COLOURS
|
||||
# standard terminal output hues
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
yellow='\033[1;33m'
|
||||
cyan='\033[0;36m'
|
||||
nc='\033[0m'
|
||||
|
||||
# CONFIGURATION
|
||||
# defaults to mullvad dns for privacy-respecting resolution
|
||||
resolver="@194.242.2.2"
|
||||
timeout_val=2
|
||||
ua="mozilla/5.0 (x11; linux x86_64) applewebkit/537.36"
|
||||
|
||||
# USAGE GUIDE
|
||||
# displays instructions if arguments are missing
|
||||
usage() {
|
||||
printf "${cyan}usage:${nc} reconctl [-t timeout] <domain>\n"
|
||||
printf " -t set network timeout in seconds (default: 2)\n"
|
||||
printf " -h show help menu\n\n"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts "t:h" opt; do
|
||||
case ${opt} in
|
||||
t) timeout_val=$OPTARG ;;
|
||||
h) usage ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
domain="$1"
|
||||
[[ -z "$domain" ]] && usage
|
||||
|
||||
# INPUT VALIDATION
|
||||
# rejects malformed hostnames before they reach dig/curl/openssl/whois
|
||||
if [[ ! "$domain" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]]; then
|
||||
printf "${red}error: '%s' is not a valid domain${nc}\n" "$domain"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# HELPER FUNCTIONS
|
||||
# standardises section headers for clean output
|
||||
section_header() {
|
||||
printf "\n${cyan}%s${nc}\n" "$1"
|
||||
}
|
||||
|
||||
# DEPENDENCY AUDIT
|
||||
# validates required packages before execution
|
||||
for cmd in dig curl jq openssl whois awk; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
printf "${red}error: missing dependency '%s'${nc}\n" "$cmd"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# DNS ANALYSIS
|
||||
# extracts primary records and security configurations
|
||||
section_header "DNS RECORDS"
|
||||
ipv4=$(dig $resolver +short A "$domain")
|
||||
ipv6=$(dig $resolver +short AAAA "$domain")
|
||||
cname=$(dig $resolver +short CNAME "$domain")
|
||||
mx=$(dig $resolver +short MX "$domain")
|
||||
caa=$(dig $resolver +short CAA "$domain")
|
||||
txt=$(dig $resolver +short TXT "$domain" | grep -Ei "spf|dmarc" || true)
|
||||
dnskey=$(dig $resolver +short DNSKEY "$domain")
|
||||
|
||||
printf " ipv4:\n%s\n" "$([[ -n "$ipv4" ]] && echo "$ipv4" | sed 's/^/ /' || echo " n/a")"
|
||||
printf " ipv6:\n%s\n" "$([[ -n "$ipv6" ]] && echo "$ipv6" | sed 's/^/ /' || echo " n/a")"
|
||||
printf " cname:\n %s\n" "${cname:-none detected}"
|
||||
printf " nameservers:\n%s\n" "$(dig $resolver +short NS "$domain" | sed 's/^/ /')"
|
||||
printf " mail exchangers:\n%s\n" "$([[ -n "$mx" ]] && echo "$mx" | sed 's/^/ /' || echo " none detected")"
|
||||
printf " caa records:\n%s\n" "$([[ -n "$caa" ]] && echo "$caa" | sed 's/^/ /' || echo " none detected")"
|
||||
printf " security txt:\n%s\n" "$([[ -n "$txt" ]] && echo "$txt" | sed 's/^/ /' || echo " none detected")"
|
||||
printf " dnssec: %s\n" "$([[ -n "$dnskey" ]] && echo "enabled" || echo "not detected")"
|
||||
|
||||
# REVERSE LOOKUP
|
||||
# checks ptr records for resolved v4 and v6 addresses
|
||||
section_header "REVERSE DNS"
|
||||
if [[ -n "$ipv4" || -n "$ipv6" ]]; then
|
||||
for ip in $ipv4 $ipv6; do
|
||||
ptr=$(dig $resolver +short -x "$ip")
|
||||
printf " %s -> %s\n" "$ip" "${ptr:-n/a}"
|
||||
done
|
||||
else
|
||||
printf " n/a\n"
|
||||
fi
|
||||
|
||||
# HTTP PERFORMANCE
|
||||
# tests routing responses and redirection chains
|
||||
section_header "HTTP ROUTING"
|
||||
http_status=$(timeout "$timeout_val" curl -A "$ua" -sL -o /dev/null -w "%{http_code}" "http://$domain" || echo "timeout")
|
||||
https_status=$(timeout "$timeout_val" curl -A "$ua" -sL -o /dev/null -w "%{http_code}" "https://$domain" 2>/dev/null || echo "timeout")
|
||||
redirects=$(timeout "$timeout_val" curl -A "$ua" -sI -L "http://$domain" 2>/dev/null | grep -i "^location:" | sed 's/[Ll]ocation: //g' | tr -d '\r')
|
||||
|
||||
printf " http status: %s\n" "$http_status"
|
||||
printf " https status: %s\n" "$https_status"
|
||||
printf " redirects:\n%s\n" "$([[ -n "$redirects" ]] && echo "$redirects" | sed 's/^/ -> /' || echo " none")"
|
||||
|
||||
# ENCRYPTION AUDIT
|
||||
# pulls subject, issuer, and expiration from the tls certificate
|
||||
section_header "TLS CERTIFICATE"
|
||||
tls=$(timeout "$timeout_val" openssl s_client -connect "$domain:443" -servername "$domain" -showcerts </dev/null 2>/dev/null)
|
||||
expiry=$(echo "$tls" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
||||
subject_issuer=$(echo "$tls" | grep -E "subject=|issuer=" | sed 's/^[ \t]*//')
|
||||
printf " subject/issuer:\n%s\n" "$([[ -n "$subject_issuer" ]] && echo "$subject_issuer" | sed 's/^/ /' || echo " unknown")"
|
||||
printf " expiry date: %s\n" "${expiry:-unknown}"
|
||||
|
||||
# SECURITY HEADERS
|
||||
# evaluates presence of critical web security directives
|
||||
section_header "SECURITY POSTURE"
|
||||
headers=$(timeout "$timeout_val" curl -A "$ua" -sI "https://$domain" 2>/dev/null | tr -d '\r')
|
||||
for h in "strict-transport-security" "content-security-policy" "x-frame-options" "x-content-type-options"; do
|
||||
val=$(echo "$headers" | grep -i "^$h:" | cut -d: -f2- | xargs)
|
||||
if [[ -n "$val" ]]; then
|
||||
printf " %-25s ${green}present${nc}\n" "$h"
|
||||
else
|
||||
printf " %-25s ${yellow}missing${nc}\n" "$h"
|
||||
fi
|
||||
done
|
||||
|
||||
# INFRASTRUCTURE PROBE
|
||||
# checks common critical ports on the primary resolved ip, results sorted after concurrent scan
|
||||
section_header "PORT SCAN"
|
||||
ports=(21 22 25 53 80 443 3306 8080 8443)
|
||||
target_ip=$(echo "$ipv4" | head -n1)
|
||||
|
||||
if [[ -n "$target_ip" ]]; then
|
||||
scan_tmp=$(mktemp)
|
||||
exec 3>&2
|
||||
exec 2>/dev/null
|
||||
for port in "${ports[@]}"; do
|
||||
(timeout 1 bash -c "</dev/tcp/$target_ip/$port" &>/dev/null && echo "$port" >> "$scan_tmp") &
|
||||
done
|
||||
wait
|
||||
exec 2>&3
|
||||
if [[ -s "$scan_tmp" ]]; then
|
||||
sort -n "$scan_tmp" | while read -r port; do
|
||||
printf " port %-5s ${green}open${nc}\n" "$port"
|
||||
done
|
||||
else
|
||||
printf " no open ports detected\n"
|
||||
fi
|
||||
rm -f "$scan_tmp"
|
||||
else
|
||||
printf " ${red}no ipv4 address for scanning.${nc}\n"
|
||||
fi
|
||||
|
||||
# ASSET ENUMERATION
|
||||
# queries certificate transparency logs for child infrastructure via crt.sh json endpoint
|
||||
section_header "SUBDOMAINS"
|
||||
subdomains=$(timeout 15 curl -s "https://crt.sh/?q=%25.$domain&output=json" | jq -r '.[].name_value' 2>/dev/null | tr ',' '\n' | sed 's/\*\.//' | sort -u | head -n 15)
|
||||
[[ -n "$subdomains" ]] && echo "$subdomains" | sed 's/^/ /' || echo " enumeration failed or none found"
|
||||
|
||||
# REGISTRY AUDIT
|
||||
# extracts baseline domain registration metadata
|
||||
section_header "WHOIS DATA"
|
||||
whois_data=$(timeout "$timeout_val" whois "$domain" 2>/dev/null | grep -Ei "^registrar:|^creation date:|^updated date:" | tr -s ' ' | head -n 5)
|
||||
[[ -n "$whois_data" ]] && echo "$whois_data" | sed 's/^/ /' || echo " extraction limited"
|
||||
|
||||
# RISK SCORE
|
||||
# calculates a basic hazard rating based on missing protections and open ports
|
||||
section_header "RISK SUMMARY"
|
||||
risk=0
|
||||
[[ ! "$https_status" =~ ^(2|3) ]] && ((risk += 2))
|
||||
echo "$headers" | grep -qi "^strict-transport-security:" || ((risk += 1))
|
||||
echo "$headers" | grep -qi "^content-security-policy:" || ((risk += 1))
|
||||
[[ -z "$dnskey" ]] && ((risk += 1))
|
||||
timeout 1 bash -c "</dev/tcp/$target_ip/22" &>/dev/null && ((risk += 1))
|
||||
timeout 1 bash -c "</dev/tcp/$target_ip/21" &>/dev/null && ((risk += 2))
|
||||
|
||||
if [ $risk -le 1 ]; then
|
||||
posture_color=$green
|
||||
posture_text="low risk"
|
||||
else
|
||||
posture_color=$red
|
||||
posture_text="risk identified"
|
||||
fi
|
||||
|
||||
printf " domain: %s\n" "$domain"
|
||||
printf " ip: %s\n" "${target_ip:-n/a}"
|
||||
printf " posture: ${posture_color}%s${nc} (score: %d)\n\n" "$posture_text" "$risk"
|
||||
|
||||
11
.local/bin/restore-brightness
Executable file
11
.local/bin/restore-brightness
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-08-08 13:04 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
main() {
|
||||
sleep 1
|
||||
brightnessctl set 10%}
|
||||
}
|
||||
main "$@"
|
||||
30
.local/bin/scratch
Executable file
30
.local/bin/scratch
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env bash
|
||||
# ~/.local/bin/scratchpadctl
|
||||
# MANAGE SPATIAL SCRATCHPAD STATE
|
||||
|
||||
APP_ID="$1"
|
||||
SPAWN_CMD="$2"
|
||||
HIDDEN_WORKSPACE="scratchpad_vault"
|
||||
|
||||
WINDOW_JSON=$(niri msg -j windows | jq -c ".[] | select(.app_id == \"$APP_ID\")" | head -n 1)
|
||||
|
||||
if [ -z "$WINDOW_JSON" ]; then
|
||||
eval "$SPAWN_CMD" &
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# extract geometric identifiers
|
||||
WINDOW_ID=$(echo "$WINDOW_JSON" | jq -r '.id')
|
||||
WINDOW_WS=$(echo "$WINDOW_JSON" | jq -r '.workspace_id')
|
||||
|
||||
# query active operational space
|
||||
CURRENT_WS=$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .id')
|
||||
|
||||
if [ "$WINDOW_WS" == "$CURRENT_WS" ]; then
|
||||
niri msg action focus-window --id "$WINDOW_ID"
|
||||
niri msg action move-window-to-workspace "$HIDDEN_WORKSPACE"
|
||||
else
|
||||
niri msg action focus-window --id "$WINDOW_ID"
|
||||
niri msg action move-window-to-workspace "$CURRENT_WS"
|
||||
niri msg action focus-window --id "$WINDOW_ID"
|
||||
fi
|
||||
27
.local/bin/start-audio
Executable file
27
.local/bin/start-audio
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-08-04 21:37 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# TERMINATE EXISTING PROCESSES
|
||||
# ensure a clean state before starting
|
||||
killall -q wireplumber pipewire-pulse pipewire mpd
|
||||
sleep 1
|
||||
|
||||
# INITIALISE AUDIO DAEMONS
|
||||
# start the core pipewire service
|
||||
pipewire &
|
||||
sleep 1
|
||||
|
||||
# start the session manager
|
||||
wireplumber &
|
||||
|
||||
# start the pulseaudio compatibility layer
|
||||
pipewire-pulse &
|
||||
|
||||
# START MUSIC PLAYER DAEMON
|
||||
# launch the background daemon for local music
|
||||
mpd &
|
||||
|
||||
|
||||
365
.local/bin/status
Executable file
365
.local/bin/status
Executable file
|
|
@ -0,0 +1,365 @@
|
|||
#!/usr/bin/env bash
|
||||
set -o pipefail
|
||||
export LC_ALL=C
|
||||
|
||||
# COLOURS
|
||||
# define standard terminal output hues
|
||||
green='\033[1;32m'
|
||||
cyan='\033[1;36m'
|
||||
yellow='\033[1;33m'
|
||||
red='\033[1;31m'
|
||||
nc='\033[0m'
|
||||
|
||||
# HELPER FUNCTIONS
|
||||
# standardises section headers and validates executable presence
|
||||
section_header() {
|
||||
printf "\n${green}%s${nc}\n" "$1"
|
||||
}
|
||||
|
||||
cmd_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# SYSTEM CORE
|
||||
# prints basic kernel and hardware identity
|
||||
print_system_core() {
|
||||
local kernel cpu uptime_raw load_avg
|
||||
kernel=$(uname -r)
|
||||
cpu=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs)
|
||||
uptime_raw=$(uptime -p | sed 's/up //')
|
||||
load_avg=$(awk '{print $1, $2, $3}' /proc/loadavg)
|
||||
|
||||
section_header "SYSTEM CORE"
|
||||
printf " kernel: %s\n" "$kernel"
|
||||
printf " cpu: %s\n" "$cpu"
|
||||
printf " load: %s\n" "$load_avg"
|
||||
printf " uptime: %s\n" "$uptime_raw"
|
||||
}
|
||||
|
||||
# GRAPHICS
|
||||
# detects the display protocol and active compositor
|
||||
print_graphics() {
|
||||
local wm_name session_type
|
||||
session_type="${XDG_SESSION_TYPE:-unknown}"
|
||||
|
||||
wm_name="unknown"
|
||||
if [ "$session_type" = "wayland" ]; then
|
||||
wm_name="${XDG_CURRENT_DESKTOP:-Wayland}"
|
||||
elif cmd_exists xprop; then
|
||||
wm_name=$(xprop -root _NET_WM_NAME 2>/dev/null | cut -d'"' -f2)
|
||||
[ -z "$wm_name" ] && wm_name=$(xprop -root WM_NAME 2>/dev/null | cut -d'"' -f2)
|
||||
fi
|
||||
|
||||
section_header "GRAPHICS"
|
||||
printf " session: %s\n" "$session_type"
|
||||
printf " compositor: %s\n" "$wm_name"
|
||||
|
||||
if cmd_exists lspci; then
|
||||
local gpus
|
||||
gpus=$(lspci -mm | awk -F'"' -v OFS='' '
|
||||
tolower($2) ~ /^(vga compatible controller|3d controller|display controller)$/ {print $4}' | paste -sd ', ' -)
|
||||
printf " gpu: %s\n" "${gpus:-unknown}"
|
||||
else
|
||||
printf " gpu: (lspci missing)\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# HARDWARE
|
||||
# polls thermal sensors, power supply, and drive health
|
||||
print_hardware() {
|
||||
section_header "HARDWARE"
|
||||
|
||||
local temp=""
|
||||
if cmd_exists sensors; then
|
||||
if cmd_exists jq; then
|
||||
temp=$(sensors -j 2>/dev/null | jq -r '.. | ."Package id 0"? // ."Tctl"? // empty | .temp1_input' 2>/dev/null)
|
||||
if [ -n "$temp" ]; then
|
||||
temp=$(printf "%.1f°C" "$temp")
|
||||
fi
|
||||
fi
|
||||
if [ -z "$temp" ]; then
|
||||
temp=$(sensors 2>/dev/null | grep -m1 -E 'Package id 0|Tctl|Tdie' | awk '{print $4}' | sed 's/+//;s/°C//')
|
||||
[ -n "$temp" ] && temp="${temp}°C"
|
||||
fi
|
||||
[ -z "$temp" ] && temp=$(sensors 2>/dev/null | grep -m1 'temp1' | awk '{print $2}' | sed 's/+//')
|
||||
else
|
||||
for zone in /sys/class/thermal/thermal_zone*; do
|
||||
local type temp_val
|
||||
type=$(cat "$zone/type" 2>/dev/null)
|
||||
temp_val=$(cat "$zone/temp" 2>/dev/null)
|
||||
if [ -n "$temp_val" ]; then
|
||||
temp="$((temp_val/1000))°C ($type)"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
printf " temp: %s\n" "${temp:-unknown}"
|
||||
|
||||
local bat_info=""
|
||||
for bat in /sys/class/power_supply/BAT*; do
|
||||
if [ -d "$bat" ]; then
|
||||
local level status name
|
||||
name=$(basename "$bat")
|
||||
level=$(cat "$bat/capacity" 2>/dev/null || echo "?")
|
||||
status=$(cat "$bat/status" 2>/dev/null || echo "unknown")
|
||||
bat_info="${bat_info}${name}: ${level}% (${status}) "
|
||||
fi
|
||||
done
|
||||
if [ -n "$bat_info" ]; then
|
||||
printf " battery: %s\n" "$(echo "$bat_info" | xargs)"
|
||||
fi
|
||||
|
||||
local smart_health=""
|
||||
if cmd_exists nvme && [ -e /dev/nvme0 ]; then
|
||||
smart_health=$(nvme smart-log /dev/nvme0 2>/dev/null | awk '/percentage_used/ {print $3"%" used}')
|
||||
elif cmd_exists smartctl; then
|
||||
smart_health=$(smartctl -H /dev/sda 2>/dev/null | awk '/SMART overall-health|SMART Health Status/ {print $NF}')
|
||||
fi
|
||||
printf " disk health: %s\n" "${smart_health:-not checked}"
|
||||
}
|
||||
|
||||
# RESOURCES
|
||||
# calculates memory pressure and storage consumption
|
||||
print_resources() {
|
||||
local mem_data total used avail mem_pct swap_data s_total s_used
|
||||
mem_data=$(free -m | awk '/^Mem:/ {print $2,$3,$7}')
|
||||
read -r total used avail <<<"$mem_data"
|
||||
mem_pct=$((100 * used / total))
|
||||
|
||||
swap_data=$(free -m | awk '/^Swap:/ {print $2,$3}')
|
||||
read -r s_total s_used <<<"$swap_data"
|
||||
|
||||
section_header "RESOURCES"
|
||||
printf " ram: %sMi / %sMi (%s%% used)\n" "$used" "$total" "$mem_pct"
|
||||
printf " avail: %sMi reclaimable\n" "$avail"
|
||||
printf " swap: %sMi / %sMi used\n" "$s_used" "$s_total"
|
||||
printf " disk: %s\n" "$(df -h / | awk 'NR==2 {print $3 " / " $2 " (" $5 " used)"}')"
|
||||
|
||||
if [ -d /var/cache/pacman/pkg ]; then
|
||||
local cache_size
|
||||
cache_size=$(du -sh /var/cache/pacman/pkg 2>/dev/null | cut -f1)
|
||||
printf " pkg cache: %s\n" "${cache_size:-?}"
|
||||
else
|
||||
printf " pkg cache: (missing)\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# NETWORK
|
||||
# outputs ipv4 addressing and routing states
|
||||
print_network() {
|
||||
section_header "NETWORK"
|
||||
|
||||
ip -4 addr show | grep "inet " | grep -v "127.0.0.1" | \
|
||||
awk '{print " " $NF ": " $2}' | sed 's|/.*||'
|
||||
|
||||
local gateway
|
||||
gateway=$(ip route | grep default | awk '{print $3}' | head -n1)
|
||||
printf " gateway: %s\n" "$gateway"
|
||||
|
||||
local dns
|
||||
dns=$(grep nameserver /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -n2 | xargs)
|
||||
[ -z "$dns" ] && dns="(none)"
|
||||
printf " dns: %s\n" "$dns"
|
||||
|
||||
for iface in /sys/class/net/*; do
|
||||
if [ "$(cat "$iface/operstate" 2>/dev/null)" = "up" ]; then
|
||||
local iface_name
|
||||
iface_name=$(basename "$iface")
|
||||
local speed
|
||||
speed=$(cat "$iface/speed" 2>/dev/null)
|
||||
[ -n "$speed" ] && printf " %s speed: %s Mbit/s\n" "$iface_name" "$speed"
|
||||
|
||||
if cmd_exists iw; then
|
||||
local ssid
|
||||
ssid=$(iw dev "$iface_name" link 2>/dev/null | awk '/SSID/ {print $2}')
|
||||
[ -n "$ssid" ] && printf " %s ssid: %s\n" "$iface_name" "$ssid"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if cmd_exists curl; then
|
||||
local ext_ip
|
||||
ext_ip=$(curl -s --max-time 2 ifconfig.me 2>/dev/null)
|
||||
[ -n "$ext_ip" ] && printf " external: %s\n" "$ext_ip"
|
||||
fi
|
||||
}
|
||||
|
||||
# PACKAGES
|
||||
# queries the local pacman database for state and updates
|
||||
print_packages() {
|
||||
if ! cmd_exists pacman; then
|
||||
return
|
||||
fi
|
||||
|
||||
local pkg_total pkg_explicit pkg_aur pkg_updates pkg_orphan
|
||||
pkg_total=$(pacman -Qq 2>/dev/null | wc -l)
|
||||
pkg_explicit=$(pacman -Qe 2>/dev/null | wc -l)
|
||||
pkg_aur=$(pacman -Qm 2>/dev/null | wc -l)
|
||||
pkg_orphan=$(pacman -Qtdq 2>/dev/null | wc -l)
|
||||
|
||||
section_header "PACKAGES"
|
||||
printf " total: %s\n" "$pkg_total"
|
||||
printf " explicit: %s\n" "$pkg_explicit"
|
||||
printf " aur: %s\n" "$pkg_aur"
|
||||
printf " orphans: %s\n" "$pkg_orphan"
|
||||
|
||||
if cmd_exists checkupdates; then
|
||||
pkg_updates=$(checkupdates 2>/dev/null | wc -l)
|
||||
if [ "$pkg_updates" -gt 0 ]; then
|
||||
printf " updates: ${yellow}%s pending${nc}\n" "$pkg_updates"
|
||||
else
|
||||
printf " updates: up to date\n"
|
||||
fi
|
||||
else
|
||||
printf " updates: (checkupdates not installed)\n"
|
||||
fi
|
||||
|
||||
local last_sync
|
||||
last_sync=$(grep -i "full system upgrade" /var/log/pacman.log 2>/dev/null | tail -1 | awk '{print $1, $2}' | sed 's/\[//;s/\]//;s/ \[PACMAN\]//')
|
||||
printf " last sync: %s\n" "${last_sync:-unknown}"
|
||||
|
||||
if grep -q '^\[testing\]' /etc/pacman.conf 2>/dev/null; then
|
||||
printf " testing: ${yellow}enabled${nc}\n"
|
||||
else
|
||||
printf " testing: disabled\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# SECURITY
|
||||
# checks kernel hardening and audits package vulnerabilities
|
||||
print_security() {
|
||||
section_header "SECURITY"
|
||||
|
||||
local kernel_type
|
||||
kernel_type=$(uname -r | grep -qi "hardened" && echo "hardened" || echo "standard")
|
||||
printf " kernel: %s\n" "$kernel_type"
|
||||
|
||||
if cmd_exists arch-audit; then
|
||||
local vulns
|
||||
vulns=$(arch-audit -q 2>/dev/null | wc -l)
|
||||
if [ "$vulns" -gt 0 ]; then
|
||||
printf " vulns: ${red}%d packages${nc}\n" "$vulns"
|
||||
else
|
||||
printf " vulns: none\n"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# PERFORMANCE
|
||||
# reports on cpu scaling and underlying disk scheduling
|
||||
print_performance() {
|
||||
section_header "PERFORMANCE"
|
||||
|
||||
if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then
|
||||
local gov freq
|
||||
gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
|
||||
printf " governor: %s\n" "$gov"
|
||||
if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq ]; then
|
||||
freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq)
|
||||
printf " cpu freq: %s MHz\n" $((freq / 1000))
|
||||
fi
|
||||
else
|
||||
printf " governor: not available\n"
|
||||
fi
|
||||
|
||||
local root_dev
|
||||
root_dev=$(findmnt -n -o SOURCE / 2>/dev/null)
|
||||
if [ -n "$root_dev" ]; then
|
||||
local disk_name
|
||||
disk_name=$(lsblk -ndo PKNAME "$root_dev" 2>/dev/null)
|
||||
[ -z "$disk_name" ] && disk_name=$(lsblk -ndo NAME "$root_dev" 2>/dev/null)
|
||||
if [ -n "$disk_name" ] && [ -f "/sys/block/$disk_name/queue/scheduler" ]; then
|
||||
local scheduler
|
||||
scheduler=$(grep -oP '\[\K[^\]]+' "/sys/block/$disk_name/queue/scheduler")
|
||||
printf " scheduler: %s\n" "${scheduler:-unknown}"
|
||||
else
|
||||
printf " scheduler: (no sysfs entry)\n"
|
||||
fi
|
||||
else
|
||||
printf " scheduler: (root device unknown)\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# SYSTEMD HEALTH
|
||||
# audits systemd for failed services and boot times
|
||||
print_systemd_health() {
|
||||
if ! cmd_exists systemctl; then
|
||||
return
|
||||
fi
|
||||
|
||||
section_header "SYSTEMD HEALTH"
|
||||
|
||||
local failed_units
|
||||
failed_units=$(systemctl --failed --no-legend 2>/dev/null | wc -l)
|
||||
if [ "$failed_units" -gt 0 ]; then
|
||||
printf " failed units: ${red}%d${nc}\n" "$failed_units"
|
||||
systemctl --failed --no-legend 2>/dev/null | awk '{print " " $1}'
|
||||
else
|
||||
printf " failed units: none\n"
|
||||
fi
|
||||
|
||||
if cmd_exists journalctl; then
|
||||
local boot_time
|
||||
boot_time=$(journalctl --list-boots 2>/dev/null | tail -1 | awk '{print $4}')
|
||||
[ -n "$boot_time" ] && printf " boot time: %s\n" "$boot_time"
|
||||
fi
|
||||
}
|
||||
|
||||
# CONTAINERS AND VIRTUALISATION
|
||||
# detects running instances across incus, docker, and libvirt
|
||||
print_containers_virt() {
|
||||
section_header "CONTAINERS & VIRTUALISATION"
|
||||
|
||||
if cmd_exists incus; then
|
||||
local incus_count
|
||||
incus_count=$(incus list status=running -c n --format csv 2>/dev/null | wc -l)
|
||||
printf " incus: %d active instances\n" "$incus_count"
|
||||
fi
|
||||
|
||||
if cmd_exists docker && systemctl -q is-active docker 2>/dev/null; then
|
||||
local containers
|
||||
containers=$(docker ps -q 2>/dev/null | wc -l)
|
||||
printf " docker: %d running containers\n" "$containers"
|
||||
fi
|
||||
|
||||
if cmd_exists podman && systemctl -q is-active podman 2>/dev/null; then
|
||||
local pods
|
||||
pods=$(podman ps -q 2>/dev/null | wc -l)
|
||||
printf " podman: %d running containers\n" "$pods"
|
||||
fi
|
||||
|
||||
if cmd_exists virsh && systemctl -q is-active libvirtd 2>/dev/null; then
|
||||
local vms
|
||||
vms=$(virsh list --name 2>/dev/null | grep -v "^$" | wc -l)
|
||||
printf " libvirt: %d active VMs\n" "$vms"
|
||||
fi
|
||||
}
|
||||
|
||||
# TIMERS
|
||||
# counts active systemd chron tasks
|
||||
print_timers() {
|
||||
if ! cmd_exists systemctl; then
|
||||
return
|
||||
fi
|
||||
|
||||
local timer_count
|
||||
timer_count=$(systemctl list-timers --no-legend 2>/dev/null | wc -l)
|
||||
if [ "$timer_count" -gt 0 ]; then
|
||||
section_header "TIMERS"
|
||||
printf " active: %d systemd timers\n" "$timer_count"
|
||||
fi
|
||||
}
|
||||
|
||||
# EXECUTION
|
||||
print_system_core
|
||||
print_graphics
|
||||
print_hardware
|
||||
print_resources
|
||||
print_network
|
||||
print_packages
|
||||
print_security
|
||||
print_performance
|
||||
print_systemd_health
|
||||
print_containers_virt
|
||||
print_timers
|
||||
printf "\n"
|
||||
14
.local/bin/strap-archlinux-vm
Executable file
14
.local/bin/strap-archlinux-vm
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-08-03 18:35 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
main() {
|
||||
incus init archlinux-vm --empty --vm -c security.secureboot=false
|
||||
incus config device add archlinux-vm install-iso disk source=//home/sgbv/.iso/archlinux.iso boot.priority=10
|
||||
incus start archlinux-vm
|
||||
incus console archlinux-vm --type=vga
|
||||
}
|
||||
|
||||
main "$@"
|
||||
17
.local/bin/strap-artix-vm
Executable file
17
.local/bin/strap-artix-vm
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR
|
||||
# daesorin
|
||||
# CREATED
|
||||
# 2026-08-03 18:35 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
main() {
|
||||
aria2c --dir=//home/sgbv/.iso --seed-time=0 //home/sgbv/Downloads/artix-base-openrc-20260729-x86_64.iso.torrent
|
||||
incus init artix-vm --empty --vm -c security.secureboot=false
|
||||
incus config device add artix-vm install-iso disk source=//home/sgbv/.iso/artix-base-openrc-20260729-x86_64.iso boot.priority=10
|
||||
incus start artix-vm
|
||||
incus console artix-vm --type=vga
|
||||
}
|
||||
|
||||
main "$@"
|
||||
24
.local/bin/volume-notify
Executable file
24
.local/bin/volume-notify
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-08-04 05:01 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$1" = "toggle" ]; then
|
||||
pamixer -t
|
||||
elif [[ "$1" == +* ]]; then
|
||||
val="${1#+}"
|
||||
pamixer -i "${val%\%}"
|
||||
elif [[ "$1" == -* ]]; then
|
||||
val="${1#-}"
|
||||
pamixer -d "${val%\%}"
|
||||
fi
|
||||
|
||||
volume=$(pamixer --get-volume)
|
||||
muted=$(pamixer --get-mute)
|
||||
|
||||
if [ "$muted" = "true" ]; then
|
||||
notify-send -h string:x-canonical-private-synchronous:volume -u low "Volume: Muted"
|
||||
else
|
||||
notify-send -h string:x-canonical-private-synchronous:volume -u low "Volume: ${volume}%"
|
||||
fi
|
||||
14
.local/bin/waybar-net
Executable file
14
.local/bin/waybar-net
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
if ! vnstat -i wlan0 --oneline >/dev/null 2>&1; then
|
||||
echo "{\"text\": \"Offline\", \"tooltip\": \"vnstat daemon unavailable\"}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
raw_data=$(vnstat -i wlan0 --oneline)
|
||||
|
||||
rx_today=$(echo "$raw_data" | cut -d';' -f4)
|
||||
tx_today=$(echo "$raw_data" | cut -d';' -f5)
|
||||
total_today=$(echo "$raw_data" | cut -d';' -f6)
|
||||
|
||||
echo "{\"text\": \"↓ $rx_today ↑ $tx_today\", \"tooltip\": \"today total: $total_today\"}"
|
||||
18
.local/bin/weather
Executable file
18
.local/bin/weather
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: 2026-07-18 11:26 +0100
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# DEFINE API ENDPOINT
|
||||
# store the target url and authentication parameter in a shell variable
|
||||
readonly API_URL="http://api.weatherapi.com/v1/current.json?key=d06994d0d97744ae89d172923261707&q=Idogbo&aqi=no"
|
||||
|
||||
# FETCH AND PARSE WEATHER PAYLOAD
|
||||
# curl fetches the data silently and pipes the stream directly into jq for extraction
|
||||
curl -s "$API_URL" | jq -r '
|
||||
"Location: \(.location.name), \(.location.region), \(.location.country)",
|
||||
"Local Time: \(.location.localtime)",
|
||||
"Temperature: \(.current.temp_c)°C",
|
||||
"Condition: \(.current.condition.text)"
|
||||
'
|
||||
1
.zsh/.functions/cdc
Normal file
1
.zsh/.functions/cdc
Normal file
|
|
@ -0,0 +1 @@
|
|||
cd "$1" && clear;
|
||||
47
.zsh/.functions/copy
Normal file
47
.zsh/.functions/copy
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# PIPED INPUT HANDLING
|
||||
# read directly from standard input when no arguments are provided
|
||||
if [ ! -t 0 ] && [ $# -eq 0 ]; then
|
||||
wl-copy
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ARGUMENT VALIDATION
|
||||
# ensure at least one file argument is passed when not piping
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "usage: copy <file...> or <command> | copy" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# FILE EXISTENCE CHECK
|
||||
# verify the target argument is a readable file
|
||||
if [ ! -r "$1" ]; then
|
||||
echo "error: cannot read file '$1'" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# MIME TYPE DETECTION
|
||||
# inspect the file header to determine the correct clipboard target
|
||||
local mime_type
|
||||
mime_type=$(file --mime-type -b "$1")
|
||||
|
||||
# CLIPBOARD DISPATCH
|
||||
# route the file to wl-copy with the appropriate target flags
|
||||
case "$mime_type" in
|
||||
image/png|image/jpeg|image/webp|image/gif|image/bmp)
|
||||
wl-copy -t "$mime_type" < "$1"
|
||||
echo "copied $1 ($mime_type) to clipboard."
|
||||
;;
|
||||
image/svg+xml)
|
||||
if command -v rsvg-convert >/dev/null 2>&1; then
|
||||
rsvg-convert "$1" | wl-copy -t image/png
|
||||
echo "rasterised $1 to png and copied to clipboard."
|
||||
else
|
||||
wl-copy -t image/svg+xml < "$1"
|
||||
echo "copied $1 (raw svg) to clipboard. install librsvg for automatic png rasterisation."
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
cat "$@" | wl-copy
|
||||
echo "copied text content ($# file(s)) to clipboard."
|
||||
;;
|
||||
esac
|
||||
4
.zsh/.functions/git.abort
Normal file
4
.zsh/.functions/git.abort
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
local branch
|
||||
branch=$(git branch --show-current)
|
||||
git fetch origin &&
|
||||
git reset --hard "origin/$branch"
|
||||
5
.zsh/.functions/git.fire
Normal file
5
.zsh/.functions/git.fire
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
local branch="fire-$(date +%Y%m%d-%H%M)"
|
||||
git checkout -b "$branch" &&
|
||||
git add -A &&
|
||||
git commit -m "FIRE EMERGENCY: $branch" --no-verify &&
|
||||
git push -u origin "$branch"
|
||||
1
.zsh/.functions/git.grab
Normal file
1
.zsh/.functions/git.grab
Normal file
|
|
@ -0,0 +1 @@
|
|||
git checkout "$1" -- "$2"
|
||||
8
.zsh/.functions/git.lazy
Normal file
8
.zsh/.functions/git.lazy
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
if [ -z "$1" ]; then
|
||||
echo "Usage: git.lazy \"commit message\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
git add -A &&
|
||||
git commit -m "$1" &&
|
||||
git push
|
||||
7
.zsh/.functions/git.nuke
Normal file
7
.zsh/.functions/git.nuke
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
if [ -z "$1" ]; then
|
||||
echo "Usage: git.nuke <branch>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
git branch -D "$1"
|
||||
git push origin --delete "$1"
|
||||
11
.zsh/.functions/hl-bar
Normal file
11
.zsh/.functions/hl-bar
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# HISTOGRAM RENDERING
|
||||
# render horizontal bar chart of account balances
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" histogram --journal "$journal" "${@:-expenses}"
|
||||
else
|
||||
echo "warning: hledger-charts not found, defaulting to native percentage breakdown" >&2
|
||||
hledger -f "$journal" balance "${@:-expenses}" --percent
|
||||
fi
|
||||
9
.zsh/.functions/hl-debt
Normal file
9
.zsh/.functions/hl-debt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# DEBT AND LIABILITY TRACKING
|
||||
# view loan balances over time using inverted multi-line sparklines
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" trend --journal "$journal" -i -r 2 "${@:-liabilities}"
|
||||
else
|
||||
hledger -f "$journal" balance -M "${@:-liabilities}" --invert
|
||||
fi
|
||||
11
.zsh/.functions/hl-group
Normal file
11
.zsh/.functions/hl-group
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# GROUPED BAR RENDERING
|
||||
# render monthly bar comparisons by account
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" grouped --journal "$journal" "${@:-expenses}"
|
||||
else
|
||||
echo "warning: hledger-charts not found, defaulting to monthly tabular matrix" >&2
|
||||
hledger -f "$journal" balance -M "${@:-expenses}"
|
||||
fi
|
||||
10
.zsh/.functions/hl-inc
Normal file
10
.zsh/.functions/hl-inc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# INCOME DISTRIBUTION
|
||||
# view earnings breakdown with inverted mathematical signs for positive display
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" pie --journal "$journal" -i "${@:-income}"
|
||||
else
|
||||
hledger -f "$journal" balance "${@:-income}" --invert
|
||||
fi
|
||||
11
.zsh/.functions/hl-pie
Normal file
11
.zsh/.functions/hl-pie
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# PIE AND PROPORTIONAL RENDERING
|
||||
# render proportional ascii bar and distribution chart
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" pie --journal "$journal" "${@:-expenses}"
|
||||
else
|
||||
echo "warning: hledger-charts not found, defaulting to native percentage breakdown" >&2
|
||||
hledger -f "$journal" balance "${@:-expenses}" --percent
|
||||
fi
|
||||
11
.zsh/.functions/hl-stack
Normal file
11
.zsh/.functions/hl-stack
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# STACKED BAR RENDERING
|
||||
# render stacked proportional bars across time periods
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" stacked --journal "$journal" "${@:-expenses}"
|
||||
else
|
||||
echo "warning: hledger-charts not found, defaulting to monthly tabular matrix" >&2
|
||||
hledger -f "$journal" balance -M "${@:-expenses}"
|
||||
fi
|
||||
11
.zsh/.functions/hl-trend
Normal file
11
.zsh/.functions/hl-trend
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# TREND RENDERING
|
||||
# render multi-period balance trends with unicode sparklines
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" trend --journal "$journal" "${@:-expenses}"
|
||||
else
|
||||
echo "warning: hledger-charts not found, defaulting to monthly tabular matrix" >&2
|
||||
hledger -f "$journal" balance -M "${@:-expenses}"
|
||||
fi
|
||||
14
.zsh/.functions/hl-view
Normal file
14
.zsh/.functions/hl-view
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# GRAPHICAL IMAGE RENDERING
|
||||
# generate matplotlib png image and open immediately with imv
|
||||
local subcommand="${1:-pie}"
|
||||
shift 2>/dev/null || true
|
||||
local chart_script="$HOME/.local/bin/hledger-charts"
|
||||
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
|
||||
local tmp_image="/tmp/hledger_chart.png"
|
||||
|
||||
if [[ -x "$chart_script" ]]; then
|
||||
"$chart_script" "$subcommand" --journal "$journal" --png "$tmp_image" --theme "${HL_THEME:-dark_background}" "${@:-expenses}" && imv "$tmp_image"
|
||||
else
|
||||
echo "error: hledger-charts binary not executable at $chart_script" >&2
|
||||
return 1
|
||||
fi
|
||||
11
.zsh/.functions/lanscan
Normal file
11
.zsh/.functions/lanscan
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local iface_info=$(ip -o -f inet addr show | awk '/scope global/ {print $2, $4}' | head -1)
|
||||
local iface=$(echo $iface_info | awk '{print $1}')
|
||||
local subnet=$(echo $iface_info | awk '{print $2}')
|
||||
|
||||
if [ -z "$subnet" ]; then
|
||||
echo "❌ No active network connection found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "🔍 Scanning subnet \033[33m$subnet\033[0m on \033[33m$iface\033[0m..."
|
||||
doas nmap -sn -n "$subnet" | grep -E "Nmap scan|MAC Address" | sed -e 's/Nmap scan report for /IP: /g' -e 's/MAC Address: /MAC: /g'
|
||||
1
.zsh/.functions/mkcd
Normal file
1
.zsh/.functions/mkcd
Normal file
|
|
@ -0,0 +1 @@
|
|||
mkdir -p "$1" && cd "$1"
|
||||
46
.zsh/.functions/nbash
Normal file
46
.zsh/.functions/nbash
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
local target="${1:-}"
|
||||
if [[ -z "$target" ]]; then
|
||||
echo "usage: nbash <new_script_name>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# PREVENT OVERWRITES
|
||||
# exit immediately if the target file already exists
|
||||
if [[ -f "$target" ]]; then
|
||||
echo "error: file '$target' already exists."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# CREATE DIRECTORIES
|
||||
# ensure the parent directory structure exists before creating the file
|
||||
mkdir -p "$(dirname "$target")"
|
||||
|
||||
# TEMPLATE SELECTION
|
||||
# use the external template if available, otherwise generate inline
|
||||
local template="$HOME/.bash_templates/script.sh"
|
||||
if [[ -f "$template" ]]; then
|
||||
cp "$template" "$target"
|
||||
else
|
||||
cat > "$target" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: $(date +"%F %R %z")
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
main() {
|
||||
# main execution block
|
||||
echo "Hello, world!"
|
||||
}
|
||||
|
||||
main "\$@"
|
||||
EOF
|
||||
fi
|
||||
|
||||
# FILE PERMISSIONS
|
||||
# grant read, write, and execute to the owner, and read to others
|
||||
chmod u=rwx,go=r "$target"
|
||||
|
||||
# LAUNCH EDITOR
|
||||
# open the target in the user environment editor
|
||||
${EDITOR:-vim} "$target"
|
||||
5
.zsh/.functions/netspeed
Normal file
5
.zsh/.functions/netspeed
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
echo "\033[36m📊 Network Interface Speeds:\033[0m"
|
||||
for iface in $(ip link show | grep "^[0-9]" | awk '{print $2}' | tr -d ':' | grep -v "lo"); do
|
||||
speed=$(cat /sys/class/net/$iface/speed 2>/dev/null)
|
||||
[[ -n "$speed" && "$speed" != "-1" ]] && echo "├── $iface: ${speed} Mb/s"
|
||||
done
|
||||
47
.zsh/.functions/npython
Normal file
47
.zsh/.functions/npython
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
local target="${1:-}"
|
||||
if [[ -z "$target" ]]; then
|
||||
echo "usage: npython <new_script_name.py>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# PREVENT OVERWRITES
|
||||
# exit immediately if the target file already exists
|
||||
if [[ -f "$target" ]]; then
|
||||
echo "error: file '$target' already exists."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# CREATE DIRECTORIES
|
||||
# ensure the parent directory structure exists before creating the file
|
||||
mkdir -p "$(dirname "$target")"
|
||||
|
||||
# TEMPLATE SELECTION
|
||||
# use the external template if available, otherwise generate inline
|
||||
local template="$HOME/.python_templates/script.py"
|
||||
if [[ -f "$template" ]]; then
|
||||
cp "$template" "$target"
|
||||
else
|
||||
cat > "$target" <<EOF
|
||||
#!/usr/bin/env python3
|
||||
# AUTHOR: Daesorin
|
||||
# CREATED: $(date +"%F %R %z")
|
||||
|
||||
import sys
|
||||
|
||||
def main():
|
||||
# main execution block
|
||||
print("Hello, world!")
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
EOF
|
||||
fi
|
||||
|
||||
# FILE PERMISSIONS
|
||||
# grant read, write, and execute to the owner, and read to others
|
||||
chmod u=rwx,go=r "$target"
|
||||
|
||||
# LAUNCH EDITOR
|
||||
# open the target in the user environment editor
|
||||
${EDITOR:-vim} "$target"
|
||||
11
.zsh/.functions/pingall
Normal file
11
.zsh/.functions/pingall
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local hosts=("8.8.8.8" "1.1.1.1" "codeberg.org" "archlinux.org")
|
||||
|
||||
echo "\033[36m🌐 Testing connectivity...\033[0m"
|
||||
|
||||
for host in "${hosts[@]}"; do
|
||||
if ping -c 1 -W 2 "$host" &>/dev/null; then
|
||||
echo "✅ $host: reachable"
|
||||
else
|
||||
echo "❌ $host: unreachable"
|
||||
fi
|
||||
done
|
||||
6
.zsh/.functions/pm-orphans
Normal file
6
.zsh/.functions/pm-orphans
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
orphans=$(pacman -Qtdq)
|
||||
if [[ -n "$orphans" ]]; then
|
||||
sudo pacman -Rns $orphans
|
||||
else
|
||||
printf "no orphan packages found.\n"
|
||||
fi
|
||||
3
.zsh/.functions/portscan
Normal file
3
.zsh/.functions/portscan
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[[ -z "$1" ]] && { echo "Usage: portscan <host>"; return 1; }
|
||||
nmap "$1"
|
||||
|
||||
3
.zsh/.functions/serve
Normal file
3
.zsh/.functions/serve
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# LOCAL WEB SERVER
|
||||
# host current directory on specified port or default to 8000
|
||||
python3 -m http.server "${1:-8000}"
|
||||
5
.zsh/.functions/speedtest
Normal file
5
.zsh/.functions/speedtest
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
if command -v speedtest-cli &> /dev/null; then
|
||||
speedtest-cli
|
||||
else
|
||||
echo "Install speedtest-cli: sudo pacman -S speedtest-cli"
|
||||
fi
|
||||
35
.zsh/.functions/sshkeygen
Normal file
35
.zsh/.functions/sshkeygen
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# SSH KEY GENERATOR
|
||||
# creates an ed25519 keypair and copies the public key to the clipboard
|
||||
local email=$1
|
||||
local keytype="ed25519"
|
||||
local keyname=${2:-id_ed25519}
|
||||
|
||||
if [[ -z "$email" ]]; then
|
||||
echo "Usage: sshkeygen <email> [keyname]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ssh_dir="$HOME/.ssh"
|
||||
mkdir -p "$ssh_dir"
|
||||
chmod 700 "$ssh_dir"
|
||||
|
||||
local keypath="$ssh_dir/$keyname"
|
||||
if [[ -f "$keypath" ]]; then
|
||||
echo "\033[31m❌ Key already exists at $keypath\033[0m"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ssh-keygen -t "$keytype" -C "$email" -f "$keypath" -N ""
|
||||
echo "\033[32m✅ SSH key created at $keypath\033[0m"
|
||||
|
||||
# CLIPBOARD INTEGRATION
|
||||
# auto-copy the public key based on the active display server
|
||||
if command -v wl-copy &> /dev/null; then
|
||||
cat "$keypath.pub" | wl-copy
|
||||
echo "\033[36m📋 Public key copied to clipboard (Wayland)!\033[0m"
|
||||
elif command -v xclip &> /dev/null; then
|
||||
cat "$keypath.pub" | xclip -selection clipboard
|
||||
echo "\033[36m📋 Public key copied to clipboard (X11)!\033[0m"
|
||||
else
|
||||
cat "$keypath.pub"
|
||||
fi
|
||||
4
.zsh/.functions/sshkeylist
Normal file
4
.zsh/.functions/sshkeylist
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# SSH KEY LISTER
|
||||
# displays all public keys currently stored in the local ssh directory
|
||||
echo "\033[36m📂 Existing SSH keys in ~/.ssh:\033[0m"
|
||||
ls -lh "$HOME/.ssh/"*.pub 2>/dev/null || echo "No SSH keys found."
|
||||
18
.zsh/.functions/venv
Normal file
18
.zsh/.functions/venv
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# VIRTUAL ENVIRONMENT MANAGER
|
||||
# creates a new environment if it does not exist, then activates it
|
||||
local venv_dir="${1:-.venv}"
|
||||
|
||||
if [[ ! -d "$venv_dir" ]]; then
|
||||
echo "initialising new virtual environment in ./$venv_dir..."
|
||||
python3 -m venv "$venv_dir"
|
||||
fi
|
||||
|
||||
# ACTIVATE ENVIRONMENT
|
||||
# source the activation script into the current shell session
|
||||
if [[ -f "$venv_dir/bin/activate" ]]; then
|
||||
source "$venv_dir/bin/activate"
|
||||
echo "activated $venv_dir"
|
||||
else
|
||||
echo "error: activation script not found in ./$venv_dir"
|
||||
return 1
|
||||
fi
|
||||
7
.zsh/.zshrc
Normal file
7
.zsh/.zshrc
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[[ -o interactive ]] || return
|
||||
|
||||
if [[ -d "$ZDOTDIR" ]]; then
|
||||
for config in "$ZDOTDIR"/*.zsh(N); do
|
||||
source "$config"
|
||||
done
|
||||
fi
|
||||
3
.zsh/00-umask.zsh
Normal file
3
.zsh/00-umask.zsh
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# FILE CREATION MASK
|
||||
# set default permissions for newly created files and directories
|
||||
umask 0077
|
||||
14
.zsh/10-path.zsh
Normal file
14
.zsh/10-path.zsh
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
typeset -U path PATH
|
||||
|
||||
path=(
|
||||
"$HOME/.local/bin"
|
||||
"$HOME/.filen-cli/bin"
|
||||
"$HOME/.cargo/bin"
|
||||
"$HOME/go/bin"
|
||||
"$HOME/.npm-global/bin"
|
||||
/usr/local/bin
|
||||
/usr/lib
|
||||
$path
|
||||
)
|
||||
|
||||
export PATH
|
||||
23
.zsh/20-options.zsh
Normal file
23
.zsh/20-options.zsh
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# LINTING DIRECTIVES
|
||||
# ignore file during static analysis due to lack of zsh support
|
||||
# shellcheck disable=all
|
||||
setopt AUTO_CD # enter dir without prepending `cd`
|
||||
setopt AUTO_PUSHD
|
||||
setopt PUSHD_IGNORE_DUPS
|
||||
setopt PUSHD_SILENT
|
||||
setopt CDABLE_VARS
|
||||
setopt NO_CASE_GLOB
|
||||
setopt COMPLETE_IN_WORD
|
||||
setopt ALWAYS_TO_END
|
||||
setopt AUTO_LIST
|
||||
setopt GLOB_DOTS
|
||||
setopt hist_ignore_space
|
||||
|
||||
# GLOBBING OPTIONS
|
||||
# set expansion rules
|
||||
setopt EXTENDED_GLOB
|
||||
setopt NUMERIC_GLOB_SORT
|
||||
|
||||
# SAFETY OPTIONS
|
||||
# prevent accidental file overwriting
|
||||
setopt NO_CLOBBER
|
||||
22
.zsh/30-history.zsh
Normal file
22
.zsh/30-history.zsh
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# HISTORY FILE CONFIGURATION
|
||||
# define history location and limits
|
||||
HISTFILE="$ZDOTDIR/.history"
|
||||
HISTSIZE=1000
|
||||
SAVEHIST=2000
|
||||
|
||||
# HISTORY OPTIONS
|
||||
# set recording and retrieval parameters
|
||||
setopt APPEND_HISTORY
|
||||
setopt INC_APPEND_HISTORY
|
||||
setopt HIST_IGNORE_DUPS
|
||||
setopt HIST_IGNORE_ALL_DUPS
|
||||
setopt HIST_SAVE_NO_DUPS
|
||||
setopt HIST_REDUCE_BLANKS
|
||||
setopt HIST_FIND_NO_DUPS
|
||||
setopt HIST_VERIFY
|
||||
setopt SHARE_HISTORY
|
||||
setopt EXTENDED_HISTORY
|
||||
|
||||
# HISTORY ALIASES
|
||||
# define output format for history review
|
||||
alias hist='fc -l -t "%Y-%m-%d %H:%M:%S"'
|
||||
29
.zsh/40-env.zsh
Normal file
29
.zsh/40-env.zsh
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# shellcheck disable=all
|
||||
# SYSTEM DEFAULTS
|
||||
# define default editors and pagers
|
||||
export EDITOR="nvim"
|
||||
export VISUAL="nvim"
|
||||
export PAGER="bat"
|
||||
export MANPAGER="nvim +Man!"
|
||||
export BAT_THEME="base16"
|
||||
export NODE_OPTIONS=--no-deprecation
|
||||
|
||||
# HLEDGER CONFIGURATION
|
||||
# point system commands to the primary journal file
|
||||
export LEDGER_FILE="$HOME/obsidian/finances/journals/2026.journal"
|
||||
|
||||
# PAGER COLOURS
|
||||
# define terminal capability codes for less
|
||||
export LESS_TERMCAP_mb=$'\E[1;31m'
|
||||
export LESS_TERMCAP_md=$'\E[1;36m'
|
||||
export LESS_TERMCAP_me=$'\E[0m'
|
||||
export LESS_TERMCAP_so=$'\E[01;44;33m'
|
||||
export LESS_TERMCAP_se=$'\E[0m'
|
||||
export LESS_TERMCAP_us=$'\E[1;32m'
|
||||
export LESS_TERMCAP_ue=$'\E[0m'
|
||||
|
||||
# VIDEODRIVER
|
||||
export SDL_VIDEODRIVER=wayland
|
||||
|
||||
export W3M_DIR="$HOME/.config/w3m"
|
||||
export W3M_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/w3m"
|
||||
19
.zsh/50-completion.zsh
Normal file
19
.zsh/50-completion.zsh
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# COMPLETION INITIALISATION
|
||||
# load and start the completion system
|
||||
autoload -Uz compinit
|
||||
fpath=(
|
||||
/usr/share/zsh/site-functions
|
||||
$fpath
|
||||
)
|
||||
compinit
|
||||
|
||||
# STYLING OPTIONS
|
||||
# apply cache and interface rules to completions
|
||||
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
|
||||
zstyle ':completion:*' menu select
|
||||
zstyle ':completion:*' list-dirs-first true
|
||||
zstyle ':completion:*' use-cache yes
|
||||
zstyle ':completion:*' cache-path ~/.cache/zsh
|
||||
zstyle ':completion:*' group-name ''
|
||||
zstyle ':completion:*' smart-case yes
|
||||
zstyle ':completion:*' completer _expand _complete _correct _approximate
|
||||
30
.zsh/60-plugins.zsh
Normal file
30
.zsh/60-plugins.zsh
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# STARSHIP INITIALISATION
|
||||
# start the prompt
|
||||
eval "$(starship init zsh)"
|
||||
|
||||
# ZOXIDE INITIALISATION
|
||||
# start the smart directory jumper
|
||||
if command -v zoxide >/dev/null 2>&1; then
|
||||
eval "$(zoxide init zsh --cmd cd)"
|
||||
fi
|
||||
|
||||
# TMUX CHECK
|
||||
# verify active multiplexer sessions
|
||||
if [ -n "$TMUX" ] && ! tmux info >/dev/null 2>&1; then
|
||||
unset TMUX
|
||||
unset TMUX_PANE
|
||||
fi
|
||||
|
||||
# ZSH PLUGINS
|
||||
# source external extension scripts
|
||||
if [[ -f /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh ]]; then
|
||||
source /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh
|
||||
fi
|
||||
|
||||
if [[ -f /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh ]]; then
|
||||
source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
|
||||
fi
|
||||
|
||||
if [[ -f /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]]; then
|
||||
source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
|
||||
fi
|
||||
14
.zsh/70-binds.zsh
Normal file
14
.zsh/70-binds.zsh
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# INPUT MODES
|
||||
# enforce vi keybindings
|
||||
bindkey -e
|
||||
|
||||
# SEARCH BINDINGS
|
||||
# map arrows to history search functions
|
||||
bindkey '^[[A' history-substring-search-up
|
||||
bindkey '^[[B' history-substring-search-down
|
||||
|
||||
# HELP MANUAL
|
||||
# map keyboard shortcut to local runbook
|
||||
unalias run-help 2>/dev/null
|
||||
autoload -Uz run-help
|
||||
bindkey '^[h' run-help
|
||||
63
.zsh/80-aliases.zsh
Normal file
63
.zsh/80-aliases.zsh
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# shellcheck disable=all
|
||||
alias pm='sudo pacman'
|
||||
alias pr='paru'
|
||||
|
||||
alias z='cd'
|
||||
|
||||
alias ls='ls --color=always'
|
||||
alias lz='eza --group-directories-first'
|
||||
alias l='eza -l --group-directories-first --git'
|
||||
alias li='eza -laai --group-directories-first --git'
|
||||
alias la='eza -la --group-directories-first --git'
|
||||
alias lt='eza -l --tree --group-directories-first --git'
|
||||
alias lta='eza -la --tree --group-directories-first --git'
|
||||
alias lt2='eza --tree --level=2'
|
||||
alias lt3='eza --tree --level=3'
|
||||
alias cat='bat --paging=never'
|
||||
alias bap='bat --paging=always'
|
||||
alias b='bat'
|
||||
|
||||
alias cp='cp -iv'
|
||||
alias mv='mv -iv'
|
||||
alias rm='rm -Iv'
|
||||
alias rmr='rm -Irv'
|
||||
alias rmf='rm -frv'
|
||||
alias mkdir='mkdir -pv'
|
||||
|
||||
alias ,='nvim'
|
||||
alias ,,='sudo nvim'
|
||||
|
||||
alias grep='grep --color=auto'
|
||||
alias diff='diff --color=auto'
|
||||
alias ip='ip --color=auto'
|
||||
alias df='df -h'
|
||||
alias du='du -h'
|
||||
alias free='free -h'
|
||||
alias chmod='chmod -v'
|
||||
alias suchmod='sudo chmod -v'
|
||||
alias cls='clear'
|
||||
alias path='echo $PATH | tr ":" "\n"'
|
||||
alias reload='exec bash'
|
||||
|
||||
alias -g L='| less'
|
||||
alias -g G='| grep -i'
|
||||
alias -g WC='| wc -l'
|
||||
alias -g NUL='>/dev/null 2>&1'
|
||||
alias -g E='2>&1'
|
||||
alias -g C='; clear'
|
||||
alias -g COPY='| wl-copy'
|
||||
alias -g ANSICOPY='| ansifilter | wl-copy'
|
||||
|
||||
alias fullcharge='sudo tlp fullcharge'
|
||||
alias paste='wl-paste'
|
||||
alias ic='incus'
|
||||
|
||||
alias fastfetch='fastfetch --config examples/25.jsonc'
|
||||
|
||||
alias py='python3'
|
||||
alias ipy='ipython'
|
||||
|
||||
alias zt='zathura'
|
||||
|
||||
alias sys='systemctl --user'
|
||||
alias susys='sudo systemctl'
|
||||
10
.zsh/81-hledger-aliases.zsh
Normal file
10
.zsh/81-hledger-aliases.zsh
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
alias hl='hledger'
|
||||
alias hlb='hledger bal'
|
||||
alias hlbe='hledger bal Expenses'
|
||||
alias hlbi='hledger bal Income'
|
||||
alias hlbs='hledger balancesheet'
|
||||
alias hlr='hledger reg'
|
||||
alias hlis='hledger incomestatement'
|
||||
alias hlu='hledger-ui'
|
||||
alias hlw='hledger-web'
|
||||
alias hlupdate='md-to-hledger > $HOME/obsidian/finances/journals/compiled.journal && hledger bal'
|
||||
7
.zsh/82-tmux-aliases.zsh
Normal file
7
.zsh/82-tmux-aliases.zsh
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
alias tx='tmux'
|
||||
alias txn='tmux new -s'
|
||||
alias txnmain='tmux new -s main'
|
||||
alias txa='tmux attach -t'
|
||||
alias txkw='tmux kill-window -t'
|
||||
alias txks='tmux kill-session -t'
|
||||
alias txkp='tmux kill-pane -t'
|
||||
21
.zsh/83-git-aliases.zsh
Normal file
21
.zsh/83-git-aliases.zsh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
alias gs='git status'
|
||||
alias gst='git status -sb'
|
||||
alias gl='git log --oneline --graph --decorate --all'
|
||||
alias gd='git diff'
|
||||
alias gds='git diff --staged'
|
||||
alias ga='git add'
|
||||
alias gaa='git add -A'
|
||||
alias gc='git commit'
|
||||
alias gcm='git commit -m'
|
||||
alias gca='git commit -am'
|
||||
alias gco='git checkout'
|
||||
alias gb='git branch'
|
||||
alias gm='git merge'
|
||||
alias gp='git push'
|
||||
alias gpl='git pull'
|
||||
alias gun='git reset HEAD --'
|
||||
alias dot='git dot'
|
||||
alias git.last='git log -1 HEAD'
|
||||
alias git.oops='git commit --amend --no-edit'
|
||||
alias git.unstage='git reset HEAD --'
|
||||
alias git.today="git log --since='24 hours ago' --oneline --graph --all"
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue