From 7d0f90da66985b402c6a25eb3eca8cc9e6060ced Mon Sep 17 00:00:00 2001
From: Craig Jennings
Date: Sat, 24 Jan 2026 18:52:34 -0600
Subject: fix(testing): remove obsolete --skip-slow-packages option
This flag was removed from archsetup but remained in test scripts.
---
.gitignore | 10 +
.stignore | 2 +
arch-distrobox | 494 +
archsetup | 1914 +
archsetup.conf.example | 54 +
assets/2026-01-17-gvfs-smb-feature-request.txt | 6 +
assets/2026-01-17-zfs-sanoid-feature-request.txt | 202 +
assets/2026-01-19-remove-zfs-scripts-request.md | 29 +
assets/2026-01-20-console-display-issues.txt | 112 +
assets/2026-01-21-grub-timeout-request.txt | 4 +
assets/2026-01-21-syncthing-service-conflict.org | 72 +
assets/2026-01-23-avahi-mdns-fixes.org | 125 +
assets/dwm.desktop | 11 +
assets/security-and-hardening-recommendations.txt | 119 +
assets/wireguard/USCALA.conf | 15 +
assets/wireguard/USCASF.conf | 16 +
assets/wireguard/USDC.conf | 15 +
assets/wireguard/USGAAT.conf | 15 +
assets/wireguard/USNY.conf | 16 +
assets/wireguard/switzerlan-zurich1.conf | 15 +
assets/wireguard/switzerlan-zurich2.conf | 15 +
dotfiles/.gitignore | 7 +
dotfiles/hyprland/.config/gammastep/config.ini | 25 +
dotfiles/hyprland/.config/hypr/hypridle.conf | 34 +
dotfiles/hyprland/.config/hypr/hyprland.conf | 287 +
dotfiles/hyprland/.config/hypr/hyprlock.conf | 75 +
dotfiles/hyprland/.config/waybar/config | 50 +
dotfiles/hyprland/.config/waybar/style.css | 68 +
dotfiles/hyprland/.config/wofi/config | 12 +
dotfiles/hyprland/.config/wofi/style.css | 59 +
dotfiles/system/.Xmodmap | 6 +
dotfiles/system/.Xresources | 138 +
dotfiles/system/.authcode | 1 +
dotfiles/system/.authinfo.gpg | 12 +
dotfiles/system/.bash_logout | 6 +
dotfiles/system/.bashrc | 59 +
dotfiles/system/.config/.cmailpass.gpg | 1 +
dotfiles/system/.config/.gmailpass.gpg | 1 +
dotfiles/system/.config/.tidal-dl.json | 1 +
dotfiles/system/.config/.tidal-dl.token.json | 1 +
.../PyCharmCE2024.1/app-internal-state.db | Bin 0 -> 24576 bytes
.../PyCharmCE2024.1/early-access-registry.txt | 2 +
.../PyCharmCE2024.1/options/colors.scheme.xml | 5 +
.../PyCharmCE2024.1/options/console-font.xml | 5 +
.../PyCharmCE2024.1/options/editor-font.xml | 8 +
.../JetBrains/PyCharmCE2024.1/options/editor.xml | 6 +
.../options/features.usage.statistics.xml | 97 +
.../PyCharmCE2024.1/options/filetypes.xml | 5 +
.../options/ide-features-trainer.xml | 9 +
.../PyCharmCE2024.1/options/ide.general.xml | 5 +
.../PyCharmCE2024.1/options/log-categories.xml | 3 +
.../JetBrains/PyCharmCE2024.1/options/other.xml | 36 +
.../PyCharmCE2024.1/options/settingsSync.xml | 5 +
.../JetBrains/PyCharmCE2024.1/options/updates.xml | 6 +
.../PyCharmCE2024.1/options/window.state.xml | 8 +
.../PyCharmCE2024.1/updatedBrokenPlugins.db | Bin 0 -> 214268 bytes
dotfiles/system/.config/Thunar/accels.scm | 138 +
dotfiles/system/.config/Thunar/uca.xml | 15 +
dotfiles/system/.config/audacious/QtUi.conf | 3 +
dotfiles/system/.config/audacious/config | 23 +
dotfiles/system/.config/audacious/playlist-state | 12 +
.../system/.config/audacious/playlists/1000.audpl | 5 +
dotfiles/system/.config/audacious/playlists/order | 1 +
dotfiles/system/.config/audacious/plugin-registry | 926 +
.../.config/calibre/conversion/azw3_output.py | 0
.../.config/calibre/conversion/comic_input.py | 0
.../system/.config/calibre/conversion/debug.py | 0
.../.config/calibre/conversion/docx_input.py | 0
.../.config/calibre/conversion/docx_output.py | 0
.../.config/calibre/conversion/epub_output.py | 0
.../system/.config/calibre/conversion/fb2_input.py | 0
.../.config/calibre/conversion/fb2_output.py | 0
.../.config/calibre/conversion/heuristics.py | 0
.../.config/calibre/conversion/htmlz_output.py | 0
.../.config/calibre/conversion/kepub_output.py | 0
.../.config/calibre/conversion/look_and_feel.py | 0
.../.config/calibre/conversion/lrf_output.py | 0
.../system/.config/calibre/conversion/metadata.py | 0
.../.config/calibre/conversion/mobi_output.py | 0
.../.config/calibre/conversion/page_setup.py | 3 +
.../.config/calibre/conversion/pdb_output.py | 0
.../system/.config/calibre/conversion/pdf_input.py | 0
.../.config/calibre/conversion/pdf_output.py | 0
.../.config/calibre/conversion/pmlz_output.py | 0
.../system/.config/calibre/conversion/rb_output.py | 0
.../system/.config/calibre/conversion/rtf_input.py | 0
.../calibre/conversion/search_and_replace.py | 0
.../.config/calibre/conversion/snb_output.py | 0
.../calibre/conversion/structure_detection.py | 0
dotfiles/system/.config/calibre/conversion/toc.py | 0
.../system/.config/calibre/conversion/txt_input.py | 0
.../.config/calibre/conversion/txt_output.py | 0
.../.config/calibre/conversion/txtz_output.py | 0
.../custom_recipes/The Economist_1001.recipe | 684 +
.../custom_recipes/The New York Times_1000.recipe | 368 +
.../.config/calibre/custom_recipes/index.json | 6 +
dotfiles/system/.config/calibre/customize.py.json | 76 +
.../calibre/device_drivers_KOBOTOUCH.py.json | 59 +
.../device_drivers_KOBOTOUCHEXTENDED.py.json | 62 +
.../calibre/device_drivers_USER_DEFINED.py.json | 24 +
.../.config/calibre/fonts/scanner_cache.json | 24018 +
dotfiles/system/.config/calibre/global.py.json | 82 +
dotfiles/system/.config/calibre/gui.py.json | 115 +
dotfiles/system/.config/calibre/history.plist | 25 +
dotfiles/system/.config/calibre/icons-any.rcc | Bin 0 -> 1291528 bytes
dotfiles/system/.config/calibre/icons-dark.rcc | Bin 0 -> 204531 bytes
.../.config/calibre/metadata-sources-cache.json | 18 +
.../.config/calibre/metadata_sources/global.json | 23 +
dotfiles/system/.config/calibre/mtp_devices.json | 9 +
.../.config/calibre/plugins/Apple Books covers.zip | Bin 0 -> 22815 bytes
.../.config/calibre/plugins/Barnes & Noble.zip | Bin 0 -> 120252 bytes
.../.config/calibre/plugins/Clean Comments.zip | Bin 0 -> 41082 bytes
.../.config/calibre/plugins/Extract ISBN.zip | Bin 0 -> 183577 bytes
.../.config/calibre/plugins/Favourites Menu.json | 140 +
.../.config/calibre/plugins/Favourites Menu.zip | Bin 0 -> 124019 bytes
.../.config/calibre/plugins/Find Duplicates.json | 13 +
.../.config/calibre/plugins/Find Duplicates.zip | Bin 0 -> 519469 bytes
.../calibre/plugins/KePub Metadata Reader.zip | Bin 0 -> 24156 bytes
.../calibre/plugins/KePub Metadata Writer.zip | Bin 0 -> 24958 bytes
.../calibre/plugins/Kindle hi-res covers.zip | Bin 0 -> 15129 bytes
.../.config/calibre/plugins/Kobo Metadata.zip | Bin 0 -> 556602 bytes
.../.config/calibre/plugins/Kobo Utilities.json | 122 +
.../.config/calibre/plugins/Kobo Utilities.zip | Bin 0 -> 264321 bytes
.../.config/calibre/plugins/KoboTouchExtended.zip | Bin 0 -> 49513 bytes
.../system/.config/calibre/plugins/Open With.json | 61 +
.../system/.config/calibre/plugins/Open With.zip | Bin 0 -> 225919 bytes
.../.config/calibre/plugins/Reading List.json | 8 +
.../.config/calibre/plugins/Reading List.zip | Bin 0 -> 425425 bytes
.../calibre/plugins/Search The Internet.json | 1097 +
.../system/.config/calibre/plugins/Wikidata.zip | Bin 0 -> 39319 bytes
.../system/.config/calibre/save_to_disk.py.json | 15 +
dotfiles/system/.config/calibre/scheduler.xml | 166 +
dotfiles/system/.config/calibre/server-config.txt | 0
.../calibre/server-custom-list-template.json | 14 +
.../.config/calibre/server-search-the-net.json | 7 +
.../system/.config/calibre/server-users.sqlite | Bin 0 -> 12288 bytes
.../system/.config/calibre/shortcuts/main.json | 15 +
dotfiles/system/.config/calibre/smtp.py.json | 77 +
dotfiles/system/.config/calibre/tag-map-rules.json | 10 +
.../system/.config/calibre/viewer-webengine.json | 327 +
dotfiles/system/.config/calibre/viewer.json | 13 +
...2d30ecac05080ddf3e9e288a22c4f49866ba57c8b2.json | 1 +
...188c47051f047f05e84d828fca5e5545396b94f14c.json | 1 +
...a2b70e5ba9a2f61369f97c7fcc415321753e7c8bea.json | 1 +
...1c4a0652d20e908edc16409bc7697635a28f96478e.json | 1 +
...40ef2d3cca061e54ce37143a9e142524f61028cdd9.json | 1 +
...62bc5f6e6b0f18bdb829384144fdd13cc3b487deb1.json | 1 +
...761ea678a4ccacef1f5002917bda43970cd6096b19.json | 1 +
...3672b6a7cddc243254b55897adfdd5671fe7b2aacf.json | 1 +
dotfiles/system/.config/conky/conky.conf | 24 +
dotfiles/system/.config/dunst/dunstrc | 201 +
dotfiles/system/.config/environment.d/envvars.conf | 1 +
dotfiles/system/.config/flameshot/flameshot.ini | 11 +
dotfiles/system/.config/fontconfig/fonts.conf | 27 +
dotfiles/system/.config/ghostty/config | 44 +
dotfiles/system/.config/gtk-3.0/gtk.css | 6 +
dotfiles/system/.config/gtk-3.0/settings.ini | 18 +
dotfiles/system/.config/htop/htoprc | 63 +
dotfiles/system/.config/lf/cleaner | 4 +
dotfiles/system/.config/lf/draw_img | 67 +
dotfiles/system/.config/lf/image | 18 +
dotfiles/system/.config/lf/lfrc | 333 +
dotfiles/system/.config/lf/preview | 91 +
dotfiles/system/.config/mc/panels.ini | 0
dotfiles/system/.config/mopidy/mopidy.conf | 91 +
dotfiles/system/.config/mpd/mpd.conf | 437 +
dotfiles/system/.config/mpd/musicpd.conf | 436 +
dotfiles/system/.config/mpv/input.conf | 4 +
dotfiles/system/.config/mpv/mpv.conf | 1 +
dotfiles/system/.config/ncmpcpp/bindings | 551 +
dotfiles/system/.config/ncmpcpp/config | 71 +
dotfiles/system/.config/nitrogen/bg-saved.cfg | 4 +
dotfiles/system/.config/picom.conf | 56 +
dotfiles/system/.config/pychess/config | 213 +
dotfiles/system/.config/pychess/engines.json | 526 +
dotfiles/system/.config/pychess/pydock.xml | 1 +
.../system/.config/qalculate/qalculate-gtk.cfg | 329 +
dotfiles/system/.config/qt5ct/qt5ct.conf | 32 +
dotfiles/system/.config/ranger/commands.py | 62 +
dotfiles/system/.config/ranger/commands_full.py | 1836 +
dotfiles/system/.config/ranger/rc.conf | 790 +
dotfiles/system/.config/ranger/rifle.conf | 257 +
dotfiles/system/.config/ranger/scope.sh | 216 +
dotfiles/system/.config/redshift.conf | 32 +
dotfiles/system/.config/rofi/config.rasi | 6 +
.../.config/rofi/themes/rounded-gray-dark.rasi | 106 +
.../sublime-merge/Local/License.sublime_license | Bin 0 -> 394 bytes
dotfiles/system/.config/sxhkd/sxhkdrc | 106 +
dotfiles/system/.config/systemd/user/emacs.service | 19 +
.../.config/systemd/user/geoclue-agent.service | 8 +
.../environment.conf | 3 +
dotfiles/system/.config/tickrs/config.yml | 95 +
dotfiles/system/.config/topgrade.toml | 246 +
.../touchpad-indicator/touchpad-indicator.conf | 1 +
dotfiles/system/.config/transmission-daemon | 1 +
dotfiles/system/.config/transmission/settings.json | 129 +
dotfiles/system/.config/user-dirs.dirs | 15 +
dotfiles/system/.config/user-dirs.locale | 1 +
dotfiles/system/.config/youtube-dl/youtube-dl.conf | 11 +
dotfiles/system/.config/zathura/zathurarc | 8 +
dotfiles/system/.gitconfig | 19 +
dotfiles/system/.gitignore | 1 +
dotfiles/system/.gnupg/gpg-agent.conf | 18 +
dotfiles/system/.gnupg/pinentry-dmenu.conf | 10 +
dotfiles/system/.gtkrc-2.0 | 19 +
dotfiles/system/.hushlogin | 0
dotfiles/system/.latexmkrc | 1 +
dotfiles/system/.local/bin/AAXtoMP3 | 908 +
dotfiles/system/.local/bin/ai-assistants | 45 +
dotfiles/system/.local/bin/airplanemodetoggle | 33 +
dotfiles/system/.local/bin/any2flac | 44 +
dotfiles/system/.local/bin/any2opus | 102 +
dotfiles/system/.local/bin/audioselect | 68 +
dotfiles/system/.local/bin/battery_monitor | 52 +
dotfiles/system/.local/bin/bookfind | 5 +
dotfiles/system/.local/bin/brightness | 36 +
dotfiles/system/.local/bin/bsdnet_bounce | 6 +
dotfiles/system/.local/bin/build-emacs.sh | 213 +
dotfiles/system/.local/bin/build.emacs.aur.sh | 6 +
dotfiles/system/.local/bin/calibre-install | 10 +
dotfiles/system/.local/bin/clobberall | 20 +
dotfiles/system/.local/bin/colorpick | 6 +
dotfiles/system/.local/bin/cron/README.md | 11 +
dotfiles/system/.local/bin/cron/checkup | 17 +
dotfiles/system/.local/bin/cron/crontog | 6 +
dotfiles/system/.local/bin/dab | 72 +
dotfiles/system/.local/bin/debugemacs | 4 +
dotfiles/system/.local/bin/displayselect | 83 +
dotfiles/system/.local/bin/dmenuexitmenu | 12 +
dotfiles/system/.local/bin/dmenuhandler | 21 +
dotfiles/system/.local/bin/dmenumount | 67 +
dotfiles/system/.local/bin/dmenumountcifs | 19 +
dotfiles/system/.local/bin/dmenurecord | 123 +
dotfiles/system/.local/bin/dmenuumount | 44 +
dotfiles/system/.local/bin/dmenuunicode | 18 +
dotfiles/system/.local/bin/dotfiles_pushall | 6 +
dotfiles/system/.local/bin/ec | 2 +
dotfiles/system/.local/bin/em | 2 +
dotfiles/system/.local/bin/et | 2 +
dotfiles/system/.local/bin/exitmenu | 15 +
dotfiles/system/.local/bin/extractaudio | 2 +
dotfiles/system/.local/bin/get-arch-iso.sh | 78 +
dotfiles/system/.local/bin/gitconfig_defaults | 5 +
dotfiles/system/.local/bin/gruv | 3 +
dotfiles/system/.local/bin/ifinstalled | 12 +
dotfiles/system/.local/bin/lfrun | 19 +
dotfiles/system/.local/bin/lfub | 24 +
dotfiles/system/.local/bin/linkhandler | 26 +
dotfiles/system/.local/bin/lkg | 4 +
dotfiles/system/.local/bin/lkg_rollback | 12 +
dotfiles/system/.local/bin/lsbak | 1 +
dotfiles/system/.local/bin/mkplaylist | 173 +
dotfiles/system/.local/bin/monitor | 50 +
dotfiles/system/.local/bin/mpd_play_yt_stream | 14 +
dotfiles/system/.local/bin/msmtp-enqueue.sh | 44 +
dotfiles/system/.local/bin/msmtp-listqueue.sh | 8 +
dotfiles/system/.local/bin/msmtp-runqueue.sh | 69 +
dotfiles/system/.local/bin/open-file-in-eww | 2 +
dotfiles/system/.local/bin/opus2mp3 | 3 +
dotfiles/system/.local/bin/org-capture.sh | 159 +
dotfiles/system/.local/bin/project | 100 +
dotfiles/system/.local/bin/prompt | 8 +
dotfiles/system/.local/bin/protonvpn | 3 +
dotfiles/system/.local/bin/ps-mem | 28 +
dotfiles/system/.local/bin/recordnow | 26 +
dotfiles/system/.local/bin/refresharchkeys | 6 +
dotfiles/system/.local/bin/remaps | 11 +
dotfiles/system/.local/bin/reset-auth | 36 +
dotfiles/system/.local/bin/resetmimetypes | 295 +
dotfiles/system/.local/bin/samedir | 10 +
dotfiles/system/.local/bin/screenshotmenu | 13 +
dotfiles/system/.local/bin/setbg | 34 +
dotfiles/system/.local/bin/ssh-createkeys | 3 +
dotfiles/system/.local/bin/starth | 9 +
dotfiles/system/.local/bin/statusbar/sb-battery | 37 +
dotfiles/system/.local/bin/statusbar/sb-clock | 29 +
dotfiles/system/.local/bin/statusbar/sb-cpu | 12 +
dotfiles/system/.local/bin/statusbar/sb-cpubars | 44 +
dotfiles/system/.local/bin/statusbar/sb-disk | 23 +
dotfiles/system/.local/bin/statusbar/sb-doppler | 279 +
dotfiles/system/.local/bin/statusbar/sb-forecast | 35 +
dotfiles/system/.local/bin/statusbar/sb-help-icon | 17 +
dotfiles/system/.local/bin/statusbar/sb-internet | 26 +
dotfiles/system/.local/bin/statusbar/sb-iplocate | 10 +
dotfiles/system/.local/bin/statusbar/sb-kbselect | 16 +
dotfiles/system/.local/bin/statusbar/sb-mailbox | 20 +
dotfiles/system/.local/bin/statusbar/sb-memory | 12 +
dotfiles/system/.local/bin/statusbar/sb-moonphase | 37 +
dotfiles/system/.local/bin/statusbar/sb-mpdup | 8 +
dotfiles/system/.local/bin/statusbar/sb-music | 19 +
dotfiles/system/.local/bin/statusbar/sb-nettraf | 29 +
dotfiles/system/.local/bin/statusbar/sb-news | 17 +
.../system/.local/bin/statusbar/sb-pacpackages | 29 +
dotfiles/system/.local/bin/statusbar/sb-popupgrade | 9 +
dotfiles/system/.local/bin/statusbar/sb-price | 50 +
dotfiles/system/.local/bin/statusbar/sb-tasks | 20 +
dotfiles/system/.local/bin/statusbar/sb-torrent | 27 +
dotfiles/system/.local/bin/statusbar/sb-volume | 30 +
dotfiles/system/.local/bin/steam | 2 +
dotfiles/system/.local/bin/sudo-update-grub | 1 +
dotfiles/system/.local/bin/sysupdate | 5 +
dotfiles/system/.local/bin/td-toggle | 12 +
dotfiles/system/.local/bin/timezone-change | 68 +
dotfiles/system/.local/bin/timezone-set | 16 +
dotfiles/system/.local/bin/toggle-touchpad | 16 +
dotfiles/system/.local/bin/torwrap | 7 +
.../system/.local/bin/touchpad-indicator-start | 12 +
dotfiles/system/.local/bin/transadd | 9 +
.../system/.local/bin/update-backup-repositories | 56 +
dotfiles/system/.local/bin/updatemirrors | 20 +
dotfiles/system/.local/bin/virtstart | 8 +
dotfiles/system/.local/bin/wallsearch | 43 +
dotfiles/system/.local/bin/warpinator-start | 11 +
dotfiles/system/.local/bin/ytp | 1 +
.../system/.local/share/applications/dwm.desktop | 7 +
.../share/applications/emacsclient-mail.desktop | 20 +
.../system/.local/share/applications/file.desktop | 4 +
.../system/.local/share/applications/img.desktop | 4 +
.../.local/share/applications/lock-screen.desktop | 7 +
.../.local/share/applications/logout.desktop | 7 +
.../system/.local/share/applications/mail.desktop | 4 +
.../system/.local/share/applications/mimeapps.list | 0
.../.local/share/applications/org-protocol.desktop | 12 +
.../system/.local/share/applications/pdf.desktop | 4 +
.../.local/share/applications/reboot.desktop | 7 +
.../.local/share/applications/shutdown.desktop | 7 +
.../.local/share/applications/suspend.desktop | 7 +
.../system/.local/share/applications/text.desktop | 4 +
.../.local/share/applications/torrent.desktop | 4 +
.../system/.local/share/audacious/Skins/2a03.wsz | Bin 0 -> 19553 bytes
.../.local/share/audacious/Skins/Adidas2-3.zip | Bin 0 -> 29938 bytes
.../system/.local/share/audacious/Skins/Blac.wsz | Bin 0 -> 129798 bytes
.../Skins/Classic_70's_Marshall_Stack.wsz | Bin 0 -> 226617 bytes
.../share/audacious/Skins/Digital_Stereo_73.wsz | Bin 0 -> 64065 bytes
.../audacious/Skins/Expensive_HI_FI_Sony_2005.wsz | Bin 0 -> 244168 bytes
.../share/audacious/Skins/Future_Audio_1.wsz | Bin 0 -> 92650 bytes
.../share/audacious/Skins/Modern_Stereo_Amp_2.wsz | Bin 0 -> 75766 bytes
.../Skins/Modern_Style_Marshall_Stack.wsz | Bin 0 -> 311586 bytes
.../share/audacious/Skins/Nucleo_NLog_v102_.wsz | Bin 0 -> 139475 bytes
.../.local/share/audacious/Skins/PioneerAmp.wsz | Bin 0 -> 55340 bytes
.../Skins/RAZORIX 50 % Uriel - 50% Kript.wsz | Bin 0 -> 105829 bytes
.../system/.local/share/audacious/Skins/SONY3.WSZ | Bin 0 -> 42268 bytes
.../.local/share/audacious/Skins/Sony MD.wsz | Bin 0 -> 79957 bytes
.../.local/share/audacious/Skins/Spy Amp.zip | Bin 0 -> 51211 bytes
.../share/audacious/Skins/SpyAMP Pro Heaven.wsz | Bin 0 -> 202473 bytes
.../share/audacious/Skins/SpyAMP Pro Zeus.wsz | Bin 0 -> 198970 bytes
.../Skins/SpyAMP_professional_edition_mV1.wsz | Bin 0 -> 191945 bytes
.../share/audacious/Skins/SpyAmp-Pro-Heaven.wsz | Bin 0 -> 208361 bytes
.../.local/share/audacious/Skins/Steel_Stereo.wsz | Bin 0 -> 224116 bytes
.../share/audacious/Skins/Technoia_ver001.wsz | Bin 0 -> 148428 bytes
.../.local/share/audacious/Skins/base-2.91.wsz | Bin 0 -> 101121 bytes
.../.local/share/audacious/Skins/deviantamp.zip | Bin 0 -> 77218 bytes
.../.local/share/audacious/Skins/iWinamp_V1.wsz | Bin 0 -> 142681 bytes
.../system/.local/share/audacious/Skins/nadamp.zip | Bin 0 -> 38073 bytes
.../.local/share/audacious/Skins/s_Pioneer.wsz | Bin 0 -> 54003 bytes
.../.local/share/audacious/Skins/satellite.wsz | Bin 0 -> 46562 bytes
.../share/audacious/Skins/sonympfx3lcdv111.wsz | Bin 0 -> 41131 bytes
.../system/.local/share/audacious/Skins/spyamp.zip | Bin 0 -> 76065 bytes
.../.local/share/audacious/Skins/spyamp_sepia.wsz | Bin 0 -> 61038 bytes
.../.local/share/audacious/Skins/spyampy2k.wsz | Bin 0 -> 124124 bytes
.../.local/share/audacious/Skins/spyampy2k3.wsz | Bin 0 -> 100716 bytes
.../v2_technics_white_resting_by_johnnyg0.wsz | Bin 0 -> 143271 bytes
dotfiles/system/.local/share/emoji | 1593 +
.../system/.local/share/fonts/AppleColorEmoji.ttf | Bin 0 -> 42722048 bytes
.../.local/share/fonts/BerkeleyMono-Bold.otf | Bin 0 -> 74596 bytes
.../.local/share/fonts/BerkeleyMono-Bold.ttf | Bin 0 -> 114872 bytes
.../.local/share/fonts/BerkeleyMono-Bold.woff | Bin 0 -> 41892 bytes
.../.local/share/fonts/BerkeleyMono-Bold.woff2 | Bin 0 -> 38584 bytes
.../.local/share/fonts/BerkeleyMono-BoldItalic.otf | Bin 0 -> 75756 bytes
.../.local/share/fonts/BerkeleyMono-BoldItalic.ttf | Bin 0 -> 116664 bytes
.../share/fonts/BerkeleyMono-BoldItalic.woff | Bin 0 -> 42536 bytes
.../share/fonts/BerkeleyMono-BoldItalic.woff2 | Bin 0 -> 39272 bytes
.../.local/share/fonts/BerkeleyMono-Italic.otf | Bin 0 -> 75172 bytes
.../.local/share/fonts/BerkeleyMono-Italic.ttf | Bin 0 -> 115636 bytes
.../.local/share/fonts/BerkeleyMono-Italic.woff | Bin 0 -> 42116 bytes
.../.local/share/fonts/BerkeleyMono-Italic.woff2 | Bin 0 -> 38948 bytes
.../.local/share/fonts/BerkeleyMono-Regular.otf | Bin 0 -> 72688 bytes
.../.local/share/fonts/BerkeleyMono-Regular.ttf | Bin 0 -> 114656 bytes
.../.local/share/fonts/BerkeleyMono-Regular.woff | Bin 0 -> 40708 bytes
.../.local/share/fonts/BerkeleyMono-Regular.woff2 | Bin 0 -> 37736 bytes
.../share/fonts/BerkeleyMonoVariable-Italic.ttf | Bin 0 -> 103704 bytes
.../share/fonts/BerkeleyMonoVariable-Italic.woff | Bin 0 -> 49132 bytes
.../share/fonts/BerkeleyMonoVariable-Italic.woff2 | Bin 0 -> 40404 bytes
.../share/fonts/BerkeleyMonoVariable-Regular.ttf | Bin 0 -> 101012 bytes
.../share/fonts/BerkeleyMonoVariable-Regular.woff | Bin 0 -> 45572 bytes
.../share/fonts/BerkeleyMonoVariable-Regular.woff2 | Bin 0 -> 37568 bytes
.../.local/share/fonts/CartographCF-Bold.otf | Bin 0 -> 112176 bytes
.../.local/share/fonts/CartographCF-BoldItalic.otf | Bin 0 -> 120580 bytes
.../.local/share/fonts/CartographCF-DemiBold.otf | Bin 0 -> 110188 bytes
.../share/fonts/CartographCF-DemiBoldItalic.otf | Bin 0 -> 121136 bytes
.../.local/share/fonts/CartographCF-ExtraBold.otf | Bin 0 -> 111532 bytes
.../share/fonts/CartographCF-ExtraBoldItalic.otf | Bin 0 -> 120080 bytes
.../.local/share/fonts/CartographCF-ExtraLight.otf | Bin 0 -> 108024 bytes
.../share/fonts/CartographCF-ExtraLightItalic.otf | Bin 0 -> 118496 bytes
.../.local/share/fonts/CartographCF-Heavy.otf | Bin 0 -> 113700 bytes
.../share/fonts/CartographCF-HeavyItalic.otf | Bin 0 -> 123024 bytes
.../.local/share/fonts/CartographCF-Light.otf | Bin 0 -> 108372 bytes
.../share/fonts/CartographCF-LightItalic.otf | Bin 0 -> 118272 bytes
.../.local/share/fonts/CartographCF-Regular.otf | Bin 0 -> 107884 bytes
.../share/fonts/CartographCF-RegularItalic.otf | Bin 0 -> 117452 bytes
.../.local/share/fonts/CartographCF-Thin.otf | Bin 0 -> 105640 bytes
.../.local/share/fonts/CartographCF-ThinItalic.otf | Bin 0 -> 116284 bytes
.../.local/share/fonts/CodeliaLigatures-Bold.otf | Bin 0 -> 118064 bytes
.../share/fonts/CodeliaLigatures-BoldItalic.otf | Bin 0 -> 117860 bytes
.../.local/share/fonts/CodeliaLigatures-Italic.otf | Bin 0 -> 114384 bytes
.../share/fonts/CodeliaLigatures-Regular.otf | Bin 0 -> 116684 bytes
.../.local/share/fonts/ComicCodeLigatures-Bold.otf | Bin 0 -> 92016 bytes
.../share/fonts/ComicCodeLigatures-BoldItalic.otf | Bin 0 -> 92188 bytes
.../share/fonts/ComicCodeLigatures-Italic.otf | Bin 0 -> 95120 bytes
.../share/fonts/ComicCodeLigatures-Light.otf | Bin 0 -> 95088 bytes
.../share/fonts/ComicCodeLigatures-LightItalic.otf | Bin 0 -> 96648 bytes
.../share/fonts/ComicCodeLigatures-Medium.otf | Bin 0 -> 94056 bytes
.../fonts/ComicCodeLigatures-MediumItalic.otf | Bin 0 -> 95256 bytes
.../share/fonts/ComicCodeLigatures-Regular.otf | Bin 0 -> 93004 bytes
.../share/fonts/ComicCodeLigatures-SBIta.otf | Bin 0 -> 96380 bytes
.../share/fonts/ComicCodeLigatures-Semibold.otf | Bin 0 -> 94744 bytes
.../.local/share/fonts/ComicCodeLigatures-Thin.otf | Bin 0 -> 89560 bytes
.../share/fonts/ComicCodeLigatures-ThinItalic.otf | Bin 0 -> 90044 bytes
.../share/fonts/ComicCodeLigatures-ULIta.otf | Bin 0 -> 97832 bytes
.../share/fonts/ComicCodeLigatures-UltraLight.otf | Bin 0 -> 95728 bytes
.../share/fonts/Courier 10 Pitch Regular.otf | Bin 0 -> 35196 bytes
dotfiles/system/.local/share/fonts/MERIFONT.TTF | Bin 0 -> 49504 bytes
.../.local/share/fonts/Merriweather-Black.ttf | Bin 0 -> 141700 bytes
.../share/fonts/Merriweather-BlackItalic.ttf | Bin 0 -> 142620 bytes
.../.local/share/fonts/Merriweather-Bold.ttf | Bin 0 -> 142040 bytes
.../.local/share/fonts/Merriweather-BoldItalic.ttf | Bin 0 -> 143832 bytes
.../.local/share/fonts/Merriweather-Italic.ttf | Bin 0 -> 142648 bytes
.../.local/share/fonts/Merriweather-Light.ttf | Bin 0 -> 148124 bytes
.../share/fonts/Merriweather-LightItalic.ttf | Bin 0 -> 142056 bytes
.../.local/share/fonts/Merriweather-Regular.ttf | Bin 0 -> 149120 bytes
.../system/.local/share/fonts/MonoLisa-Bold.otf | Bin 0 -> 118736 bytes
.../.local/share/fonts/MonoLisa-BoldItalic.otf | Bin 0 -> 119056 bytes
.../system/.local/share/fonts/MonoLisa-Regular.otf | Bin 0 -> 116244 bytes
.../.local/share/fonts/MonoLisa-RegularItalic.otf | Bin 0 -> 116892 bytes
.../.local/share/fonts/NovaletraSerifCF-Bold.otf | Bin 0 -> 64528 bytes
.../share/fonts/NovaletraSerifCF-BoldItalic.otf | Bin 0 -> 66320 bytes
.../share/fonts/NovaletraSerifCF-DemiBold.otf | Bin 0 -> 64428 bytes
.../fonts/NovaletraSerifCF-DemiBoldItalic.otf | Bin 0 -> 65864 bytes
.../share/fonts/NovaletraSerifCF-ExtBold.otf | Bin 0 -> 63976 bytes
.../share/fonts/NovaletraSerifCF-ExtBoldItalic.otf | Bin 0 -> 65820 bytes
.../.local/share/fonts/NovaletraSerifCF-Heavy.otf | Bin 0 -> 63884 bytes
.../share/fonts/NovaletraSerifCF-HeavyItalic.otf | Bin 0 -> 66264 bytes
.../.local/share/fonts/NovaletraSerifCF-Light.otf | Bin 0 -> 64140 bytes
.../share/fonts/NovaletraSerifCF-LightItalic.otf | Bin 0 -> 66732 bytes
.../.local/share/fonts/NovaletraSerifCF-Medium.otf | Bin 0 -> 64364 bytes
.../share/fonts/NovaletraSerifCF-MediumItalic.otf | Bin 0 -> 66216 bytes
.../share/fonts/NovaletraSerifCF-Regular.otf | Bin 0 -> 64392 bytes
.../share/fonts/NovaletraSerifCF-RegularItalic.otf | Bin 0 -> 66240 bytes
.../system/.local/share/fonts/PragmataProB_09.ttf | Bin 0 -> 4607292 bytes
.../.local/share/fonts/PragmataProB_liga_09.ttf | Bin 0 -> 4624112 bytes
.../system/.local/share/fonts/PragmataProI_09.ttf | Bin 0 -> 4490692 bytes
.../.local/share/fonts/PragmataProI_liga_09.ttf | Bin 0 -> 4507588 bytes
.../system/.local/share/fonts/PragmataProR_09.ttf | Bin 0 -> 5051440 bytes
.../.local/share/fonts/PragmataProR_liga_09.ttf | Bin 0 -> 5068260 bytes
.../system/.local/share/fonts/PragmataProZ_09.ttf | Bin 0 -> 4439452 bytes
.../.local/share/fonts/PragmataProZ_liga_09.ttf | Bin 0 -> 4456268 bytes
.../.local/share/fonts/PragmataPro_Mono_B_09.ttf | Bin 0 -> 3900260 bytes
.../share/fonts/PragmataPro_Mono_B_liga_09.ttf | Bin 0 -> 3917076 bytes
.../.local/share/fonts/PragmataPro_Mono_I_09.ttf | Bin 0 -> 3822344 bytes
.../share/fonts/PragmataPro_Mono_I_liga_09.ttf | Bin 0 -> 3839140 bytes
.../.local/share/fonts/PragmataPro_Mono_R_09.ttf | Bin 0 -> 4233500 bytes
.../share/fonts/PragmataPro_Mono_R_liga_09.ttf | Bin 0 -> 4250320 bytes
.../.local/share/fonts/PragmataPro_Mono_Z_09.ttf | Bin 0 -> 3752628 bytes
.../share/fonts/PragmataPro_Mono_Z_liga_09.ttf | Bin 0 -> 3769444 bytes
.../system/.local/share/fonts/all-the-icons.ttf | Bin 0 -> 44732 bytes
.../system/.local/share/rhythmbox/playlists.xml | 179 +
.../.local/share/rhythmbox/podcast-timestamp | 0
.../system/.local/share/rhythmbox/rhythmdb.xml | 701604 ++++++++++++++++++
.../share/thequestionconcerningtechnology.txt | 180 +
dotfiles/system/.mbsyncrc | 144 +
dotfiles/system/.msmtprc | 38 +
dotfiles/system/.profile | 137 +
dotfiles/system/.profile.d/arch-linux-downgrade.sh | 50 +
dotfiles/system/.profile.d/auto-tmux-session.sh | 12 +
dotfiles/system/.profile.d/chronographic.sh | 120 +
dotfiles/system/.profile.d/compress.sh | 75 +
dotfiles/system/.profile.d/dd.sh | 19 +
dotfiles/system/.profile.d/display.sh | 14 +
dotfiles/system/.profile.d/emacs.sh | 33 +
dotfiles/system/.profile.d/extract.sh | 27 +
dotfiles/system/.profile.d/framework.sh | 10 +
dotfiles/system/.profile.d/freebsd.sh | 10 +
dotfiles/system/.profile.d/fzf.sh | 123 +
dotfiles/system/.profile.d/git.sh | 24 +
dotfiles/system/.profile.d/media.sh | 41 +
dotfiles/system/.profile.d/zoxide.sh | 11 +
dotfiles/system/.ssh/config | 8 +
dotfiles/system/.ssh/decrypt_ssh | 1 +
dotfiles/system/.ssh/set_perms | 7 +
dotfiles/system/.ssh/ssh.tar.gz.gpg | Bin 0 -> 543 bytes
.../dic/dictd_www.dict.org_web1913.dict.dz | Bin 0 -> 27330569 bytes
.../.stardict/dic/dictd_www.dict.org_web1913.idx | Bin 0 -> 3024035 bytes
.../dic/dictd_www.dict.org_web1913.idx.oft | Bin 0 -> 20062 bytes
.../.stardict/dic/dictd_www.dict.org_web1913.ifo | 8 +
dotfiles/system/.stow-global-ignore | 2 +
dotfiles/system/.ticker.yaml | 10 +
dotfiles/system/.tmux.conf | 88 +
dotfiles/system/.vale.ini | 8 +
dotfiles/system/.wegorc | 61 +
dotfiles/system/.xinitrc | 75 +
dotfiles/system/.xscreensaver | 301 +
dotfiles/system/.zsh/README.md | 138 +
dotfiles/system/.zsh/fzf-tab.zsh | 389 +
dotfiles/system/.zsh/lib/-ftb-colorize | 34 +
dotfiles/system/.zsh/lib/-ftb-fzf | 102 +
dotfiles/system/.zsh/lib/-ftb-generate-complist | 113 +
dotfiles/system/.zsh/lib/-ftb-generate-header | 35 +
dotfiles/system/.zsh/lib/-ftb-generate-query | 36 +
dotfiles/system/.zsh/lib/ftb-switch-group | 38 +
dotfiles/system/.zsh/lib/ftb-tmux-popup | 83 +
dotfiles/system/.zsh/lib/zsh-ls-colors/LICENSE | 21 +
dotfiles/system/.zsh/lib/zsh-ls-colors/README.md | 114 +
dotfiles/system/.zsh/lib/zsh-ls-colors/demo | 65 +
.../system/.zsh/lib/zsh-ls-colors/ls-colors.zsh | 186 +
dotfiles/system/.zsh/modules/.cvsignore | 16 +
dotfiles/system/.zsh/modules/.distfiles | 4 +
dotfiles/system/.zsh/modules/.editorconfig | 15 +
dotfiles/system/.zsh/modules/.gitignore | 155 +
dotfiles/system/.zsh/modules/.preconfig | 7 +
dotfiles/system/.zsh/modules/Config/.cvsignore | 2 +
dotfiles/system/.zsh/modules/Config/.distfiles | 2 +
dotfiles/system/.zsh/modules/Config/aczshoot.m4 | 8 +
dotfiles/system/.zsh/modules/Config/clean.mk | 43 +
dotfiles/system/.zsh/modules/Config/config.mk | 42 +
dotfiles/system/.zsh/modules/Config/defs.mk.in | 114 +
dotfiles/system/.zsh/modules/Config/installfns.sh | 74 +
.../system/.zsh/modules/Config/uninstallfns.sh | 59 +
dotfiles/system/.zsh/modules/Config/version.mk | 31 +
dotfiles/system/.zsh/modules/LICENCE | 37 +
dotfiles/system/.zsh/modules/Makefile.in | 87 +
dotfiles/system/.zsh/modules/RECOMPILE_REQUEST | 1 +
dotfiles/system/.zsh/modules/Src/.cvsignore | 35 +
dotfiles/system/.zsh/modules/Src/.distfiles | 2 +
dotfiles/system/.zsh/modules/Src/.exrc | 2 +
dotfiles/system/.zsh/modules/Src/.indent.pro | 27 +
dotfiles/system/.zsh/modules/Src/Makefile.in | 164 +
dotfiles/system/.zsh/modules/Src/Makemod.in.in | 192 +
dotfiles/system/.zsh/modules/Src/aloxaf/.cvsignore | 18 +
dotfiles/system/.zsh/modules/Src/aloxaf/.distfiles | 2 +
dotfiles/system/.zsh/modules/Src/aloxaf/.exrc | 2 +
dotfiles/system/.zsh/modules/Src/aloxaf/.gitignore | 8 +
dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.c | 546 +
dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.mdd | 7 +
dotfiles/system/.zsh/modules/Src/builtin.c | 7236 +
dotfiles/system/.zsh/modules/Src/compat.c | 742 +
dotfiles/system/.zsh/modules/Src/exec.c | 6250 +
dotfiles/system/.zsh/modules/Src/glob.c | 3913 +
dotfiles/system/.zsh/modules/Src/hashtable.c | 1617 +
dotfiles/system/.zsh/modules/Src/hashtable.h | 69 +
dotfiles/system/.zsh/modules/Src/init.c | 1792 +
dotfiles/system/.zsh/modules/Src/input.c | 701 +
dotfiles/system/.zsh/modules/Src/jobs.c | 2894 +
dotfiles/system/.zsh/modules/Src/lex.c | 2203 +
dotfiles/system/.zsh/modules/Src/loop.c | 795 +
dotfiles/system/.zsh/modules/Src/makepro.awk | 166 +
dotfiles/system/.zsh/modules/Src/mem.c | 1899 +
dotfiles/system/.zsh/modules/Src/mkbltnmlst.sh | 116 +
dotfiles/system/.zsh/modules/Src/mkmakemod.sh | 468 +
dotfiles/system/.zsh/modules/Src/module.c | 3641 +
dotfiles/system/.zsh/modules/Src/options.c | 955 +
dotfiles/system/.zsh/modules/Src/params.c | 5884 +
dotfiles/system/.zsh/modules/Src/parse.c | 3977 +
dotfiles/system/.zsh/modules/Src/pattern.c | 4336 +
dotfiles/system/.zsh/modules/Src/prompt.c | 2046 +
dotfiles/system/.zsh/modules/Src/prototypes.h | 134 +
dotfiles/system/.zsh/modules/Src/signals.c | 1479 +
dotfiles/system/.zsh/modules/Src/signals.h | 142 +
dotfiles/system/.zsh/modules/Src/signames1.awk | 19 +
dotfiles/system/.zsh/modules/Src/signames2.awk | 106 +
dotfiles/system/.zsh/modules/Src/string.c | 213 +
dotfiles/system/.zsh/modules/Src/utils.c | 7520 +
dotfiles/system/.zsh/modules/Src/wcwidth9.h | 1325 +
dotfiles/system/.zsh/modules/Src/zsh.h | 3305 +
dotfiles/system/.zsh/modules/Src/zsh.mdd | 147 +
dotfiles/system/.zsh/modules/Src/zsh.rc | 8 +
dotfiles/system/.zsh/modules/Src/zsh_system.h | 900 +
dotfiles/system/.zsh/modules/Src/ztype.h | 89 +
dotfiles/system/.zsh/modules/Test/.cvsignore | 3 +
dotfiles/system/.zsh/modules/Test/.distfiles | 2 +
dotfiles/system/.zsh/modules/Test/A01grammar.ztst | 790 +
dotfiles/system/.zsh/modules/Test/A02alias.ztst | 139 +
dotfiles/system/.zsh/modules/Test/A03quoting.ztst | 80 +
dotfiles/system/.zsh/modules/Test/A04redirect.ztst | 588 +
.../system/.zsh/modules/Test/A05execution.ztst | 312 +
dotfiles/system/.zsh/modules/Test/A06assign.ztst | 631 +
dotfiles/system/.zsh/modules/Test/A07control.ztst | 165 +
dotfiles/system/.zsh/modules/Test/B01cd.ztst | 144 +
dotfiles/system/.zsh/modules/Test/B02typeset.ztst | 723 +
dotfiles/system/.zsh/modules/Test/B03print.ztst | 336 +
dotfiles/system/.zsh/modules/Test/B04read.ztst | 112 +
dotfiles/system/.zsh/modules/Test/B05eval.ztst | 34 +
dotfiles/system/.zsh/modules/Test/B06fc.ztst | 25 +
dotfiles/system/.zsh/modules/Test/B07emulate.ztst | 253 +
dotfiles/system/.zsh/modules/Test/B08shift.ztst | 33 +
dotfiles/system/.zsh/modules/Test/B09hash.ztst | 79 +
dotfiles/system/.zsh/modules/Test/C01arith.ztst | 422 +
dotfiles/system/.zsh/modules/Test/C02cond.ztst | 448 +
dotfiles/system/.zsh/modules/Test/C03traps.ztst | 761 +
dotfiles/system/.zsh/modules/Test/C04funcdef.ztst | 502 +
dotfiles/system/.zsh/modules/Test/C05debug.ztst | 159 +
dotfiles/system/.zsh/modules/Test/D01prompt.ztst | 203 +
dotfiles/system/.zsh/modules/Test/D02glob.ztst | 688 +
.../system/.zsh/modules/Test/D03procsubst.ztst | 151 +
.../system/.zsh/modules/Test/D04parameter.ztst | 2058 +
dotfiles/system/.zsh/modules/Test/D05array.ztst | 112 +
.../system/.zsh/modules/Test/D06subscript.ztst | 268 +
.../system/.zsh/modules/Test/D07multibyte.ztst | 587 +
dotfiles/system/.zsh/modules/Test/D08cmdsubst.ztst | 169 +
dotfiles/system/.zsh/modules/Test/D09brace.ztst | 114 +
dotfiles/system/.zsh/modules/Test/E01options.ztst | 1313 +
dotfiles/system/.zsh/modules/Test/E02xtrace.ztst | 148 +
dotfiles/system/.zsh/modules/Test/Makefile.in | 75 +
dotfiles/system/.zsh/modules/Test/README | 30 +
.../system/.zsh/modules/Test/V02zregexparse.ztst | 382 +
dotfiles/system/.zsh/modules/Test/V03mathfunc.ztst | 141 +
dotfiles/system/.zsh/modules/Test/V04features.ztst | 172 +
dotfiles/system/.zsh/modules/Test/V05styles.ztst | 143 +
dotfiles/system/.zsh/modules/Test/V07pcre.ztst | 139 +
dotfiles/system/.zsh/modules/Test/V08zpty.ztst | 29 +
dotfiles/system/.zsh/modules/Test/V09datetime.ztst | 74 +
dotfiles/system/.zsh/modules/Test/V10private.ztst | 304 +
dotfiles/system/.zsh/modules/Test/W01history.ztst | 60 +
dotfiles/system/.zsh/modules/Test/comptest | 177 +
dotfiles/system/.zsh/modules/Test/runtests.zsh | 27 +
dotfiles/system/.zsh/modules/Test/ztst.zsh | 547 +
dotfiles/system/.zsh/modules/aclocal.m4 | 77 +
dotfiles/system/.zsh/modules/aczsh.m4 | 690 +
dotfiles/system/.zsh/modules/config.guess | 1501 +
dotfiles/system/.zsh/modules/config.h.in | 1242 +
dotfiles/system/.zsh/modules/config.sub | 1705 +
dotfiles/system/.zsh/modules/configure | 14547 +
dotfiles/system/.zsh/modules/configure.ac | 3213 +
dotfiles/system/.zsh/modules/copy_from_zsh_src.zsh | 29 +
dotfiles/system/.zsh/modules/install-sh | 507 +
dotfiles/system/.zsh/modules/mkinstalldirs | 162 +
dotfiles/system/.zsh/modules/patch_cfgac.diff | 73 +
dotfiles/system/.zsh/modules/stamp-h.in | 1 +
dotfiles/system/.zshrc | 179 +
.../bazzite-fractal-save-desktop.sd.tar.gz | Bin 0 -> 1387318 bytes
.../system/documents/bazzite-fractal.sd.tar.gz | Bin 0 -> 1415141 bytes
dotfiles/system/music/60s Sounds.m3u | 3 +
dotfiles/system/music/90s Sounds.m3u | 3 +
dotfiles/system/music/Ambient Sleeping Pill.m3u | 9 +
dotfiles/system/music/BAGeL Radio.m3u | 1 +
dotfiles/system/music/BBC World Service.m3u | 41 +
dotfiles/system/music/Baroque Sonatas.m3u | 5 +
.../system/music/Black Flamingos - Space Bar.m4a | Bin 0 -> 3819479 bytes
dotfiles/system/music/Blues Radio.m3u | 1 +
dotfiles/system/music/Dark Ambient.m3u | 9 +
dotfiles/system/music/Flux FM Radio.m3u | 3 +
dotfiles/system/music/Jazz Radio Happy Hour.m3u | 5 +
dotfiles/system/music/Jazz Radio Latin Jazz.m3u | 5 +
dotfiles/system/music/Jazz Radio New Orleans.m3u | 1 +
dotfiles/system/music/Jazz Radio Only Women.m3u | 5 +
.../Malvern Radio International Classical.m3u | 5 +
dotfiles/system/music/NPR 24 Hour Radio.m3u | 9 +
.../system/music/Radio Caprice Acoustic Blues.m3u | 1 +
dotfiles/system/music/Radio Caprice Breakbeat.m3u | 1 +
.../music/Radio Caprice Classical Baroque.m3u | 1 +
.../system/music/Radio Caprice Classical Cello.m3u | 1 +
.../Radio Caprice Classical Impressionism.m3u | 1 +
.../system/music/Radio Caprice Classical Lute.m3u | 1 +
.../music/Radio Caprice Classical Medieval.m3u | 1 +
.../system/music/Radio Caprice Classical Piano.m3u | 1 +
.../music/Radio Caprice Classical Renaissance.m3u | 1 +
.../music/Radio Caprice Classical Sonata.m3u | 1 +
.../music/Radio Caprice Classical Strings.m3u | 1 +
.../music/Radio Caprice Classical Violin.m3u | 1 +
.../system/music/Radio Caprice Delta Blues.m3u | 5 +
dotfiles/system/music/Radio Caprice Downtempo.m3u | 1 +
dotfiles/system/music/Radio Caprice Dubstep.m3u | 1 +
.../music/Radio Caprice Gregorian Chants.m3u | 1 +
.../system/music/Radio Caprice Hardcore Punk.m3u | 1 +
dotfiles/system/music/Radio Caprice Jazz Bebop.m3u | 1 +
dotfiles/system/music/Radio Caprice Jazz Rap.m3u | 1 +
.../music/Radio Caprice Old School Hip-Hop.m3u | 1 +
dotfiles/system/music/Radio Caprice Oldies.m3u | 1 +
.../system/music/Radio Caprice Reggae Roots.m3u | 1 +
dotfiles/system/music/Radio Caprice Reggae.m3u | 1 +
.../system/music/Radio Caprice Street Punk.m3u | 1 +
dotfiles/system/music/Radio Caprice Trip Hop.m3u | 4 +
.../system/music/Radio Swiss Classic French.m3u | 9 +
.../system/music/Radio Swiss Classic German.m3u | 9 +
.../system/music/Radio Swiss Classic Italian.m3u | 13 +
dotfiles/system/music/SomaFM Beat Blender.m3u | 6 +
dotfiles/system/music/SomaFM Black Rock FM.m3u | 4 +
dotfiles/system/music/SomaFM Boot Liquor.m3u | 4 +
dotfiles/system/music/SomaFM Cliqhop IDM.m3u | 4 +
dotfiles/system/music/SomaFM Dark Zone.m3u | 4 +
dotfiles/system/music/SomaFM Deep Space One.m3u | 4 +
dotfiles/system/music/SomaFM Digitalis.m3u | 4 +
dotfiles/system/music/SomaFM Drone Zone.m3u | 5 +
dotfiles/system/music/SomaFM Dub Step Beyond.m3u | 4 +
dotfiles/system/music/SomaFM Fluid.m3u | 4 +
dotfiles/system/music/SomaFM Folk Forward.m3u | 4 +
.../system/music/SomaFM Groove Salad Classic.m3u | 4 +
dotfiles/system/music/SomaFM Groove Salad.m3u | 4 +
.../system/music/SomaFM Heavyweight Reggae.m3u | 4 +
.../system/music/SomaFM Illinois Street Lounge.m3u | 4 +
dotfiles/system/music/SomaFM Indie Pop Rocks.m3u | 4 +
dotfiles/system/music/SomaFM PopTron.m3u | 4 +
dotfiles/system/music/SomaFM Secret Agent.m3u | 4 +
dotfiles/system/music/SomaFM Sonic Universe.m3u | 4 +
.../system/music/SomaFM Space Station Soma.m3u | 4 +
dotfiles/system/music/SomaFM Suburbs of Goa.m3u | 4 +
dotfiles/system/music/SomaFM Synphaera.m3u | 4 +
dotfiles/system/music/SomaFM The Trip.m3u | 4 +
dotfiles/system/music/SomaFM Underground 80s.m3u | 5 +
dotfiles/system/music/SomaFM Vaporwaves.m3u | 4 +
dotfiles/system/music/SomaFM n5MD Radio.m3u | 4 +
dotfiles/system/music/Sunday Baroque.m3u | 1 +
dotfiles/system/music/Surf Rock.m3u | 37 +
dotfiles/system/music/Venice Classic Radio.m3u | 1 +
dotfiles/system/music/WWNO.m3u | 13 +
dotfiles/system/music/WWOZ New Orleans.m3u | 4 +
dotfiles/system/pictures/cjennings.jpg | Bin 0 -> 31438 bytes
dotfiles/system/pictures/devilman.jpg | Bin 0 -> 41707 bytes
.../pictures/wallpaper/Trolley-New-Orleans.jpg | Bin 0 -> 287530 bytes
.../a-sexy-curvy-beautiful-bottom_earth.jpg | Bin 0 -> 176441 bytes
.../system/pictures/wallpaper/atari-canyon.png | Bin 0 -> 6054214 bytes
.../barrelled_teahupoo-french-polynesia.jpg | Bin 0 -> 739550 bytes
.../beatles_st_pancras_old_church_1968.jpg | Bin 0 -> 80887 bytes
.../wallpaper/big-j-mcneely-los-angeles-1951.jpg | Bin 0 -> 233590 bytes
dotfiles/system/pictures/wallpaper/black.jpg | Bin 0 -> 25537 bytes
.../wallpaper/blues-brothers-and-hooker.png | Bin 0 -> 641584 bytes
.../bondi-beach-new-south-wales-australia.jpg | Bin 0 -> 197164 bytes
.../pictures/wallpaper/capetown-south-africa.jpg | Bin 0 -> 6793104 bytes
.../system/pictures/wallpaper/dagerotips-wheel.jpg | Bin 0 -> 992931 bytes
.../wallpaper/damrak-amsterdam-netherlands.jpg | Bin 0 -> 1520134 bytes
dotfiles/system/pictures/wallpaper/dark-lion.jpg | Bin 0 -> 419505 bytes
dotfiles/system/pictures/wallpaper/doe.jpg | Bin 0 -> 633911 bytes
.../system/pictures/wallpaper/dolomites-italy.jpg | Bin 0 -> 551757 bytes
.../duckdive_teahupoo-french-polynesia.jpg | Bin 0 -> 557884 bytes
.../wallpaper/eltz-castle-wiershem-germany.jpg | Bin 0 -> 2517682 bytes
.../pictures/wallpaper/fu_some-field-near-you.jpg | Bin 0 -> 6806375 bytes
.../system/pictures/wallpaper/hawker-sea-fury.jpg | Bin 0 -> 91595 bytes
.../pictures/wallpaper/highway-59-kansas-us.jpg | Bin 0 -> 336799 bytes
.../pictures/wallpaper/it.saves.more.than.text.png | Bin 0 -> 390778 bytes
.../pictures/wallpaper/jack.rabbit.slims.jpeg | Bin 0 -> 138405 bytes
.../wallpaper/jackson-lake-lodge-wyoming-us.jpg | Bin 0 -> 265839 bytes
.../pictures/wallpaper/khan-al-khalili-egypt.jpg | Bin 0 -> 2024124 bytes
.../wallpaper/knockturn-alley-london-uk.jpg | Bin 0 -> 3743760 bytes
.../last-jedis-neighbor_faroe-islands-denmark.jpg | Bin 0 -> 1218998 bytes
.../pictures/wallpaper/loch-muick-scotland-uk.jpg | Bin 0 -> 2069036 bytes
dotfiles/system/pictures/wallpaper/maps.jpg | Bin 0 -> 6982816 bytes
.../wallpaper/napa-valley-california-us.jpg | Bin 0 -> 3582085 bytes
.../pictures/wallpaper/nazar\303\251-portugal.jpg" | Bin 0 -> 573000 bytes
.../pictures/wallpaper/notre-dame-paris-france.jpg | Bin 0 -> 481347 bytes
.../pictures/wallpaper/petit-piton-st-lucia.jpg | Bin 0 -> 1735652 bytes
...ng-room_new-york-public-library-new-york.us.jpg | Bin 0 -> 4385677 bytes
.../saville-dam-barkhamsted-connecticut-us.jpg | Bin 0 -> 2417461 bytes
.../wallpaper/teahupoo-french-polynesia.jpg | Bin 0 -> 391815 bytes
.../pictures/wallpaper/that-one-up-there.jpg | Bin 0 -> 1223366 bytes
.../system/pictures/wallpaper/tlulum-mexico.jpg | Bin 0 -> 877326 bytes
.../trinity-college-library-dublin-ireland-uk.jpg | Bin 0 -> 2663941 bytes
dotfiles/system/pictures/wallpaper/zendopeak.jpg | Bin 0 -> 3195673 bytes
init | 24 +
scripts/create-archiso-zfs.sh | 33 +
scripts/games.sh | 33 +
scripts/gitrepos.sh | 6 +
scripts/post-install.sh | 58 +
scripts/protonmail-bridge.sh | 17 +
scripts/testing/README.org | 508 +
scripts/testing/archinstall-config.json | 117 +
scripts/testing/cleanup-tests.sh | 171 +
scripts/testing/create-base-vm.sh | 173 +
scripts/testing/debug-vm.sh | 133 +
scripts/testing/finalize-base-vm.sh | 31 +
scripts/testing/lib/finalize-base-vm.sh | 21 +
scripts/testing/lib/logging.sh | 151 +
scripts/testing/lib/network-diagnostics.sh | 60 +
scripts/testing/lib/validation.sh | 1080 +
scripts/testing/lib/vm-utils.sh | 321 +
scripts/testing/run-test-baremetal.sh | 310 +
scripts/testing/run-test.sh | 367 +
scripts/testing/setup-testing-env.sh | 191 +
scripts/wip-bootcandy.sh | 17 +
scripts/wireguard-proton.sh | 13 +
scripts/zfs-replicate | 72 +
spec.org | 103 +
todo.org | 966 +
wipedisk | 30 +
782 files changed, 864628 insertions(+)
create mode 100644 .gitignore
create mode 100644 .stignore
create mode 100755 arch-distrobox
create mode 100755 archsetup
create mode 100644 archsetup.conf.example
create mode 100644 assets/2026-01-17-gvfs-smb-feature-request.txt
create mode 100644 assets/2026-01-17-zfs-sanoid-feature-request.txt
create mode 100644 assets/2026-01-19-remove-zfs-scripts-request.md
create mode 100644 assets/2026-01-20-console-display-issues.txt
create mode 100644 assets/2026-01-21-grub-timeout-request.txt
create mode 100644 assets/2026-01-21-syncthing-service-conflict.org
create mode 100644 assets/2026-01-23-avahi-mdns-fixes.org
create mode 100644 assets/dwm.desktop
create mode 100644 assets/security-and-hardening-recommendations.txt
create mode 100644 assets/wireguard/USCALA.conf
create mode 100644 assets/wireguard/USCASF.conf
create mode 100644 assets/wireguard/USDC.conf
create mode 100644 assets/wireguard/USGAAT.conf
create mode 100644 assets/wireguard/USNY.conf
create mode 100644 assets/wireguard/switzerlan-zurich1.conf
create mode 100644 assets/wireguard/switzerlan-zurich2.conf
create mode 100644 dotfiles/.gitignore
create mode 100644 dotfiles/hyprland/.config/gammastep/config.ini
create mode 100644 dotfiles/hyprland/.config/hypr/hypridle.conf
create mode 100644 dotfiles/hyprland/.config/hypr/hyprland.conf
create mode 100644 dotfiles/hyprland/.config/hypr/hyprlock.conf
create mode 100644 dotfiles/hyprland/.config/waybar/config
create mode 100644 dotfiles/hyprland/.config/waybar/style.css
create mode 100644 dotfiles/hyprland/.config/wofi/config
create mode 100644 dotfiles/hyprland/.config/wofi/style.css
create mode 100644 dotfiles/system/.Xmodmap
create mode 100644 dotfiles/system/.Xresources
create mode 100644 dotfiles/system/.authcode
create mode 100644 dotfiles/system/.authinfo.gpg
create mode 100644 dotfiles/system/.bash_logout
create mode 100644 dotfiles/system/.bashrc
create mode 100644 dotfiles/system/.config/.cmailpass.gpg
create mode 100644 dotfiles/system/.config/.gmailpass.gpg
create mode 100644 dotfiles/system/.config/.tidal-dl.json
create mode 100644 dotfiles/system/.config/.tidal-dl.token.json
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/app-internal-state.db
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/early-access-registry.txt
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/colors.scheme.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/console-font.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor-font.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/features.usage.statistics.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/filetypes.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide-features-trainer.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide.general.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/log-categories.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/other.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/settingsSync.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/updates.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/window.state.xml
create mode 100644 dotfiles/system/.config/JetBrains/PyCharmCE2024.1/updatedBrokenPlugins.db
create mode 100644 dotfiles/system/.config/Thunar/accels.scm
create mode 100644 dotfiles/system/.config/Thunar/uca.xml
create mode 100644 dotfiles/system/.config/audacious/QtUi.conf
create mode 100644 dotfiles/system/.config/audacious/config
create mode 100644 dotfiles/system/.config/audacious/playlist-state
create mode 100644 dotfiles/system/.config/audacious/playlists/1000.audpl
create mode 100644 dotfiles/system/.config/audacious/playlists/order
create mode 100644 dotfiles/system/.config/audacious/plugin-registry
create mode 100644 dotfiles/system/.config/calibre/conversion/azw3_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/comic_input.py
create mode 100644 dotfiles/system/.config/calibre/conversion/debug.py
create mode 100644 dotfiles/system/.config/calibre/conversion/docx_input.py
create mode 100644 dotfiles/system/.config/calibre/conversion/docx_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/epub_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/fb2_input.py
create mode 100644 dotfiles/system/.config/calibre/conversion/fb2_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/heuristics.py
create mode 100644 dotfiles/system/.config/calibre/conversion/htmlz_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/kepub_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/look_and_feel.py
create mode 100644 dotfiles/system/.config/calibre/conversion/lrf_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/metadata.py
create mode 100644 dotfiles/system/.config/calibre/conversion/mobi_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/page_setup.py
create mode 100644 dotfiles/system/.config/calibre/conversion/pdb_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/pdf_input.py
create mode 100644 dotfiles/system/.config/calibre/conversion/pdf_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/pmlz_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/rb_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/rtf_input.py
create mode 100644 dotfiles/system/.config/calibre/conversion/search_and_replace.py
create mode 100644 dotfiles/system/.config/calibre/conversion/snb_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/structure_detection.py
create mode 100644 dotfiles/system/.config/calibre/conversion/toc.py
create mode 100644 dotfiles/system/.config/calibre/conversion/txt_input.py
create mode 100644 dotfiles/system/.config/calibre/conversion/txt_output.py
create mode 100644 dotfiles/system/.config/calibre/conversion/txtz_output.py
create mode 100644 dotfiles/system/.config/calibre/custom_recipes/The Economist_1001.recipe
create mode 100644 dotfiles/system/.config/calibre/custom_recipes/The New York Times_1000.recipe
create mode 100644 dotfiles/system/.config/calibre/custom_recipes/index.json
create mode 100644 dotfiles/system/.config/calibre/customize.py.json
create mode 100644 dotfiles/system/.config/calibre/device_drivers_KOBOTOUCH.py.json
create mode 100644 dotfiles/system/.config/calibre/device_drivers_KOBOTOUCHEXTENDED.py.json
create mode 100644 dotfiles/system/.config/calibre/device_drivers_USER_DEFINED.py.json
create mode 100644 dotfiles/system/.config/calibre/fonts/scanner_cache.json
create mode 100644 dotfiles/system/.config/calibre/global.py.json
create mode 100644 dotfiles/system/.config/calibre/gui.py.json
create mode 100644 dotfiles/system/.config/calibre/history.plist
create mode 100644 dotfiles/system/.config/calibre/icons-any.rcc
create mode 100644 dotfiles/system/.config/calibre/icons-dark.rcc
create mode 100644 dotfiles/system/.config/calibre/metadata-sources-cache.json
create mode 100644 dotfiles/system/.config/calibre/metadata_sources/global.json
create mode 100644 dotfiles/system/.config/calibre/mtp_devices.json
create mode 100644 dotfiles/system/.config/calibre/plugins/Apple Books covers.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Barnes & Noble.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Clean Comments.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Extract ISBN.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Favourites Menu.json
create mode 100644 dotfiles/system/.config/calibre/plugins/Favourites Menu.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Find Duplicates.json
create mode 100644 dotfiles/system/.config/calibre/plugins/Find Duplicates.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/KePub Metadata Reader.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/KePub Metadata Writer.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Kindle hi-res covers.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Kobo Metadata.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Kobo Utilities.json
create mode 100644 dotfiles/system/.config/calibre/plugins/Kobo Utilities.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/KoboTouchExtended.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Open With.json
create mode 100644 dotfiles/system/.config/calibre/plugins/Open With.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Reading List.json
create mode 100644 dotfiles/system/.config/calibre/plugins/Reading List.zip
create mode 100644 dotfiles/system/.config/calibre/plugins/Search The Internet.json
create mode 100644 dotfiles/system/.config/calibre/plugins/Wikidata.zip
create mode 100644 dotfiles/system/.config/calibre/save_to_disk.py.json
create mode 100644 dotfiles/system/.config/calibre/scheduler.xml
create mode 100644 dotfiles/system/.config/calibre/server-config.txt
create mode 100644 dotfiles/system/.config/calibre/server-custom-list-template.json
create mode 100644 dotfiles/system/.config/calibre/server-search-the-net.json
create mode 100644 dotfiles/system/.config/calibre/server-users.sqlite
create mode 100644 dotfiles/system/.config/calibre/shortcuts/main.json
create mode 100644 dotfiles/system/.config/calibre/smtp.py.json
create mode 100644 dotfiles/system/.config/calibre/tag-map-rules.json
create mode 100644 dotfiles/system/.config/calibre/viewer-webengine.json
create mode 100644 dotfiles/system/.config/calibre/viewer.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/19f02e8b622152fd5d7c642d30ecac05080ddf3e9e288a22c4f49866ba57c8b2.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/33083ace2855943c7e4d7d188c47051f047f05e84d828fca5e5545396b94f14c.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/5856c3e5aa41dd1b47711fa2b70e5ba9a2f61369f97c7fcc415321753e7c8bea.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/5d4b018509f9383872d23f1c4a0652d20e908edc16409bc7697635a28f96478e.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/6fd06a181469267e9c09d240ef2d3cca061e54ce37143a9e142524f61028cdd9.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/90922c33b4cfd6cdf2f2f462bc5f6e6b0f18bdb829384144fdd13cc3b487deb1.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/ab0b0aa00cc90f53470da2761ea678a4ccacef1f5002917bda43970cd6096b19.json
create mode 100644 dotfiles/system/.config/calibre/viewer/annots/c5a80ad08eb5ae859fefd73672b6a7cddc243254b55897adfdd5671fe7b2aacf.json
create mode 100644 dotfiles/system/.config/conky/conky.conf
create mode 100644 dotfiles/system/.config/dunst/dunstrc
create mode 100644 dotfiles/system/.config/environment.d/envvars.conf
create mode 100644 dotfiles/system/.config/flameshot/flameshot.ini
create mode 100644 dotfiles/system/.config/fontconfig/fonts.conf
create mode 100644 dotfiles/system/.config/ghostty/config
create mode 100644 dotfiles/system/.config/gtk-3.0/gtk.css
create mode 100644 dotfiles/system/.config/gtk-3.0/settings.ini
create mode 100644 dotfiles/system/.config/htop/htoprc
create mode 100755 dotfiles/system/.config/lf/cleaner
create mode 100755 dotfiles/system/.config/lf/draw_img
create mode 100755 dotfiles/system/.config/lf/image
create mode 100644 dotfiles/system/.config/lf/lfrc
create mode 100755 dotfiles/system/.config/lf/preview
create mode 100644 dotfiles/system/.config/mc/panels.ini
create mode 100644 dotfiles/system/.config/mopidy/mopidy.conf
create mode 100644 dotfiles/system/.config/mpd/mpd.conf
create mode 100644 dotfiles/system/.config/mpd/musicpd.conf
create mode 100644 dotfiles/system/.config/mpv/input.conf
create mode 100644 dotfiles/system/.config/mpv/mpv.conf
create mode 100644 dotfiles/system/.config/ncmpcpp/bindings
create mode 100644 dotfiles/system/.config/ncmpcpp/config
create mode 100644 dotfiles/system/.config/nitrogen/bg-saved.cfg
create mode 100644 dotfiles/system/.config/picom.conf
create mode 100644 dotfiles/system/.config/pychess/config
create mode 100644 dotfiles/system/.config/pychess/engines.json
create mode 100644 dotfiles/system/.config/pychess/pydock.xml
create mode 100644 dotfiles/system/.config/qalculate/qalculate-gtk.cfg
create mode 100644 dotfiles/system/.config/qt5ct/qt5ct.conf
create mode 100644 dotfiles/system/.config/ranger/commands.py
create mode 100644 dotfiles/system/.config/ranger/commands_full.py
create mode 100644 dotfiles/system/.config/ranger/rc.conf
create mode 100644 dotfiles/system/.config/ranger/rifle.conf
create mode 100755 dotfiles/system/.config/ranger/scope.sh
create mode 100644 dotfiles/system/.config/redshift.conf
create mode 100644 dotfiles/system/.config/rofi/config.rasi
create mode 100644 dotfiles/system/.config/rofi/themes/rounded-gray-dark.rasi
create mode 100644 dotfiles/system/.config/sublime-merge/Local/License.sublime_license
create mode 100644 dotfiles/system/.config/sxhkd/sxhkdrc
create mode 100644 dotfiles/system/.config/systemd/user/emacs.service
create mode 100644 dotfiles/system/.config/systemd/user/geoclue-agent.service
create mode 100644 dotfiles/system/.config/systemd/user/xdg-desktop-portal-gtk.service.d/environment.conf
create mode 100644 dotfiles/system/.config/tickrs/config.yml
create mode 100644 dotfiles/system/.config/topgrade.toml
create mode 100644 dotfiles/system/.config/touchpad-indicator/touchpad-indicator.conf
create mode 120000 dotfiles/system/.config/transmission-daemon
create mode 100644 dotfiles/system/.config/transmission/settings.json
create mode 100644 dotfiles/system/.config/user-dirs.dirs
create mode 100644 dotfiles/system/.config/user-dirs.locale
create mode 100644 dotfiles/system/.config/youtube-dl/youtube-dl.conf
create mode 100644 dotfiles/system/.config/zathura/zathurarc
create mode 100644 dotfiles/system/.gitconfig
create mode 100644 dotfiles/system/.gitignore
create mode 100644 dotfiles/system/.gnupg/gpg-agent.conf
create mode 100644 dotfiles/system/.gnupg/pinentry-dmenu.conf
create mode 100644 dotfiles/system/.gtkrc-2.0
create mode 100644 dotfiles/system/.hushlogin
create mode 100644 dotfiles/system/.latexmkrc
create mode 100755 dotfiles/system/.local/bin/AAXtoMP3
create mode 100755 dotfiles/system/.local/bin/ai-assistants
create mode 100755 dotfiles/system/.local/bin/airplanemodetoggle
create mode 100755 dotfiles/system/.local/bin/any2flac
create mode 100755 dotfiles/system/.local/bin/any2opus
create mode 100755 dotfiles/system/.local/bin/audioselect
create mode 100755 dotfiles/system/.local/bin/battery_monitor
create mode 100755 dotfiles/system/.local/bin/bookfind
create mode 100755 dotfiles/system/.local/bin/brightness
create mode 100755 dotfiles/system/.local/bin/bsdnet_bounce
create mode 100755 dotfiles/system/.local/bin/build-emacs.sh
create mode 100755 dotfiles/system/.local/bin/build.emacs.aur.sh
create mode 100755 dotfiles/system/.local/bin/calibre-install
create mode 100755 dotfiles/system/.local/bin/clobberall
create mode 100755 dotfiles/system/.local/bin/colorpick
create mode 100644 dotfiles/system/.local/bin/cron/README.md
create mode 100755 dotfiles/system/.local/bin/cron/checkup
create mode 100755 dotfiles/system/.local/bin/cron/crontog
create mode 100755 dotfiles/system/.local/bin/dab
create mode 100755 dotfiles/system/.local/bin/debugemacs
create mode 100755 dotfiles/system/.local/bin/displayselect
create mode 100755 dotfiles/system/.local/bin/dmenuexitmenu
create mode 100755 dotfiles/system/.local/bin/dmenuhandler
create mode 100755 dotfiles/system/.local/bin/dmenumount
create mode 100755 dotfiles/system/.local/bin/dmenumountcifs
create mode 100755 dotfiles/system/.local/bin/dmenurecord
create mode 100755 dotfiles/system/.local/bin/dmenuumount
create mode 100755 dotfiles/system/.local/bin/dmenuunicode
create mode 100755 dotfiles/system/.local/bin/dotfiles_pushall
create mode 100755 dotfiles/system/.local/bin/ec
create mode 100755 dotfiles/system/.local/bin/em
create mode 100755 dotfiles/system/.local/bin/et
create mode 100755 dotfiles/system/.local/bin/exitmenu
create mode 100755 dotfiles/system/.local/bin/extractaudio
create mode 100755 dotfiles/system/.local/bin/get-arch-iso.sh
create mode 100755 dotfiles/system/.local/bin/gitconfig_defaults
create mode 100755 dotfiles/system/.local/bin/gruv
create mode 100755 dotfiles/system/.local/bin/ifinstalled
create mode 100755 dotfiles/system/.local/bin/lfrun
create mode 100755 dotfiles/system/.local/bin/lfub
create mode 100755 dotfiles/system/.local/bin/linkhandler
create mode 100755 dotfiles/system/.local/bin/lkg
create mode 100755 dotfiles/system/.local/bin/lkg_rollback
create mode 100755 dotfiles/system/.local/bin/lsbak
create mode 100755 dotfiles/system/.local/bin/mkplaylist
create mode 100755 dotfiles/system/.local/bin/monitor
create mode 100755 dotfiles/system/.local/bin/mpd_play_yt_stream
create mode 100755 dotfiles/system/.local/bin/msmtp-enqueue.sh
create mode 100755 dotfiles/system/.local/bin/msmtp-listqueue.sh
create mode 100755 dotfiles/system/.local/bin/msmtp-runqueue.sh
create mode 100755 dotfiles/system/.local/bin/open-file-in-eww
create mode 100755 dotfiles/system/.local/bin/opus2mp3
create mode 100755 dotfiles/system/.local/bin/org-capture.sh
create mode 100755 dotfiles/system/.local/bin/project
create mode 100755 dotfiles/system/.local/bin/prompt
create mode 100755 dotfiles/system/.local/bin/protonvpn
create mode 100755 dotfiles/system/.local/bin/ps-mem
create mode 100755 dotfiles/system/.local/bin/recordnow
create mode 100755 dotfiles/system/.local/bin/refresharchkeys
create mode 100755 dotfiles/system/.local/bin/remaps
create mode 100755 dotfiles/system/.local/bin/reset-auth
create mode 100755 dotfiles/system/.local/bin/resetmimetypes
create mode 100755 dotfiles/system/.local/bin/samedir
create mode 100755 dotfiles/system/.local/bin/screenshotmenu
create mode 100755 dotfiles/system/.local/bin/setbg
create mode 100755 dotfiles/system/.local/bin/ssh-createkeys
create mode 100755 dotfiles/system/.local/bin/starth
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-battery
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-clock
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-cpu
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-cpubars
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-disk
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-doppler
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-forecast
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-help-icon
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-internet
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-iplocate
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-kbselect
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-mailbox
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-memory
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-moonphase
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-mpdup
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-music
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-nettraf
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-news
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-pacpackages
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-popupgrade
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-price
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-tasks
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-torrent
create mode 100755 dotfiles/system/.local/bin/statusbar/sb-volume
create mode 100755 dotfiles/system/.local/bin/steam
create mode 100755 dotfiles/system/.local/bin/sudo-update-grub
create mode 100755 dotfiles/system/.local/bin/sysupdate
create mode 100755 dotfiles/system/.local/bin/td-toggle
create mode 100755 dotfiles/system/.local/bin/timezone-change
create mode 100755 dotfiles/system/.local/bin/timezone-set
create mode 100755 dotfiles/system/.local/bin/toggle-touchpad
create mode 100755 dotfiles/system/.local/bin/torwrap
create mode 100755 dotfiles/system/.local/bin/touchpad-indicator-start
create mode 100755 dotfiles/system/.local/bin/transadd
create mode 100755 dotfiles/system/.local/bin/update-backup-repositories
create mode 100755 dotfiles/system/.local/bin/updatemirrors
create mode 100755 dotfiles/system/.local/bin/virtstart
create mode 100755 dotfiles/system/.local/bin/wallsearch
create mode 100755 dotfiles/system/.local/bin/warpinator-start
create mode 100755 dotfiles/system/.local/bin/ytp
create mode 100644 dotfiles/system/.local/share/applications/dwm.desktop
create mode 100644 dotfiles/system/.local/share/applications/emacsclient-mail.desktop
create mode 100644 dotfiles/system/.local/share/applications/file.desktop
create mode 100644 dotfiles/system/.local/share/applications/img.desktop
create mode 100644 dotfiles/system/.local/share/applications/lock-screen.desktop
create mode 100644 dotfiles/system/.local/share/applications/logout.desktop
create mode 100644 dotfiles/system/.local/share/applications/mail.desktop
create mode 100644 dotfiles/system/.local/share/applications/mimeapps.list
create mode 100644 dotfiles/system/.local/share/applications/org-protocol.desktop
create mode 100644 dotfiles/system/.local/share/applications/pdf.desktop
create mode 100644 dotfiles/system/.local/share/applications/reboot.desktop
create mode 100644 dotfiles/system/.local/share/applications/shutdown.desktop
create mode 100644 dotfiles/system/.local/share/applications/suspend.desktop
create mode 100644 dotfiles/system/.local/share/applications/text.desktop
create mode 100644 dotfiles/system/.local/share/applications/torrent.desktop
create mode 100644 dotfiles/system/.local/share/audacious/Skins/2a03.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Adidas2-3.zip
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Blac.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Classic_70's_Marshall_Stack.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Digital_Stereo_73.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Expensive_HI_FI_Sony_2005.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Future_Audio_1.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Modern_Stereo_Amp_2.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Modern_Style_Marshall_Stack.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Nucleo_NLog_v102_.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/PioneerAmp.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/RAZORIX 50 % Uriel - 50% Kript.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/SONY3.WSZ
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Sony MD.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Spy Amp.zip
create mode 100644 dotfiles/system/.local/share/audacious/Skins/SpyAMP Pro Heaven.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/SpyAMP Pro Zeus.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/SpyAMP_professional_edition_mV1.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/SpyAmp-Pro-Heaven.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Steel_Stereo.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/Technoia_ver001.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/base-2.91.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/deviantamp.zip
create mode 100644 dotfiles/system/.local/share/audacious/Skins/iWinamp_V1.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/nadamp.zip
create mode 100644 dotfiles/system/.local/share/audacious/Skins/s_Pioneer.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/satellite.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/sonympfx3lcdv111.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/spyamp.zip
create mode 100644 dotfiles/system/.local/share/audacious/Skins/spyamp_sepia.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/spyampy2k.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/spyampy2k3.wsz
create mode 100644 dotfiles/system/.local/share/audacious/Skins/v2_technics_white_resting_by_johnnyg0.wsz
create mode 100644 dotfiles/system/.local/share/emoji
create mode 100644 dotfiles/system/.local/share/fonts/AppleColorEmoji.ttf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Bold.otf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Bold.ttf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Bold.woff
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Bold.woff2
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-BoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-BoldItalic.ttf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-BoldItalic.woff
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-BoldItalic.woff2
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Italic.otf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Italic.ttf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Italic.woff
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Italic.woff2
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Regular.otf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Regular.ttf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Regular.woff
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMono-Regular.woff2
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMonoVariable-Italic.ttf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMonoVariable-Italic.woff
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMonoVariable-Italic.woff2
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMonoVariable-Regular.ttf
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMonoVariable-Regular.woff
create mode 100644 dotfiles/system/.local/share/fonts/BerkeleyMonoVariable-Regular.woff2
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-Bold.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-BoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-DemiBold.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-DemiBoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-ExtraBold.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-ExtraBoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-ExtraLight.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-ExtraLightItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-Heavy.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-HeavyItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-Light.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-LightItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-Regular.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-RegularItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-Thin.otf
create mode 100644 dotfiles/system/.local/share/fonts/CartographCF-ThinItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CodeliaLigatures-Bold.otf
create mode 100644 dotfiles/system/.local/share/fonts/CodeliaLigatures-BoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CodeliaLigatures-Italic.otf
create mode 100644 dotfiles/system/.local/share/fonts/CodeliaLigatures-Regular.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-Bold.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-BoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-Italic.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-Light.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-LightItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-Medium.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-MediumItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-Regular.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-SBIta.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-Semibold.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-Thin.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-ThinItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-ULIta.otf
create mode 100644 dotfiles/system/.local/share/fonts/ComicCodeLigatures-UltraLight.otf
create mode 100644 dotfiles/system/.local/share/fonts/Courier 10 Pitch Regular.otf
create mode 100644 dotfiles/system/.local/share/fonts/MERIFONT.TTF
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-Black.ttf
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-BlackItalic.ttf
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-Bold.ttf
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-BoldItalic.ttf
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-Italic.ttf
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-Light.ttf
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-LightItalic.ttf
create mode 100644 dotfiles/system/.local/share/fonts/Merriweather-Regular.ttf
create mode 100644 dotfiles/system/.local/share/fonts/MonoLisa-Bold.otf
create mode 100644 dotfiles/system/.local/share/fonts/MonoLisa-BoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/MonoLisa-Regular.otf
create mode 100644 dotfiles/system/.local/share/fonts/MonoLisa-RegularItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-Bold.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-BoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-DemiBold.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-DemiBoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-ExtBold.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-ExtBoldItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-Heavy.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-HeavyItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-Light.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-LightItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-Medium.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-MediumItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-Regular.otf
create mode 100644 dotfiles/system/.local/share/fonts/NovaletraSerifCF-RegularItalic.otf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProB_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProB_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProI_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProI_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProR_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProR_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProZ_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataProZ_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_B_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_B_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_I_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_I_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_R_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_R_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_Z_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/PragmataPro_Mono_Z_liga_09.ttf
create mode 100644 dotfiles/system/.local/share/fonts/all-the-icons.ttf
create mode 100644 dotfiles/system/.local/share/rhythmbox/playlists.xml
create mode 100644 dotfiles/system/.local/share/rhythmbox/podcast-timestamp
create mode 100644 dotfiles/system/.local/share/rhythmbox/rhythmdb.xml
create mode 100644 dotfiles/system/.local/share/thequestionconcerningtechnology.txt
create mode 100644 dotfiles/system/.mbsyncrc
create mode 100644 dotfiles/system/.msmtprc
create mode 100644 dotfiles/system/.profile
create mode 100755 dotfiles/system/.profile.d/arch-linux-downgrade.sh
create mode 100644 dotfiles/system/.profile.d/auto-tmux-session.sh
create mode 100644 dotfiles/system/.profile.d/chronographic.sh
create mode 100644 dotfiles/system/.profile.d/compress.sh
create mode 100644 dotfiles/system/.profile.d/dd.sh
create mode 100644 dotfiles/system/.profile.d/display.sh
create mode 100644 dotfiles/system/.profile.d/emacs.sh
create mode 100644 dotfiles/system/.profile.d/extract.sh
create mode 100644 dotfiles/system/.profile.d/framework.sh
create mode 100644 dotfiles/system/.profile.d/freebsd.sh
create mode 100644 dotfiles/system/.profile.d/fzf.sh
create mode 100644 dotfiles/system/.profile.d/git.sh
create mode 100644 dotfiles/system/.profile.d/media.sh
create mode 100755 dotfiles/system/.profile.d/zoxide.sh
create mode 100644 dotfiles/system/.ssh/config
create mode 100644 dotfiles/system/.ssh/decrypt_ssh
create mode 100644 dotfiles/system/.ssh/set_perms
create mode 100644 dotfiles/system/.ssh/ssh.tar.gz.gpg
create mode 100644 dotfiles/system/.stardict/dic/dictd_www.dict.org_web1913.dict.dz
create mode 100644 dotfiles/system/.stardict/dic/dictd_www.dict.org_web1913.idx
create mode 100644 dotfiles/system/.stardict/dic/dictd_www.dict.org_web1913.idx.oft
create mode 100644 dotfiles/system/.stardict/dic/dictd_www.dict.org_web1913.ifo
create mode 100644 dotfiles/system/.stow-global-ignore
create mode 100644 dotfiles/system/.ticker.yaml
create mode 100644 dotfiles/system/.tmux.conf
create mode 100644 dotfiles/system/.vale.ini
create mode 100644 dotfiles/system/.wegorc
create mode 100755 dotfiles/system/.xinitrc
create mode 100644 dotfiles/system/.xscreensaver
create mode 100644 dotfiles/system/.zsh/README.md
create mode 100644 dotfiles/system/.zsh/fzf-tab.zsh
create mode 100644 dotfiles/system/.zsh/lib/-ftb-colorize
create mode 100755 dotfiles/system/.zsh/lib/-ftb-fzf
create mode 100644 dotfiles/system/.zsh/lib/-ftb-generate-complist
create mode 100644 dotfiles/system/.zsh/lib/-ftb-generate-header
create mode 100644 dotfiles/system/.zsh/lib/-ftb-generate-query
create mode 100644 dotfiles/system/.zsh/lib/ftb-switch-group
create mode 100755 dotfiles/system/.zsh/lib/ftb-tmux-popup
create mode 100644 dotfiles/system/.zsh/lib/zsh-ls-colors/LICENSE
create mode 100644 dotfiles/system/.zsh/lib/zsh-ls-colors/README.md
create mode 100755 dotfiles/system/.zsh/lib/zsh-ls-colors/demo
create mode 100644 dotfiles/system/.zsh/lib/zsh-ls-colors/ls-colors.zsh
create mode 100644 dotfiles/system/.zsh/modules/.cvsignore
create mode 100644 dotfiles/system/.zsh/modules/.distfiles
create mode 100644 dotfiles/system/.zsh/modules/.editorconfig
create mode 100644 dotfiles/system/.zsh/modules/.gitignore
create mode 100755 dotfiles/system/.zsh/modules/.preconfig
create mode 100644 dotfiles/system/.zsh/modules/Config/.cvsignore
create mode 100644 dotfiles/system/.zsh/modules/Config/.distfiles
create mode 100644 dotfiles/system/.zsh/modules/Config/aczshoot.m4
create mode 100644 dotfiles/system/.zsh/modules/Config/clean.mk
create mode 100644 dotfiles/system/.zsh/modules/Config/config.mk
create mode 100644 dotfiles/system/.zsh/modules/Config/defs.mk.in
create mode 100755 dotfiles/system/.zsh/modules/Config/installfns.sh
create mode 100755 dotfiles/system/.zsh/modules/Config/uninstallfns.sh
create mode 100644 dotfiles/system/.zsh/modules/Config/version.mk
create mode 100644 dotfiles/system/.zsh/modules/LICENCE
create mode 100644 dotfiles/system/.zsh/modules/Makefile.in
create mode 100644 dotfiles/system/.zsh/modules/RECOMPILE_REQUEST
create mode 100644 dotfiles/system/.zsh/modules/Src/.cvsignore
create mode 100644 dotfiles/system/.zsh/modules/Src/.distfiles
create mode 100644 dotfiles/system/.zsh/modules/Src/.exrc
create mode 100644 dotfiles/system/.zsh/modules/Src/.indent.pro
create mode 100644 dotfiles/system/.zsh/modules/Src/Makefile.in
create mode 100644 dotfiles/system/.zsh/modules/Src/Makemod.in.in
create mode 100644 dotfiles/system/.zsh/modules/Src/aloxaf/.cvsignore
create mode 100644 dotfiles/system/.zsh/modules/Src/aloxaf/.distfiles
create mode 100644 dotfiles/system/.zsh/modules/Src/aloxaf/.exrc
create mode 100644 dotfiles/system/.zsh/modules/Src/aloxaf/.gitignore
create mode 100644 dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.c
create mode 100644 dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.mdd
create mode 100644 dotfiles/system/.zsh/modules/Src/builtin.c
create mode 100644 dotfiles/system/.zsh/modules/Src/compat.c
create mode 100644 dotfiles/system/.zsh/modules/Src/exec.c
create mode 100644 dotfiles/system/.zsh/modules/Src/glob.c
create mode 100644 dotfiles/system/.zsh/modules/Src/hashtable.c
create mode 100644 dotfiles/system/.zsh/modules/Src/hashtable.h
create mode 100644 dotfiles/system/.zsh/modules/Src/init.c
create mode 100644 dotfiles/system/.zsh/modules/Src/input.c
create mode 100644 dotfiles/system/.zsh/modules/Src/jobs.c
create mode 100644 dotfiles/system/.zsh/modules/Src/lex.c
create mode 100644 dotfiles/system/.zsh/modules/Src/loop.c
create mode 100644 dotfiles/system/.zsh/modules/Src/makepro.awk
create mode 100644 dotfiles/system/.zsh/modules/Src/mem.c
create mode 100644 dotfiles/system/.zsh/modules/Src/mkbltnmlst.sh
create mode 100644 dotfiles/system/.zsh/modules/Src/mkmakemod.sh
create mode 100644 dotfiles/system/.zsh/modules/Src/module.c
create mode 100644 dotfiles/system/.zsh/modules/Src/options.c
create mode 100644 dotfiles/system/.zsh/modules/Src/params.c
create mode 100644 dotfiles/system/.zsh/modules/Src/parse.c
create mode 100644 dotfiles/system/.zsh/modules/Src/pattern.c
create mode 100644 dotfiles/system/.zsh/modules/Src/prompt.c
create mode 100644 dotfiles/system/.zsh/modules/Src/prototypes.h
create mode 100644 dotfiles/system/.zsh/modules/Src/signals.c
create mode 100644 dotfiles/system/.zsh/modules/Src/signals.h
create mode 100644 dotfiles/system/.zsh/modules/Src/signames1.awk
create mode 100644 dotfiles/system/.zsh/modules/Src/signames2.awk
create mode 100644 dotfiles/system/.zsh/modules/Src/string.c
create mode 100644 dotfiles/system/.zsh/modules/Src/utils.c
create mode 100644 dotfiles/system/.zsh/modules/Src/wcwidth9.h
create mode 100644 dotfiles/system/.zsh/modules/Src/zsh.h
create mode 100644 dotfiles/system/.zsh/modules/Src/zsh.mdd
create mode 100644 dotfiles/system/.zsh/modules/Src/zsh.rc
create mode 100644 dotfiles/system/.zsh/modules/Src/zsh_system.h
create mode 100644 dotfiles/system/.zsh/modules/Src/ztype.h
create mode 100644 dotfiles/system/.zsh/modules/Test/.cvsignore
create mode 100644 dotfiles/system/.zsh/modules/Test/.distfiles
create mode 100644 dotfiles/system/.zsh/modules/Test/A01grammar.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/A02alias.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/A03quoting.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/A04redirect.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/A05execution.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/A06assign.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/A07control.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B01cd.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B02typeset.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B03print.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B04read.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B05eval.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B06fc.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B07emulate.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B08shift.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/B09hash.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/C01arith.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/C02cond.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/C03traps.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/C04funcdef.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/C05debug.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D01prompt.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D02glob.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D03procsubst.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D04parameter.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D05array.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D06subscript.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D07multibyte.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D08cmdsubst.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/D09brace.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/E01options.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/E02xtrace.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/Makefile.in
create mode 100644 dotfiles/system/.zsh/modules/Test/README
create mode 100644 dotfiles/system/.zsh/modules/Test/V02zregexparse.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/V03mathfunc.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/V04features.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/V05styles.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/V07pcre.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/V08zpty.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/V09datetime.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/V10private.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/W01history.ztst
create mode 100644 dotfiles/system/.zsh/modules/Test/comptest
create mode 100644 dotfiles/system/.zsh/modules/Test/runtests.zsh
create mode 100755 dotfiles/system/.zsh/modules/Test/ztst.zsh
create mode 100644 dotfiles/system/.zsh/modules/aclocal.m4
create mode 100644 dotfiles/system/.zsh/modules/aczsh.m4
create mode 100755 dotfiles/system/.zsh/modules/config.guess
create mode 100644 dotfiles/system/.zsh/modules/config.h.in
create mode 100755 dotfiles/system/.zsh/modules/config.sub
create mode 100755 dotfiles/system/.zsh/modules/configure
create mode 100644 dotfiles/system/.zsh/modules/configure.ac
create mode 100755 dotfiles/system/.zsh/modules/copy_from_zsh_src.zsh
create mode 100755 dotfiles/system/.zsh/modules/install-sh
create mode 100755 dotfiles/system/.zsh/modules/mkinstalldirs
create mode 100644 dotfiles/system/.zsh/modules/patch_cfgac.diff
create mode 100644 dotfiles/system/.zsh/modules/stamp-h.in
create mode 100644 dotfiles/system/.zshrc
create mode 100644 dotfiles/system/documents/bazzite-fractal-save-desktop.sd.tar.gz
create mode 100644 dotfiles/system/documents/bazzite-fractal.sd.tar.gz
create mode 100644 dotfiles/system/music/60s Sounds.m3u
create mode 100644 dotfiles/system/music/90s Sounds.m3u
create mode 100644 dotfiles/system/music/Ambient Sleeping Pill.m3u
create mode 100644 dotfiles/system/music/BAGeL Radio.m3u
create mode 100644 dotfiles/system/music/BBC World Service.m3u
create mode 100644 dotfiles/system/music/Baroque Sonatas.m3u
create mode 100644 dotfiles/system/music/Black Flamingos - Space Bar.m4a
create mode 100644 dotfiles/system/music/Blues Radio.m3u
create mode 100644 dotfiles/system/music/Dark Ambient.m3u
create mode 100644 dotfiles/system/music/Flux FM Radio.m3u
create mode 100644 dotfiles/system/music/Jazz Radio Happy Hour.m3u
create mode 100644 dotfiles/system/music/Jazz Radio Latin Jazz.m3u
create mode 100644 dotfiles/system/music/Jazz Radio New Orleans.m3u
create mode 100644 dotfiles/system/music/Jazz Radio Only Women.m3u
create mode 100644 dotfiles/system/music/Malvern Radio International Classical.m3u
create mode 100644 dotfiles/system/music/NPR 24 Hour Radio.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Acoustic Blues.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Breakbeat.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Baroque.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Cello.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Impressionism.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Lute.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Medieval.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Piano.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Renaissance.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Sonata.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Strings.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Classical Violin.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Delta Blues.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Downtempo.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Dubstep.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Gregorian Chants.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Hardcore Punk.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Jazz Bebop.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Jazz Rap.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Old School Hip-Hop.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Oldies.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Reggae Roots.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Reggae.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Street Punk.m3u
create mode 100644 dotfiles/system/music/Radio Caprice Trip Hop.m3u
create mode 100644 dotfiles/system/music/Radio Swiss Classic French.m3u
create mode 100644 dotfiles/system/music/Radio Swiss Classic German.m3u
create mode 100644 dotfiles/system/music/Radio Swiss Classic Italian.m3u
create mode 100644 dotfiles/system/music/SomaFM Beat Blender.m3u
create mode 100644 dotfiles/system/music/SomaFM Black Rock FM.m3u
create mode 100644 dotfiles/system/music/SomaFM Boot Liquor.m3u
create mode 100644 dotfiles/system/music/SomaFM Cliqhop IDM.m3u
create mode 100644 dotfiles/system/music/SomaFM Dark Zone.m3u
create mode 100644 dotfiles/system/music/SomaFM Deep Space One.m3u
create mode 100644 dotfiles/system/music/SomaFM Digitalis.m3u
create mode 100644 dotfiles/system/music/SomaFM Drone Zone.m3u
create mode 100644 dotfiles/system/music/SomaFM Dub Step Beyond.m3u
create mode 100644 dotfiles/system/music/SomaFM Fluid.m3u
create mode 100644 dotfiles/system/music/SomaFM Folk Forward.m3u
create mode 100644 dotfiles/system/music/SomaFM Groove Salad Classic.m3u
create mode 100644 dotfiles/system/music/SomaFM Groove Salad.m3u
create mode 100644 dotfiles/system/music/SomaFM Heavyweight Reggae.m3u
create mode 100644 dotfiles/system/music/SomaFM Illinois Street Lounge.m3u
create mode 100644 dotfiles/system/music/SomaFM Indie Pop Rocks.m3u
create mode 100644 dotfiles/system/music/SomaFM PopTron.m3u
create mode 100644 dotfiles/system/music/SomaFM Secret Agent.m3u
create mode 100644 dotfiles/system/music/SomaFM Sonic Universe.m3u
create mode 100644 dotfiles/system/music/SomaFM Space Station Soma.m3u
create mode 100644 dotfiles/system/music/SomaFM Suburbs of Goa.m3u
create mode 100644 dotfiles/system/music/SomaFM Synphaera.m3u
create mode 100644 dotfiles/system/music/SomaFM The Trip.m3u
create mode 100644 dotfiles/system/music/SomaFM Underground 80s.m3u
create mode 100644 dotfiles/system/music/SomaFM Vaporwaves.m3u
create mode 100644 dotfiles/system/music/SomaFM n5MD Radio.m3u
create mode 100644 dotfiles/system/music/Sunday Baroque.m3u
create mode 100644 dotfiles/system/music/Surf Rock.m3u
create mode 100644 dotfiles/system/music/Venice Classic Radio.m3u
create mode 100644 dotfiles/system/music/WWNO.m3u
create mode 100644 dotfiles/system/music/WWOZ New Orleans.m3u
create mode 100644 dotfiles/system/pictures/cjennings.jpg
create mode 100644 dotfiles/system/pictures/devilman.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/Trolley-New-Orleans.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/a-sexy-curvy-beautiful-bottom_earth.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/atari-canyon.png
create mode 100644 dotfiles/system/pictures/wallpaper/barrelled_teahupoo-french-polynesia.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/beatles_st_pancras_old_church_1968.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/big-j-mcneely-los-angeles-1951.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/black.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/blues-brothers-and-hooker.png
create mode 100644 dotfiles/system/pictures/wallpaper/bondi-beach-new-south-wales-australia.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/capetown-south-africa.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/dagerotips-wheel.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/damrak-amsterdam-netherlands.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/dark-lion.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/doe.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/dolomites-italy.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/duckdive_teahupoo-french-polynesia.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/eltz-castle-wiershem-germany.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/fu_some-field-near-you.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/hawker-sea-fury.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/highway-59-kansas-us.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/it.saves.more.than.text.png
create mode 100644 dotfiles/system/pictures/wallpaper/jack.rabbit.slims.jpeg
create mode 100644 dotfiles/system/pictures/wallpaper/jackson-lake-lodge-wyoming-us.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/khan-al-khalili-egypt.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/knockturn-alley-london-uk.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/last-jedis-neighbor_faroe-islands-denmark.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/loch-muick-scotland-uk.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/maps.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/napa-valley-california-us.jpg
create mode 100644 "dotfiles/system/pictures/wallpaper/nazar\303\251-portugal.jpg"
create mode 100644 dotfiles/system/pictures/wallpaper/notre-dame-paris-france.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/petit-piton-st-lucia.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/reading-room_new-york-public-library-new-york.us.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/saville-dam-barkhamsted-connecticut-us.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/teahupoo-french-polynesia.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/that-one-up-there.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/tlulum-mexico.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/trinity-college-library-dublin-ireland-uk.jpg
create mode 100644 dotfiles/system/pictures/wallpaper/zendopeak.jpg
create mode 100755 init
create mode 100644 scripts/create-archiso-zfs.sh
create mode 100755 scripts/games.sh
create mode 100755 scripts/gitrepos.sh
create mode 100755 scripts/post-install.sh
create mode 100644 scripts/protonmail-bridge.sh
create mode 100644 scripts/testing/README.org
create mode 100644 scripts/testing/archinstall-config.json
create mode 100755 scripts/testing/cleanup-tests.sh
create mode 100755 scripts/testing/create-base-vm.sh
create mode 100755 scripts/testing/debug-vm.sh
create mode 100755 scripts/testing/finalize-base-vm.sh
create mode 100755 scripts/testing/lib/finalize-base-vm.sh
create mode 100755 scripts/testing/lib/logging.sh
create mode 100644 scripts/testing/lib/network-diagnostics.sh
create mode 100644 scripts/testing/lib/validation.sh
create mode 100755 scripts/testing/lib/vm-utils.sh
create mode 100755 scripts/testing/run-test-baremetal.sh
create mode 100755 scripts/testing/run-test.sh
create mode 100755 scripts/testing/setup-testing-env.sh
create mode 100644 scripts/wip-bootcandy.sh
create mode 100755 scripts/wireguard-proton.sh
create mode 100755 scripts/zfs-replicate
create mode 100644 spec.org
create mode 100644 todo.org
create mode 100644 wipedisk
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9a36d8d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+/.vagrant/
+/assets/sounds/
+/docs/
+/inbox/
+
+# VM Testing Infrastructure
+/vm-images/
+/test-results/
+*.qcow2
+*.iso
diff --git a/.stignore b/.stignore
new file mode 100644
index 0000000..6dcf143
--- /dev/null
+++ b/.stignore
@@ -0,0 +1,2 @@
+.git
+vm-images/
diff --git a/arch-distrobox b/arch-distrobox
new file mode 100755
index 0000000..4afe3d1
--- /dev/null
+++ b/arch-distrobox
@@ -0,0 +1,494 @@
+#!/bin/sh
+# ArchDistrobox - Craig Jennings
+# License: GNU GPLv3
+
+# Commentary
+#
+# This script sets up an Arch Linux distrobox setup for software development.
+#
+# There are two levels of errors:
+# * CRASH: Issues that will halt forward progress, aborting this script.
+# * ERROR: Issues not serious enough to halt the script.
+# Both are printed on screen and in the $logfile.
+# Stderr is also printed to the $logfile for all relevant info.
+#
+# # Code
+
+# uncomment to stop on any error
+# set -e
+
+### Constants
+
+username=cjennings
+
+dotfiles_repo="https://git.cjennings.net/dotfiles"
+dotemacs_repo="https://git.cjennings.net/dotemacs.git"
+dotfiles_home="/home/$username/.dotfiles"
+
+# aur/git source directories go here, logs go in base directiory
+source_dir="/home/$username/.local/src"
+logfile="$source_dir/archdistrobox-$(date +'%Y-%m-%d-%H-%M-%S').log"
+packages_before="$source_dir/archdistrobox-preexisting-package-list.txt"
+packages_after="/$source_dir/archdistrobox-post-install-package-list.txt"
+archdistrobox_packages="$source_dir/archdistrobox-installed-packages.txt"
+
+### Intro
+intro() {
+ printf "\n\nArchDistrobox launched @ %s\n" "$(date +'%D %T')"| tee -a "$logfile"
+ STARTTIME=$(date +%s)
+ errors_encountered=0
+
+ # Check that we're not root; error and exit if we are
+ if [ $(id -u) -eq 0 ]; then
+ echo "Please do not run this script as root or using sudo!"
+ exit 1
+ fi
+
+ # on fresh distrobox, $source_dir may not exist yet
+ [ -d "$source_dir" ] || mkdir "$source_dir" ]
+
+ # begin with a clean logfile
+ [ -f "$logfile" ] && rm -f "$logfile"
+ touch "$logfile"
+
+ # count the arch packages before install
+ pacman -Q > "$packages_before" || \
+ error "crash" "generating pre-install package list" "$?"
+}
+
+### General Functions
+# Error
+error () {
+ # $1 = type ERROR, noted but the script will continue to run.
+ # anything else will produce CRASH, which halts the script
+ # $2 = what was happening (e.g., "adding $username to group $groupname")
+ # $3 = the error code (i.e., "$?")
+
+ errors_encountered=$((errors_encountered+1))
+ case "$1" in
+ "error")
+ printf "ERROR: %s failed with error code %s @ %s\n" \
+ "$2" "$3" "$(date +'%T')" | tee -a "$logfile"
+ return 1;
+ ;;
+ *)
+ printf "CRASH: %s failed with error: %s @ %s. Script halted.\n" \
+ "$2" "$3" "$(date +'%T')" | tee -a "$logfile"
+ exit 1;
+ ;;
+ esac
+}
+
+# Display
+display () {
+ # $1 = type (TITLE, ACTION)
+ # $2 = description (answers: "what are you trying to do?")
+
+ case "$1" in
+ "title")
+ printf "\n##### %s\n" "$2" | tee -a "$logfile"
+ return 1;
+ ;;
+ "subtitle")
+ printf "\n%s\n" "$2" | tee -a "$logfile"
+ ;;
+ "task")
+ printf "...%s @ %s\n" "$2" "$(date +'%T')" | tee -a "$logfile"
+ return 1;
+ ;;
+ *)
+ printf "CRASH: display () called with incorrect arguments.\n"
+ printf "...called %s type, %s action @ %s\n" \
+ "$1" "$2" "$(date +'%T')" | tee -a "$logfile"
+ exit 1;
+ ;;
+ esac
+
+}
+
+# Pacman Install
+pacman_install() {
+ action="installing $1 via pacman" && display "task" "$action"
+ if ! (sudo pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1); then
+ action="retrying $1" && display "task" "$action"
+ if ! (sudo pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1); then
+ action="retrying $1 once more" && display "task" "$action"
+ (sudo pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1) ||
+ error "error" "$action" "$?"
+ fi
+ fi
+}
+
+# AUR Install
+aur_install() {
+ action="installing $1 via the AUR" && display "task" "$action"
+ if ! (yay -S --noconfirm "$1" >> "$logfile" 2>&1); then
+ action="retrying $1" && display "task" "$action"
+ if ! (yay -S --noconfirm "$1" >> "$logfile" 2>&1); then
+ action="retrying $1 once more" && display "task" "$action"
+ (yay -S --noconfirm "$1" >> "$logfile" 2>&1) ||
+ error "error" "$action" "$?"
+ fi
+ fi
+}
+
+# PIP Install
+pip_install() {
+ [ -x "$(command -v "pip")" ] || pacman_install python-pip
+ action="installing $1 via PIP" && display "task" "$action"
+ (yes | pip install "$1" >> "$logfile" 2>&1) || \
+ error "error" "$action" "$?"
+}
+
+### Prerequisites
+prerequisites() {
+ # why these software packages are 'required'
+ # base_devel - required tools to compile
+ # coreutils - comparing package lists
+ # curl - to transfer source code
+ # git - tools required to work with git source respositories
+ # go - required to build yay, the aur installer
+ # python - required for python pip installs
+ # stow - places dotfiles (see: https://bit.ly/41GmysO)
+ # tar - extract unix archives
+ # vi - should things go wrong, we'll need an editor
+ # zsh - we need a shell interpreter for yay; this one's mine
+
+ display "title" "Prerequisites"
+
+ # display "subtitle" "Bootstrapping"
+ # action="refreshing the package cache" && display "task" "$action"
+ # (pacman -Syu --noconfirm >> "$logfile" 2>&1) || error "crash" "$action" "$?"
+
+ display "subtitle" "Required Software"
+
+ for software in base-devel coreutils curl git go python \
+ stow tar vi zsh; do
+ pacman_install "$software"
+ done
+}
+
+### User Customizations
+# user_customizations() {
+# Change cjennings's shell to zsh
+chsh -s $(which zsh)
+
+# Pull relevant dotfiles and put them in their proper place
+
+
+# -----------------------------------------------------
+# action="User Customizations" && display "title" "$action"
+
+# action="cloning dotfiles" && display "task" "$action"
+# (git clone --depth 1 $dotfiles_repo "$dotfiles_home" \
+# >> "$logfile" 2>&1) || error "error" "$action" "$?"
+
+# action="moving dotfiles into place" && display "task" "$action"
+# (cd "$dotfiles_home" && stow --no-folding --adopt * \
+# >> "$logfile" 2>&1 ) || error "error" "$action" "$?"
+
+# action="restoring dotfile versions" && display "task" "$action"
+# (cd "$dotfiles_home" && git restore . \
+# >> "$logfile" 2>&1 ) || error "error" "$action" "$?"
+# }
+
+### AUR Installer
+aur_installer () {
+ display "title" "AUR Installer"
+
+ yay_repo="https://aur.archlinux.org/yay.git"
+ build_dir="$source_dir/yay"
+
+ display "task" "creating yay build directory"
+ if ! (mkdir -p "$build_dir" >> "$logfile" 2>&1); then
+ error "crash" "creating yay build directory"
+ fi
+
+ display "task" "fetching source code for yay"
+ if ! (git clone --depth 1 "$yay_repo" "$build_dir" >> "$logfile" 2>&1); then
+ error "error" "cloning source code for yay"
+ (cd "$build_dir" && git pull --force origin master >> "$logfile" 2>&1) || \
+ error "crash" "changing directories to $build_dir and pulling source code" "$?"
+ fi
+
+ action="packaging and installing yay"; display "task" "$action"
+ (cd "$build_dir" && makepkg --noconfirm -si >> "$logfile" 2>&1) || \
+ error "crash" "$action" "$?"
+}
+
+### Desktop Environment
+environment_tools() {
+ display "title" "Environment Tools"
+
+ # System Utilities
+
+ action="System Utilities" && display "subtitle" "$action"
+ pacman_install dmidecode
+ pacman_install dosfstools
+ pacman_install exfat-utils
+ pacman_install lshw
+ pacman_install ntfs-3g
+ pacman_install sshfs
+ pacman_install testdisk
+ pacman_install udisks2
+ aur_install downgrade
+ aur_install inxi
+
+ # File Associations
+
+ action="File/Application Associations" && display "subtitle" "$action"
+ pacman_install perl-file-mimeinfo
+ pacman_install xdg-utils
+
+ # Authentication Tools
+
+ action="Authentication Tools" && display "subtitle" "$action"
+ pacman_install gnupg
+ pacman_install polkit
+ pacman_install gnome-keyring
+
+ # ensure correct permissions on .gpg directory
+ # the colon means the user's group will have perms
+ [ -d /home/"$username"/.gnupg ] || mkdir /home/"$username"/.gnupg
+ chown -R "$username": /home/"$username"/.gnupg
+ find /home/"$username"/.gnupg -type f -exec chmod 600 {} \;
+ find /home/"$username"/.gnupg -type d -exec chmod 700 {} \;
+
+
+ # Command Line Utilities
+
+ action="Command Line Utilities" && display "subtitle" "$action"
+ for software in htop mc ncdu tmux fzf zip unzip atool wget detox \
+ lsof usbutils moreutils; do
+ pacman_install "$software"
+ done;
+
+ for software in task-spooler speedtest-go gotop-bin rar; do
+ aur_install "$software"
+ done;
+
+ # Help And Documentation
+
+ action="Help and Documentation" && display "subtitle" "$action"
+ pacman_install man
+ pacman_install arch-wiki-docs
+ pacman_install tealdeer
+ aur_install cht.sh-git
+}
+
+### Developer Workstation
+developer_workstation () {
+
+ action="Developer Workstation" && display "title" "$action"
+
+ action="Programming Languages and Utilities" && display "subtitle" "$action"
+ # C
+ pacman_install clang # C/C++ compiler
+ pacman_install cmake # make system
+ pacman_install gdb # the GNU debugger
+ pacman_install splint # C programming static analysis
+ pacman_install valgrind # memory management utility
+
+ # java
+ pacman_install jdk-openjdk # Java Development Kit
+ pacman_install openjdk-doc # ...and the documentation
+
+ # Lisps
+ pacman_install guile # GNU Scheme
+ pacman_install sbcl # Steel Bank Common Lisp
+ pacman_install racket # Racket + SICP mit-scheme emulation
+
+ # Rust
+ pacman_install rust # Rust programming language
+
+ # Python
+ pacman_install pyright # Python language server
+ pacman_install pyenv # Python environment manager
+
+ # Shell
+ pacman_install shellcheck # Shell script linter
+ pacman_install shfmt # Shell script formatter
+
+ # Go
+ pacman_install delve # Go programming language debugger
+ pacman_install go-tools # Go language utilities
+ pacman_install gopls # Go language server
+ pacman_install staticcheck # Go programming language linter
+
+ # Typescript
+ pacman_install jq # JSON processor
+ pacman_install typescript # Typescript programming language
+ pacman_install nodejs # Node-js JavaScript runtime environment
+ pacman_install npm # Node-js package manager
+ aur_install nvm # Node-js version manager
+
+ # HTML
+ pacman_install tidy # HTML formatter
+ pacman_install prettier # Multi-language formatter
+
+ # General Utilities
+ pacman_install meld # Visual diff
+ pacman_install ripgrep # Fast grep utility
+ aur_install the_silver_searcher # Another fast grep utility
+
+ action="Programming Editors" && display "subtitle" "$action"
+ pacman_install mg
+ pacman_install neovim
+}
+
+emacs_install() {
+
+ action="Emacs Dependencies" && display "subtitle" "$action"
+ pacman_install emacs
+
+ # supporting utilities used by my emacs configuration
+ aur_install exercism-bin # command line tool for exercism.io
+ aur_install isync # email sync
+ aur_install mu # email indexer and utilities
+ aur_install multimarkdown # markdown conversion
+ aur_install proselint # grammar checker
+ aur_install gist # command-line gist poster
+ pacman_install aspell # spell check system
+ pacman_install aspell-en # spell check english files
+ pacman_install fd # a faster find for dired/dirvish
+ pacman_install ffmpegthumbnailer # video previews in dired/dirvish
+ pacman_install imagemagick # image previews for dired/dirvish
+ pacman_install libgccjit # native compilation for Emacs
+ pacman_install mediainfo # generating media info in dired/dirvish
+ pacman_install mpv # video viewer
+ pacman_install mailutils # Emacs' IMAP mail support backend
+ pacman_install msmtp # mail transport for Mu4e
+ pacman_install msmtp-mta # mail transport for Mu4e
+ pacman_install python-lsp-server # python language support
+ pacman_install rlwrap # adds readline support to programs (SBCL-related)
+ pacman_install sdcv # stardict dictionary system
+ pacman_install yt-dlp # video download
+
+ action="setting up emacs configuration files" && display "task" "$action"
+ (git clone --recurse-submodules $dotemacs_repo "/home/$username/.emacs.d" >> \
+ "$logfile" 2>&1) || error "error" "$action" "$?"
+
+ action="Android Utilities" && display "subtitle" "$action"
+ pacman_install android-file-transfer
+ pacman_install android-tools
+
+ action="DevOps Utilities" && display "subtitle" "$action"
+
+ action="installing devops virtualization and automation tools" && display "task" "$action"
+ pacman_install vagrant
+ pacman_install ansible
+
+ # Docker within Podman!
+
+ pacman_install docker
+ pacman_install docker-compose
+ action="adding user to docker group" && display "task" "$action"
+ (gpasswd -a $username docker >> "$logfile" 2>&1) || error "error" "$action" "$?"
+ action="enabling docker service to launch on boot" && display "task" "$action"
+ systemctl enable docker.service >> "$logfile" 2>&1 || error "error" "$action" "$?"
+}
+
+### Supplemental Software
+supplemental_software() {
+
+ display "title" "Supplemental Software"
+
+ # pacman installs
+ pacman_install aria2 # fast downloader
+ pacman_install code # visual studio code
+ pacman_install dash # posix compliant /bin/sh
+ pacman_install dfc # better display of available space on mounted filesystems
+ pacman_install docx2txt # recovers text from docx files
+ pacman_install entr # run arbitrary commands when files change
+ pacman_install figlet # words into ascii art
+ pacman_install filezilla # ftp gui
+ pacman_install gparted # disk partition utility
+ pacman_install gucharmap # gui display of character maps
+ pacman_install gzip # compression tool
+ pacman_install libconfig # library for processing structured config files
+ pacman_install mosh # alt SSH terminal with roaming and responsiveness support
+ pacman_install neofetch # cli system information tool
+ pacman_install odt2txt # converts from open document to text
+ pacman_install p7zip # p7zip compression tool
+ pacman_install pandoc # universal document converter
+ pacman_install poppler-glib # poppler-glib document viewer library
+ pacman_install pv # monitor progress of data through pipeline
+ pacman_install ranger # terminal file manager
+ pacman_install rclone # syncs files from gdrive, s3, dropbox, etc.
+ pacman_install texlive-meta # latex
+ pacman_install w3m # text based browser
+ pacman_install xz # general purpose data compression tool
+ pacman_install zathura # document viewer
+ pacman_install zathura-cb # zathura plugin for comics
+ pacman_install zathura-djvu # zathura plugin for djvu books
+ pacman_install zathura-pdf-mupdf # zathura plugin for pdf
+ pacman_install zlib # compression library
+
+ # aur installs
+ aur_install dtrx # extraction tool
+ aur_install figlet-fonts # fonts for figlet
+ aur_install hfsprogs # file system tools for Mac OS
+ aur_install mcomix # image viewer for comic books
+ aur_install nsxiv # image viewer
+ aur_install shell-gpt # gpt in your terminal
+ aur_install snore-git # sleep with feedback
+ aur_install tageditor # metadata editor for mkv, webm and related video files
+ aur_install zsh-fast-syntax-highlighting-git # Optimized and extended zsh-syntax-highlighting
+
+ # git installs
+ git_install https://github.com/clamiax/snore.git # sleep with feedback
+}
+
+### Distrobox Exports
+distrobox_exports() {
+ for software in emacs \
+ filezilla \
+ gparted \
+ meld \
+ code ; do
+ distrobox-export --app "$software"
+ done
+}
+
+### Outro
+outro() {
+
+ display "subtitle" "Statistics"
+ action="identifying newly installed packages" && display "task" "$action"
+ pacman -Q > "$packages_after" || error "error" "$action" "$?"
+ (comm -13 --nocheck-order "$packages_before" "$packages_after" > "$archdistrobox_packages") || \
+ error "error" "$action" "$?"
+
+ action="comparing timestamps" && display "task" "$action"
+ ENDTIME=$(date +%s)
+ totalsecs=$(($ENDTIME - $STARTTIME))
+ mins=$(($totalsecs / 60))
+ secs=$(($totalsecs % 60))
+
+ new_packages=$(cat $archdistrobox_packages | wc -l)
+
+ printf "\n"
+ printf "Completion time : %s\n" "$(date +'%D %T')" | tee -a "$logfile"
+ printf "Elapsed time : %s minutes, %s seconds\n" $mins $secs | tee -a "$logfile"
+ printf "Errors encountered : %s\n" $errors_encountered | tee -a "$logfile"
+ printf "Log file location : %s\n" "$logfile"
+ printf "Packages installed : %s\n" "$new_packages"
+ printf "\n"
+ printf "Please reboot before working with your new workstation.\n\n"
+}
+
+### Installation Steps
+intro
+
+prerequisites
+# user_customizations
+aur_installer
+environment_tools
+developer_workstation
+emacs_install
+supplemental_software
+distrobox_exports
+
+outro
+
+exit 0
diff --git a/archsetup b/archsetup
new file mode 100755
index 0000000..c5acb36
--- /dev/null
+++ b/archsetup
@@ -0,0 +1,1914 @@
+#!/bin/bash
+# ArchSetup - Craig Jennings
+# https://cjennings.net/archsetup
+# License: GNU GPLv3
+
+# Commentary
+#
+# There are two levels of errors:
+# * CRASH: Issues that will halt forward progress, aborting this script.
+# * ERROR: Issues not serious enough to halt the script.
+# Both are printed on screen and in the $logfile.
+# Stderr is also printed to the $logfile for all relevant info.
+#
+# This script creates a tmpfs RAM disk for all compilation. This speeds up
+# building and installing, and all source code no longer exists after reboot.
+#
+# # Code
+
+# uncomment to stop on any error
+# set -e
+
+### Root Check
+
+if [ "$EUID" -ne 0 ]; then
+ echo "ERROR: This script must be run as root"
+ echo "Usage: sudo $0"
+ exit 1
+fi
+
+### Parse Arguments
+
+config_file=""
+fresh_install=false
+show_status_only=false
+skip_gpu_drivers=false
+enable_autologin="" # empty=auto-detect, true=force enable, false=skip
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --config-file)
+ if [[ -n "$2" && ! "$2" =~ ^- ]]; then
+ config_file="$2"
+ shift 2
+ else
+ echo "Error: --config-file requires a path argument"
+ exit 1
+ fi
+ ;;
+ --fresh)
+ fresh_install=true
+ shift
+ ;;
+ --status)
+ show_status_only=true
+ shift
+ ;;
+ --no-gpu-drivers)
+ skip_gpu_drivers=true
+ shift
+ ;;
+ --autologin)
+ enable_autologin=true
+ shift
+ ;;
+ --no-autologin)
+ enable_autologin=false
+ shift
+ ;;
+ --help|-h)
+ echo "Usage: $0 [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " --config-file PATH Use config file for unattended installation"
+ echo " --fresh Start fresh, ignore previous progress"
+ echo " --status Show installation progress and exit"
+ echo " --no-gpu-drivers Skip GPU driver detection/installation"
+ echo " --autologin Enable automatic console login"
+ echo " --no-autologin Disable automatic console login"
+ echo " --help, -h Show this help message"
+ echo ""
+ echo "See archsetup.conf.example for config file template."
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1"
+ echo "Usage: $0 [--config-file PATH] [--fresh] [--status] [--autologin]"
+ exit 1
+ ;;
+ esac
+done
+
+### Load Config File
+
+load_config() {
+ local config_path="$1"
+ if [[ ! -f "$config_path" ]]; then
+ echo "Error: Config file not found: $config_path"
+ exit 1
+ fi
+ echo "Loading config from: $config_path"
+ # SECURITY NOTE: source executes config as bash. Only use trusted config files.
+ # This is acceptable because: (1) user explicitly provides path, (2) script runs
+ # as root anyway, (3) if attacker can write config, they likely have root access.
+ # shellcheck disable=SC1090
+ source "$config_path"
+
+ # Map config variables to script variables
+ [[ -n "$USERNAME" ]] && username="$USERNAME"
+ [[ -n "$PASSWORD" ]] && password="$PASSWORD"
+ [[ "$AUTOLOGIN" == "yes" ]] && enable_autologin=true
+ [[ "$AUTOLOGIN" == "no" ]] && enable_autologin=false
+ [[ "$NO_GPU_DRIVERS" == "yes" ]] && skip_gpu_drivers=true
+
+ # Repository overrides
+ [[ -n "$DWM_REPO" ]] && dwm_repo="$DWM_REPO"
+ [[ -n "$DMENU_REPO" ]] && dmenu_repo="$DMENU_REPO"
+ [[ -n "$ST_REPO" ]] && st_repo="$ST_REPO"
+ [[ -n "$SLOCK_REPO" ]] && slock_repo="$SLOCK_REPO"
+ [[ -n "$DOTEMACS_REPO" ]] && dotemacs_repo="$DOTEMACS_REPO"
+ [[ -n "$ARCHSETUP_REPO" ]] && archsetup_repo="$ARCHSETUP_REPO"
+ [[ -n "$LOCALE" ]] && locale="$LOCALE"
+ [[ -n "$DESKTOP_ENV" ]] && desktop_env="$DESKTOP_ENV"
+}
+
+# Load config if specified
+[[ -n "$config_file" ]] && load_config "$config_file"
+
+### Configuration Defaults
+# These can be overridden via --config-file
+
+username="${username:-cjennings}"
+password="${password:-welcome}" # CHANGE ON FIRST LOGIN
+locale="${locale:-}" # set via prompt if not configured
+desktop_env="${desktop_env:-hyprland}" # options: dwm, hyprland, none
+
+archsetup_dir="$(dirname "$(readlink -f "$0")")"
+dotfiles_dir="$archsetup_dir/dotfiles"
+
+# Git repositories for suckless tools and dotfiles
+# Override these in config file to use your own forks
+dwm_repo="${dwm_repo:-https://git.cjennings.net/dwm.git}"
+dmenu_repo="${dmenu_repo:-https://git.cjennings.net/dmenu.git}"
+st_repo="${st_repo:-https://git.cjennings.net/st.git}"
+slock_repo="${slock_repo:-https://git.cjennings.net/slock.git}"
+dotemacs_repo="${dotemacs_repo:-https://git.cjennings.net/dotemacs.git}"
+archsetup_repo="${archsetup_repo:-https://git.cjennings.net/archsetup.git}"
+
+logfile="/var/log/archsetup-$(date +'%Y-%m-%d-%H-%M-%S').log"
+source_dir="/home/$username/.local/src" # aur/git source goes here
+packages_before="/var/log/archsetup-preexisting-package-list.txt"
+packages_after="/var/log/archsetup-post-install-package-list.txt"
+archsetup_packages="/var/log/archsetup-installed-packages.txt"
+
+min_disk_space_gb=20
+state_dir="/var/lib/archsetup/state"
+error_messages=()
+errors_encountered=0
+
+# Installation steps (single source of truth for show_status and main execution)
+# display_server and window_manager steps are conditional based on $desktop_env
+STEPS=(intro prerequisites create_user user_customizations aur_installer
+ essential_services display_server window_manager desktop_environment developer_workstation
+ supplemental_software boot_ux)
+
+### Cleanup Trap
+# Ensures tmpfs is unmounted if script exits unexpectedly
+cleanup() {
+ if mountpoint -q "$source_dir" 2>/dev/null; then
+ umount "$source_dir" 2>/dev/null
+ fi
+}
+trap cleanup EXIT
+
+### State Tracking
+# Enables resuming from where the script left off if interrupted.
+# State is stored as marker files in $state_dir.
+# Use --fresh to start over, --status to check progress.
+
+step_completed() {
+ [ -f "$state_dir/$1" ]
+}
+
+mark_complete() {
+ mkdir -p "$state_dir"
+ echo "$(date +'%Y-%m-%d %H:%M:%S')" > "$state_dir/$1"
+}
+
+run_step() {
+ step_name="$1"
+ step_func="$2"
+
+ if step_completed "$step_name"; then
+ printf "Skipping %s (already completed)\n" "$step_name"
+ return 0
+ fi
+
+ if $step_func; then
+ mark_complete "$step_name"
+ return 0
+ else
+ printf "FAILED: %s\n" "$step_name"
+ printf "To retry this step, remove: %s/%s\n" "$state_dir" "$step_name"
+ return 1
+ fi
+}
+
+show_status() {
+ echo "Archsetup State Status"
+ echo "======================"
+ echo "State directory: $state_dir"
+ echo ""
+ if [ ! -d "$state_dir" ]; then
+ echo "No state found. Script has not been run or was run with --fresh."
+ exit 0
+ fi
+ echo "Completed steps:"
+ for step in "${STEPS[@]}"; do
+ if step_completed "$step"; then
+ timestamp=$(cat "$state_dir/$step")
+ printf " [x] %-25s (%s)\n" "$step" "$timestamp"
+ else
+ printf " [ ] %-25s\n" "$step"
+ fi
+ done
+ exit 0
+}
+
+# Handle --status flag (must be after state_dir is defined)
+if $show_status_only; then
+ show_status
+fi
+
+# Handle --fresh flag
+if $fresh_install; then
+ echo "Starting fresh installation (removing previous state)..."
+ rm -rf "$state_dir"
+fi
+
+### Pre-flight Checks
+preflight_checks() {
+ echo "Running pre-flight checks..."
+
+ # Check disk space (need at least 20GB free on root partition)
+ available_kb=$(df / | awk 'NR==2 {print $4}')
+ available_gb=$((available_kb / 1024 / 1024))
+ if [ "$available_gb" -lt "$min_disk_space_gb" ]; then
+ echo "ERROR: Insufficient disk space"
+ echo " Required: ${min_disk_space_gb}GB"
+ echo " Available: ${available_gb}GB"
+ echo " Free up disk space before running archsetup."
+ exit 1
+ fi
+ echo " [OK] Disk space: ${available_gb}GB available"
+
+ # Check network connectivity
+ if ! ping -c 1 -W 5 archlinux.org > /dev/null 2>&1; then
+ echo "ERROR: No network connectivity"
+ echo " Cannot reach archlinux.org"
+ echo " Ensure network is configured before running archsetup."
+ echo " Try: ip link, dhcpcd, or nmtui"
+ exit 1
+ fi
+ echo " [OK] Network: archlinux.org reachable"
+
+ # Check pacman is available
+ if ! command -v pacman > /dev/null 2>&1; then
+ echo "ERROR: pacman not found"
+ echo " This script requires Arch Linux with pacman installed."
+ exit 1
+ fi
+ echo " [OK] Package manager: pacman available"
+
+ # Check we're on Arch Linux
+ if [ ! -f /etc/arch-release ]; then
+ echo "ERROR: Not running on Arch Linux"
+ echo " This script is designed for Arch Linux only."
+ exit 1
+ fi
+ echo " [OK] System: Arch Linux detected"
+
+ # Check locale configuration
+ if grep -q "^LANG=" /etc/locale.conf 2>/dev/null; then
+ current_locale=$(grep "^LANG=" /etc/locale.conf | cut -d= -f2)
+ echo " [OK] Locale: $current_locale"
+ elif [[ -n "$locale" ]]; then
+ echo " [OK] Locale: $locale (from config)"
+ else
+ echo ""
+ echo "Locale not configured. Please select:"
+ echo " 1) en_US.UTF-8 (US English)"
+ echo " 2) en_GB.UTF-8 (UK English)"
+ echo " 3) de_DE.UTF-8 (German)"
+ echo " 4) es_ES.UTF-8 (Spanish)"
+ echo " 5) fr_FR.UTF-8 (French)"
+ echo " 6) pt_BR.UTF-8 (Brazilian Portuguese)"
+ echo " 7) ja_JP.UTF-8 (Japanese)"
+ echo " 8) zh_CN.UTF-8 (Chinese)"
+ echo " 9) Other (enter manually)"
+ read -r -p "Choice [1]: " choice
+ case "${choice:-1}" in
+ 1) locale="en_US.UTF-8" ;;
+ 2) locale="en_GB.UTF-8" ;;
+ 3) locale="de_DE.UTF-8" ;;
+ 4) locale="es_ES.UTF-8" ;;
+ 5) locale="fr_FR.UTF-8" ;;
+ 6) locale="pt_BR.UTF-8" ;;
+ 7) locale="ja_JP.UTF-8" ;;
+ 8) locale="zh_CN.UTF-8" ;;
+ 9)
+ read -r -p "Enter locale (e.g., nl_NL.UTF-8): " locale
+ [[ -z "$locale" ]] && locale="en_US.UTF-8"
+ ;;
+ *) locale="en_US.UTF-8" ;;
+ esac
+ echo " [OK] Locale: $locale (selected)"
+ fi
+
+ echo "Pre-flight checks passed."
+ echo ""
+}
+
+### Intro
+intro() {
+ # begin with a clean logfile
+ [ -f "$logfile" ] && rm -f "$logfile"
+ touch "$logfile"
+
+ printf "\n\nArchSetup launched @ %s\n" "$(date +'%D %T')" | tee -a "$logfile"
+
+ # count the arch packages before install
+ pacman -Q > "$packages_before" || \
+ error_fatal "generating pre-install package list" "$?"
+}
+
+### Error Handling
+
+# Non-fatal error - log and continue
+# Usage: error_warn "what failed" "$?"
+error_warn() {
+ errors_encountered=$((errors_encountered+1))
+ local msg="$1 (error code: $2)"
+ error_messages+=("$msg")
+ printf "ERROR: %s @ %s\n" "$msg" "$(date +'%T')" | tee -a "$logfile"
+ return 1
+}
+
+# Fatal error - log and exit
+# Usage: error_fatal "what failed" "$?"
+error_fatal() {
+ printf "CRASH: %s (error: %s) @ %s. Halting.\n" \
+ "$1" "$2" "$(date +'%T')" | tee -a "$logfile"
+ exit 1
+}
+
+### Output & Logging
+
+display() {
+ case "$1" in
+ "title")
+ printf "\n##### %s\n" "$2" | tee -a "$logfile"
+ ;;
+ "subtitle")
+ printf "\n%s\n" "$2" | tee -a "$logfile"
+ ;;
+ "task")
+ printf "...%s @ %s\n" "$2" "$(date +'%T')" | tee -a "$logfile"
+ ;;
+ *)
+ printf "CRASH: display() called with incorrect arguments.\n"
+ printf "...called %s type, %s action @ %s\n" \
+ "$1" "$2" "$(date +'%T')" | tee -a "$logfile"
+ exit 1
+ ;;
+ esac
+}
+
+### Installation Helpers
+
+MAX_INSTALL_RETRIES=3
+retry_install() {
+ local pkg="$1"
+ local source="$2"
+ local cmd="$3"
+ local attempt=1
+ local last_exit_code=0
+
+ display "task" "installing $pkg via $source"
+ while [ $attempt -le $MAX_INSTALL_RETRIES ]; do
+ if eval "$cmd" >> "$logfile" 2>&1; then
+ return 0
+ fi
+ last_exit_code=$?
+ attempt=$((attempt + 1))
+ if [ $attempt -le $MAX_INSTALL_RETRIES ]; then
+ display "task" "retrying $pkg (attempt $attempt/$MAX_INSTALL_RETRIES)"
+ fi
+ done
+ error_warn "$pkg ($source)" "$last_exit_code"
+}
+
+# Pacman Install
+pacman_install() {
+ retry_install "$1" "pacman" "pacman --noconfirm --needed -S \"$1\""
+}
+
+# AUR Install
+aur_install() {
+ retry_install "$1" "AUR" "sudo -u \"$username\" yay -S --noconfirm \"$1\""
+}
+
+# Git Install
+git_install() {
+ local prog_name
+ prog_name="$(basename "$1" .git)"
+ local build_dir="$source_dir/$prog_name"
+
+ display "task" "building & installing $prog_name from source"
+
+ if ! (sudo -u "$username" git clone --depth 1 "$1" "$build_dir" >> "$logfile" 2>&1); then
+ error_warn "cloning $prog_name - directory may exist, removing and retrying" "$?"
+ rm -rf "$build_dir" >> "$logfile" 2>&1 || \
+ error_warn "removing existing directory for $prog_name" "$?"
+ sudo -u "$username" git clone --depth 1 "$1" "$build_dir" >> "$logfile" 2>&1 || \
+ error_fatal "re-cloning $prog_name after cleanup" "$?"
+ fi
+
+ (cd "$build_dir" && make install >> "$logfile" 2>&1) || \
+ error_warn "building $prog_name from source" "$?"
+}
+
+# PIP Install (using pipx for isolated environments)
+pip_install() {
+ [ -x "$(command -v "pipx")" ] || pacman_install python-pipx
+ action="installing $1 via pipx" && display "task" "$action"
+ (sudo -u "$username" pipx install "$1" >> "$logfile" 2>&1) || \
+ error_warn "$action" "$?"
+}
+
+### System Detection
+
+is_zfs_root() {
+ # Returns 0 (true) if root filesystem is ZFS, 1 (false) otherwise
+ [ "$(findmnt -n -o FSTYPE /)" = "zfs" ]
+}
+
+is_btrfs_root() {
+ # Returns 0 (true) if root filesystem is btrfs, 1 (false) otherwise
+ [ "$(findmnt -n -o FSTYPE /)" = "btrfs" ]
+}
+
+# Encryption Detection
+is_encrypted_root() {
+ # Returns 0 (true) if root filesystem is on an encrypted volume
+ # Detects both LUKS (dm-crypt) and ZFS native encryption
+
+ # Check for LUKS/dm-crypt: root device path contains dm- and backing device is crypt type
+ local root_dev
+ root_dev=$(findmnt -n -o SOURCE /)
+ if lsblk -nlo TYPE "$root_dev" 2>/dev/null | grep -q "crypt"; then
+ return 0
+ fi
+
+ # Check for ZFS native encryption
+ if is_zfs_root; then
+ local root_dataset
+ root_dataset=$(findmnt -n -o SOURCE /)
+ local encryption
+ encryption=$(zfs get -H -o value encryption "$root_dataset" 2>/dev/null)
+ if [ -n "$encryption" ] && [ "$encryption" != "off" ]; then
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+# NVMe Detection
+has_nvme_drives() {
+ # Returns 0 (true) if system has NVMe drives
+ ls /dev/nvme* &>/dev/null
+}
+
+### System Configuration
+
+configure_autologin() {
+ local do_autologin=false
+
+ # Determine whether to enable autologin
+ if [ "$enable_autologin" = "true" ]; then
+ do_autologin=true
+ elif [ "$enable_autologin" = "false" ]; then
+ do_autologin=false
+ else
+ # Auto-detect: only prompt if root is encrypted
+ if is_encrypted_root; then
+ display "task" "Encrypted root detected"
+ echo ""
+ echo "Since the disk is encrypted, you already authenticate at boot."
+ echo "Automatic login skips the redundant login prompt after decryption."
+ echo ""
+ read -r -p "Enable automatic console login for $username? [Y/n] " response
+ case "$response" in
+ [nN][oO]|[nN])
+ do_autologin=false
+ ;;
+ *)
+ do_autologin=true
+ ;;
+ esac
+ else
+ # Not encrypted, skip autologin silently
+ return 0
+ fi
+ fi
+
+ if [ "$do_autologin" = "true" ]; then
+ action="configuring automatic console login" && display "task" "$action"
+ mkdir -p /etc/systemd/system/getty@tty1.service.d
+ cat << EOF > /etc/systemd/system/getty@tty1.service.d/autologin.conf
+[Service]
+ExecStart=
+ExecStart=-/sbin/agetty -o '-p -f -- \\\\u' --noclear --autologin $username %I \$TERM
+EOF
+ else
+ display "task" "Skipping automatic login configuration"
+ fi
+
+ return 0
+}
+
+# GPU Driver Installation
+install_gpu_drivers() {
+ if $skip_gpu_drivers; then
+ display "task" "Skipping GPU driver installation (--no-gpu-drivers)"
+ return 0
+ fi
+
+ display "subtitle" "GPU Drivers"
+
+ # Detect GPU vendors using modalias (more reliable than parsing lspci)
+ # Modalias is the kernel's hardware identification system used by drivers
+ # Vendor IDs: 1002=AMD, 8086=Intel, 10DE=NVIDIA
+ local detected_intel=false
+ local detected_amd=false
+ local detected_nvidia=false
+
+ # Primary detection: check modalias from DRM subsystem
+ for modalias_file in /sys/class/drm/card*/device/modalias; do
+ if [[ -r "$modalias_file" ]]; then
+ modalias=$(cat "$modalias_file" 2>/dev/null)
+ case "$modalias" in
+ *v00008086*) detected_intel=true ;;
+ *v00001002*) detected_amd=true ;;
+ *v000010DE*|*v000010de*) detected_nvidia=true ;;
+ esac
+ fi
+ done
+
+ # Fallback: check PCI bus modalias if DRM not available (early boot/chroot)
+ if ! $detected_intel && ! $detected_amd && ! $detected_nvidia; then
+ for modalias_file in /sys/bus/pci/devices/*/modalias; do
+ if [[ -r "$modalias_file" ]]; then
+ modalias=$(cat "$modalias_file" 2>/dev/null)
+ # Only check display class devices (bc03 = display controller)
+ if [[ "$modalias" == *bc03* ]]; then
+ case "$modalias" in
+ *v00008086*) detected_intel=true ;;
+ *v00001002*) detected_amd=true ;;
+ *v000010DE*|*v000010de*) detected_nvidia=true ;;
+ esac
+ fi
+ fi
+ done
+ fi
+
+ # Install drivers based on detected hardware
+ if $detected_intel; then
+ display "task" "Intel GPU detected (via modalias) - installing drivers"
+ pacman_install mesa
+ pacman_install intel-media-driver # hardware video acceleration
+ pacman_install vulkan-intel # Vulkan support
+ fi
+
+ if $detected_amd; then
+ display "task" "AMD GPU detected (via modalias) - installing drivers"
+ pacman_install mesa
+ pacman_install xf86-video-amdgpu
+ pacman_install vulkan-radeon
+ pacman_install libva-mesa-driver # hardware video acceleration
+ fi
+
+ if $detected_nvidia; then
+ display "task" "NVIDIA GPU detected (via modalias) - installing drivers"
+ pacman_install nvidia-dkms # DKMS version for kernel flexibility
+ pacman_install nvidia-utils
+ pacman_install nvidia-settings
+ pacman_install libva-nvidia-driver # hardware video acceleration
+ fi
+
+ # Fallback for VMs or unknown hardware
+ if ! $detected_intel && ! $detected_amd && ! $detected_nvidia; then
+ display "task" "No GPU detected via modalias - installing generic drivers"
+ pacman_install mesa
+ pacman_install xf86-video-vesa
+ fi
+}
+
+### Prerequisites
+prerequisites() {
+ # why these software packages are 'required'
+ # linux-firmware - ensuring hardware can be detected properly
+ # wireless-regdb - regulatory database for wireless (prevents kernel warnings)
+ # base_devel - required tools to compile
+ # ca_certificates - for validation of keyrings, etc.
+ # coreutils - comparing package lists
+ # curl - to transfer source code
+ # git - tools required to work with git source respositories
+ # go - required to build yay, the aur installer
+ # chrony - must communicate with other servers in synchronized manner
+ # python - required for python pip installs
+ # stow - places the dotfiles (see: https://bit.ly/41GmysO)
+ # tar - extract unix archives
+ # vi - should things go wrong, we'll need an editor
+ # zsh - we need a shell interpreter for yay; this one's mine
+
+ display "title" "Prerequisites"
+
+ display "subtitle" "Bootstrapping"
+
+ action="ensuring current Arch Linux keyring" && display "task" "$action"
+ (pacman -Syy) >> "$logfile" 2>&1 || error_fatal "$action" "$?"
+ (pacman -S --noconfirm archlinux-keyring) >> "$logfile" 2>&1 || \
+ error_fatal "$action" "$?"
+
+ display "task" "verifying Arch Linux keys"
+ (pacman-key --populate archlinux >> "$logfile" 2>&1) || \
+ error_fatal "verifying Arch Linux keys" "$?"
+
+ action="refreshing the package cache" && display "task" "$action"
+ (pacman -Syu --noconfirm >> "$logfile" 2>&1) || error_fatal "$action" "$?"
+
+ display "subtitle" "Required Software"
+
+ for software in linux-firmware wireless-regdb base-devel ca-certificates \
+ chrony coreutils curl git go openssh python \
+ stow tar vi zsh; do
+ pacman_install "$software"
+ done
+
+ display "subtitle" "Environment Configuration"
+
+ # configure locale (must happen before package installs that depend on locale)
+ # Skip if already configured by installer
+ if ! grep -q "^LANG=" /etc/locale.conf 2>/dev/null; then
+ action="configuring locale ($locale)" && display "task" "$action"
+ # Uncomment the selected locale in locale.gen (format: "en_US.UTF-8 UTF-8")
+ locale_entry="${locale} ${locale##*.}" # e.g., "en_US.UTF-8 UTF-8"
+ sed -i "s/^#${locale_entry}/${locale_entry}/" /etc/locale.gen
+ (locale-gen >> "$logfile" 2>&1) || error_warn "$action" "$?"
+ echo "LANG=$locale" > /etc/locale.conf
+ export LANG="$locale"
+ else
+ display "task" "locale already configured ($(grep ^LANG= /etc/locale.conf))"
+ export LANG="$(grep ^LANG= /etc/locale.conf | cut -d= -f2)"
+ fi
+
+ # sync the time on this machine (one-shot chrony sync)
+ action="synchronizing system time" && display "task" "$action"
+ (chronyd -q 'server 0.us.pool.ntp.org iburst' >> "$logfile" 2>&1) || error_warn "$action" "$?"
+
+ # enable chrony for ongoing time sync and create config to suppress warning
+ action="enabling chrony time sync service" && display "task" "$action"
+ mkdir -p /etc/sysconfig
+ echo 'OPTIONS=""' > /etc/sysconfig/chronyd
+ systemctl enable chronyd.service >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ action="configuring compiler to use all processor cores" && display "task" "$action"
+ sed -i "s/-j2/-j$(nproc)/;s/^#MAKEFLAGS/MAKEFLAGS/" /etc/makepkg.conf >> "$logfile" 2>&1
+
+ action="disabling debug packages in makepkg" && display "task" "$action"
+ sed -i 's/^OPTIONS=.*/OPTIONS=(strip docs !libtool !staticlibs emptydirs zipman purge !debug)/' /etc/makepkg.conf >> "$logfile" 2>&1
+
+ # enable pacman concurrent downloads and color
+ action="enabling concurrent downloads" && display "task" "$action"
+ sed -i "s/^#ParallelDownloads.*$/ParallelDownloads = 10/;s/^#Color$/Color/" /etc/pacman.conf
+
+ action="Package Mirrors" && display "subtitle" "$action"
+ pacman_install reflector
+
+ action="configuring reflector" && display "task" "$action"
+ cat << 'EOF' > /etc/xdg/reflector/reflector.conf
+--connection-timeout 3
+--download-timeout 3
+--protocol https
+--age 12
+--latest 20
+--score 10
+--fastest 5
+--sort score
+--save /etc/pacman.d/mirrorlist
+EOF
+
+ action="enabling the reflector timer" && display "task" "$action"
+ (systemctl enable reflector.timer >> "$logfile" 2>&1) || \
+ error_warn "$action" "$?"
+
+ action="replacing sudoers file if new package version exists" && display "task" "$action"
+ [ -f /etc/sudoers.pacnew ] && cp /etc/sudoers.pacnew /etc/sudoers >> "$logfile" 2>&1
+
+ action="creating a directory to build/install software from git/AUR."
+ (mkdir -p "$source_dir") || error_fatal "creating the directory $source_dir" "$?"
+
+}
+
+### Create User
+create_user() {
+ display "title" "User Creation"
+
+ display "task" "checking if user exists"
+ # halt if $username exists
+ ( id -u "$username" >/dev/null 2>&1; ) && \
+ error_fatal "user '$username' already exists" "user exists"
+
+ # create $username with home, group, shell, password
+ action="creating user and home directory" && display "task" "$action"
+ (useradd -m -G wheel -s /bin/zsh "$username" >> "$logfile" 2>&1) || \
+ error_fatal "adding user '$username'" "$?"
+
+ display "task" "assigning the password"
+ echo "$username:$password" | chpasswd # any text is allowable! be careful!
+ unset password # clear from memory after use
+
+ display "task" "adding to appropriate groups"
+ (usermod -aG \
+ sys,adm,network,scanner,power,uucp,audio,lp,rfkill,video,storage,optical,users \
+ "$username" >> "$logfile" 2>&1) || error_fatal "adding $username to groups" "$?"
+
+ display "task" "configuring shell"
+ # zsh cache required: $username will install via yay; zsh will run those commands
+ mkdir -p "/home/$username/.cache/zsh/"
+
+ # give $username sudo nopasswd rights (required for aur installs)
+ display "task" "granting permissions"
+ (echo "%$username ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers) \
+ || error_warn "$action" "$?"
+
+ # mount as ramdisk to speed aur/git build/installs
+ (mount -t tmpfs -o size=4G archsetup "$source_dir" >> "$logfile" 2>&1) || \
+ error_fatal "mounting the RAM disk for archsetup" "$?"
+
+ (chown -R "$username":wheel "$source_dir" >> "$logfile" 2>&1) || \
+ error_fatal "changing ownership of $source_dir" "$?"
+
+ # Bootstrap DNS for git clones and AUR installs (full config in essential_services)
+ if [ ! -L /etc/resolv.conf ] || [ "$(readlink /etc/resolv.conf)" != "/run/systemd/resolve/stub-resolv.conf" ]; then
+ display "task" "bootstrapping DNS resolution"
+ # Must start systemd-resolved first - stub-resolv.conf only exists when it's running
+ systemctl start systemd-resolved >> "$logfile" 2>&1 || \
+ error_warn "starting systemd-resolved for DNS bootstrap" "$?"
+ ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf >> "$logfile" 2>&1 || \
+ error_warn "bootstrapping DNS resolution" "$?"
+ fi
+}
+
+### User Customizations
+user_customizations() {
+ action="User Customizations" && display "title" "$action"
+
+ # Clone archsetup to user's home directory so dotfile symlinks are accessible.
+ # This ensures symlinks point to a user-readable location regardless of how
+ # archsetup was invoked (curl|bash, from /root, etc.)
+ user_archsetup_dir="/home/$username/code/archsetup"
+ action="cloning archsetup to user's home directory" && display "task" "$action"
+ (mkdir -p "$(dirname "$user_archsetup_dir")" && \
+ git clone --depth 1 "$archsetup_repo" "$user_archsetup_dir" && \
+ chown -R "$username": "/home/$username/code") \
+ >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Update dotfiles_dir to point to user-accessible location
+ dotfiles_dir="$user_archsetup_dir/dotfiles"
+
+ action="linking dotfiles into place" && display "task" "$action"
+ (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt system \
+ >> "$logfile" 2>&1 ) || error_warn "$action" "$?"
+
+ # Stow desktop-environment-specific dotfiles
+ if [[ "$desktop_env" == "hyprland" ]]; then
+ action="linking hyprland dotfiles" && display "task" "$action"
+ (cd "$dotfiles_dir" && stow --target="/home/$username" --no-folding --adopt hyprland \
+ >> "$logfile" 2>&1 ) || error_warn "$action" "$?"
+ fi
+
+ # install desktop-file-utils before updating database (provides update-desktop-database)
+ pacman_install desktop-file-utils
+
+ action="updating desktop database" && display "task" "$action"
+ # Exit code 1 means warnings (missing icons, etc.) but database still updated
+ (sudo -u "$username" update-desktop-database "/home/$username/.local/share/applications" \
+ >> "$logfile" 2>&1 ) || true
+
+ action="restoring dotfile versions" && display "task" "$action"
+ (cd "$dotfiles_dir" && git config --global --add safe.directory "$user_archsetup_dir" && \
+ git restore . >> "$logfile" 2>&1 ) || error_warn "$action" "$?"
+
+ action="creating common directories" && display "task" "$action"
+ # Create default directories and grant permissions
+ {
+ mkdir -p -m 751 "/home/$username/code"
+ mkdir -p -m 751 "/home/$username/documents"
+ mkdir -p -m 751 "/home/$username/downloads/torrents/complete"
+ mkdir -p -m 751 "/home/$username/downloads/torrents/incomplete"
+ mkdir -p -m 751 "/home/$username/downloads/torrents/files"
+ mkdir -p -m 751 "/home/$username/downloads/ebooks"
+ mkdir -p -m 751 "/home/$username/music"
+ mkdir -p -m 751 "/home/$username/projects"
+ mkdir -p -m 751 "/home/$username/pictures/screenshots"
+ mkdir -p -m 751 "/home/$username/videos"
+ mkdir -p -m 751 "/home/$username/vms"
+ chown -R "$username": "/home/$username"
+
+ mkdir -p -m 751 /media/backup
+ mkdir -p -m 751 /media/remote0
+ mkdir -p -m 751 /media/remote1
+ mkdir -p -m 751 /media/remote2
+ chown -R "$username": /media
+ } >> "$logfile" 2>&1
+}
+
+### AUR Installer
+aur_installer() {
+ display "title" "AUR Installer"
+
+ yay_repo="https://aur.archlinux.org/yay.git"
+ build_dir="$source_dir/yay"
+
+ display "task" "fetching source code for yay"
+ if ! (sudo -u "$username" git clone --depth 1 "$yay_repo" "$build_dir" >> "$logfile" 2>&1); then
+ error_warn "cloning source code for yay - directory may exist, removing and retrying" "$?"
+ (rm -rf "$build_dir" >> "$logfile" 2>&1) || \
+ error_fatal "removing existing directory for yay" "$?"
+ (sudo -u "$username" git clone --depth 1 "$yay_repo" "$build_dir" >> "$logfile" 2>&1) || \
+ error_fatal "re-cloning source code for yay after cleanup" "$?"
+ fi
+
+ action="packaging and installing yay"; display "task" "$action"
+ (cd "$build_dir" && sudo -u "$username" makepkg --noconfirm -si >> "$logfile" 2>&1) || \
+ error_fatal "$action" "$?"
+}
+
+### Essential Services
+essential_services() {
+ display "title" "Essential Services"
+
+ # Randomness
+
+ display "subtitle" "Randomness"
+ pacman_install rng-tools
+ action="enabling rngd service" && display "task" "$action"
+ systemctl enable rngd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ action="starting rngd service" && display "task" "$action"
+ systemctl start rngd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Networking
+
+ display "subtitle" "Networking"
+ pacman_install networkmanager
+ action="enabling NetworkManager" && display "task" "$action"
+ systemctl enable NetworkManager.service >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ action="configuring MAC address randomization" && display "task" "$action"
+ mkdir -p /etc/NetworkManager/conf.d
+ cat << 'EOF' > /etc/NetworkManager/conf.d/wifi-privacy.conf
+[device-mac-randomization]
+# Randomize MAC during WiFi scans
+wifi.scan-rand-mac-address=yes
+
+[connection-mac-randomization]
+# Random MAC for each WiFi connection (prevents tracking)
+wifi.cloned-mac-address=random
+# Stable MAC for ethernet (avoids issues with MAC-based DHCP reservations)
+ethernet.cloned-mac-address=stable
+EOF
+
+ # Configure wireless regulatory domain (enables full WiFi capabilities for region)
+ # Derive region code from locale (e.g., en_US.UTF-8 → US, de_DE.UTF-8 → DE)
+ current_lang="${LANG:-en_US.UTF-8}"
+ wireless_region="${current_lang:3:2}" # extract country code (positions 3-4)
+ action="configuring wireless regulatory domain ($wireless_region)" && display "task" "$action"
+ sed -i "s/^#WIRELESS_REGDOM=\"$wireless_region\"/WIRELESS_REGDOM=\"$wireless_region\"/" /etc/conf.d/wireless-regdom
+
+ # Encrypted DNS (DNS over TLS)
+
+ action="configuring encrypted DNS (DNS over TLS)" && display "task" "$action"
+ mkdir -p /etc/systemd/resolved.conf.d
+ cat << 'EOF' > /etc/systemd/resolved.conf.d/dns-over-tls.conf
+[Resolve]
+# Use Cloudflare and Quad9 with DNS-over-TLS
+DNS=1.1.1.1#cloudflare-dns.com 9.9.9.9#dns.quad9.net
+FallbackDNS=1.0.0.1#cloudflare-dns.com 149.112.112.112#dns.quad9.net
+DNSOverTLS=yes
+DNSSEC=yes
+# Disable mDNS in resolved - avahi handles .local resolution exclusively
+MulticastDNS=no
+EOF
+
+ # Configure NetworkManager to use systemd-resolved
+ cat << 'EOF' > /etc/NetworkManager/conf.d/dns.conf
+[main]
+dns=systemd-resolved
+EOF
+
+ # Note: If Docker containers have DNS issues, systemd-resolved's stub resolver
+ # (127.0.0.53) may be the cause. Fix: configure Docker to use direct DNS, or
+ # disable systemd-resolved and use /etc/resolv.conf directly. (2026-01-18)
+ action="enabling systemd-resolved" && display "task" "$action"
+ systemctl enable systemd-resolved >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Create resolv.conf symlink to systemd-resolved
+ action="linking resolv.conf to systemd-resolved" && display "task" "$action"
+ ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Power
+
+ display "subtitle" "Power"
+ pacman_install upower
+ action="enabling upower service" && display "task" "$action"
+ systemctl enable upower >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Secure Shell
+
+ display "subtitle" "Secure Shell"
+ pacman_install openssh
+ action="enabling the openssh service to run at boot" && display "task" "$action"
+ systemctl enable sshd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ action="starting the openssh service" && display "task" "$action"
+ systemctl start sshd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # SSH Brute Force Protection
+
+ display "subtitle" "SSH Brute Force Protection"
+ pacman_install fail2ban
+
+ action="configuring fail2ban for SSH protection" && display "task" "$action"
+ cat << 'EOF' > /etc/fail2ban/jail.local
+[DEFAULT]
+# Ban for 10 minutes by default
+bantime = 10m
+findtime = 10m
+maxretry = 5
+# Use ufw for ban actions
+banaction = ufw
+
+[sshd]
+enabled = true
+port = ssh
+# Stricter settings for SSH: 3 attempts, 1 hour ban
+maxretry = 3
+bantime = 1h
+EOF
+
+ action="enabling fail2ban service" && display "task" "$action"
+ systemctl enable fail2ban >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ action="starting fail2ban service" && display "task" "$action"
+ systemctl start fail2ban >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ display "subtitle" "Firewall"
+ pacman_install ufw
+
+ action="configuring ufw to deny by default" && display "task" "$action"
+ ufw default deny incoming >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Firewall rules - only open ports for services we actually run
+ for protocol in \
+ "IMAP" "IMAPS" \
+ "ssh" \
+ "22000/tcp" "22000/udp" "21027/udp" \
+ "42000/tcp" "42000/udp" \
+ "42001/tcp" "42001/udp" \
+ "5353/tcp" "5353/udp" \
+ "transmission" \
+ ; do
+ # IMAP/IMAPS: Thunderbird email client
+ # ssh: OpenSSH server
+ # 22000/tcp,udp + 21027/udp: Syncthing file sync
+ # 42000/tcp,udp: Warpinator file transfers
+ # 42001/tcp,udp: Warpinator registration/auth
+ # 5353/tcp,udp: mDNS/Avahi local network discovery
+ # transmission: BitTorrent client
+ action="adding ufw rule to allow $protocol" && display "task" "$action"
+ (ufw allow "$protocol" >> "$logfile" 2>&1) || error_warn "$action" "$?"
+ done
+
+ action="rate-limiting SSH to protect from brute force attacks" && display "task" "$action"
+ (ufw limit 22/tcp >> "$logfile" 2>&1) || error_warn "$action" "$?"
+
+ action="enabling firewall" && display "task" "$action"
+ ufw --force enable >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ action="enabling firewall service to launch on boot" && display "task" "$action"
+ systemctl enable ufw.service >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Verify firewall is actually active
+ # Note: In VM environments, UFW may show inactive due to missing kernel
+ # netfilter modules. This is a test environment limitation, not a bug.
+ # On real hardware with proper kernel support, UFW activates correctly.
+ action="verifying firewall is active" && display "task" "$action"
+ if ! ufw status | grep -q "Status: active"; then
+ error_messages=("FIREWALL NOT ACTIVE - run: sudo ufw enable" "${error_messages[@]}")
+ error_warn "$action" "1"
+ fi
+
+ # Service Discovery
+
+ display "subtitle" "Network Service Discovery"
+ pacman_install nss-mdns # GNU Name Service Switch host name resolution
+
+ # Note: systemd-resolved handles DNS (with DoT), avahi handles mDNS (.local)
+ if systemctl is-active --quiet avahi-daemon.service; then
+ display "task" "skipping avahi (already running)"
+ else
+ pacman_install avahi # service discovery on a local network using mdns
+ action="enabling avahi for mDNS discovery" && display "task" "$action"
+ systemctl enable avahi-daemon.service >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ fi
+
+ pacman_install geoclue # geolocation service for location-aware apps
+ action="enabling geoclue geolocation service" && display "task" "$action"
+ systemctl enable geoclue.service >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Fix dbus-broker race condition with sysusers (geoclue user must exist before dbus parses service files)
+ action="configuring dbus-broker to wait for sysusers" && display "task" "$action"
+ mkdir -p /etc/systemd/system/dbus-broker.service.d
+ cat > /etc/systemd/system/dbus-broker.service.d/after-sysusers.conf << 'EOF'
+[Unit]
+After=systemd-sysusers.service
+EOF
+
+ # Job Scheduling
+
+ display "subtitle" "Job Scheduling"
+ pacman_install cronie
+ action="enabling cronie to launch at boot" && display "task" "$action"
+ systemctl enable cronie >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ pacman_install at
+ action="enabling the batch delayed command scheduler" && display "task" "$action"
+ systemctl enable atd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Package Repository Cache Maintenance
+
+ display "subtitle" "Package Repository Cache Maintenance"
+ pacman_install pacman-contrib
+ action="enabling the package cache cleanup timer" && display "task" "$action"
+ systemctl enable --now paccache.timer >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ action="configuring paccache to keep 3 versions" && display "task" "$action"
+ sed -i 's/^PACCACHE_ARGS=.*/PACCACHE_ARGS=-k3/' /etc/conf.d/pacman-contrib
+
+ # Snapshot Service - filesystem-aware
+ display "subtitle" "Snapshot Service"
+
+ if is_zfs_root; then
+ # ZFS: Install sanoid for snapshot management
+ display "task" "ZFS detected - installing sanoid"
+ aur_install sanoid
+
+ action="creating sanoid configuration" && display "task" "$action"
+ mkdir -p /etc/sanoid
+ cat << 'EOF' > /etc/sanoid/sanoid.conf
+# Sanoid configuration for ZFS snapshots
+# Less aggressive - TrueNAS handles long-term backups
+
+#############################
+# Templates
+#############################
+
+[template_production]
+ # Local rollback capability
+ hourly = 6
+ daily = 7
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_backup]
+ # Less frequent for large/static data
+ hourly = 0
+ daily = 3
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_none]
+ autosnap = no
+ autoprune = yes
+
+#############################
+# Datasets
+#############################
+
+[zroot/ROOT/default]
+ use_template = production
+
+[zroot/home]
+ use_template = production
+ recursive = yes
+
+[zroot/media]
+ use_template = backup
+
+[zroot/vms]
+ use_template = backup
+
+[zroot/var/log]
+ use_template = production
+
+[zroot/var/lib/pacman]
+ use_template = production
+
+[zroot/var/cache]
+ use_template = none
+
+[zroot/var/tmp]
+ use_template = none
+
+[zroot/tmp]
+ use_template = none
+EOF
+
+ action="installing zfs-replicate script" && display "task" "$action"
+ cp "$user_archsetup_dir/scripts/zfs-replicate" /usr/local/bin/zfs-replicate
+ chmod +x /usr/local/bin/zfs-replicate
+
+ action="creating zfs-replicate systemd service" && display "task" "$action"
+ cat << 'EOF' > /etc/systemd/system/zfs-replicate.service
+[Unit]
+Description=ZFS Replication to TrueNAS
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/zfs-replicate
+User=root
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+ action="creating zfs-replicate systemd timer" && display "task" "$action"
+ cat << 'EOF' > /etc/systemd/system/zfs-replicate.timer
+[Unit]
+Description=Run ZFS replication nightly
+
+[Timer]
+OnCalendar=*-*-* 02:00:00
+RandomizedDelaySec=1800
+Persistent=true
+
+[Install]
+WantedBy=timers.target
+EOF
+
+ action="enabling sanoid timer" && display "task" "$action"
+ systemctl enable sanoid.timer >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ action="enabling weekly ZFS scrub" && display "task" "$action"
+ # Get pool name dynamically (usually zroot)
+ zfs_pool=$(zpool list -H -o name | head -1)
+ systemctl enable "zfs-scrub-weekly@${zfs_pool}.timer" >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Note: zfs-replicate.timer is NOT enabled automatically
+ # User must set up SSH key auth to TrueNAS first, then run:
+ # systemctl enable --now zfs-replicate.timer
+ display "task" "zfs-replicate timer created (enable after SSH key setup to TrueNAS)"
+
+ elif is_btrfs_root; then
+ # Btrfs: Install timeshift-autosnap and grub-btrfs
+ display "task" "btrfs detected - installing snapshot tools"
+ aur_install timeshift-autosnap
+ pacman_install grub-btrfs
+ action="enabling snapshot boot menu updates" && display "task" "$action"
+ systemctl enable grub-btrfsd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ action="starting snapshot boot menu updates" && display "task" "$action"
+ # starting and stopping service to generate the grub-btrfs config
+ systemctl start grub-btrfsd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ systemctl stop grub-btrfsd >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ # edit grub-btrfs config for timeshift auto snapshot support
+ sed -i \
+ 's|ExecStart=/usr/bin/grub-btrfsd --syslog /.snapshots|ExecStart=/usr/bin/grub-btrfsd --syslog --timeshift-auto|' \
+ /etc/systemd/system/grub-btrfsd.service
+
+ action="regenerating boot menu" && display "task" "$action"
+ grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ else
+ # ext4 or other filesystem - no automatic snapshots
+ display "task" "ext4/other filesystem detected - skipping snapshot tools"
+ fi
+}
+
+### Xorg Display Manager
+xorg() {
+ action="Xorg Display Server Dependencies" && display "subtitle" "$action"
+ pacman_install libglvnd
+
+ action="Xorg Display Server" && display "subtitle" "$action"
+ for software in xorg-server xorg-xinit xorg-xsetroot \
+ xsel xorg-xbacklight xorg-xev \
+ xf86-input-libinput xorg-xeyes \
+ xorg-xdpyinfo xorg-xprop \
+ xorg-xwininfo xorg-xhost \
+ xorg-xinput xorg-xkill ; do
+ pacman_install "$software"
+ done
+
+ # disallow vt switching or zapping the xorg server to bypass screen lock
+ cat << 'EOF' > /etc/X11/xorg.conf.d/00-no-vt-or-zap.conf
+Section "ServerFlags"
+ Option "DontVTSwitch" "True"
+ Option "DontZap" "True"
+EndSection
+EOF
+ action="configuring xorg server" && display "task" "$action"
+ chmod 644 /etc/X11/xorg.conf.d/00-no-vt-or-zap.conf >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Install GPU-specific drivers
+ install_gpu_drivers
+}
+
+### DWM Window Manager
+dwm() {
+
+ action="DWM Window Manager Dependencies" && display "subtitle" "$action"
+ for software in coreutils fontconfig freetype2 glibc harfbuzz libx11 libxft libxinerama; do
+ pacman_install "$software"
+ done
+
+ action="DWM Window Manager" && display "subtitle" "$action"
+
+ git_install "$dwm_repo"
+ git_install "$dmenu_repo"
+ git_install "$st_repo"
+ git_install "$slock_repo"
+ aur_install pinentry-dmenu # password entry that leverages dmenu
+}
+
+### Wayland Display Server
+wayland() {
+ action="Wayland Display Server" && display "subtitle" "$action"
+ for software in wayland wayland-protocols xorg-xwayland wl-clipboard; do
+ pacman_install "$software"
+ done
+
+ # Portal for screen sharing, file dialogs, etc.
+ action="XDG Desktop Portal" && display "subtitle" "$action"
+ pacman_install xdg-desktop-portal
+ pacman_install xdg-desktop-portal-gtk
+ aur_install xdg-desktop-portal-hyprland
+
+ # Install GPU-specific drivers
+ install_gpu_drivers
+}
+
+### Hyprland Window Manager
+hyprland() {
+ action="Hyprland Compositor Dependencies" && display "subtitle" "$action"
+ for software in polkit-kde-agent qt5-wayland qt6-wayland; do
+ pacman_install "$software"
+ done
+
+ action="Hyprland Compositor" && display "subtitle" "$action"
+ pacman_install hyprland
+ pacman_install hypridle
+ pacman_install hyprlock
+ pacman_install hyprpaper
+
+ action="Hyprland Utilities" && display "subtitle" "$action"
+ pacman_install waybar # status bar
+ pacman_install wofi # app launcher
+ pacman_install swww # wallpaper
+ pacman_install grim # screenshot
+ pacman_install slurp # region select
+ pacman_install gammastep # night light (replaces redshift)
+ pacman_install brightnessctl # brightness control
+ pacman_install pamixer # audio control
+
+ # st terminal still works on Wayland via XWayland
+ git_install "$st_repo"
+}
+
+### Display Server (conditional)
+display_server() {
+ display "title" "Display Server"
+ case "$desktop_env" in
+ dwm)
+ xorg
+ ;;
+ hyprland)
+ wayland
+ ;;
+ none)
+ action="Skipping display server (DESKTOP_ENV=none)" && display "task" "$action"
+ ;;
+ *)
+ error_fatal "Unknown DESKTOP_ENV: $desktop_env. Valid options: dwm, hyprland, none" "1"
+ ;;
+ esac
+}
+
+### Window Manager (conditional)
+window_manager() {
+ display "title" "Window Manager"
+ case "$desktop_env" in
+ dwm)
+ dwm
+ ;;
+ hyprland)
+ hyprland
+ ;;
+ none)
+ action="Skipping window manager (DESKTOP_ENV=none)" && display "task" "$action"
+ ;;
+ *)
+ error_fatal "Unknown DESKTOP_ENV: $desktop_env. Valid options: dwm, hyprland, none" "1"
+ ;;
+ esac
+}
+
+### Desktop Environment
+desktop_environment() {
+ display "title" "Desktop Environment"
+
+ # Fonts
+
+ action="Fonts" && display "subtitle" "$action"
+ pacman_install noto-fonts-emoji
+ pacman_install terminus-font
+ pacman_install ttf-firacode-nerd
+ pacman_install ttf-hack-nerd
+ pacman_install ttf-jetbrains-mono-nerd
+ pacman_install ttf-meslo-nerd
+ pacman_install ttf-nerd-fonts-symbols-mono
+ aur_install ttf-all-the-icons
+ aur_install ttf-lato
+ aur_install ttf-ms-fonts
+ aur_install ttf-ubraille
+ aur_install lexend-fonts-git # readable font family
+
+
+ # System Utilities
+
+ action="System Utilities" && display "subtitle" "$action"
+ pacman_install dmidecode
+ pacman_install dosfstools
+ pacman_install exfatprogs
+ pacman_install lshw
+ pacman_install ntfs-3g
+ pacman_install sshfs
+ pacman_install testdisk
+ pacman_install tickrs
+ pacman_install udisks2
+ aur_install touchpad-indicator-git
+ aur_install dotpac
+ aur_install downgrade
+ aur_install duf
+ aur_install inxi
+ aur_install mkinitcpio-firmware # suppresses missing firmware warnings during mkinitcpio
+
+ # File Associations
+
+ action="File/Application Associations" && display "subtitle" "$action"
+ pacman_install perl-file-mimeinfo
+ pacman_install xdg-utils
+
+ # Authentication Tools
+
+ action="Authentication Tools" && display "subtitle" "$action"
+ pacman_install gnupg
+ pacman_install polkit
+ pacman_install gnome-keyring
+ pacman_install seahorse
+ pacman_install pass
+
+ # ensure correct permissions on .gpg directory
+ # the colon means the user's group will have perms
+ [ -d /home/"$username"/.gnupg ] || mkdir /home/"$username"/.gnupg
+ chown -R "$username": /home/"$username"/.gnupg
+ find /home/"$username"/.gnupg -type f -exec chmod 600 {} \;
+ find /home/"$username"/.gnupg -type d -exec chmod 700 {} \;
+
+ # pre-create gnome-keyring structure so it uses 'login' keyring
+ # (auto-unlocks at login) instead of creating 'Default_keyring' (prompts for password)
+ keyring_dir="/home/$username/.local/share/keyrings"
+ mkdir -p "$keyring_dir"
+ echo "login" > "$keyring_dir/default"
+ chown -R "$username": "/home/$username/.local/share/keyrings"
+ chmod 700 "$keyring_dir"
+
+ # configure PAM to auto-unlock gnome-keyring on console login
+ # this passes the login password to gnome-keyring-daemon at session start
+ action="configuring PAM for gnome-keyring auto-unlock" && display "task" "$action"
+ pam_login="/etc/pam.d/login"
+ if ! grep -q "pam_gnome_keyring.so" "$pam_login"; then
+ # add auth line after the last auth line
+ sed -i '/^auth.*system-local-login/a auth optional pam_gnome_keyring.so' "$pam_login"
+ # add session line after the last session line
+ sed -i '/^session.*system-local-login/a session optional pam_gnome_keyring.so auto_start' "$pam_login"
+ fi
+
+ # Power Management
+
+ action="Power Management" && display "subtitle" "$action"
+ pacman_install acpi
+ pacman_install powertop
+
+ # Audio System
+
+ action="Audio System" && display "subtitle" "$action"
+ for software in alsa-utils pipewire wireplumber pipewire-pulse \
+ pipewire-docs pamixer pulsemixer ffmpeg rtkit; do
+ pacman_install "$software"
+ done
+ # disable the pc speaker beep
+ rmmod pcspkr 2>/dev/null || true
+ echo "blacklist pcspkr" > /etc/modprobe.d/nobeep.conf
+
+ # Keyboard Shortcut Manager
+
+ action="Keyboard Shortcut Manager" && display "subtitle" "$action"
+ pacman_install sxhkd
+
+ # Notifications
+
+ action="Notification System" && display "subtitle" "$action"
+ pacman_install libnotify
+ pacman_install dunst
+
+ # Screen Color Temperature
+
+ action="Screen Color Temperature" && display "subtitle" "$action"
+ pacman_install redshift
+
+ # Bluetooth Devices
+
+ action="Bluetooth System" && display "subtitle" "$action"
+ for software in bluez bluez-utils blueman; do
+ pacman_install "$software"
+ done
+ action="enabling bluetooth to launch at boot" && display "task" "$action"
+ systemctl enable bluetooth.service >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ # Command Line Utilities
+
+ action="Command Line Utilities" && display "subtitle" "$action"
+ for software in htop mc ncdu tmux fzf zip unzip atool wget detox \
+ lsof usbutils moreutils; do
+ pacman_install "$software"
+ done
+
+ for software in task-spooler-cpu speedtest-go-bin gotop-bin rar; do
+ aur_install "$software"
+ done
+
+ # Help And Documentation
+
+ action="Help and Documentation" && display "subtitle" "$action"
+ pacman_install man-db
+ pacman_install arch-wiki-docs
+ pacman_install tealdeer
+ aur_install cht.sh-git
+
+ # Sync Services
+
+ action="Sync Services" && display "subtitle" "$action"
+
+ pacman_install syncthing
+ # Use user service (not system service) to avoid lock file conflicts if both get enabled
+ loginctl enable-linger "$username"
+ # Enable syncthing user service by creating symlink directly
+ # (systemctl --user fails during install - no user session bus available)
+ action="enabling syncthing user service" && display "task" "$action"
+ user_systemd_dir="/home/$username/.config/systemd/user/default.target.wants"
+ mkdir -p "$user_systemd_dir"
+ ln -sf /usr/lib/systemd/user/syncthing.service "$user_systemd_dir/syncthing.service"
+ chown -R "$username:$username" "/home/$username/.config/systemd"
+
+ # Desktop Environment Utilities
+
+ action="Desktop Environment Utilities" && display "subtitle" "$action"
+
+ for software in brightnessctl network-manager-applet xclip rofi \
+ conky qalculate-gtk feh; do
+ pacman_install "$software"
+ done
+
+ aur_install caffeine-ng
+ aur_install flameshot
+ aur_install python-pulsectl
+ aur_install xautolock
+ aur_install xcolor
+
+ # Theme and Cursor
+
+ action="UI Theme" && display "subtitle" "$action"
+
+ for software in picom lxappearance gnome-themes-extra; do
+ pacman_install "$software"
+ done
+
+ for software in vimix-cursors \
+ papirus-icon-theme qt6ct qt5ct; do
+ aur_install "$software"
+ done
+
+ pacman_install libappindicator-gtk3 # required by some applets
+
+ # Browsers
+
+ action="Browsers" && display "subtitle" "$action"
+ pacman_install firefox
+ pacman_install torbrowser-launcher # downloads/updates tor browser automatically
+ aur_install google-chrome
+
+ # Install Printing
+
+ action="Print System" && display "subtitle" "$action"
+ pacman_install cups # the printing service
+ pacman_install cups-pdf # allows printing to pdf
+ pacman_install foomatic-db-engine # generates printer drivers, queues, and jobs
+ pacman_install foomatic-db-ppds # printer driver descriptions
+ pacman_install foomatic-db-nonfree-ppds # non-free printer driver descriptions
+ pacman_install gutenprint # printer driver engine for older computers
+ pacman_install foomatic-db-gutenprint-ppds # gutenprint prebuilt ppd files
+ pacman_install system-config-printer # graphical printer config tool
+
+ action="enabling printing service to launch at boot" && display "task" "$action"
+ (systemctl enable cups.service >> "$logfile" 2>&1) || error_warn "$action" "$?"
+}
+
+### Developer Workstation
+developer_workstation() {
+
+ action="Developer Workstation" && display "title" "$action"
+
+ action="Programming Languages and Utilities" && display "subtitle" "$action"
+ # C
+ pacman_install clang # C/C++ compiler
+ pacman_install cmake # make system
+ pacman_install gdb # the GNU debugger
+ pacman_install splint # C programming static analysis
+ pacman_install valgrind # memory management utility
+
+ # Lisps
+ pacman_install guile # GNU Scheme
+ pacman_install sbcl # Steel Bank Common Lisp
+
+ # Python
+ pacman_install pyright # Python language server
+ pacman_install pyenv # Python environment manager
+
+ # Shell
+ pacman_install shellcheck # Shell script linter
+ pacman_install shfmt # Shell script formatter
+
+ # Go
+ pacman_install delve # Go programming language debugger
+ pacman_install go-tools # Go language utilities
+ pacman_install gopls # Go language server
+ pacman_install staticcheck # Go programming language linter
+
+ # Typescript
+ pacman_install jq # JSON processor
+ pacman_install typescript # Typescript programming language
+ pacman_install nodejs # Node-js JavaScript runtime environment
+ pacman_install npm # Node-js package manager
+ aur_install nvm # Node-js version manager
+
+ # AI coding assistant (native install to ~/.local/bin)
+ action="installing claude-code via native installer" && display "task" "$action"
+ (sudo -u "$username" bash -c 'curl -fsSL https://claude.ai/install.sh | sh' >> "$logfile" 2>&1) || \
+ error_warn "$action" "$?"
+
+ # HTML
+ pacman_install tidy # HTML formatter
+ pacman_install prettier # Multi-language formatter
+
+ # General Utilities
+ pacman_install difftastic # Structural diff tool
+ pacman_install eza # Modern ls replacement
+ pacman_install meld # Visual diff
+ pacman_install ripgrep # Fast grep utility
+ pacman_install zoxide # Smart cd command that learns your habits
+
+ action="Programming Editors" && display "subtitle" "$action"
+ pacman_install mg # mini emacs
+
+ action="Emacs Dependencies" && display "subtitle" "$action"
+ pacman_install emacs
+
+ # supporting utilities used by my emacs configuration
+ aur_install exercism-bin # command line tool for exercism.io
+ pacman_install libvips # image previewer library
+ pacman_install isync # email sync
+ aur_install epub-thumbnailer-git # epub previews in dirvish
+ aur_install mu # email indexer and utilities
+ aur_install multimarkdown # markdown conversion
+ aur_install proselint # grammar checker
+ aur_install gist # command-line gist poster
+ pacman_install aspell # spell check system
+ pacman_install aspell-en # spell check english files
+ pacman_install fd # a faster find for dired/dirvish
+ pacman_install ffmpegthumbnailer # video previews in dired/dirvish
+ pacman_install imagemagick # image previews for dired/dirvish
+ pacman_install libgccjit # native compilation for Emacs
+ pacman_install mediainfo # generating media info in dired/dirvish
+ pacman_install 7zip # archive info for dirvish
+ pacman_install mpv # video viewer
+ pacman_install mailutils # Emacs' IMAP mail support backend
+ pacman_install msmtp # mail transport for Mu4e
+ pacman_install msmtp-mta # mail transport for Mu4e
+ pacman_install python-lsp-server # python language support
+ pacman_install rlwrap # adds readline support to programs (SBCL-related)
+ pacman_install sdcv # stardict dictionary system
+ pacman_install yt-dlp # video download
+
+ action="setting up emacs configuration files" && display "task" "$action"
+ emacs_dir="/home/$username/.emacs.d"
+ if [ -d "$emacs_dir" ]; then
+ (cd "$emacs_dir" && sudo -u "$username" git pull --recurse-submodules >> "$logfile" 2>&1) || \
+ error_warn "$action" "$?"
+ else
+ (sudo -u "$username" git clone --recurse-submodules "$dotemacs_repo" "$emacs_dir" >> \
+ "$logfile" 2>&1) || error_warn "$action" "$?"
+ fi
+
+ action="Android Utilities" && display "subtitle" "$action"
+ pacman_install android-file-transfer
+ pacman_install android-tools
+
+ action="VPN Tools" && display "subtitle" "$action"
+ pacman_install wireguard-tools # VPN - add configs to /etc/wireguard/
+ pacman_install tailscale # mesh VPN - run 'tailscale up' to authenticate
+
+ action="enabling tailscale service" && display "task" "$action"
+ systemctl enable tailscaled >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ action="DevOps Utilities" && display "subtitle" "$action"
+
+ action="installing devops virtualization and automation tools" && display "task" "$action"
+ aur_install vagrant # VM management (moved to AUR)
+ pacman_install ansible
+
+ # distrobox related (uses docker as backend)
+ pacman_install distrobox
+ aur_install ptyxis
+
+ # boxes
+ pacman_install gnome-boxes
+
+ # docker
+ pacman_install docker
+ pacman_install docker-compose
+ action="adding user to docker group" && display "task" "$action"
+ (gpasswd -a "$username" docker >> "$logfile" 2>&1) || error_warn "$action" "$?"
+ if is_zfs_root; then
+ action="configuring docker to use ZFS storage driver" && display "task" "$action"
+ mkdir -p /etc/docker
+ cat > /etc/docker/daemon.json << 'EOF'
+{
+ "storage-driver": "zfs"
+}
+EOF
+ fi
+ action="enabling docker service to launch on boot" && display "task" "$action"
+ systemctl enable docker.service >> "$logfile" 2>&1 || error_warn "$action" "$?"
+}
+
+### Supplemental Software
+supplemental_software() {
+
+ display "title" "Supplemental Software"
+
+ # pacman installs
+ pacman_install arandr # xrandr gui for monitor settings
+ pacman_install arch-install-scripts # for making arch installers
+ pacman_install archinstall # the archinstall python program
+ pacman_install aria2 # fast downloader
+ pacman_install audacity # audio editor
+ pacman_install bc # arbitrary precision calculator
+ pacman_install calibre # ebook manager/viewer
+ pacman_install dash # posix compliant /bin/sh
+ pacman_install devtools # tools for arch linux package maintenance
+ pacman_install docx2txt # recovers text from docx files
+ pacman_install entr # run arbitrary commands when files change
+ pacman_install faac # open source mpeg 3 and aac encoder
+ pacman_install faad2 # processes an aac stream
+ pacman_install fastfetch # cli system information tool
+ pacman_install fdupes # identify binary duplicates
+ pacman_install filezilla # ftp gui
+ pacman_install gimp # image editor
+ pacman_install gparted # disk partition utility
+ pacman_install gst-plugin-pipewire # gstreamer audio plugin for pipewire
+ pacman_install gst-plugins-base # gstreamer base audio plugins
+ pacman_install gst-plugins-good # gstreamer common codecs (FLAC, JPEG, PNG)
+ pacman_install gst-plugins-bad # gstreamer additional codecs (AAC, MKV/WebM)
+ pacman_install gst-plugins-ugly # gstreamer patent-encumbered codecs (MP3, x264)
+ pacman_install gst-libav # gstreamer FFmpeg-based codecs
+ pacman_install gstreamer # pipeline based multimedia framework
+ pacman_install gucharmap # gui display of character maps
+ pacman_install gzip # compression tool
+ pacman_install handbrake # video transcoder
+ pacman_install libconfig # library for processing structured config files
+ pacman_install libmad # mpeg audio decoder
+ pacman_install libmpeg2 # library for decoding mpeg video streams
+ pacman_install maim # screenshot utility
+ pacman_install mosh # alt SSH terminal with roaming and responsiveness support
+ pacman_install odt2txt # converts from open document to text
+ pacman_install pandoc-cli # universal document converter
+ pacman_install protonmail-bridge # ProtonMail desktop bridge
+ pacman_install perl-image-exiftool # reads/writes exif info for raw photo files
+ pacman_install poppler-glib # poppler-glib document viewer library
+ pacman_install pv # monitor progress of data through pipeline
+ pacman_install ranger # terminal file manager
+ pacman_install rclone # syncs files from gdrive, s3, dropbox, etc.
+ pacman_install signal-desktop # secure messenger
+ pacman_install iperf3 # network bandwidth testing
+ pacman_install net-tools # network tools (netstat for security auditing)
+ pacman_install smartmontools # monitors hard drives
+ pacman_install lynis # security auditing tool
+ pacman_install telegram-desktop # messenger application
+ # LaTeX - minimal set for document compilation with latexmk
+ # (texlive-meta is 2GB; this set is ~335MB)
+ pacman_install texlive-bin # core TeX binaries
+ pacman_install texlive-basic # essential TeX files
+ pacman_install texlive-latex # base LaTeX packages
+ pacman_install texlive-latexrecommended # commonly used LaTeX packages
+ pacman_install texlive-binextra # latexmk, bibtex, and other tools
+ pacman_install texlive-fontsrecommended # standard fonts
+ pacman_install thunderbird # email, calendar, rss feeds
+ pacman_install transmission-cli # bittorrent client
+ pacman_install transmission-remote-gtk # bittorrent client
+ pacman_install unclutter # hides mouse cursor when not being used
+ pacman_install vlc # media player
+ pacman_install w3m # text based browser
+ pacman_install opus # opus audio codec (all music in opus format)
+ pacman_install wavpack # audio compression format
+ pacman_install webkit2gtk # web content engine for GTK
+ pacman_install xcb-util-cursor # calibre dependency (calibre installed manually)
+ pacman_install xdotool # command line xorg automation tool
+ pacman_install xz # general purpose data compression tool
+ pacman_install zathura # document viewer
+ pacman_install zathura-cb # zathura plugin for comics
+ pacman_install zathura-djvu # zathura plugin for djvu books
+ pacman_install zathura-pdf-mupdf # zathura plugin for pdf
+ pacman_install zlib # compression library
+
+ # aur installs
+ aur_install dtrx # extraction tool
+ aur_install hfsprogs # file system tools for Mac OS
+ aur_install insync # Google Drive sync client
+ aur_install nsxiv # image viewer
+ aur_install snore-git # sleep with feedback
+ pacman_install thunar # file manager
+ pacman_install gvfs-smb # SMB network share browsing in Thunar
+ aur_install topgrade # upgrade everything utility
+ aur_install ueberzug # allows for displaying images in terminals
+ aur_install warpinator # secure file transfers
+ aur_install zsh-fast-syntax-highlighting-git # Optimized and extended zsh-syntax-highlighting
+
+ # working around an temp integ issue with python-lyricsgenius expiration date
+ action="prep to workaround tidal-dl issue" && display "task" "$action"
+ yay -S --noconfirm --mflags --skipinteg python-lyricsgenius >> "$logfile" 2>&1 || error_warn "$action" "$?"
+ aur_install tidal-dl # tidal-dl:tidal as yt-dlp:youtube
+}
+
+### Boot-UX
+boot_ux() {
+ action="Boot UX" && display "title" "$action"
+
+ # Add nvme module for early loading on NVMe systems
+ # Ensures NVMe devices are available when ZFS/other hooks try to access them
+ if has_nvme_drives; then
+ action="adding nvme to mkinitcpio MODULES for early loading" && display "task" "$action"
+ if grep -q "^MODULES=()" /etc/mkinitcpio.conf; then
+ sed -i 's/^MODULES=()/MODULES=(nvme)/' /etc/mkinitcpio.conf
+ elif grep -q "^MODULES=(" /etc/mkinitcpio.conf && ! grep -q "nvme" /etc/mkinitcpio.conf; then
+ sed -i '/^MODULES=(/ s/)/ nvme)/' /etc/mkinitcpio.conf
+ fi
+ fi
+
+ action="removing distro and date/time from initial screen" && display "task" "$action"
+ (: >/etc/issue) || error_warn "$action" "$?"
+
+ action="preventing kernel messages on the console" && display "task" "$action"
+ (echo "kernel.printk = 3 3 3 3" >/etc/sysctl.d/20-quiet-printk.conf) || \
+ error_warn "$action" "$?"
+
+ action="configuring console font" && display "task" "$action"
+ if grep -q "^FONT=" /etc/vconsole.conf 2>/dev/null; then
+ sed -i 's/^FONT=.*/FONT=ter-132n/' /etc/vconsole.conf
+ else
+ echo "FONT=ter-132n" >> /etc/vconsole.conf
+ fi
+
+ # Only switch to systemd hook for non-ZFS systems
+ # ZFS initramfs hook is busybox-based and incompatible with systemd hook
+ if ! is_zfs_root; then
+ action="delegating fsck messages from udev to systemd" && display "task" "$action"
+ sed -i '/^HOOKS=/ s/\budev\b/systemd/' /etc/mkinitcpio.conf || error_warn "$action" "$?"
+ mkinitcpio -P >> "$logfile" 2>&1 || error_warn "running mkinitcpio -P to silence fsck messages" "$?"
+ fi
+
+ action="configuring quiet fsck output" && display "task" "$action"
+ mkdir -p /etc/systemd/system/systemd-fsck-root.service.d
+ cat << 'EOF' > /etc/systemd/system/systemd-fsck-root.service.d/quiet.conf
+[Service]
+StandardOutput=null
+StandardError=journal+console
+EOF
+
+ mkdir -p /etc/systemd/system/systemd-fsck@.service.d
+ cat << 'EOF' > /etc/systemd/system/systemd-fsck@.service.d/quiet.conf
+[Service]
+StandardOutput=null
+StandardError=journal+console
+EOF
+
+ # Automatic login for encrypted systems (prompts if no CLI flag and root is encrypted)
+ configure_autologin
+
+ action="silencing the unneeded and chatty watchdog module" && display "task" "$action"
+ echo "blacklist iTCO_wdt" >/etc/modprobe.d/nowatchdog.conf || error_warn "$action" "$?"
+
+ action="configuring journald retention" && display "task" "$action"
+ mkdir -p /etc/systemd/journald.conf.d
+ cat << 'EOF' > /etc/systemd/journald.conf.d/retention.conf
+[Journal]
+SystemMaxUse=500M
+EOF
+
+ # GRUB: reset timeouts, adjust log levels, larger menu for HiDPI screens, and show splashscreen
+ # Note: nvme.noacpi=1 disables NVMe ACPI power management to prevent freezes on some drives.
+ # Safe to keep on newer drives (minor power cost), remove if battery life is critical.
+ # Note: random.trust_cpu=off disables trusting CPU for RNG (avoids AMD RDSEED warnings).
+ action="configuring boot menu for silence and bootsplash" && display "task" "$action"
+ if [ -f /etc/default/grub ]; then
+ action="resetting timeouts and adjusting log levels on grub boot" && display "task" "$action"
+ sed -i "s/.*GRUB_TIMEOUT=.*/GRUB_TIMEOUT=2/g" /etc/default/grub
+ sed -i "s/.*GRUB_DEFAULT=.*/GRUB_DEFAULT=0/g" /etc/default/grub
+ sed -i 's/.*GRUB_TERMINAL_OUTPUT=console/GRUB_TERMINAL_OUTPUT=gfxterm/' /etc/default/grub
+ sed -i 's/.*GRUB_GFXMODE=auto/GRUB_GFXMODE=1024x768/' /etc/default/grub
+ sed -i "s/.*GRUB_RECORDFAIL_TIMEOUT=.*/GRUB_RECORDFAIL_TIMEOUT=2/g" /etc/default/grub
+ sed -i "s/.*GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"rw loglevel=2 rd.systemd.show_status=auto rd.udev.log_level=2 nvme.noacpi=1 mem_sleep_default=deep nowatchdog random.trust_cpu=off quiet splash\"/g" /etc/default/grub
+ grub-mkconfig -o /boot/grub/grub.cfg >> "$logfile" 2>&1 || error_warn "generating grub config" "$?"
+ fi
+}
+
+
+### Outro
+outro() {
+
+ action="Cleanup" && display "title" "$action"
+
+ action="forcing user password change on first login" && display "task" "$action"
+ chage -d 0 "$username" >> "$logfile" 2>&1 || error_warn "$action" "$?"
+
+ display "subtitle" "Statistics"
+ action="identifying newly installed packages" && display "task" "$action"
+ pacman -Q > "$packages_after" || error_warn "$action" "$?"
+ (comm -13 --nocheck-order "$packages_before" "$packages_after" > "$archsetup_packages") || \
+ error_warn "$action" "$?"
+
+ action="comparing timestamps" && display "task" "$action"
+ ENDTIME=$(date +%s)
+ totalsecs=$((ENDTIME - STARTTIME))
+ mins=$((totalsecs / 60))
+ secs=$((totalsecs % 60))
+
+ new_packages=$(wc -l < "$archsetup_packages")
+
+ printf "\n"
+ printf "Completion time : %s\n" "$(date +'%D %T')" | tee -a "$logfile"
+ printf "Elapsed time : %s minutes, %s seconds\n" "$mins" "$secs" | tee -a "$logfile"
+ printf "Errors encountered : %s\n" "$errors_encountered" | tee -a "$logfile"
+ printf "Log file location : %s\n" "$logfile"
+ printf "Packages installed : %s\n" "$new_packages"
+
+ if [ ${#error_messages[@]} -gt 0 ]; then
+ printf "\nError Summary:\n" | tee -a "$logfile"
+ for msg in "${error_messages[@]}"; do
+ printf " - %s\n" "$msg" | tee -a "$logfile"
+ done
+ fi
+
+ printf "\n"
+ printf "Please reboot before working with your new workstation.\n\n"
+
+ # Completion marker for automated testing
+ printf "=== ARCHSETUP_EXECUTION_COMPLETE ===\n" | tee -a "$logfile"
+}
+
+### Installation Steps
+preflight_checks # verify system requirements (always runs)
+STARTTIME=$(date +%s) # must be outside intro() since it may be skipped on resume
+
+for step in "${STEPS[@]}"; do
+ run_step "$step" "$step"
+done
+
+outro # take end stats; show summary (always runs)
+
+exit 0
diff --git a/archsetup.conf.example b/archsetup.conf.example
new file mode 100644
index 0000000..0fff4d8
--- /dev/null
+++ b/archsetup.conf.example
@@ -0,0 +1,54 @@
+# archsetup.conf - Configuration for unattended installation
+#
+# Copy this file and edit values for your setup:
+# cp archsetup.conf.example archsetup.conf
+#
+# Then run:
+# ./archsetup --config-file archsetup.conf
+#
+# All fields have defaults - only specify what you want to change.
+
+#############################
+# User Configuration
+#############################
+
+# Username for the primary user account (default: cjennings)
+USERNAME=cjennings
+
+# Initial password - CHANGE ON FIRST LOGIN (default: welcome)
+PASSWORD=welcome
+
+#############################
+# System Options
+#############################
+
+# Automatic console login after disk decryption (default: prompt on encrypted systems)
+# Options: yes, no
+# Only relevant for systems with encrypted root (LUKS or ZFS native encryption)
+#AUTOLOGIN=yes
+
+# Skip GPU driver auto-detection and installation (default: no)
+# Set to "yes" if you want to handle GPU drivers manually
+#NO_GPU_DRIVERS=no
+
+# System locale (default: prompt if not already configured)
+# Common options: en_US.UTF-8, en_GB.UTF-8, de_DE.UTF-8, es_ES.UTF-8,
+# fr_FR.UTF-8, pt_BR.UTF-8, ja_JP.UTF-8, zh_CN.UTF-8
+#LOCALE=en_US.UTF-8
+
+# Desktop environment (default: dwm)
+# Options: dwm (Xorg + DWM), hyprland (Wayland + Hyprland), none (headless/server)
+#DESKTOP_ENV=dwm
+
+#############################
+# Git Repositories
+#############################
+# Override these to use your own forks of suckless tools and dotfiles.
+# Defaults point to git.cjennings.net - fork these for your own setup.
+
+#DWM_REPO=https://github.com/yourusername/dwm.git
+#DMENU_REPO=https://github.com/yourusername/dmenu.git
+#ST_REPO=https://github.com/yourusername/st.git
+#SLOCK_REPO=https://github.com/yourusername/slock.git
+#DOTEMACS_REPO=https://github.com/yourusername/dotemacs.git
+#ARCHSETUP_REPO=https://github.com/yourusername/archsetup.git
diff --git a/assets/2026-01-17-gvfs-smb-feature-request.txt b/assets/2026-01-17-gvfs-smb-feature-request.txt
new file mode 100644
index 0000000..79892f7
--- /dev/null
+++ b/assets/2026-01-17-gvfs-smb-feature-request.txt
@@ -0,0 +1,6 @@
+Install gvfs-smb for Thunar SMB network browsing
+
+Package: gvfs-smb
+Install: sudo pacman -S gvfs-smb
+
+Without this package, Thunar cannot browse SMB/CIFS network shares.
diff --git a/assets/2026-01-17-zfs-sanoid-feature-request.txt b/assets/2026-01-17-zfs-sanoid-feature-request.txt
new file mode 100644
index 0000000..87207f2
--- /dev/null
+++ b/assets/2026-01-17-zfs-sanoid-feature-request.txt
@@ -0,0 +1,202 @@
+ZFS Detection and Sanoid Installation
+======================================
+
+When archsetup runs, it should detect if the system is on ZFS and install sanoid.
+
+Detection:
+- Check if root filesystem is ZFS: `findmnt -n -o FSTYPE /` returns "zfs"
+- Or check if zpool exists: `zpool list -H 2>/dev/null`
+
+If ZFS detected:
+1. Install sanoid from AUR: `yay -S sanoid`
+2. Create /etc/sanoid/sanoid.conf (see below)
+3. Enable the timer: `systemctl enable --now sanoid.timer`
+4. Create the syncoid replication script and systemd units (see below)
+
+Context:
+- install-archzfs can't install sanoid (AUR package)
+- archsetup already has AUR helper setup, so it's the right place to install it
+- syncoid (for TrueNAS replication) comes with the sanoid package
+
+Added: 2026-01-17
+
+================================================================================
+SANOID CONFIGURATION (/etc/sanoid/sanoid.conf)
+================================================================================
+
+# Sanoid configuration for ZFS snapshots
+# Less aggressive - TrueNAS handles long-term backups
+
+#############################
+# Templates
+#############################
+
+[template_production]
+ # Local rollback capability
+ hourly = 6
+ daily = 7
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_backup]
+ # Less frequent for large/static data
+ hourly = 0
+ daily = 3
+ weekly = 2
+ monthly = 1
+ autosnap = yes
+ autoprune = yes
+
+[template_none]
+ autosnap = no
+ autoprune = yes
+
+#############################
+# Datasets
+#############################
+
+[zroot/ROOT/default]
+ use_template = production
+
+[zroot/home]
+ use_template = production
+ recursive = yes
+
+[zroot/media]
+ use_template = backup
+
+[zroot/vms]
+ use_template = backup
+
+[zroot/var/log]
+ use_template = production
+
+[zroot/var/lib/pacman]
+ use_template = production
+
+[zroot/var/cache]
+ use_template = none
+
+[zroot/var/tmp]
+ use_template = none
+
+[zroot/tmp]
+ use_template = none
+
+================================================================================
+SYNCOID REPLICATION SCRIPT (/usr/local/bin/zfs-replicate)
+================================================================================
+
+#!/bin/bash
+# zfs-replicate - Replicate ZFS datasets to TrueNAS
+#
+# Usage:
+# zfs-replicate # Replicate all configured datasets
+# zfs-replicate [dataset] # Replicate specific dataset
+
+set -e
+
+# TrueNAS Configuration
+# Try local network first, fall back to tailscale
+TRUENAS_LOCAL="truenas.local"
+TRUENAS_TAILSCALE="truenas"
+TRUENAS_USER="root"
+TRUENAS_POOL="vault"
+BACKUP_PATH="backups" # TODO: Configure actual path
+
+# Datasets to replicate
+DATASETS="zroot/ROOT/default zroot/home zroot/media zroot/vms"
+
+# Colors
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+NC='\033[0m'
+
+info() { echo -e "${GREEN}[INFO]${NC} $1"; }
+warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
+error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
+
+command -v syncoid >/dev/null 2>&1 || error "syncoid not found. Install sanoid package."
+
+# Determine which host to use
+determine_host() {
+ if ping -c 1 -W 2 "$TRUENAS_LOCAL" &>/dev/null; then
+ echo "$TRUENAS_LOCAL"
+ elif ping -c 1 -W 2 "$TRUENAS_TAILSCALE" &>/dev/null; then
+ echo "$TRUENAS_TAILSCALE"
+ else
+ error "Cannot reach TrueNAS at $TRUENAS_LOCAL or $TRUENAS_TAILSCALE"
+ fi
+}
+
+TRUENAS_HOST=$(determine_host)
+info "Using TrueNAS host: $TRUENAS_HOST"
+
+# Single dataset mode
+if [[ -n "$1" ]]; then
+ dataset="$1"
+ dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}"
+ info "Replicating $dataset -> $dest"
+ syncoid --recursive "$dataset" "$dest"
+ exit 0
+fi
+
+# Full replication
+info "Starting ZFS replication to $TRUENAS_HOST"
+echo ""
+
+for dataset in $DATASETS; do
+ dest="$TRUENAS_USER@$TRUENAS_HOST:$TRUENAS_POOL/$BACKUP_PATH/${dataset#zroot/}"
+ info "Replicating $dataset -> $dest"
+
+ if syncoid --recursive "$dataset" "$dest"; then
+ info " Success"
+ else
+ warn " Failed (will retry next run)"
+ fi
+ echo ""
+done
+
+info "Replication complete."
+
+================================================================================
+SYSTEMD SERVICE (/etc/systemd/system/zfs-replicate.service)
+================================================================================
+
+[Unit]
+Description=ZFS Replication to TrueNAS
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/zfs-replicate
+User=root
+
+[Install]
+WantedBy=multi-user.target
+
+================================================================================
+SYSTEMD TIMER (/etc/systemd/system/zfs-replicate.timer)
+================================================================================
+
+[Unit]
+Description=Run ZFS replication nightly
+
+[Timer]
+OnCalendar=*-*-* 02:00:00
+RandomizedDelaySec=1800
+Persistent=true
+
+[Install]
+WantedBy=timers.target
+
+================================================================================
+ENABLE REPLICATION
+================================================================================
+
+After SSH key auth is set up to TrueNAS:
+ systemctl enable --now zfs-replicate.timer
diff --git a/assets/2026-01-19-remove-zfs-scripts-request.md b/assets/2026-01-19-remove-zfs-scripts-request.md
new file mode 100644
index 0000000..f67aa47
--- /dev/null
+++ b/assets/2026-01-19-remove-zfs-scripts-request.md
@@ -0,0 +1,29 @@
+# Task: Remove zfssnapshot and zfsrollback from archsetup
+
+## Summary
+Remove the `zfssnapshot` and `zfsrollback` scripts from archsetup's dotfiles. These scripts are now provided by the archzfs ISO and installed to `/usr/local/bin/` during `install-archzfs`.
+
+## Files to Remove
+- `dotfiles/system/.local/bin/zfssnapshot`
+- `dotfiles/system/.local/bin/zfsrollback`
+
+## Reason for Change
+These scripts need to be available immediately after a fresh install from the archzfs ISO, before archsetup runs. Key use cases:
+
+1. **Rescue scenarios**: Rolling back from live USB when the installed system won't boot
+2. **Genesis rollback**: If archsetup fails mid-run, user can rollback to genesis and retry
+3. **Script availability**: The scripts themselves must survive a genesis rollback (they're now part of genesis snapshot)
+
+By including them in the ISO and `install-archzfs`, they're guaranteed to be present from first boot, with fzf also installed as a dependency.
+
+## Changes Made in archzfs
+- Added `custom/zfssnapshot` and `custom/zfsrollback`
+- `build.sh` copies them to `/usr/local/bin/` on the ISO
+- `install-archzfs` installs `fzf` to target system (required by zfsrollback)
+- fzf was already in ISO package list
+
+## Note: Keep fzf in archsetup
+Archsetup should continue to install `fzf` in its package list. Archsetup can run on vanilla Arch installs with ext4 or btrfs (not just ZFS from archzfs ISO), where `install-archzfs` would not have run and fzf wouldn't be present.
+
+## Date
+2026-01-19
diff --git a/assets/2026-01-20-console-display-issues.txt b/assets/2026-01-20-console-display-issues.txt
new file mode 100644
index 0000000..f8dc710
--- /dev/null
+++ b/assets/2026-01-20-console-display-issues.txt
@@ -0,0 +1,112 @@
+Console Display Issues - Potential Causes in archsetup
+======================================================
+Date: 2026-01-20
+Source: archzfs testing on ratio - console not showing after install
+
+SUMMARY
+-------
+After running install-archzfs and archsetup on ratio, the console stopped
+displaying. The system boots but shows no console output. These are the
+suspected culprits in archsetup.
+
+SUSPECTED ISSUES
+----------------
+
+1. Console Font Configuration (boot_ux, lines 1574-1579)
+
+ File: archsetup
+ Lines: 1574-1579
+
+ Code:
+ if grep -q "^FONT=" /etc/vconsole.conf 2>/dev/null; then
+ sed -i 's/^FONT=.*/FONT=ter-132n/' /etc/vconsole.conf
+ else
+ echo "FONT=ter-132n" >> /etc/vconsole.conf
+ fi
+
+ Problem: Sets console font to ter-132n (Terminus 32pt). If the font
+ is missing, corrupted, or incompatible with the framebuffer, the
+ console may fail to display anything.
+
+ Fix: Verify terminus-font package is installed and font exists before
+ setting. Add fallback handling.
+
+2. mkinitcpio Hook Change (boot_ux, lines 1581-1583)
+
+ File: archsetup
+ Lines: 1581-1583
+
+ Code:
+ sed -i '/^HOOKS=/ s/\budev\b/systemd/' /etc/mkinitcpio.conf
+ mkinitcpio -P
+
+ Problem: Changes mkinitcpio from 'udev' to 'systemd' hook and
+ regenerates ALL initramfs images. This is a significant change that
+ affects early boot. If the systemd hook isn't properly configured
+ or conflicts with other hooks, boot may fail or console may not
+ initialize properly.
+
+ Fix: Ensure all required systemd-related hooks are present. Consider
+ whether this change is necessary or could be made optional.
+
+3. GRUB Quiet Boot Settings (boot_ux, line 1624)
+
+ File: archsetup
+ Line: 1624
+
+ Code:
+ sed -i "s/.*GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"rw loglevel=2 rd.systemd.show_status=auto rd.udev.log_level=2 nvme.noacpi=1 mem_sleep_default=deep nowatchdog quiet splash\"/g" /etc/default/grub
+
+ Problem: Adds 'quiet splash' and sets loglevel=2, which suppresses
+ most boot messages. If something goes wrong during boot, you won't
+ see any output. The 'splash' option may also interfere with console.
+
+ Fix: Consider removing 'splash' or making quiet boot optional.
+ For debugging, temporarily remove 'quiet splash' from GRUB.
+
+4. Kernel Message Suppression (boot_ux, lines 1571-1572)
+
+ File: archsetup
+ Lines: 1571-1572
+
+ Code:
+ echo "kernel.printk = 3 3 3 3" >/etc/sysctl.d/20-quiet-printk.conf
+
+ Problem: Suppresses kernel messages to console. Combined with other
+ quiet settings, this could hide important boot information.
+
+ Fix: For debugging, remove or adjust this setting.
+
+5. Xorg VT Switching Disabled (xorg, lines 1102-1107)
+
+ File: archsetup
+ Lines: 1102-1107
+
+ Code:
+ cat << EOF > /etc/X11/xorg.conf.d/00-no-vt-or-zap.conf
+ Section "ServerFlags"
+ Option "DontVTSwitch" "True"
+ Option "DontZap" "True"
+ EndSection
+ EOF
+
+ Problem: Disables VT switching when X is running. If X starts
+ automatically, you cannot switch to a text console with Ctrl+Alt+F2.
+ This is a security feature but makes debugging harder.
+
+ Note: This only affects post-X boot, not early console display.
+
+DEBUGGING STEPS
+---------------
+1. Boot with 'nomodeset' kernel parameter to rule out GPU/framebuffer issues
+2. Remove 'quiet splash' from GRUB temporarily
+3. Check if ter-132n font exists: ls /usr/share/kbd/consolefonts/ter-*
+4. Review mkinitcpio.conf HOOKS line for conflicts
+5. Check journalctl -b for boot errors
+
+RECOMMENDED CHANGES
+-------------------
+- Make quiet boot optional or add a debug boot menu entry
+- Verify font exists before setting in vconsole.conf
+- Document the udev->systemd hook change and its implications
+- Consider adding a recovery boot option that skips quiet settings
diff --git a/assets/2026-01-21-grub-timeout-request.txt b/assets/2026-01-21-grub-timeout-request.txt
new file mode 100644
index 0000000..fa03f62
--- /dev/null
+++ b/assets/2026-01-21-grub-timeout-request.txt
@@ -0,0 +1,4 @@
+* TODO Increase GRUB_TIMEOUT to 2 seconds
+Currently setting GRUB_TIMEOUT=0 which doesn't give users time to access GRUB menu.
+Change to GRUB_TIMEOUT=2 for a reasonable delay while keeping boot fast.
+
diff --git a/assets/2026-01-21-syncthing-service-conflict.org b/assets/2026-01-21-syncthing-service-conflict.org
new file mode 100644
index 0000000..7f86b39
--- /dev/null
+++ b/assets/2026-01-21-syncthing-service-conflict.org
@@ -0,0 +1,72 @@
+#+TITLE: Syncthing Service Conflict Issue
+#+DATE: 2026-01-21
+
+* Problem
+
+archsetup enables the system service:
+#+begin_src bash
+systemctl enable "syncthing@$username.service"
+#+end_src
+
+However, the user service can also get enabled (either by default or manually):
+#+begin_src bash
+systemctl --user enable syncthing.service
+#+end_src
+
+When BOTH services are enabled, they fight over the same lock file:
+=~/.local/state/syncthing/syncthing.lock=
+
+This causes one or both to fail with:
+: Failed to acquire lock: is another Syncthing instance already running?
+
+* Symptoms
+
+- Syncthing fails to start or keeps crashing
+- Lock file errors in journalctl
+- Two syncthing processes running with different parent services
+- Config changes don't persist (one service overwrites the other)
+
+* Recommendation
+
+Standardize on ONE service type. Options:
+
+** Option A: User Service (recommended for desktops)
+
+Runs when user logs in. Cleaner for desktop use.
+
+Change archsetup from:
+#+begin_src bash
+systemctl enable "syncthing@$username.service"
+#+end_src
+
+To:
+#+begin_src bash
+# Enable user service (requires user session)
+sudo -u "$username" systemctl --user enable syncthing.service
+#+end_src
+
+Note: User services require lingering or an active session:
+#+begin_src bash
+loginctl enable-linger "$username"
+#+end_src
+
+** Option B: System Service (recommended for headless/servers)
+
+Runs at boot without user login. Better for servers.
+
+Keep current archsetup config, but ensure user service is disabled:
+#+begin_src bash
+systemctl enable "syncthing@$username.service"
+# Explicitly disable user service to prevent conflicts
+sudo -u "$username" systemctl --user disable syncthing.service 2>/dev/null || true
+#+end_src
+
+* Resolution on ratio (2026-01-21)
+
+Disabled system service, kept user service:
+#+begin_src bash
+sudo systemctl stop syncthing@cjennings.service
+sudo systemctl disable syncthing@cjennings.service
+systemctl --user enable syncthing.service
+systemctl --user start syncthing.service
+#+end_src
diff --git a/assets/2026-01-23-avahi-mdns-fixes.org b/assets/2026-01-23-avahi-mdns-fixes.org
new file mode 100644
index 0000000..89b005e
--- /dev/null
+++ b/assets/2026-01-23-avahi-mdns-fixes.org
@@ -0,0 +1,125 @@
+#+TITLE: Avahi/mDNS Configuration Fixes
+#+DATE: 2026-01-23
+
+* Problem Summary
+
+On velox, mDNS hostname resolution was not working correctly from other machines on the LAN (e.g., ratio). Attempting to access =http://velox.local:8384= (Syncthing web UI) failed, while accessing via IP address worked.
+
+* Issues Identified
+
+** Issue 1: Hostname Conflict (velox-3.local)
+
+*Symptom:* Avahi was running as =velox-3.local= instead of =velox.local=
+
+*Cause:* Avahi was publishing on multiple network interfaces including virtual ones:
+- =enp0s13f0u3= (physical LAN - correct)
+- =docker0= (Docker bridge)
+- =virbr0= (libvirt bridge)
+- =vnet0= (VM virtual NIC)
+- =tailscale0= (Tailscale VPN)
+
+Each interface was effectively registering as a separate host, causing mDNS hostname conflicts with itself.
+
+*Solution:* Restrict Avahi to only the physical LAN interface.
+
+#+begin_src conf
+# /etc/avahi/avahi-daemon.conf
+[server]
+allow-interfaces=enp0s13f0u3
+#+end_src
+
+** Issue 2: IPv6-Only Resolution
+
+*Symptom:* =velox.local= resolved to IPv6 link-local address (=fe80::...=) only, but Syncthing was listening on IPv4 only (=0.0.0.0:8384=).
+
+*Cause:* Default Avahi configuration does not publish A records (IPv4) in response to AAAA queries (IPv6).
+
+*Solution:* Enable =publish-a-on-ipv6= to ensure IPv4 addresses are returned.
+
+#+begin_src conf
+# /etc/avahi/avahi-daemon.conf
+[publish]
+publish-a-on-ipv6=yes
+#+end_src
+
+** Issue 3: Conflicting mDNS Stacks
+
+*Symptom:* Avahi logged warning: "Detected another IPv4 mDNS stack running on this host"
+
+*Cause:* Both =avahi-daemon= and =systemd-resolved= were configured to handle mDNS:
+
+#+begin_src conf
+# /etc/systemd/resolved.conf (before fix)
+[Resolve]
+MulticastDNS=yes
+#+end_src
+
+*Solution:* Disable mDNS in systemd-resolved, let Avahi handle it exclusively.
+
+#+begin_src conf
+# /etc/systemd/resolved.conf
+[Resolve]
+Domains=~local
+MulticastDNS=no
+#+end_src
+
+* Complete Fix Applied
+
+** Files Modified
+
+*** /etc/avahi/avahi-daemon.conf
+
+Changes made:
+#+begin_src diff
+-#allow-interfaces=eth0
++allow-interfaces=enp0s13f0u3
+
+-#publish-a-on-ipv6=no
++publish-a-on-ipv6=yes
+#+end_src
+
+*** /etc/systemd/resolved.conf
+
+Changes made:
+#+begin_src diff
+-MulticastDNS=yes
++MulticastDNS=no
+#+end_src
+
+** Services Restarted
+
+#+begin_src bash
+sudo systemctl restart systemd-resolved
+sudo systemctl restart avahi-daemon
+#+end_src
+
+* Verification
+
+After fixes:
+- Avahi runs as =velox.local= (not =velox-3.local=)
+- No mDNS stack conflict warning
+- From ratio: =avahi-resolve -n velox.local= returns =192.168.86.42=
+- From ratio: =curl http://velox.local:8384/= returns HTTP 200
+
+* Notes for archsetup
+
+These configurations should be added to the Arch setup scripts:
+
+1. Install avahi: =pacman -S avahi nss-mdns=
+
+2. Configure =/etc/avahi/avahi-daemon.conf=:
+ - Set =allow-interfaces= to physical LAN interface (determine dynamically or prompt user)
+ - Set =publish-a-on-ipv6=yes=
+
+3. Configure =/etc/systemd/resolved.conf=:
+ - Set =MulticastDNS=no= to avoid conflict with Avahi
+
+4. Enable and start avahi-daemon:
+ #+begin_src bash
+ systemctl enable --now avahi-daemon
+ #+end_src
+
+5. Ensure =/etc/nsswitch.conf= has mdns in hosts line:
+ #+begin_src conf
+ hosts: mymachines mdns_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] files dns
+ #+end_src
diff --git a/assets/dwm.desktop b/assets/dwm.desktop
new file mode 100644
index 0000000..16ba7b9
--- /dev/null
+++ b/assets/dwm.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=DWM
+Comment=It's DWM, asshole
+Terminal=false
+Exec=/usr/local/bin/startdwm
+TryExec=/usr/local/bin/startdwm
+Icon=dwm
+Type=Application
+
+[X-Window Manager]
+SessionManaged=True
\ No newline at end of file
diff --git a/assets/security-and-hardening-recommendations.txt b/assets/security-and-hardening-recommendations.txt
new file mode 100644
index 0000000..22a0c53
--- /dev/null
+++ b/assets/security-and-hardening-recommendations.txt
@@ -0,0 +1,119 @@
+# Security and Hardening Recommendations for archsetup
+
+These recommendations come from the install-archzfs base install.
+The base system is minimal - archsetup should handle hardening.
+
+## SSH Hardening (Priority: High)
+
+If SSH was enabled during install (for headless servers), it uses password auth.
+archsetup should:
+
+1. Install and configure fail2ban
+ - pacman -S fail2ban
+ - Enable sshd jail
+ - Configure ban times (suggested: 10m first offense, escalating)
+ - Consider integration with firewalld/nftables
+
+2. Switch to key-based authentication
+ - Prompt user for SSH public key or generate keypair
+ - Disable password authentication in /etc/ssh/sshd_config:
+ PasswordAuthentication no
+ PermitRootLogin prohibit-password (or 'no' for desktop)
+
+3. Consider changing default SSH port (optional, security through obscurity)
+
+## Firewall (Priority: High)
+
+Base install has no firewall configured. Options:
+
+1. firewalld (recommended for most users)
+ - pacman -S firewalld
+ - systemctl enable --now firewalld
+ - Default zone should block incoming except SSH
+
+2. nftables (for advanced users)
+ - Already installed as iptables backend
+ - Needs manual configuration
+
+3. ufw (simpler alternative)
+ - pacman -S ufw
+ - Good for users coming from Ubuntu
+
+## ZFS-Specific Recommendations
+
+1. Sanoid/Syncoid for automated snapshots
+ - pacman -S sanoid
+ - Configure /etc/sanoid/sanoid.conf for automatic snapshot retention
+ - Suggested policy: hourly for 24h, daily for 7d, monthly for 12m
+
+2. ZFS scrub timer
+ - systemctl enable zfs-scrub-weekly.timer
+ - Or create monthly timer for large pools
+
+3. ZED (ZFS Event Daemon) email alerts
+ - Configure /etc/zfs/zed.d/zed.rc
+ - Set ZED_EMAIL_ADDR for pool health notifications
+
+4. Consider zfs-auto-snapshot as alternative to sanoid
+
+## User Account Setup
+
+Base install only has root. archsetup should:
+
+1. Create primary user account with sudo access
+2. Lock root account for direct login (sudo only)
+3. Configure sudo timeout and logging
+
+## Package Manager Hardening
+
+1. Enable pacman hooks for security
+ - Verify package signatures (already default)
+
+2. Consider enabling reflector timer
+ - Keeps mirrorlist updated with fastest/most recent mirrors
+
+3. Install pacman-contrib for paccache
+ - Configure paccache.timer to clean old package cache
+
+## Automatic Updates (Optional)
+
+For servers that need unattended security updates:
+- Consider pacman-auto-update or similar
+- ZFS pre-pacman snapshots (already in install-archzfs) make this safer
+
+## AppArmor/SELinux (Optional, Advanced)
+
+For high-security environments:
+- AppArmor is easier: pacman -S apparmor
+- Requires kernel parameter: lsm=apparmor
+
+## Misc Recommendations
+
+1. Install and enable systemd-timesyncd or chrony for NTP
+
+2. Configure journald retention
+ - /etc/systemd/journald.conf
+ - SystemMaxUse=500M (or appropriate for system)
+
+3. Disable core dumps for security (optional)
+ - /etc/security/limits.conf: * hard core 0
+
+4. Install lynis for security auditing
+ - pacman -S lynis
+ - Run: lynis audit system
+
+## Desktop-Specific (if applicable)
+
+1. Consider firejail for sandboxing applications
+2. Install a password manager (pass, keepassxc)
+3. Configure automatic screen lock
+
+## Server-Specific (if applicable)
+
+1. Install and configure logwatch or logrotate
+2. Consider setting up centralized logging
+3. Install monitoring (prometheus node_exporter, netdata, etc.)
+
+---
+Generated by install-archzfs build system
+These are recommendations - implement based on your security requirements.
diff --git a/assets/wireguard/USCALA.conf b/assets/wireguard/USCALA.conf
new file mode 100644
index 0000000..7d902d4
--- /dev/null
+++ b/assets/wireguard/USCALA.conf
@@ -0,0 +1,15 @@
+[Interface]
+# Bouncing = 8
+# NetShield = 1
+# Moderate NAT = on
+# NAT-PMP (Port Forwarding) = off
+# VPN Accelerator = on
+PrivateKey = aDhBPBlyRGAtWz2eaP6mPmEC5e6uNJj/YFleWACZdEk=
+Address = 10.2.0.2/32
+DNS = 10.2.0.1
+
+[Peer]
+# US-CA#187
+PublicKey = fXtINk5LcWvNoCxNwx9WkmHieyyw+zIcLiiRM6eyECc=
+AllowedIPs = 0.0.0.0/0
+Endpoint = 146.70.174.162:51820
\ No newline at end of file
diff --git a/assets/wireguard/USCASF.conf b/assets/wireguard/USCASF.conf
new file mode 100644
index 0000000..7948ae4
--- /dev/null
+++ b/assets/wireguard/USCASF.conf
@@ -0,0 +1,16 @@
+[Interface]
+# Key for velox
+# Bouncing = 26
+# NetShield = 1
+# Moderate NAT = on
+# NAT-PMP (Port Forwarding) = off
+# VPN Accelerator = on
+PrivateKey = 4Al9epK8qlWSiASFx1D8YPtqaqdUKUA6SRQhfhmL81g=
+Address = 10.2.0.2/32
+DNS = 10.2.0.1
+
+[Peer]
+# US-CA#75
+PublicKey = xRu4XSIeCCNh4wQqit2w0PwAqzAs7JVA4zQqxGOhSSY=
+AllowedIPs = 0.0.0.0/0
+Endpoint = 79.127.185.222:51820
\ No newline at end of file
diff --git a/assets/wireguard/USDC.conf b/assets/wireguard/USDC.conf
new file mode 100644
index 0000000..62ede76
--- /dev/null
+++ b/assets/wireguard/USDC.conf
@@ -0,0 +1,15 @@
+[Interface]
+# Bouncing = 1
+# NetShield = 1
+# Moderate NAT = on
+# NAT-PMP (Port Forwarding) = off
+# VPN Accelerator = on
+PrivateKey = ODgff/xOftY7+v64+J9vPs9C2ZK83xepaM9+OdJUong=
+Address = 10.2.0.2/32
+DNS = 10.2.0.1
+
+[Peer]
+# US-DC#29
+PublicKey = 3Lz5VpqnS7wfnOWVYFNCFHl+JuuanJ/hB2TqOKQZxVI=
+AllowedIPs = 0.0.0.0/0
+Endpoint = 185.247.68.50:51820
\ No newline at end of file
diff --git a/assets/wireguard/USGAAT.conf b/assets/wireguard/USGAAT.conf
new file mode 100644
index 0000000..b4cfc7d
--- /dev/null
+++ b/assets/wireguard/USGAAT.conf
@@ -0,0 +1,15 @@
+[Interface]
+# Bouncing = 0
+# NetShield = 1
+# Moderate NAT = on
+# NAT-PMP (Port Forwarding) = off
+# VPN Accelerator = on
+PrivateKey = gMms305eLQY1Q/GTC1/nTffFh9ou4tIVzpQuWo0P6XU=
+Address = 10.2.0.2/32
+DNS = 10.2.0.1
+
+[Peer]
+# US-GA#319
+PublicKey = vrQlzOff8/CWCDVaesXMZLfQaOE4qrdY2BJUjWeRHyA=
+AllowedIPs = 0.0.0.0/0
+Endpoint = 149.22.94.113:51820
\ No newline at end of file
diff --git a/assets/wireguard/USNY.conf b/assets/wireguard/USNY.conf
new file mode 100644
index 0000000..ddf43a6
--- /dev/null
+++ b/assets/wireguard/USNY.conf
@@ -0,0 +1,16 @@
+[Interface]
+# Key for New York
+# Bouncing = 8
+# NetShield = 1
+# Moderate NAT = off
+# NAT-PMP (Port Forwarding) = off
+# VPN Accelerator = on
+PrivateKey = APAkVGvrTIXjgSCy9fUM7q4B9Fgj4M8PVbakpVEQQnE=
+Address = 10.2.0.2/32
+DNS = 10.2.0.1
+
+[Peer]
+# US-NY#524
+PublicKey = 8NeySGpnCMtwtgwVARpoCNonu9qxQxrE6hFztMcMDkA=
+AllowedIPs = 0.0.0.0/0
+Endpoint = 146.70.72.130:51820
\ No newline at end of file
diff --git a/assets/wireguard/switzerlan-zurich1.conf b/assets/wireguard/switzerlan-zurich1.conf
new file mode 100644
index 0000000..4d7908e
--- /dev/null
+++ b/assets/wireguard/switzerlan-zurich1.conf
@@ -0,0 +1,15 @@
+[Interface]
+# Bouncing = 18
+# NetShield = 1
+# Moderate NAT = off
+# NAT-PMP (Port Forwarding) = off
+# VPN Accelerator = on
+PrivateKey = CJSPw7zcMMBDJbQDYlwFvdVcXvvsGns592PiDHmhTks=
+Address = 10.2.0.2/32
+DNS = 10.2.0.1
+
+[Peer]
+# CH#185
+PublicKey = XPVCz7LndzqWe7y3+WSo51hvNOX8nX5CTwVTWhzg8g8=
+AllowedIPs = 0.0.0.0/0
+Endpoint = 149.88.27.234:51820
\ No newline at end of file
diff --git a/assets/wireguard/switzerlan-zurich2.conf b/assets/wireguard/switzerlan-zurich2.conf
new file mode 100644
index 0000000..c2d390f
--- /dev/null
+++ b/assets/wireguard/switzerlan-zurich2.conf
@@ -0,0 +1,15 @@
+[Interface]
+# Bouncing = 10
+# NetShield = 1
+# Moderate NAT = off
+# NAT-PMP (Port Forwarding) = off
+# VPN Accelerator = on
+PrivateKey = ACCwCDY+Y+RlSH2dSt+IumCBYAo5Sk4an9eXZKt8jEE=
+Address = 10.2.0.2/32
+DNS = 10.2.0.1
+
+[Peer]
+# CH#177
+PublicKey = XPVCz7LndzqWe7y3+WSo51hvNOX8nX5CTwVTWhzg8g8=
+AllowedIPs = 0.0.0.0/0
+Endpoint = 149.88.27.234:51820
\ No newline at end of file
diff --git a/dotfiles/.gitignore b/dotfiles/.gitignore
new file mode 100644
index 0000000..1ddb109
--- /dev/null
+++ b/dotfiles/.gitignore
@@ -0,0 +1,7 @@
+/system/.ssh/id_ed25519
+/system/.ssh/id_ed25519.pub
+/system/.config/calibre/dynamic.pickle.json
+/system/.config/calibre/gui.json
+/system/.config/calibre/tweak_book_gui.json
+/system/.config/sublime-merge/Packages/
+/system/.config/sublime-merge/Local/Session.sublime_session
diff --git a/dotfiles/hyprland/.config/gammastep/config.ini b/dotfiles/hyprland/.config/gammastep/config.ini
new file mode 100644
index 0000000..f50a701
--- /dev/null
+++ b/dotfiles/hyprland/.config/gammastep/config.ini
@@ -0,0 +1,25 @@
+[gammastep]
+; Color temperature in Kelvin (same as redshift)
+temp-day=6500
+temp-night=4500
+
+; Brightness (1.0 = full, 0.8 = 20% dimmed)
+brightness-day=1.0
+brightness-night=1.0
+
+; Gamma correction
+gamma=1.0
+
+; Fade between day/night
+fade=1
+
+; Wayland adjustment method (not randr)
+adjustment-method=wayland
+
+; Location for sunrise/sunset calculation
+location-provider=manual
+
+[manual]
+; New Orleans, LA
+lat=29.951065
+lon=-90.071533
diff --git a/dotfiles/hyprland/.config/hypr/hypridle.conf b/dotfiles/hyprland/.config/hypr/hypridle.conf
new file mode 100644
index 0000000..681c741
--- /dev/null
+++ b/dotfiles/hyprland/.config/hypr/hypridle.conf
@@ -0,0 +1,34 @@
+# Hypridle configuration
+# Replaces xautolock -time 5 -locker slock
+
+general {
+ lock_cmd = pidof hyprlock || hyprlock
+ before_sleep_cmd = loginctl lock-session
+ after_sleep_cmd = hyprctl dispatch dpms on
+}
+
+# Screen dim after 4.5 minutes
+listener {
+ timeout = 270
+ on-timeout = brightnessctl -s set 10%
+ on-resume = brightnessctl -r
+}
+
+# Lock screen after 5 minutes (matching xautolock -time 5)
+listener {
+ timeout = 300
+ on-timeout = loginctl lock-session
+}
+
+# Turn off screen after 10 minutes
+listener {
+ timeout = 600
+ on-timeout = hyprctl dispatch dpms off
+ on-resume = hyprctl dispatch dpms on
+}
+
+# Suspend after 30 minutes
+listener {
+ timeout = 1800
+ on-timeout = systemctl suspend
+}
diff --git a/dotfiles/hyprland/.config/hypr/hyprland.conf b/dotfiles/hyprland/.config/hypr/hyprland.conf
new file mode 100644
index 0000000..ce45088
--- /dev/null
+++ b/dotfiles/hyprland/.config/hypr/hyprland.conf
@@ -0,0 +1,287 @@
+# Hyprland Configuration
+# Translated from DWM config.def.h and sxhkdrc
+# Craig Jennings
+
+# ============================================================================
+# Monitor Configuration
+# ============================================================================
+monitor=,preferred,auto,auto
+
+# ============================================================================
+# Startup Applications
+# ============================================================================
+exec-once = waybar
+exec-once = swww-daemon && sleep 1 && swww img ~/pictures/wallpaper/dark-lion.jpg
+exec-once = dunst
+exec-once = hypridle
+exec-once = gammastep
+exec-once = nm-applet
+exec-once = blueman-applet
+exec-once = /usr/bin/gnome-keyring-daemon --start --components=pkcs11,secrets,ssh
+exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
+exec-once = mpd
+exec-once = signal-desktop --start-in-tray --ozone-platform=wayland
+exec-once = protonmail-bridge --no-window
+exec-once = insync start
+
+# ============================================================================
+# Environment Variables
+# ============================================================================
+env = XCURSOR_SIZE,24
+env = XDG_CURRENT_DESKTOP,Hyprland
+env = XDG_SESSION_TYPE,wayland
+env = XDG_SESSION_DESKTOP,Hyprland
+env = _JAVA_AWT_WM_NONREPARENTING,1
+
+# ============================================================================
+# Appearance (matching DWM colors)
+# ============================================================================
+# DWM colors: gray1=#222222, gray2=#444444, gray3=#bbbbbb, gray4=#eeeeee, cyan=#daa520
+
+general {
+ gaps_in = 5
+ gaps_out = 10
+ border_size = 3
+ col.active_border = rgba(daa520ff)
+ col.inactive_border = rgba(444444ff)
+ layout = master
+ resize_on_border = true
+}
+
+decoration {
+ rounding = 0
+ blur {
+ enabled = false
+ }
+ shadow {
+ enabled = false
+ }
+}
+
+animations {
+ enabled = true
+ bezier = myBezier, 0.05, 0.9, 0.1, 1.05
+ animation = windows, 1, 3, myBezier
+ animation = windowsOut, 1, 3, default, popin 80%
+ animation = fade, 1, 3, default
+ animation = workspaces, 1, 3, default
+}
+
+# ============================================================================
+# Layout (master-stack like DWM tile)
+# ============================================================================
+master {
+ new_status = slave
+ mfact = 0.55
+}
+
+dwindle {
+ pseudotile = true
+ preserve_split = true
+}
+
+# ============================================================================
+# Input
+# ============================================================================
+input {
+ kb_layout = us
+ kb_options = ctrl:nocaps
+ follow_mouse = 1
+ touchpad {
+ natural_scroll = true
+ }
+}
+
+# ============================================================================
+# Misc
+# ============================================================================
+misc {
+ force_default_wallpaper = 0
+ disable_hyprland_logo = true
+}
+
+# ============================================================================
+# XWayland
+# ============================================================================
+xwayland {
+ force_zero_scaling = true
+}
+
+# ============================================================================
+# Window Rules
+# ============================================================================
+# Floating windows (from DWM rules)
+windowrulev2 = float, class:^(Gimp)$
+windowrulev2 = float, class:^(caffeine)$
+windowrulev2 = float, class:^(blueman-services)$
+windowrulev2 = float, class:^(Blueman-manager)$
+windowrulev2 = float, class:^(qalculate-gtk)$
+windowrulev2 = float, title:^(Event Tester)$
+
+# ============================================================================
+# Key Bindings
+# ============================================================================
+$mod = SUPER
+
+# Terminal and core apps (from DWM)
+bind = $mod, T, exec, st
+bind = $mod, E, exec, emacsclient -c -a "" || emacs
+bind = $mod, W, exec, $BROWSER
+bind = $mod, P, exec, wofi --show drun
+
+# From sxhkdrc
+bind = $mod, SPACE, exec, wofi --show drun
+bind = $mod SHIFT, S, exec, wofi --show ssh
+bind = $mod SHIFT, W, exec, $ALTBROWSER
+bind = CTRL ALT, W, exec, tor-browser
+bind = CTRL ALT, F, exec, thunar
+bind = $mod, V, exec, virtualbox
+bind = $mod SHIFT, L, exec, calibre
+bind = $mod SHIFT, R, exec, shortwave
+
+# Window management (from DWM)
+bind = $mod, J, layoutmsg, cyclenext
+bind = $mod, K, layoutmsg, cycleprev
+bind = $mod SHIFT, J, layoutmsg, swapnext
+bind = $mod SHIFT, K, layoutmsg, swapprev
+bind = $mod, H, resizeactive, -50 0
+bind = $mod, L, resizeactive, 50 0
+bind = $mod, RETURN, layoutmsg, swapwithmaster
+bind = $mod, G, centerwindow
+bind = $mod, TAB, workspace, previous
+bind = $mod SHIFT, C, killactive
+
+# Layouts (from DWM)
+bind = $mod SHIFT, M, exec, hyprctl keyword general:layout master
+bind = $mod SHIFT, T, exec, hyprctl keyword general:layout master
+bind = $mod SHIFT, F, togglefloating
+bind = $mod SHIFT, SPACE, togglefloating
+
+# Master layout adjustments
+bind = $mod, I, layoutmsg, addmaster
+bind = $mod, D, layoutmsg, removemaster
+
+# Gaps (from DWM)
+bind = $mod, MINUS, exec, hyprctl keyword general:gaps_out $(( $(hyprctl getoption general:gaps_out -j | jq '.int') - 5 ))
+bind = $mod, EQUAL, exec, hyprctl keyword general:gaps_out $(( $(hyprctl getoption general:gaps_out -j | jq '.int') + 5 ))
+bind = $mod SHIFT, EQUAL, exec, hyprctl keyword general:gaps_out 10
+
+# Toggle bar
+bind = $mod, B, exec, killall -SIGUSR1 waybar
+
+# Fullscreen
+bind = $mod, F11, fullscreen, 0
+
+# Workspaces 1-9 (from DWM TAGKEYS)
+bind = $mod, 1, workspace, 1
+bind = $mod, 2, workspace, 2
+bind = $mod, 3, workspace, 3
+bind = $mod, 4, workspace, 4
+bind = $mod, 5, workspace, 5
+bind = $mod, 6, workspace, 6
+bind = $mod, 7, workspace, 7
+bind = $mod, 8, workspace, 8
+bind = $mod, 9, workspace, 9
+bind = $mod, 0, workspace, 10
+
+# Move window to workspace (from DWM tag)
+bind = $mod SHIFT, 1, movetoworkspace, 1
+bind = $mod SHIFT, 2, movetoworkspace, 2
+bind = $mod SHIFT, 3, movetoworkspace, 3
+bind = $mod SHIFT, 4, movetoworkspace, 4
+bind = $mod SHIFT, 5, movetoworkspace, 5
+bind = $mod SHIFT, 6, movetoworkspace, 6
+bind = $mod SHIFT, 7, movetoworkspace, 7
+bind = $mod SHIFT, 8, movetoworkspace, 8
+bind = $mod SHIFT, 9, movetoworkspace, 9
+bind = $mod SHIFT, 0, movetoworkspace, 10
+
+# Monitor focus (from DWM focusmon)
+bind = $mod, COMMA, focusmonitor, -1
+bind = $mod, PERIOD, focusmonitor, +1
+bind = $mod SHIFT, COMMA, movewindow, mon:-1
+bind = $mod SHIFT, PERIOD, movewindow, mon:+1
+
+# Scratchpads (from DWM togglescratch)
+# Audio mixer (was MODKEY+a -> spaudio/pulsemixer)
+bind = $mod, A, togglespecialworkspace, audio
+bind = $mod, A, exec, [workspace special:audio] pgrep -x pulsemixer || st -n spaudio -e pulsemixer
+windowrulev2 = float, class:^(st)$, title:^(spaudio)$
+windowrulev2 = size 60% 60%, class:^(st)$, title:^(spaudio)$
+windowrulev2 = center, class:^(st)$, title:^(spaudio)$
+windowrulev2 = workspace special:audio, class:^(st)$, title:^(spaudio)$
+
+# System monitor (was MODKEY+m -> spmon/gotop)
+bind = $mod, M, togglespecialworkspace, monitor
+bind = $mod, M, exec, [workspace special:monitor] pgrep -x gotop || st -n spmon -e gotop
+windowrulev2 = float, class:^(st)$, title:^(spmon)$
+windowrulev2 = size 60% 60%, class:^(st)$, title:^(spmon)$
+windowrulev2 = workspace special:monitor, class:^(st)$, title:^(spmon)$
+
+# Music player (was MODKEY+slash -> spmp/ncmpcpp)
+bind = $mod, SLASH, togglespecialworkspace, music
+bind = $mod, SLASH, exec, [workspace special:music] pgrep -x ncmpcpp || st -n spmp -e ncmpcpp
+windowrulev2 = float, class:^(st)$, title:^(spmp)$
+windowrulev2 = size 60% 60%, class:^(st)$, title:^(spmp)$
+windowrulev2 = workspace special:music, class:^(st)$, title:^(spmp)$
+
+# Terminal scratchpad (was MODKEY+Shift+Return -> spterm/tmux)
+bind = $mod SHIFT, RETURN, togglespecialworkspace, term
+bind = $mod SHIFT, RETURN, exec, [workspace special:term] pgrep -f "st.*spterm" || st -n spterm -e tmux
+windowrulev2 = float, class:^(st)$, title:^(spterm)$
+windowrulev2 = size 60% 60%, class:^(st)$, title:^(spterm)$
+windowrulev2 = workspace special:term, class:^(st)$, title:^(spterm)$
+
+# File manager (was MODKEY+f -> spfm/ranger)
+bind = $mod, F, togglespecialworkspace, files
+bind = $mod, F, exec, [workspace special:files] pgrep -x ranger || st -n spfm -e ranger
+windowrulev2 = float, class:^(st)$, title:^(spfm)$
+windowrulev2 = size 60% 60%, class:^(st)$, title:^(spfm)$
+windowrulev2 = workspace special:files, class:^(st)$, title:^(spfm)$
+
+# Calculator (was MODKEY+x or MODKEY+c -> qalculate)
+bind = $mod, X, exec, qalculate-gtk
+bind = $mod, C, exec, qalculate-gtk
+
+# Htop (was MODKEY+Shift+h -> sptop/htop)
+bind = $mod SHIFT, H, togglespecialworkspace, htop
+bind = $mod SHIFT, H, exec, [workspace special:htop] pgrep -x htop || st -n sptop -e htop
+windowrulev2 = float, class:^(st)$, title:^(sptop)$
+windowrulev2 = size 60% 60%, class:^(st)$, title:^(sptop)$
+windowrulev2 = workspace special:htop, class:^(st)$, title:^(sptop)$
+
+# Media/hardware keys
+bindel = , XF86AudioRaiseVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ +5%
+bindel = , XF86AudioLowerVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ -5%
+bindl = , XF86AudioMute, exec, pactl set-sink-mute @DEFAULT_SINK@ toggle
+bindel = , XF86MonBrightnessUp, exec, brightnessctl s +10%
+bindel = , XF86MonBrightnessDown, exec, brightnessctl s 10%-
+
+# Microphone toggle (from sxhkdrc)
+bind = CTRL ALT, SPACE, exec, amixer set Capture toggle && amixer get Capture | grep '\[off\]' && notify-send "Microphone" "Muted" || notify-send "Microphone" "On"
+
+# Bluetooth (from DWM and sxhkdrc)
+bind = $mod SHIFT, B, exec, blueman-manager
+
+# Screenshots (Wayland: grim + slurp instead of maim)
+bind = $mod, S, exec, grim -g "$(slurp)" ~/pictures/screenshots/$(date +%Y.%m.%d-%H%M%S).png && notify-send "Screenshot" "Saved to ~/pictures/screenshots/"
+bind = , Print, exec, grim ~/pictures/screenshots/$(date +%Y.%m.%d-%H%M%S).png && notify-send "Screenshot" "Full screen saved"
+
+# Color picker (Wayland: hyprpicker instead of xcolor)
+# bind = $mod, C, exec, hyprpicker -a # conflicts with calculator, uncomment if needed
+
+# Lock screen (from sxhkdrc: super+Escape)
+bind = $mod, ESCAPE, exec, hyprlock
+
+# Touchpad toggle
+bind = $mod, F9, exec, toggle-touchpad
+
+# Exit/session (from DWM)
+bind = $mod SHIFT, Q, exec, wofi --show dmenu --prompt "Exit?" -D dmenu/exit="0" < /dev/null && hyprctl dispatch exit
+bind = $mod SHIFT, BACKSPACE, exit
+bind = $mod SHIFT, ESCAPE, exec, hyprctl reload
+
+# Mouse bindings (from DWM buttons)
+bindm = $mod, mouse:272, movewindow
+bindm = $mod, mouse:273, resizewindow
+bindm = $mod SHIFT, mouse:272, resizewindow
diff --git a/dotfiles/hyprland/.config/hypr/hyprlock.conf b/dotfiles/hyprland/.config/hypr/hyprlock.conf
new file mode 100644
index 0000000..b4dcfb8
--- /dev/null
+++ b/dotfiles/hyprland/.config/hypr/hyprlock.conf
@@ -0,0 +1,75 @@
+# Hyprlock configuration
+# Matching DWM colors: gray1=#222222, accent=#daa520
+
+general {
+ disable_loading_bar = false
+ hide_cursor = true
+ grace = 0
+ no_fade_in = false
+}
+
+background {
+ monitor =
+ path = screenshot
+ blur_passes = 3
+ blur_size = 8
+ noise = 0.0117
+ contrast = 0.8916
+ brightness = 0.8172
+ vibrancy = 0.1696
+ vibrancy_darkness = 0.0
+}
+
+input-field {
+ monitor =
+ size = 300, 50
+ outline_thickness = 3
+ dots_size = 0.33
+ dots_spacing = 0.15
+ dots_center = true
+ dots_rounding = -1
+ outer_color = rgb(444444)
+ inner_color = rgb(222222)
+ font_color = rgb(bbbbbb)
+ fade_on_empty = true
+ fade_timeout = 1000
+ placeholder_text = Password...
+ hide_input = false
+ rounding = 0
+ check_color = rgb(daa520)
+ fail_color = rgb(d9534f)
+ fail_text = $FAIL ($ATTEMPTS)
+ fail_transition = 300
+ capslock_color = rgb(f0ad4e)
+ numlock_color = -1
+ bothlock_color = -1
+ invert_numlock = false
+ swap_font_color = false
+ position = 0, -20
+ halign = center
+ valign = center
+}
+
+label {
+ monitor =
+ text = $TIME
+ text_align = center
+ color = rgb(bbbbbb)
+ font_size = 64
+ font_family = FiraCode Nerd Font Mono
+ position = 0, 80
+ halign = center
+ valign = center
+}
+
+label {
+ monitor =
+ text = $USER
+ text_align = center
+ color = rgb(daa520)
+ font_size = 20
+ font_family = FiraCode Nerd Font Mono
+ position = 0, -80
+ halign = center
+ valign = center
+}
diff --git a/dotfiles/hyprland/.config/waybar/config b/dotfiles/hyprland/.config/waybar/config
new file mode 100644
index 0000000..3b3a90f
--- /dev/null
+++ b/dotfiles/hyprland/.config/waybar/config
@@ -0,0 +1,50 @@
+{
+ "layer": "top",
+ "position": "top",
+ "height": 30,
+ "spacing": 4,
+
+ "modules-left": ["hyprland/workspaces"],
+ "modules-center": ["hyprland/window"],
+ "modules-right": ["tray", "battery", "disk", "clock"],
+
+ "hyprland/workspaces": {
+ "format": "{name}",
+ "on-click": "activate",
+ "sort-by-number": true
+ },
+
+ "hyprland/window": {
+ "format": "{}",
+ "max-length": 50
+ },
+
+ "tray": {
+ "spacing": 10
+ },
+
+ "battery": {
+ "bat": "BAT0",
+ "interval": 30,
+ "states": {
+ "warning": 30,
+ "critical": 15
+ },
+ "format": " {capacity}% {icon}",
+ "format-charging": " {capacity}% ",
+ "format-plugged": " {capacity}% ",
+ "format-icons": ["", "", "", "", ""]
+ },
+
+ "disk": {
+ "interval": 30,
+ "format": " {used}/{total}",
+ "path": "/"
+ },
+
+ "clock": {
+ "interval": 30,
+ "format": " {:%a %B %d} {:%I:%M %p %Z}",
+ "tooltip-format": "{calendar}"
+ }
+}
diff --git a/dotfiles/hyprland/.config/waybar/style.css b/dotfiles/hyprland/.config/waybar/style.css
new file mode 100644
index 0000000..9411b46
--- /dev/null
+++ b/dotfiles/hyprland/.config/waybar/style.css
@@ -0,0 +1,68 @@
+/* Waybar style - matching DWM colors */
+/* gray1=#222222, gray2=#444444, gray3=#bbbbbb, gray4=#eeeeee, accent=#daa520 */
+
+* {
+ font-family: "FiraCode Nerd Font Mono", monospace;
+ font-size: 12px;
+ border: none;
+ border-radius: 0;
+ min-height: 0;
+}
+
+window#waybar {
+ background-color: #222222;
+ color: #bbbbbb;
+}
+
+#workspaces button {
+ padding: 0 8px;
+ color: #bbbbbb;
+ background-color: transparent;
+}
+
+#workspaces button:hover {
+ background-color: #444444;
+}
+
+#workspaces button.active {
+ color: #eeeeee;
+ background-color: #daa520;
+}
+
+#workspaces button.urgent {
+ background-color: #900000;
+}
+
+#window {
+ color: #bbbbbb;
+ padding: 0 10px;
+}
+
+#clock,
+#battery,
+#disk,
+#tray {
+ padding: 0 10px;
+ color: #bbbbbb;
+}
+
+#battery.charging {
+ color: #daa520;
+}
+
+#battery.warning:not(.charging) {
+ color: #f0ad4e;
+}
+
+#battery.critical:not(.charging) {
+ color: #d9534f;
+}
+
+#tray > .passive {
+ -gtk-icon-effect: dim;
+}
+
+#tray > .needs-attention {
+ -gtk-icon-effect: highlight;
+ background-color: #daa520;
+}
diff --git a/dotfiles/hyprland/.config/wofi/config b/dotfiles/hyprland/.config/wofi/config
new file mode 100644
index 0000000..c023353
--- /dev/null
+++ b/dotfiles/hyprland/.config/wofi/config
@@ -0,0 +1,12 @@
+show=drun
+width=480
+height=400
+always_parse_args=true
+show_all=false
+print_command=true
+layer=overlay
+insensitive=true
+prompt=Search
+image_size=24
+columns=1
+allow_images=true
diff --git a/dotfiles/hyprland/.config/wofi/style.css b/dotfiles/hyprland/.config/wofi/style.css
new file mode 100644
index 0000000..84072bf
--- /dev/null
+++ b/dotfiles/hyprland/.config/wofi/style.css
@@ -0,0 +1,59 @@
+/* Wofi style - matching rofi rounded-gray-dark theme */
+/* Colors: bg0=#383c4a, bg1=#2d303c, fg0=#cdd1dc, fg2=#969696 */
+
+window {
+ margin: 0px;
+ border: 1px solid #2d303c;
+ border-radius: 10px;
+ background-color: rgba(56, 60, 74, 0.94);
+ font-family: "FiraCode Nerd Font Mono";
+ font-size: 10pt;
+}
+
+#input {
+ margin: 12px;
+ padding: 12px 20px;
+ border: 1px solid #2d303c;
+ border-radius: 6px;
+ background-color: #2d303c;
+ color: #cdd1dc;
+}
+
+#input:focus {
+ border-color: #4a4e5c;
+}
+
+#inner-box {
+ margin: 0px 12px 12px 12px;
+}
+
+#outer-box {
+ margin: 0px;
+ padding: 0px;
+}
+
+#scroll {
+ margin: 0px;
+}
+
+#text {
+ margin: 0px;
+ padding: 8px 16px;
+ color: #cdd1dc;
+}
+
+#entry {
+ border-radius: 6px;
+}
+
+#entry:selected {
+ background-color: #2d303c;
+}
+
+#entry:selected #text {
+ color: #ffffff;
+}
+
+#img {
+ margin-right: 8px;
+}
diff --git a/dotfiles/system/.Xmodmap b/dotfiles/system/.Xmodmap
new file mode 100644
index 0000000..981e9e7
--- /dev/null
+++ b/dotfiles/system/.Xmodmap
@@ -0,0 +1,6 @@
+!! keycode 119 = Delete
+clear lock
+clear control
+keycode 66 = Control_L
+add control = Control_L Control_R
+
diff --git a/dotfiles/system/.Xresources b/dotfiles/system/.Xresources
new file mode 100644
index 0000000..fb81dfb
--- /dev/null
+++ b/dotfiles/system/.Xresources
@@ -0,0 +1,138 @@
+!! X Font Settings
+
+!! below is the framework desktop's ultrawide monitor dpi
+! Xft.dpi: 192
+!! below is the framework laptop dpi
+! Xft.dpi: 144
+!! ultrawide monitor dpi
+Xft.dpi: 109
+
+!! cursor settings
+Xcursor.size: 32
+Xcursor.theme: Vimix-white-cursors
+
+Xft.autohint: 0
+Xft.lcdfilter: lcddefault
+Xft.hintstyle: hintfull
+Xft.hinting: 1
+Xft.antialias: 1
+Xft.rgba: rgb
+
+!! Emacs
+Emacs*toolBar: 0
+
+!! Transparency (0-1):
+!! *.alpha: 0.8
+
+!! ============================================================================
+!! COLOR SCHEMES - Choose one by commenting/uncommenting the #define statements
+!! ============================================================================
+
+!! --- Dupre Theme (based on dupre-theme.el) ---
+#define dupre_fg #f0fef0
+#define dupre_bg #000000
+#define dupre_bg_alt #151311
+#define dupre_gray_dark #58574e
+#define dupre_gray #969385
+#define dupre_gray_light #d0cbc0
+#define dupre_black #000000
+#define dupre_red #d47c59
+#define dupre_green #a4ac64
+#define dupre_yellow #d7af5f
+#define dupre_blue #67809c
+#define dupre_magenta #b2c3cc
+#define dupre_cyan #8a9496
+#define dupre_white #f0fef0
+#define dupre_br_black #474544
+#define dupre_br_red #edb08f
+#define dupre_br_green #ccc768
+#define dupre_br_yellow #ffd75f
+#define dupre_br_blue #b2c3cc
+#define dupre_br_magenta #d9e2ff
+#define dupre_br_cyan #acb0b3
+#define dupre_br_white #f0fef0
+
+!! --- Gruvbox Theme ---
+! Uncomment these to use Gruvbox instead of Dupre
+! #define gruvbox_bg #282828
+! #define gruvbox_bg #000000
+! #define gruvbox_fg #ebdbb2
+! #define gruvbox_red #fb4934
+! #define gruvbox_green #b8bb26
+! #define gruvbox_yellow #fabd2f
+! #define gruvbox_blue #83a598
+! #define gruvbox_purple #d3869b
+! #define gruvbox_aqua #8ec07c
+! #define gruvbox_gray #928374
+! #define gruvbox_orange #fe8019
+
+!! --- ACTIVE COLOR SCHEME ---
+!! Change these aliases to switch themes quickly
+#define theme_fg dupre_fg
+#define theme_bg dupre_bg
+#define theme_black dupre_black
+#define theme_red dupre_red
+#define theme_green dupre_green
+#define theme_yellow dupre_yellow
+#define theme_blue dupre_blue
+#define theme_magenta dupre_magenta
+#define theme_cyan dupre_cyan
+#define theme_white dupre_white
+#define theme_br_black dupre_br_black
+#define theme_br_red dupre_br_red
+#define theme_br_green dupre_br_green
+#define theme_br_yellow dupre_br_yellow
+#define theme_br_blue dupre_br_blue
+#define theme_br_magenta dupre_br_magenta
+#define theme_br_cyan dupre_br_cyan
+#define theme_br_white dupre_br_white
+
+!! To switch to Gruvbox:
+!! 1. Uncomment the Gruvbox color definitions above
+!! 2. Change theme_* aliases to gruvbox_* (e.g., theme_fg to gruvbox_fg)
+!! 3. Run: xrdb ~/.Xresources && pidof st | xargs kill -s USR1
+
+!! ============================================================================
+
+! ST (suckless terminal) settings
+st.font: FiraCode Nerd Font Mono:size=12
+st.background: theme_bg
+st.foreground: theme_fg
+st.color0: theme_black
+st.color1: theme_red
+st.color2: theme_green
+st.color3: theme_yellow
+st.color4: theme_blue
+st.color5: theme_magenta
+st.color6: theme_cyan
+st.color7: theme_white
+st.color8: theme_br_black
+st.color9: theme_br_red
+st.color10: theme_br_green
+st.color11: theme_br_yellow
+st.color12: theme_br_blue
+st.color13: theme_br_magenta
+st.color14: theme_br_cyan
+st.color15: theme_br_white
+
+! XTerm settings
+XTerm*faceName: FiraCode Nerd Font Mono:size=12
+XTerm*selectToClipboard: true
+XTerm*background: theme_bg
+XTerm*foreground: theme_fg
+XTerm*color0: theme_black
+XTerm*color1: theme_red
+XTerm*color2: theme_green
+XTerm*color3: theme_yellow
+XTerm*color4: theme_blue
+XTerm*color5: theme_magenta
+XTerm*color6: theme_cyan
+XTerm*color7: theme_white
+XTerm*color8: theme_br_black
+XTerm*color9: theme_br_red
+XTerm*color10: theme_br_green
+XTerm*color11: theme_br_yellow
+XTerm*color12: theme_br_blue
+XTerm*color13: theme_br_magenta
+XTerm*color14: theme_br_cyan
+XTerm*color15: theme_br_white
diff --git a/dotfiles/system/.authcode b/dotfiles/system/.authcode
new file mode 100644
index 0000000..d67b6ee
--- /dev/null
+++ b/dotfiles/system/.authcode
@@ -0,0 +1 @@
+be251501
diff --git a/dotfiles/system/.authinfo.gpg b/dotfiles/system/.authinfo.gpg
new file mode 100644
index 0000000..fa213c0
--- /dev/null
+++ b/dotfiles/system/.authinfo.gpg
@@ -0,0 +1,12 @@
+machine cjennings.net login root port su password cmjDase1ngoat13
+machine localhost port sudo login root password cmjdase1n
+machine imap.gmail.com login craigmartinjennings@gmail.com port 993 password etnkalyscubewbfp
+machine smtp.gmail.com login craigmartinjennings@gmail.com port 587 password sryskmsxxuhlgcdy
+machine mail.cjennings.net user c@cjennings.net password cmjDase1ngoat13
+machine api.openai.com login apikey password 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
+machine irc.libera.chat login craigjennings password cmjdase1n
+machine api.github.com login cjennings^forge password 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
+machine api.anthropic.com login apikey password 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
+machine api.anthropic.com login personal password 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
+machine org-gcal login 491339091045-sjje1r54s22vn2ugh45khndjafp89vto.apps.googleusercontent.com password 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
+machine api.assemblyai.com login api password bc4dbd87f9214cd8ac82c5708d7c4e53
\ No newline at end of file
diff --git a/dotfiles/system/.bash_logout b/dotfiles/system/.bash_logout
new file mode 100644
index 0000000..1e36d02
--- /dev/null
+++ b/dotfiles/system/.bash_logout
@@ -0,0 +1,6 @@
+# ~/.bash_logout: executed by bash(1) when login shell exits.
+
+# clear the screen to increase privacy when leaving the console
+if [ "$SHLVL" = 1 ]; then
+ [ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
+fi
diff --git a/dotfiles/system/.bashrc b/dotfiles/system/.bashrc
new file mode 100644
index 0000000..b5290cd
--- /dev/null
+++ b/dotfiles/system/.bashrc
@@ -0,0 +1,59 @@
+#!/bin/bash
+# cjennings .bashrc
+
+# tells shellcheck not to follow references to other files
+# shellcheck source=/dev/null
+
+# If not running interactively, don't do anything
+case $- in
+ *i*) ;;
+ *) return;;
+esac
+
+# env variables, aliases, and functions that are not bash specific
+source "$HOME"/.profile
+
+# don't put duplicate lines or lines starting with space in the history.
+HISTCONTROL=ignoreboth
+
+# infinite history
+HISTSIZE=HISTFILESIZE=
+
+# append and reload the history after each command
+PROMPT_COMMAND="history -a; history -n"
+
+# ignore the following commands from the history
+HISTIGNORE="ls:ll:cd:pwd:bg:fg:history"
+
+# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
+HISTSIZE=100000
+HISTFILESIZE=10000000
+
+# append to the history file, don't overwrite it
+shopt -s histappend
+
+# cd to directory by typing its name
+shopt -s autocd
+
+# check window size after each command + update LINES and COLUMNS values.
+shopt -s checkwinsize
+
+# enable programmable completion features
+if ! shopt -oq posix; then
+ if [ -f /usr/share/bash-completion/bash_completion ]; then
+ . /usr/share/bash-completion/bash_completion
+ elif [ -f /etc/bash_completion ]; then
+ . /etc/bash_completion
+ fi
+fi
+if [ -f /etc/bash_completion ]; then
+ . /etc/bash_completion
+fi
+
+export PS1="[\d, \t] \u@\H:\w \n$ "
+
+source "$HOME"/.profile
+
+[ -f "$HOME"/.fzf.bash ] && source "$HOME"/.fzf.bash
+. "/home/cjennings/.deno/env"
+source /home/cjennings/.local/share/bash-completion/completions/deno.bash
\ No newline at end of file
diff --git a/dotfiles/system/.config/.cmailpass.gpg b/dotfiles/system/.config/.cmailpass.gpg
new file mode 100644
index 0000000..e2f102e
--- /dev/null
+++ b/dotfiles/system/.config/.cmailpass.gpg
@@ -0,0 +1 @@
+
LFLJEdM0+G 5nn])>{\ޛ\KZȘݝG>ZėӶKv!>W~<
\ No newline at end of file
diff --git a/dotfiles/system/.config/.gmailpass.gpg b/dotfiles/system/.config/.gmailpass.gpg
new file mode 100644
index 0000000..cea3fe1
--- /dev/null
+++ b/dotfiles/system/.config/.gmailpass.gpg
@@ -0,0 +1 @@
+
q~9KEp[,/Fd?aNT҆o%#JW-rsW_dMG>v~BzW[hQr
\ No newline at end of file
diff --git a/dotfiles/system/.config/.tidal-dl.json b/dotfiles/system/.config/.tidal-dl.json
new file mode 100644
index 0000000..9c39da2
--- /dev/null
+++ b/dotfiles/system/.config/.tidal-dl.json
@@ -0,0 +1 @@
+{"albumFolderFormat": "{ArtistName}/{AlbumTitle} ({AlbumYear})", "apiKeyIndex": 1, "audioQuality": "HiFi", "checkExist": true, "downloadDelay": true, "downloadPath": "/home/cjennings/music/", "includeEP": true, "language": "0", "lyricFile": false, "multiThread": true, "playlistFolderFormat": "{PlaylistName}", "saveAlbumInfo": false, "saveCovers": true, "showProgress": false, "showTrackInfo": false, "trackFileFormat": "{TrackNumber} {TrackTitle}", "usePlaylistFolder": false, "videoFileFormat": "{VideoNumber} - {ArtistName} - {VideoTitle}", "videoQuality": "P1080"}
\ No newline at end of file
diff --git a/dotfiles/system/.config/.tidal-dl.token.json b/dotfiles/system/.config/.tidal-dl.token.json
new file mode 100644
index 0000000..64396c3
--- /dev/null
+++ b/dotfiles/system/.config/.tidal-dl.token.json
@@ -0,0 +1 @@
+eyJhY2Nlc3NUb2tlbiI6ICJleUpyYVdRaU9pSjJPVTFHYkZocVdTSXNJbUZzWnlJNklrVlRNalUySW4wLmV5SjBlWEJsSWpvaWJ6SmZZV05qWlhOeklpd2lkV2xrSWpvek5qa3pOems0TUN3aWMyTnZjR1VpT2lKeVgzVnpjaUIzWDNWemNpQjNYM04xWWlJc0ltZFdaWElpT2pBc0luTldaWElpT2pFc0ltTnBaQ0k2TkRJMU9Td2laWGh3SWpveE56WXdOamcwT1RreUxDSnphV1FpT2lKaE5UTXpaalV3WkMxa056azVMVFExTXpJdE9UUmlNUzAwTWpFd1pqVXdOakZqTURRaUxDSnBjM01pT2lKb2RIUndjem92TDJGMWRHZ3VkR2xrWVd3dVkyOXRMM1l4SW4wLk9VaFJNNFlRMXNJQzB0QTVTX0ptck1FWjlzWmMxcUVwVzlNa203YUlIbDZKQlVxdGdGbnJFcFFBTmx5dFNYYXFDa0E3NWJ2aUV6TldGUE5iQmxyQUh3IiwgImNvdW50cnlDb2RlIjogIlVTIiwgImV4cGlyZXNBZnRlciI6IDE3NjA2ODQ5OTIuMzYzMTA4MiwgInJlZnJlc2hUb2tlbiI6ICJleUpyYVdRaU9pSm9VekZLWVRkVk1DSXNJbUZzWnlJNklrVlROVEV5SW4wLmV5SjBlWEJsSWpvaWJ6SmZjbVZtY21WemFDSXNJblZwWkNJNk16WTVNemM1T0RBc0luTmpiM0JsSWpvaWNsOTFjM0lnZDE5MWMzSWdkMTl6ZFdJaUxDSmphV1FpT2pReU5Ua3NJbk5XWlhJaU9qRXNJbWRXWlhJaU9qQXNJbWx6Y3lJNkltaDBkSEJ6T2k4dllYVjBhQzUwYVdSaGJDNWpiMjB2ZGpFaWZRLkFhQ1FFV1ZvWlZGU2p1NXpPLWJiMXV4QmpGZV9FVG1nWUhRRlpIb1BJQ2xnQW1IcWFBWUY2OXFacURmN0lpVktyLVBmdTRTN1p6ZHhiM3E4Mm5xY2F0WGtBRDNmV01sS0VDekxoNGwxTE50ODBFaU9LZk51T2lWZ2pnMmxpMzdweGltR3RuNGdybW5LS3pUTVlHV0dfSnBHLXY1SWRDdjdYVmtyRTRMNFpEM05DSGo1IiwgInVzZXJpZCI6IDM2OTM3OTgwfQ==
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/app-internal-state.db b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/app-internal-state.db
new file mode 100644
index 0000000..656dfd5
Binary files /dev/null and b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/app-internal-state.db differ
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/early-access-registry.txt b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/early-access-registry.txt
new file mode 100644
index 0000000..644afaf
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/early-access-registry.txt
@@ -0,0 +1,2 @@
+ide.experimental.ui
+true
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/colors.scheme.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/colors.scheme.xml
new file mode 100644
index 0000000..19c4baa
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/colors.scheme.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/console-font.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/console-font.xml
new file mode 100644
index 0000000..2c67ad2
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/console-font.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor-font.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor-font.xml
new file mode 100644
index 0000000..f03edf7
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor-font.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor.xml
new file mode 100644
index 0000000..b43f2d4
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/editor.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/features.usage.statistics.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/features.usage.statistics.xml
new file mode 100644
index 0000000..ac11abf
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/features.usage.statistics.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/filetypes.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/filetypes.xml
new file mode 100644
index 0000000..099baf9
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/filetypes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide-features-trainer.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide-features-trainer.xml
new file mode 100644
index 0000000..54b8902
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide-features-trainer.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide.general.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide.general.xml
new file mode 100644
index 0000000..1df1fe5
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/ide.general.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/log-categories.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/log-categories.xml
new file mode 100644
index 0000000..3ff0f01
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/log-categories.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/other.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/other.xml
new file mode 100644
index 0000000..76bc172
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/other.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/settingsSync.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/settingsSync.xml
new file mode 100644
index 0000000..6c9eb82
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/settingsSync.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/updates.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/updates.xml
new file mode 100644
index 0000000..089dbf6
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/updates.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/window.state.xml b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/window.state.xml
new file mode 100644
index 0000000..078d8f1
--- /dev/null
+++ b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/options/window.state.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/updatedBrokenPlugins.db b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/updatedBrokenPlugins.db
new file mode 100644
index 0000000..63a47f8
Binary files /dev/null and b/dotfiles/system/.config/JetBrains/PyCharmCE2024.1/updatedBrokenPlugins.db differ
diff --git a/dotfiles/system/.config/Thunar/accels.scm b/dotfiles/system/.config/Thunar/accels.scm
new file mode 100644
index 0000000..1428351
--- /dev/null
+++ b/dotfiles/system/.config/Thunar/accels.scm
@@ -0,0 +1,138 @@
+; thunar GtkAccelMap rc-file -*- scheme -*-
+; this file is an automated accelerator map dump
+;
+; (gtk_accel_path "/ThunarDetailsView/expandable-folders" "")
+; (gtk_accel_path "/ThunarStandardView/sort-by-type" "")
+; (gtk_accel_path "/ThunarStatusBar/toggle-last-modified" "")
+; (gtk_accel_path "/ThunarBookmarks/d773562babb56e1a529a1bd5d640df62" "")
+; (gtk_accel_path "/ThunarBookmarks/0210e6411e61004cc4c7c68612c8463b" "")
+; (gtk_accel_path "/Thunarwindow/menu" "")
+; (gtk_accel_path "/ThunarBookmarks/f4268bf81222b9956e74a8c7903b31d1" "")
+; (gtk_accel_path "/ThunarActionManager/cut" "x")
+; (gtk_accel_path "/ThunarStandardView/sort-by-size" "")
+; (gtk_accel_path "/ThunarWindow/file-menu" "")
+; (gtk_accel_path "/ThunarWindow/close-tab" "w")
+; (gtk_accel_path "/ThunarWindow/switch-previous-tab-alt" "ISO_Left_Tab")
+; (gtk_accel_path "/ThunarStatusBar/toggle-size" "")
+; (gtk_accel_path "/ThunarWindow/new-window" "n")
+; (gtk_accel_path "/ThunarWindow/clear-directory-specific-settings" "")
+(gtk_accel_path "/ThunarWindow/close-window" "q")
+; (gtk_accel_path "/ThunarWindow/open-parent" "Up")
+; (gtk_accel_path "/ThunarWindow/view-side-pane-menu" "")
+; (gtk_accel_path "/ThunarStatusBar/toggle-size-in-bytes" "")
+; (gtk_accel_path "/ThunarWindow/switch-previous-tab" "Page_Up")
+; (gtk_accel_path "/ThunarActionManager/open" "o")
+; (gtk_accel_path "/ThunarStandardView/sort-ascending" "")
+; (gtk_accel_path "/ThunarWindow/toggle-split-view" "F3")
+; (gtk_accel_path "/ThunarActionManager/copy-2" "Insert")
+; (gtk_accel_path "/ThunarActionManager/trash-delete" "Delete")
+; (gtk_accel_path "/ThunarBookmarks/fd67aefaf5d114765fe8b24c4c89f7e9" "")
+; (gtk_accel_path "/ThunarWindow/open-recent" "")
+; (gtk_accel_path "/ThunarWindow/view-configure-toolbar" "")
+; (gtk_accel_path "/ThunarStandardView/forward" "Right")
+; (gtk_accel_path "/ThunarActionManager/restore" "")
+; (gtk_accel_path "/ThunarWindow/open-location-alt" "d")
+; (gtk_accel_path "/ThunarBookmarks/b89949686b39b7851b3f8ca433368c2b" "")
+; (gtk_accel_path "/ThunarStandardView/select-by-pattern" "s")
+; (gtk_accel_path "/ThunarWindow/zoom-out-alt" "KP_Subtract")
+; (gtk_accel_path "/ThunarWindow/contents" "F1")
+; (gtk_accel_path "/ThunarWindow/open-file-menu" "F10")
+; (gtk_accel_path "/ThunarBookmarks/1ac44f6cd07428a163baf754f81a9849" "")
+; (gtk_accel_path "/ThunarBookmarks/f659214f67b5f81d4695959941c2fe11" "")
+; (gtk_accel_path "/ThunarWindow/show-highlight" "")
+; (gtk_accel_path "/ThunarStandardView/sort-descending" "")
+; (gtk_accel_path "/ThunarStandardView/sort-by-name" "")
+; (gtk_accel_path "/ThunarStandardView/select-all-files" "a")
+; (gtk_accel_path "/ThunarActionManager/execute" "")
+; (gtk_accel_path "/ThunarStandardView/properties" "Return")
+; (gtk_accel_path "/ThunarActionManager/cut-2" "")
+; (gtk_accel_path "/ThunarStandardView/sort-by-dtime" "")
+; (gtk_accel_path "/ThunarWindow/switch-next-tab" "Page_Down")
+; (gtk_accel_path "/ThunarWindow/open-templates" "")
+; (gtk_accel_path "/ThunarActionManager/paste-2" "Insert")
+; (gtk_accel_path "/ThunarStatusBar/toggle-filetype" "")
+; (gtk_accel_path "/ThunarWindow/close-all-windows" "w")
+; (gtk_accel_path "/ThunarStandardView/create-document" "")
+; (gtk_accel_path "/ThunarWindow/detach-tab" "")
+; (gtk_accel_path "/ThunarWindow/cancel-search" "Escape")
+; (gtk_accel_path "/ThunarWindow/zoom-in-alt2" "equal")
+; (gtk_accel_path "/ThunarStatusBar/toggle-hidden-count" "")
+; (gtk_accel_path "/ThunarShortcutsPane/sendto-shortcuts" "d")
+; (gtk_accel_path "/ThunarActionManager/undo" "z")
+; (gtk_accel_path "/ThunarStandardView/toggle-sort-order" "")
+; (gtk_accel_path "/ThunarWindow/zoom-out" "minus")
+; (gtk_accel_path "/ThunarWindow/view-location-selector-entry" "")
+; (gtk_accel_path "/ThunarActionManager/paste" "v")
+; (gtk_accel_path "/ThunarWindow/zoom-in-alt1" "KP_Add")
+; (gtk_accel_path "/ThunarWindow/view-menubar" "m")
+; (gtk_accel_path "/ThunarStandardView/back" "Left")
+; (gtk_accel_path "/ThunarWindow/open-desktop" "")
+; (gtk_accel_path "/ThunarWindow/view-as-detailed-list" "2")
+; (gtk_accel_path "/ThunarActionManager/restore-show" "")
+; (gtk_accel_path "/ThunarWindow/sendto-menu" "")
+; (gtk_accel_path "/ThunarStatusBar/toggle-display-name" "")
+; (gtk_accel_path "/ThunarBookmarks/a15ad706188e797cac4c8dd8aa3b613e" "")
+; (gtk_accel_path "/ThunarWindow/go-menu" "")
+; (gtk_accel_path "/ThunarWindow/remove-from-recent" "")
+; (gtk_accel_path "/ThunarActionManager/open-with-other" "")
+; (gtk_accel_path "/ThunarStandardView/invert-selection" "i")
+; (gtk_accel_path "/ThunarBookmarks/8f10b72b429dd160dc70d6f7cc168a28" "")
+; (gtk_accel_path "/ThunarWindow/view-side-pane-shortcuts" "b")
+; (gtk_accel_path "/ThunarWindow/reload-alt-2" "Reload")
+; (gtk_accel_path "/ThunarWindow/view-location-selector-menu" "")
+; (gtk_accel_path "/ThunarStandardView/sort-by-mtime" "")
+; (gtk_accel_path "/ThunarWindow/edit-menu" "")
+; (gtk_accel_path "/ThunarActionManager/copy" "c")
+; (gtk_accel_path "/ThunarStandardView/unselect-all-files" "Escape")
+; (gtk_accel_path "/ThunarStandardView/forward-alt" "Forward")
+; (gtk_accel_path "/ThunarActionManager/move-to-trash" "")
+; (gtk_accel_path "/ThunarWindow/reload-alt-1" "F5")
+; (gtk_accel_path "/ThunarActionManager/delete-3" "KP_Delete")
+; (gtk_accel_path "/ThunarStandardView/arrange-items-menu" "")
+; (gtk_accel_path "/ThunarWindow/reload" "r")
+; (gtk_accel_path "/ThunarWindow/contents/help-menu" "")
+; (gtk_accel_path "/ThunarWindow/bookmarks-menu" "")
+; (gtk_accel_path "/ThunarBookmarks/dd7b2f7f1acb316e06e8de82ceff0f08" "")
+; (gtk_accel_path "/ThunarWindow/open-computer" "")
+; (gtk_accel_path "/ThunarWindow/toggle-image-preview" "")
+; (gtk_accel_path "/ThunarWindow/toggle-side-pane" "F9")
+; (gtk_accel_path "/ThunarWindow/view-as-icons" "1")
+; (gtk_accel_path "/ThunarActionManager/delete-2" "Delete")
+; (gtk_accel_path "/ThunarWindow/zoom-in" "plus")
+; (gtk_accel_path "/ThunarStandardView/configure-columns" "")
+; (gtk_accel_path "/ThunarStandardView/rename" "F2")
+; (gtk_accel_path "/ThunarWindow/open-location" "l")
+; (gtk_accel_path "/ThunarWindow/view-as-compact-list" "3")
+; (gtk_accel_path "/ThunarBookmarks/39adb4b734832c8ccc67032e77081c1f" "")
+; (gtk_accel_path "/ThunarWindow/view-menu" "")
+; (gtk_accel_path "/ThunarWindow/search" "f")
+; (gtk_accel_path "/ThunarWindow/new-tab" "t")
+; (gtk_accel_path "/ThunarWindow/zoom-reset" "0")
+; (gtk_accel_path "/ThunarStandardView/back-alt2" "Back")
+; (gtk_accel_path "/ThunarActionManager/open-in-new-tab" "p")
+; (gtk_accel_path "/ThunarWindow/view-location-selector-buttons" "")
+; (gtk_accel_path "/ThunarActionManager/redo" "z")
+; (gtk_accel_path "/ThunarWindow/open-trash" "")
+; (gtk_accel_path "/ThunarActionManager/open-in-new-window" "o")
+; (gtk_accel_path "/ThunarWindow/view-statusbar" "")
+; (gtk_accel_path "/ThunarActionManager/open-location" "")
+; (gtk_accel_path "/ThunarStandardView/duplicate" "")
+; (gtk_accel_path "/ThunarStandardView/back-alt1" "BackSpace")
+; (gtk_accel_path "/ThunarActionManager/trash-delete-2" "KP_Delete")
+; (gtk_accel_path "/ThunarStandardView/create-folder" "n")
+; (gtk_accel_path "/ThunarWindow/open-home" "Home")
+; (gtk_accel_path "/ThunarWindow/switch-focused-split-view-pane" "")
+; (gtk_accel_path "/ThunarWindow/show-hidden" "h")
+; (gtk_accel_path "/ThunarStandardView/set-default-app" "")
+; (gtk_accel_path "/ThunarWindow/empty-trash" "")
+; (gtk_accel_path "/ThunarWindow/preferences" "")
+; (gtk_accel_path "/ThunarActionManager/delete" "")
+; (gtk_accel_path "/ThunarWindow/open-network" "")
+; (gtk_accel_path "/ThunarWindow/view-side-pane-tree" "e")
+; (gtk_accel_path "/ThunarWindow/open-file-system" "")
+; (gtk_accel_path "/ThunarWindow/search-alt" "Search")
+; (gtk_accel_path "/ThunarWindow/switch-next-tab-alt" "Tab")
+; (gtk_accel_path "/ThunarActionManager/sendto-desktop" "")
+; (gtk_accel_path "/ThunarStandardView/make-link" "")
+; (gtk_accel_path "/ThunarWindow/zoom-reset-alt" "KP_0")
+; (gtk_accel_path "/ThunarWindow/about" "")
diff --git a/dotfiles/system/.config/Thunar/uca.xml b/dotfiles/system/.config/Thunar/uca.xml
new file mode 100644
index 0000000..0ade63f
--- /dev/null
+++ b/dotfiles/system/.config/Thunar/uca.xml
@@ -0,0 +1,15 @@
+
+
+
+ utilities-terminal
+ Open Terminal Here
+
+ 1731361150647694-1
+ exo-open --working-directory %f --launch TerminalEmulator
+ Example for a custom action
+
+ *
+
+
+
+
diff --git a/dotfiles/system/.config/audacious/QtUi.conf b/dotfiles/system/.config/audacious/QtUi.conf
new file mode 100644
index 0000000..18b3f4c
--- /dev/null
+++ b/dotfiles/system/.config/audacious/QtUi.conf
@@ -0,0 +1,3 @@
+[General]
+geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\xe\0\0\x4g\0\0\x2\xef\0\0\0\0\0\0\0\xe\0\0\x4g\0\0\x2\xef\0\0\0\0\0\0\0\0\x4h\0\0\0\0\0\0\0\xe\0\0\x4g\0\0\x2\xef)
+windowState=@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\0\0\0\x4h\0\0\x2\x93\0\0\0\x4\0\0\0\x4\0\0\0\b\0\0\0\b\xfc\0\0\0\x1\0\0\0\x2\0\0\0\x1\0\0\0\x16\0M\0\x61\0i\0n\0T\0o\0o\0l\0\x42\0\x61\0r\x1\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\0)
diff --git a/dotfiles/system/.config/audacious/config b/dotfiles/system/.config/audacious/config
new file mode 100644
index 0000000..fd1fdd4
--- /dev/null
+++ b/dotfiles/system/.config/audacious/config
@@ -0,0 +1,23 @@
+
+[audacious]
+repeat=TRUE
+
+[audgui]
+filesel_path=/home/cjennings/playlists/andor
+
+[audqt]
+icon_theme=audacious-flat
+theme=dark
+
+[qtui]
+player_height=738
+player_width=1128
+
+[skins]
+equalizer_x=178
+equalizer_y=22
+player_x=614
+player_y=442
+playlist_x=674
+playlist_y=181
+skin=/home/cjennings/.local/share/audacious/Skins/nadamp.zip
diff --git a/dotfiles/system/.config/audacious/playlist-state b/dotfiles/system/.config/audacious/playlist-state
new file mode 100644
index 0000000..985b3d2
--- /dev/null
+++ b/dotfiles/system/.config/audacious/playlist-state
@@ -0,0 +1,12 @@
+active 1
+playing 1
+playlist 0
+position 0
+shuffle 0
+resume-state 1
+resume-time 84873
+playlist 1
+position 0
+shuffle 0
+resume-state 1
+resume-time 5276
diff --git a/dotfiles/system/.config/audacious/playlists/1000.audpl b/dotfiles/system/.config/audacious/playlists/1000.audpl
new file mode 100644
index 0000000..3334184
--- /dev/null
+++ b/dotfiles/system/.config/audacious/playlists/1000.audpl
@@ -0,0 +1,5 @@
+title=New%20Playlist
+uri=file:///home/cjennings/playlists/andor/andor.opus
+length=215907
+bitrate=128
+codec=Opus
diff --git a/dotfiles/system/.config/audacious/playlists/order b/dotfiles/system/.config/audacious/playlists/order
new file mode 100644
index 0000000..c9a7c13
--- /dev/null
+++ b/dotfiles/system/.config/audacious/playlists/order
@@ -0,0 +1 @@
+1000 1001
\ No newline at end of file
diff --git a/dotfiles/system/.config/audacious/plugin-registry b/dotfiles/system/.config/audacious/plugin-registry
new file mode 100644
index 0000000..9cedebb
--- /dev/null
+++ b/dotfiles/system/.config/audacious/plugin-registry
@@ -0,0 +1,926 @@
+format 11
+transport /usr/lib/audacious/Transport/gio.so
+stamp 1758887725
+version 48
+flags 0
+name GIO Plugin
+domain audacious-plugins
+priority 0
+about 1
+config 0
+enabled 1
+scheme ftp
+scheme sftp
+scheme smb
+scheme mtp
+transport /usr/lib/audacious/Transport/mms.so
+stamp 1758887725
+version 48
+flags 0
+name MMS Plugin
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+scheme mms
+transport /usr/lib/audacious/Transport/neon.so
+stamp 1758887725
+version 48
+flags 0
+name Neon HTTP/HTTPS Plugin
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+scheme http
+scheme https
+playlist /usr/lib/audacious/Container/asx.so
+stamp 1758887725
+version 48
+flags 0
+name ASXv1/ASXv2 Playlists
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+ext asx
+saves 0
+playlist /usr/lib/audacious/Container/asx3.so
+stamp 1758887725
+version 48
+flags 0
+name ASXv3 Playlists
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+ext asx
+saves 1
+playlist /usr/lib/audacious/Container/audpl.so
+stamp 1758887725
+version 48
+flags 0
+name Audacious Playlists (audpl)
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+ext audpl
+saves 1
+playlist /usr/lib/audacious/Container/cue.so
+stamp 1758887725
+version 48
+flags 0
+name Cue Sheet Plugin
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+ext cue
+saves 0
+playlist /usr/lib/audacious/Container/m3u.so
+stamp 1758887725
+version 48
+flags 0
+name M3U Playlists
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+ext m3u
+ext m3u8
+saves 1
+playlist /usr/lib/audacious/Container/pls.so
+stamp 1758887725
+version 48
+flags 0
+name PLS Playlists
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+ext pls
+saves 1
+playlist /usr/lib/audacious/Container/xspf.so
+stamp 1758887725
+version 48
+flags 0
+name XML Shareable Playlists (XSPF)
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+ext xspf
+saves 1
+input /usr/lib/audacious/Input/xsf.so
+stamp 1758887725
+version 48
+flags 0
+name 2SF Decoder
+domain audacious-plugins
+priority 5
+about 0
+config 1
+enabled 1
+scheme 2sf
+scheme mini2sf
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/aac-raw.so
+stamp 1758887725
+version 48
+flags 0
+name AAC (Raw) Decoder
+domain audacious-plugins
+priority 5
+about 0
+config 0
+enabled 1
+scheme aac
+ext audio/aac
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/amidi-plug.so
+stamp 1758887725
+version 48
+flags 0
+name AMIDI-Plug (MIDI Player)
+domain audacious-plugins
+priority 5
+about 1
+config 1
+enabled 1
+scheme mid
+scheme midi
+scheme rmi
+scheme rmid
+ext audio/midi
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/cdaudio-ng.so
+stamp 1758887725
+version 48
+flags 0
+name Audio CD Plugin
+domain audacious-plugins
+priority 5
+about 1
+config 1
+enabled 1
+mime cdda
+subtunes 2
+writes 0
+input /usr/lib/audacious/Input/console.so
+stamp 1758887725
+version 48
+flags 0
+name Game Console Music Decoder
+domain audacious-plugins
+priority 5
+about 1
+config 1
+enabled 1
+scheme ay
+scheme gbs
+scheme gym
+scheme hes
+scheme kss
+scheme nsf
+scheme nsfe
+scheme sap
+scheme spc
+scheme vgm
+scheme vgz
+subtunes 2
+writes 0
+input /usr/lib/audacious/Input/madplug.so
+stamp 1758887725
+version 48
+flags 0
+name MPG123 Plugin
+domain audacious-plugins
+priority 5
+about 0
+config 1
+enabled 1
+scheme mp3
+scheme mp2
+scheme mp1
+scheme bmu
+ext audio/mp3
+ext audio/mpeg
+ext audio/x-mp3
+ext audio/x-mpeg
+subtunes 0
+writes 1
+input /usr/lib/audacious/Input/openmpt.so
+stamp 1758887725
+version 48
+flags 0
+name OpenMPT (Module Player)
+domain audacious-plugins
+priority 5
+about 1
+config 1
+enabled 1
+scheme 669
+scheme amf
+scheme ams
+scheme dbm
+scheme digi
+scheme dmf
+scheme dsm
+scheme far
+scheme gdm
+scheme ice
+scheme imf
+scheme it
+scheme j2b
+scheme m15
+scheme mdl
+scheme med
+scheme mmcmp
+scheme mms
+scheme mo3
+scheme mod
+scheme mptm
+scheme mt2
+scheme mtm
+scheme nst
+scheme okt
+scheme plm
+scheme ppm
+scheme psm
+scheme pt36
+scheme ptm
+scheme s3m
+scheme sfx
+scheme sfx2
+scheme st26
+scheme stk
+scheme stm
+scheme ult
+scheme umx
+scheme wow
+scheme xm
+scheme xpk
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/psf2.so
+stamp 1758887725
+version 48
+flags 0
+name OpenPSF PSF1/PSF2 Decoder
+domain audacious-plugins
+priority 5
+about 0
+config 1
+enabled 1
+scheme psf
+scheme minipsf
+scheme psf2
+scheme minipsf2
+scheme spu
+scheme spx
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/opus.so
+stamp 1758887725
+version 48
+flags 0
+name Opus Decoder
+domain audacious-plugins
+priority 5
+about 1
+config 0
+enabled 1
+scheme opus
+ext application/ogg
+ext audio/ogg
+ext audio/opus
+ext audio/x-opus+ogg
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/sid.so
+stamp 1758887725
+version 48
+flags 0
+name SID Player
+domain audacious-plugins
+priority 5
+about 0
+config 1
+enabled 1
+scheme sid
+scheme psid
+ext audio/prs.sid
+subtunes 2
+writes 0
+input /usr/lib/audacious/Input/metronom.so
+stamp 1758887725
+version 48
+flags 0
+name Tact Generator
+domain audacious-plugins
+priority 5
+about 1
+config 0
+enabled 1
+mime tact
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/tonegen.so
+stamp 1758887725
+version 48
+flags 0
+name Tone Generator
+domain audacious-plugins
+priority 5
+about 1
+config 0
+enabled 1
+mime tone
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/vtx.so
+stamp 1758887725
+version 48
+flags 0
+name VTX Decoder
+domain audacious-plugins
+priority 5
+about 1
+config 0
+enabled 1
+scheme vtx
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/wavpack.so
+stamp 1758887725
+version 48
+flags 0
+name WavPack Decoder
+domain audacious-plugins
+priority 5
+about 1
+config 0
+enabled 1
+scheme wv
+ext audio/x-wavpack
+subtunes 0
+writes 1
+input /usr/lib/audacious/Input/flacng.so
+stamp 1758887725
+version 48
+flags 0
+name FLAC Decoder
+domain audacious-plugins
+priority 6
+about 1
+config 0
+enabled 1
+scheme flac
+scheme fla
+ext audio/flac
+ext audio/x-flac
+ext audio/ogg
+ext application/ogg
+subtunes 0
+writes 1
+input /usr/lib/audacious/Input/modplug.so
+stamp 1758887725
+version 48
+flags 0
+name ModPlug (Module Player)
+domain audacious-plugins
+priority 6
+about 0
+config 1
+enabled 1
+scheme amf
+scheme ams
+scheme dbm
+scheme dbf
+scheme dsm
+scheme far
+scheme mdl
+scheme stm
+scheme ult
+scheme mt2
+scheme mod
+scheme s3m
+scheme dmf
+scheme umx
+scheme it
+scheme 669
+scheme xm
+scheme mtm
+scheme psm
+scheme ft2
+subtunes 2
+writes 0
+input /usr/lib/audacious/Input/vorbis.so
+stamp 1758887725
+version 48
+flags 0
+name Ogg Vorbis Decoder
+domain audacious-plugins
+priority 7
+about 1
+config 0
+enabled 1
+scheme ogg
+scheme ogm
+scheme oga
+ext application/ogg
+ext application/x-ogg
+ext audio/ogg
+ext audio/x-vorbis+ogg
+subtunes 0
+writes 1
+input /usr/lib/audacious/Input/sndfile.so
+stamp 1758887725
+version 48
+flags 0
+name Sndfile Plugin
+domain audacious-plugins
+priority 9
+about 1
+config 0
+enabled 1
+scheme aiff
+scheme au
+scheme raw
+scheme wav
+ext audio/wav
+ext audio/x-wav
+subtunes 0
+writes 0
+input /usr/lib/audacious/Input/ffaudio.so
+stamp 1758887725
+version 48
+flags 0
+name FFmpeg Plugin
+domain audacious-plugins
+priority 10
+about 1
+config 0
+enabled 1
+scheme mpc
+scheme mp+
+scheme mpp
+scheme wma
+scheme shn
+scheme aa3
+scheme oma
+scheme aac
+scheme ac3
+scheme adx
+scheme ape
+scheme dts
+scheme vqf
+scheme m4a
+scheme mp4
+scheme wav
+scheme ogg
+scheme oga
+scheme spx
+scheme tta
+scheme webm
+scheme mka
+scheme mkv
+ext application/ogg
+ext audio/aac
+ext audio/mp4
+ext audio/ogg
+subtunes 0
+writes 1
+effect /usr/lib/audacious/Effect/bs2b.so
+stamp 1758887725
+version 48
+flags 0
+name Bauer Stereophonic-to-Binaural (BS2B)
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/bitcrusher.so
+stamp 1758887725
+version 48
+flags 0
+name Bitcrusher
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/crystalizer.so
+stamp 1758887725
+version 48
+flags 0
+name Crystalizer
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/compressor.so
+stamp 1758887725
+version 48
+flags 0
+name Dynamic Range Compressor
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/echo.so
+stamp 1758887725
+version 48
+flags 0
+name Echo
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/stereo.so
+stamp 1758887725
+version 48
+flags 0
+name Extra Stereo
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/silence-removal.so
+stamp 1758887725
+version 48
+flags 0
+name Silence Removal
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/speed-pitch.so
+stamp 1758887725
+version 48
+flags 0
+name Speed and Pitch
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/voice_removal.so
+stamp 1758887725
+version 48
+flags 0
+name Voice Removal
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 0
+effect /usr/lib/audacious/Effect/mixer.so
+stamp 1758887725
+version 48
+flags 0
+name Channel Mixer
+domain audacious-plugins
+priority 2
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/resample.so
+stamp 1758887725
+version 48
+flags 0
+name Sample Rate Converter
+domain audacious-plugins
+priority 2
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/sox-resampler.so
+stamp 1758887725
+version 48
+flags 0
+name SoX Resampler
+domain audacious-plugins
+priority 2
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/crossfade.so
+stamp 1758887725
+version 48
+flags 0
+name Crossfade
+domain audacious-plugins
+priority 5
+about 1
+config 1
+enabled 0
+effect /usr/lib/audacious/Effect/background_music.so
+stamp 1758887725
+version 48
+flags 0
+name Background Music
+domain audacious-plugins
+priority 10
+about 1
+config 1
+enabled 0
+output /usr/lib/audacious/Output/pipewire.so
+stamp 1758887725
+version 48
+flags 0
+name PipeWire Output
+domain audacious-plugins
+priority 2
+about 1
+config 0
+enabled 0
+output /usr/lib/audacious/Output/pulse_audio.so
+stamp 1758887725
+version 48
+flags 0
+name PulseAudio Output
+domain audacious-plugins
+priority 2
+about 1
+config 1
+enabled 1
+output /usr/lib/audacious/Output/alsa.so
+stamp 1758887725
+version 48
+flags 0
+name ALSA Output
+domain audacious-plugins
+priority 5
+about 1
+config 1
+enabled 0
+output /usr/lib/audacious/Output/oss4.so
+stamp 1758887725
+version 48
+flags 0
+name OSS3 Output
+domain audacious-plugins
+priority 6
+about 1
+config 1
+enabled 0
+output /usr/lib/audacious/Output/sdlout.so
+stamp 1758887725
+version 48
+flags 0
+name SDL Output
+domain audacious-plugins
+priority 9
+about 1
+config 0
+enabled 0
+output /usr/lib/audacious/Output/filewriter.so
+stamp 1758887725
+version 48
+flags 0
+name FileWriter Plugin
+domain audacious-plugins
+priority 10
+about 1
+config 1
+enabled 0
+output /usr/lib/audacious/Output/jack-ng.so
+stamp 1758887725
+version 48
+flags 0
+name JACK Output
+domain audacious-plugins
+priority 10
+about 0
+config 1
+enabled 0
+vis /usr/lib/audacious/Visualization/blur_scope-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Blur Scope
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+vis /usr/lib/audacious/Visualization/gl-spectrum-qt.so
+stamp 1758887725
+version 48
+flags 2
+name OpenGL Spectrum Analyzer
+domain audacious-plugins
+priority 0
+about 1
+config 0
+enabled 0
+vis /usr/lib/audacious/Visualization/qt-spectrum.so
+stamp 1758887725
+version 48
+flags 2
+name Spectrum Analyzer
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 0
+vis /usr/lib/audacious/Visualization/vumeter-qt.so
+stamp 1758887725
+version 48
+flags 2
+name VU Meter
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+general /usr/lib/audacious/General/albumart-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Album Art
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 0
+general /usr/lib/audacious/General/cd-menu-items.so
+stamp 1758887725
+version 48
+flags 0
+name Audio CD Menu Items
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 1
+general /usr/lib/audacious/General/delete-files.so
+stamp 1758887725
+version 48
+flags 0
+name Delete Files
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+general /usr/lib/audacious/General/notify.so
+stamp 1758887725
+version 48
+flags 0
+name Desktop Notifications
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+general /usr/lib/audacious/General/qthotkey.so
+stamp 1758887725
+version 48
+flags 2
+name Global Hotkeys
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+general /usr/lib/audacious/General/lirc.so
+stamp 1758887725
+version 48
+flags 0
+name LIRC Plugin
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+general /usr/lib/audacious/General/lyrics-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Lyrics
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+general /usr/lib/audacious/General/mpris2.so
+stamp 1758887725
+version 48
+flags 0
+name MPRIS 2 Server
+domain audacious-plugins
+priority 0
+about 1
+config 0
+enabled 1
+general /usr/lib/audacious/General/playback-history-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Playback History
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+general /usr/lib/audacious/General/playlist-manager-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Playlist Manager
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 0
+general /usr/lib/audacious/General/scrobbler.so
+stamp 1758887725
+version 48
+flags 0
+name Scrobbler 2.0
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+general /usr/lib/audacious/General/search-tool-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Search Tool
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+general /usr/lib/audacious/General/song_change.so
+stamp 1758887725
+version 48
+flags 0
+name Song Change
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+general /usr/lib/audacious/General/song-info-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Song Info
+domain audacious-plugins
+priority 0
+about 0
+config 0
+enabled 0
+general /usr/lib/audacious/General/statusicon-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Status Icon
+domain audacious-plugins
+priority 0
+about 1
+config 1
+enabled 0
+iface /usr/lib/audacious/General/qtui.so
+stamp 1758887725
+version 48
+flags 2
+name Qt Interface
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 0
+iface /usr/lib/audacious/General/skins-qt.so
+stamp 1758887725
+version 48
+flags 2
+name Winamp Classic Interface
+domain audacious-plugins
+priority 0
+about 0
+config 1
+enabled 1
diff --git a/dotfiles/system/.config/calibre/conversion/azw3_output.py b/dotfiles/system/.config/calibre/conversion/azw3_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/comic_input.py b/dotfiles/system/.config/calibre/conversion/comic_input.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/debug.py b/dotfiles/system/.config/calibre/conversion/debug.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/docx_input.py b/dotfiles/system/.config/calibre/conversion/docx_input.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/docx_output.py b/dotfiles/system/.config/calibre/conversion/docx_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/epub_output.py b/dotfiles/system/.config/calibre/conversion/epub_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/fb2_input.py b/dotfiles/system/.config/calibre/conversion/fb2_input.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/fb2_output.py b/dotfiles/system/.config/calibre/conversion/fb2_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/heuristics.py b/dotfiles/system/.config/calibre/conversion/heuristics.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/htmlz_output.py b/dotfiles/system/.config/calibre/conversion/htmlz_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/kepub_output.py b/dotfiles/system/.config/calibre/conversion/kepub_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/look_and_feel.py b/dotfiles/system/.config/calibre/conversion/look_and_feel.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/lrf_output.py b/dotfiles/system/.config/calibre/conversion/lrf_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/metadata.py b/dotfiles/system/.config/calibre/conversion/metadata.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/mobi_output.py b/dotfiles/system/.config/calibre/conversion/mobi_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/page_setup.py b/dotfiles/system/.config/calibre/conversion/page_setup.py
new file mode 100644
index 0000000..d54ecbb
--- /dev/null
+++ b/dotfiles/system/.config/calibre/conversion/page_setup.py
@@ -0,0 +1,3 @@
+json:{
+ "output_profile": "kobo"
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/conversion/pdb_output.py b/dotfiles/system/.config/calibre/conversion/pdb_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/pdf_input.py b/dotfiles/system/.config/calibre/conversion/pdf_input.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/pdf_output.py b/dotfiles/system/.config/calibre/conversion/pdf_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/pmlz_output.py b/dotfiles/system/.config/calibre/conversion/pmlz_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/rb_output.py b/dotfiles/system/.config/calibre/conversion/rb_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/rtf_input.py b/dotfiles/system/.config/calibre/conversion/rtf_input.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/search_and_replace.py b/dotfiles/system/.config/calibre/conversion/search_and_replace.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/snb_output.py b/dotfiles/system/.config/calibre/conversion/snb_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/structure_detection.py b/dotfiles/system/.config/calibre/conversion/structure_detection.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/toc.py b/dotfiles/system/.config/calibre/conversion/toc.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/txt_input.py b/dotfiles/system/.config/calibre/conversion/txt_input.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/txt_output.py b/dotfiles/system/.config/calibre/conversion/txt_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/conversion/txtz_output.py b/dotfiles/system/.config/calibre/conversion/txtz_output.py
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/custom_recipes/The Economist_1001.recipe b/dotfiles/system/.config/calibre/custom_recipes/The Economist_1001.recipe
new file mode 100644
index 0000000..bcb8364
--- /dev/null
+++ b/dotfiles/system/.config/calibre/custom_recipes/The Economist_1001.recipe
@@ -0,0 +1,684 @@
+#!/usr/bin/env python
+# License: GPLv3 Copyright: 2008, Kovid Goyal
+
+import json
+import re
+import time
+from collections import defaultdict
+from datetime import datetime, timedelta
+from urllib.parse import quote, urlencode
+from uuid import uuid4
+
+from html5_parser import parse
+from lxml import etree
+
+from calibre import replace_entities
+from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, Tag
+from calibre.ptempfile import PersistentTemporaryFile
+from calibre.scraper.simple import read_url
+from calibre.utils.date import parse_only_date
+from calibre.web.feeds.news import BasicNewsRecipe
+
+
+def E(parent, name, text='', **attrs):
+ ans = parent.makeelement(name, **attrs)
+ ans.text = text
+ parent.append(ans)
+ return ans
+
+
+def process_node(node, html_parent):
+ ntype = node.get('type')
+ if ntype == 'tag':
+ c = html_parent.makeelement(node['name'])
+ c.attrib.update({k: v or '' for k, v in node.get('attribs', {}).items()})
+ html_parent.append(c)
+ for nc in node.get('children', ()):
+ process_node(nc, c)
+ elif ntype == 'text':
+ text = node.get('data')
+ if text:
+ text = replace_entities(text)
+ if len(html_parent):
+ t = html_parent[-1]
+ t.tail = (t.tail or '') + text
+ else:
+ html_parent.text = (html_parent.text or '') + text
+
+
+def safe_dict(data, *names):
+ ans = data
+ for x in names:
+ ans = ans.get(x) or {}
+ return ans
+
+
+class JSONHasNoContent(ValueError):
+ pass
+
+
+def load_article_from_json(raw, root):
+ # open('/t/raw.json', 'w').write(raw)
+ data = json.loads(raw)
+ body = root.xpath('//body')[0]
+ article = E(body, 'article')
+ E(article, 'div', data['flyTitle'], style='color: red; font-size:small; font-weight:bold;')
+ E(article, 'h1', data['title'], title=safe_dict(data, 'url', 'canonical') or '')
+ E(article, 'div', data['rubric'], style='font-style: italic; color:#202020;')
+ try:
+ date = data['dateModified']
+ except Exception:
+ date = data['datePublished']
+ dt = datetime.fromisoformat(date[:-1]) + timedelta(seconds=time.timezone)
+ dt = dt.strftime('%b %d, %Y %I:%M %p')
+ if data['dateline'] is None:
+ E(article, 'p', dt, style='color: gray; font-size:small;')
+ else:
+ E(article, 'p', dt + ' | ' + (data['dateline']), style='color: gray; font-size:small;')
+ main_image_url = safe_dict(data, 'image', 'main', 'url').get('canonical')
+ if main_image_url:
+ div = E(article, 'div')
+ try:
+ E(div, 'img', src=main_image_url)
+ except Exception:
+ pass
+ for node in data.get('text') or ():
+ process_node(node, article)
+
+
+def process_web_list(li_node):
+ li_html = ''
+ for li in li_node['items']:
+ if li.get('textHtml'):
+ li_html += f'
{li.get("textHtml")}
'
+ else:
+ li_html += f'
{li.get("text", "")}
'
+ return li_html
+
+
+def process_info_box(bx):
+ info = ''
+ for x in safe_dict(bx, 'components'):
+ info += f'
{process_web_node(x)}
'
+ return info
+
+
+def process_web_node(node):
+ ntype = node.get('type', '')
+ if ntype == 'CROSSHEAD':
+ if node.get('textHtml'):
+ return f'
{node.get("textHtml")}
'
+ return f'
{node.get("text", "")}
'
+ elif ntype in ['PARAGRAPH', 'BOOK_INFO']:
+ if node.get('textHtml'):
+ return f'
{node.get("textHtml")}
'
+ return f'
{node.get("text", "")}
'
+ elif ntype == 'IMAGE':
+ alt = '' if node.get('altText') is None else node.get('altText')
+ cap = ''
+ if node.get('caption'):
+ if node['caption'].get('textHtml') is not None:
+ cap = node['caption']['textHtml']
+ return f'
{cap}
'
+ elif ntype == 'PULL_QUOTE':
+ if node.get('textHtml'):
+ return f'
' % (ns.text), treebuilder='lxml', namespaceHTMLElements=False)[0]\n else:\n ns.tag = 'div'\n ans = self._render_comments(ns)\n else:\n desc = root.xpath('//div[@id=\"ps-content\"]/div[@class=\"content\"]')\n if desc:\n ans = self._render_comments(desc[0])\n else:\n ns = tuple(self.selector('#bookDescription_feature_div .a-expander-content'))\n if ns:\n ans = self._render_comments(ns[0])\n # audiobooks\n if not ans:\n elem = root.xpath('//*[@id=\"audible_desktopTabbedDescriptionOverviewContent_feature_div\"]')\n if elem:\n ans = self._render_comments(elem[0])\n desc = root.xpath(\n '//div[@id=\"productDescription\"]/*[@class=\"content\"]')\n if desc:\n ans += self._render_comments(desc[0])\n else:\n # Idiot chickens from amazon strike again. This data is now stored\n # in a JS variable inside a script tag URL encoded.\n m = re.search(br'var\\s+iframeContent\\s*=\\s*\"([^\"]+)\"', raw)\n if m is not None:\n try:\n text = unquote(m.group(1)).decode('utf-8')\n nr = parse_html(text)\n desc = nr.xpath(\n '//div[@id=\"productDescription\"]/*[@class=\"content\"]')\n if desc:\n ans += self._render_comments(desc[0])\n except Exception as e:\n self.log.warn(\n 'Parsing of obfuscated product description failed with error: %s' % as_unicode(e))\n else:\n desc = root.xpath('//div[@id=\"productDescription_fullView\"]')\n if desc:\n ans += self._render_comments(desc[0])\n\n return ans\n\n def parse_series(self, root):\n ans = (None, None)\n\n # This is found on kindle pages for books on amazon.* (including amazon.co.jp)\n series = root.xpath('//*[@id=\"rpi-attribute-book_details-series\"]')\n if series:\n spans = series[0].xpath('descendant::span')\n if spans:\n texts = [self.tostring(x, encoding='unicode', method='text', with_tail=False).strip()\n for x in spans]\n texts = list(filter(None, texts))\n if len(texts) == 2:\n idxinfo, series_name = texts\n idxinfo = idxinfo.strip()\n\n # Try Japanese pattern like: \"全5巻中第1巻\", \"全3巻中第2巻\"\n m = re.search(r'全\\s*[0-9.]+\\s*(?:巻|冊)中第\\s*([0-9.]+)\\s*(?:巻|冊)', idxinfo)\n if m is not None:\n ans = (series_name, float(m.group(1)))\n return ans\n\n # Fallback: original behaviour (first number), used for EN/etc\n m = re.search(r'[0-9.]+', idxinfo)\n if m is not None:\n ans = (series_name, float(m.group()))\n return ans\n\n # This is found on the paperback/hardback pages for books on amazon.com\n series = root.xpath('//div[@data-feature-name=\"seriesTitle\"]')\n if series:\n series = series[0]\n spans = series.xpath('./span')\n if spans:\n raw = self.tostring(\n spans[0], encoding='unicode', method='text', with_tail=False).strip()\n m = re.search(r'\\s+([0-9.]+)$', raw.strip())\n if m is not None:\n series_index = float(m.group(1))\n s = series.xpath('./a[@id=\"series-page-link\"]')\n if s:\n series = self.tostring(\n s[0], encoding='unicode', method='text', with_tail=False).strip()\n if series:\n ans = (series, series_index)\n else:\n series = root.xpath('//div[@id=\"seriesBulletWidget_feature_div\"]')\n if series:\n a = series[0].xpath('descendant::a')\n if a:\n raw = self.tostring(a[0], encoding='unicode', method='text', with_tail=False)\n if self.domain == 'jp':\n m = re.search(r'全\\s*[0-9.]+\\s*(?:巻|冊)中第\\s*(?P[0-9.]+)\\s*(?:巻|冊)\\s*:\\s*(?P.+)', raw.strip())\n else:\n m = re.search(r'(?:Book|Libro|Buch)\\s+(?P[0-9.]+)\\s+(?:of|de|von)\\s+([0-9.]+)\\s*:\\s*(?P.+)', raw.strip())\n if m is not None:\n ans = (m.group('series').strip(), float(m.group('index')))\n\n # This is found on Kindle edition pages on amazon.com\n if ans == (None, None):\n for span in root.xpath('//div[@id=\"aboutEbooksSection\"]//li/span'):\n text = (span.text or '').strip()\n m = re.match(r'Book\\s+([0-9.]+)', text)\n if m is not None:\n series_index = float(m.group(1))\n a = span.xpath('./a[@href]')\n if a:\n series = self.tostring(\n a[0], encoding='unicode', method='text', with_tail=False).strip()\n if series:\n ans = (series, series_index)\n # This is found on newer Kindle edition pages on amazon.com\n if ans == (None, None):\n for b in root.xpath('//div[@id=\"reviewFeatureGroup\"]/span/b'):\n text = (b.text or '').strip()\n m = re.match(r'Book\\s+([0-9.]+)', text)\n if m is not None:\n series_index = float(m.group(1))\n a = b.getparent().xpath('./a[@href]')\n if a:\n series = self.tostring(\n a[0], encoding='unicode', method='text', with_tail=False).partition('(')[0].strip()\n if series:\n ans = series, series_index\n\n if ans == (None, None):\n desc = root.xpath('//div[@id=\"ps-content\"]/div[@class=\"buying\"]')\n if desc:\n raw = self.tostring(desc[0], method='text', encoding='unicode')\n raw = re.sub(r'\\s+', ' ', raw)\n match = self.series_pat.search(raw)\n if match is not None:\n s, i = match.group('series'), float(match.group('index'))\n if s:\n ans = (s, i)\n if ans[0]:\n ans = (re.sub(r'\\s+Series$', '', ans[0]).strip(), ans[1])\n ans = (re.sub(r'\\(.+?\\s+Series\\)$', '', ans[0]).strip(), ans[1])\n return ans\n\n def parse_tags(self, root):\n ans = []\n exclude_tokens = {'kindle', 'a-z'}\n exclude = {'special features', 'by authors',\n 'authors & illustrators', 'books', 'new; used & rental textbooks'}\n seen = set()\n for li in root.xpath(self.tags_xpath):\n for i, a in enumerate(li.iterdescendants('a')):\n if i > 0:\n # we ignore the first category since it is almost always\n # too broad\n raw = (a.text or '').strip().replace(',', ';')\n lraw = icu_lower(raw)\n tokens = frozenset(lraw.split())\n if raw and lraw not in exclude and not tokens.intersection(exclude_tokens) and lraw not in seen:\n ans.append(raw)\n seen.add(lraw)\n return ans\n\n def parse_cover(self, root, raw=b''):\n # Look for the image URL in javascript, using the first image in the\n # image gallery as the cover\n import json\n imgpat = re.compile(r'\"hiRes\":\"(.+?)\",\"thumb\"')\n for script in root.xpath('//script'):\n m = imgpat.search(script.text or '')\n if m is not None:\n return m.group(1)\n imgpat = re.compile(r''''imageGalleryData'\\s*:\\s*(\\[\\s*{.+])''')\n for script in root.xpath('//script'):\n m = imgpat.search(script.text or '')\n if m is not None:\n try:\n return json.loads(m.group(1))[0]['mainUrl']\n except Exception:\n continue\n\n def clean_img_src(src):\n parts = src.split('/')\n if len(parts) > 3:\n bn = parts[-1]\n sparts = bn.split('_')\n if len(sparts) > 2:\n bn = re.sub(r'\\.\\.jpg$', '.jpg', (sparts[0] + sparts[-1]))\n return ('/'.join(parts[:-1])) + '/' + bn\n\n imgpat2 = re.compile(r'var imageSrc = \"([^\"]+)\"')\n for script in root.xpath('//script'):\n m = imgpat2.search(script.text or '')\n if m is not None:\n src = m.group(1)\n url = clean_img_src(src)\n if url:\n return url\n\n imgs = root.xpath(\n '//img[(@id=\"prodImage\" or @id=\"original-main-image\" or @id=\"main-image\" or @id=\"main-image-nonjs\") and @src]')\n if not imgs:\n imgs = (\n root.xpath('//div[@class=\"main-image-inner-wrapper\"]/img[@src]') or\n root.xpath('//div[@id=\"main-image-container\" or @id=\"ebooks-main-image-container\"]//img[@src]') or\n root.xpath(\n '//div[@id=\"mainImageContainer\"]//img[@data-a-dynamic-image]')\n )\n for img in imgs:\n try:\n idata = json.loads(img.get('data-a-dynamic-image'))\n except Exception:\n imgs = ()\n else:\n mwidth = 0\n try:\n url = None\n for iurl, (width, height) in idata.items():\n if width > mwidth:\n mwidth = width\n url = iurl\n\n return url\n except Exception:\n pass\n\n for img in imgs:\n src = img.get('src')\n if 'data:' in src:\n continue\n if 'loading-' in src:\n js_img = re.search(br'\"largeImage\":\"(https?://[^\"]+)\",', raw)\n if js_img:\n src = js_img.group(1).decode('utf-8')\n if ('/no-image-avail' not in src and 'loading-' not in src and '/no-img-sm' not in src):\n self.log('Found image: %s' % src)\n url = clean_img_src(src)\n if url:\n return url\n\n def parse_detail_bullets(self, root, mi, container, ul_selector='.detail-bullet-list'):\n try:\n ul = next(self.selector(ul_selector, root=container))\n except StopIteration:\n return\n for span in self.selector('.a-list-item', root=ul):\n cells = span.xpath('./span')\n if len(cells) >= 2:\n self.parse_detail_cells(mi, cells[0], cells[1])\n\n def parse_new_details(self, root, mi, non_hero):\n table = non_hero.xpath('descendant::table')[0]\n for tr in table.xpath('descendant::tr'):\n cells = tr.xpath('descendant::*[local-name()=\"td\" or local-name()=\"th\"]')\n if len(cells) == 2:\n self.parse_detail_cells(mi, cells[0], cells[1])\n\n def parse_detail_cells(self, mi, c1, c2):\n name = self.totext(c1, only_printable=True).strip().strip(':').strip()\n val = self.totext(c2)\n val = val.replace('\\u200e', '').replace('\\u200f', '')\n if not val:\n return\n if name in self.language_names:\n ans = self.lang_map.get(val)\n if not ans:\n ans = canonicalize_lang(val)\n if ans:\n mi.language = ans\n elif name in self.publisher_names:\n pub = val.partition(';')[0].partition('(')[0].strip()\n if pub:\n mi.publisher = pub\n date = val.rpartition('(')[-1].replace(')', '').strip()\n try:\n from calibre.utils.date import parse_only_date\n date = self.delocalize_datestr(date)\n mi.pubdate = parse_only_date(date, assume_utc=True)\n except Exception:\n self.log.exception('Failed to parse pubdate: %s' % val)\n elif name in {'ISBN', 'ISBN-10', 'ISBN-13'}:\n ans = check_isbn(val)\n if ans:\n self.isbn = mi.isbn = ans\n elif name in {'Publication date'}:\n from calibre.utils.date import parse_only_date\n date = self.delocalize_datestr(val)\n mi.pubdate = parse_only_date(date, assume_utc=True)\n\n def parse_isbn(self, pd):\n items = pd.xpath(\n 'descendant::*[starts-with(text(), \"ISBN\")]')\n if not items:\n items = pd.xpath(\n 'descendant::b[contains(text(), \"ISBN:\")]')\n for x in reversed(items):\n if x.tail:\n ans = check_isbn(x.tail.strip())\n if ans:\n return ans\n\n def parse_publisher(self, pd):\n for x in reversed(pd.xpath(self.publisher_xpath)):\n if x.tail:\n ans = x.tail.partition(';')[0]\n return ans.partition('(')[0].strip()\n\n def parse_pubdate(self, pd):\n from calibre.utils.date import parse_only_date\n for x in reversed(pd.xpath(self.pubdate_xpath)):\n if x.tail:\n date = x.tail.strip()\n date = self.delocalize_datestr(date)\n try:\n return parse_only_date(date, assume_utc=True)\n except Exception:\n pass\n for x in reversed(pd.xpath(self.publisher_xpath)):\n if x.tail:\n ans = x.tail\n date = ans.rpartition('(')[-1].replace(')', '').strip()\n date = self.delocalize_datestr(date)\n try:\n return parse_only_date(date, assume_utc=True)\n except Exception:\n pass\n\n def parse_language(self, pd):\n for x in reversed(pd.xpath(self.language_xpath)):\n if x.tail:\n raw = x.tail.strip().partition(',')[0].strip()\n ans = self.lang_map.get(raw, None)\n if ans:\n return ans\n ans = canonicalize_lang(ans)\n if ans:\n return ans\n# }}}\n\n\nclass Amazon(Source):\n\n name = 'Amazon.com'\n version = (1, 3, 15)\n minimum_calibre_version = (2, 82, 0)\n description = _('Downloads metadata and covers from Amazon')\n\n capabilities = frozenset(('identify', 'cover'))\n touched_fields = frozenset(('title', 'authors', 'identifier:amazon',\n 'rating', 'comments', 'publisher', 'pubdate',\n 'languages', 'series', 'tags'))\n has_html_comments = True\n supports_gzip_transfer_encoding = True\n prefer_results_with_isbn = False\n\n AMAZON_DOMAINS = {\n 'com': _('US'),\n 'fr': _('France'),\n 'de': _('Germany'),\n 'uk': _('UK'),\n 'au': _('Australia'),\n 'it': _('Italy'),\n 'jp': _('Japan'),\n 'es': _('Spain'),\n 'br': _('Brazil'),\n 'in': _('India'),\n 'nl': _('Netherlands'),\n 'cn': _('China'),\n 'ca': _('Canada'),\n 'se': _('Sweden'),\n }\n\n SERVERS = {\n 'auto': _('Choose server automatically'),\n 'amazon': _('Amazon servers'),\n 'bing': _('Bing search cache'),\n 'google': _('Google search cache'),\n 'wayback': _('Wayback machine cache (slow)'),\n 'ddg': _('DuckDuckGo search and Google cache'),\n }\n\n options = (\n Option('domain', 'choices', 'com', _('Amazon country website to use:'),\n _('Metadata from Amazon will be fetched using this '\n \"country's Amazon website.\"), choices=AMAZON_DOMAINS),\n Option('server', 'choices', 'auto', _('Server to get data from:'),\n _(\n 'Amazon has started blocking attempts to download'\n ' metadata from its servers. To get around this problem,'\n ' calibre can fetch the Amazon data from many different'\n ' places where it is cached. Choose the source you prefer.'\n ), choices=SERVERS),\n Option('use_mobi_asin', 'bool', False, _('Use the MOBI-ASIN for metadata search'),\n _(\n 'Enable this option to search for metadata with an'\n ' ASIN identifier from the MOBI file at the current country website,'\n ' unless any other amazon id is available. Note that if the'\n ' MOBI file came from a different Amazon country store, you could get'\n ' incorrect results.'\n )),\n Option('prefer_kindle_edition', 'bool', False, _('Prefer the Kindle edition, when available'),\n _(\n 'When searching for a book and the search engine returns both paper and Kindle editions,'\n ' always prefer the Kindle edition, instead of whatever the search engine returns at the'\n ' top.')\n ),\n )\n\n def __init__(self, *args, **kwargs):\n Source.__init__(self, *args, **kwargs)\n self.set_amazon_id_touched_fields()\n\n def id_from_url(self, url):\n from polyglot.urllib import urlparse\n purl = urlparse(url)\n if purl.netloc and purl.path and '/dp/' in purl.path:\n host_parts = tuple(x.lower() for x in purl.netloc.split('.'))\n if 'amazon' in host_parts:\n domain = host_parts[-1]\n parts = purl.path.split('/')\n idx = parts.index('dp')\n try:\n val = parts[idx+1]\n except IndexError:\n return\n aid = 'amazon' if domain == 'com' else ('amazon_' + domain)\n return aid, val\n\n def test_fields(self, mi):\n '''\n Return the first field from self.touched_fields that is null on the\n mi object\n '''\n for key in self.touched_fields:\n if key.startswith('identifier:'):\n key = key.partition(':')[-1]\n if key == 'amazon':\n if self.domain != 'com':\n key += '_' + self.domain\n if not mi.has_identifier(key):\n return 'identifier: ' + key\n elif mi.is_null(key):\n return key\n\n @property\n def browser(self):\n br = self._browser\n if br is None:\n ua = 'Mobile '\n while not user_agent_is_ok(ua):\n ua = random_user_agent(allow_ie=False)\n # ua = 'Mozilla/5.0 (Linux; Android 8.0.0; VTR-L29; rv:63.0) Gecko/20100101 Firefox/63.0'\n self._browser = br = browser(user_agent=ua)\n br.set_handle_gzip(True)\n if self.use_search_engine:\n br.addheaders += [\n ('Accept', accept_header_for_ua(ua)),\n ('Upgrade-insecure-requests', '1'),\n ]\n else:\n br.addheaders += [\n ('Accept', accept_header_for_ua(ua)),\n ('Upgrade-insecure-requests', '1'),\n ('Referer', self.referrer_for_domain()),\n ]\n return br\n\n def save_settings(self, *args, **kwargs):\n Source.save_settings(self, *args, **kwargs)\n self.set_amazon_id_touched_fields()\n\n def set_amazon_id_touched_fields(self):\n ident_name = 'identifier:amazon'\n if self.domain != 'com':\n ident_name += '_' + self.domain\n tf = [x for x in self.touched_fields if not\n x.startswith('identifier:amazon')] + [ident_name]\n self.touched_fields = frozenset(tf)\n\n def get_domain_and_asin(self, identifiers, extra_domains=()):\n identifiers = {k.lower(): v for k, v in identifiers.items()}\n for key, val in identifiers.items():\n if key in ('amazon', 'asin'):\n return 'com', val\n if key.startswith('amazon_'):\n domain = key.partition('_')[-1]\n if domain and (domain in self.AMAZON_DOMAINS or domain in extra_domains):\n return domain, val\n if self.prefs['use_mobi_asin']:\n val = identifiers.get('mobi-asin')\n if val is not None:\n return self.domain, val\n return None, None\n\n def referrer_for_domain(self, domain=None):\n domain = domain or self.domain\n return {\n 'uk': 'https://www.amazon.co.uk/',\n 'au': 'https://www.amazon.com.au/',\n 'br': 'https://www.amazon.com.br/',\n 'jp': 'https://www.amazon.co.jp/',\n 'mx': 'https://www.amazon.com.mx/',\n }.get(domain, 'https://www.amazon.%s/' % domain)\n\n def _get_book_url(self, identifiers): # {{{\n domain, asin = self.get_domain_and_asin(\n identifiers, extra_domains=('au', 'ca'))\n if domain and asin:\n url = None\n r = self.referrer_for_domain(domain)\n if r is not None:\n url = r + 'dp/' + asin\n if url:\n idtype = 'amazon' if domain == 'com' else 'amazon_' + domain\n return domain, idtype, asin, url\n\n def get_book_url(self, identifiers):\n ans = self._get_book_url(identifiers)\n if ans is not None:\n return ans[1:]\n\n def get_book_url_name(self, idtype, idval, url):\n if idtype == 'amazon':\n return self.name\n return 'A' + idtype.replace('_', '.')[1:]\n # }}}\n\n @property\n def domain(self):\n x = getattr(self, 'testing_domain', None)\n if x is not None:\n return x\n domain = self.prefs['domain']\n if domain not in self.AMAZON_DOMAINS:\n domain = 'com'\n\n return domain\n\n @property\n def server(self):\n x = getattr(self, 'testing_server', None)\n if x is not None:\n return x\n server = self.prefs['server']\n if server not in self.SERVERS:\n server = 'auto'\n return server\n\n @property\n def use_search_engine(self):\n return self.server != 'amazon'\n\n def clean_downloaded_metadata(self, mi):\n docase = (\n mi.language == 'eng' or\n (mi.is_null('language') and self.domain in {'com', 'uk', 'au'})\n )\n if mi.title and docase:\n # Remove series information from title\n m = re.search(r'\\S+\\s+(\\(.+?\\s+Book\\s+\\d+\\))$', mi.title)\n if m is not None:\n mi.title = mi.title.replace(m.group(1), '').strip()\n mi.title = fixcase(mi.title)\n mi.authors = fixauthors(mi.authors)\n if mi.tags and docase:\n mi.tags = list(map(fixcase, mi.tags))\n mi.isbn = check_isbn(mi.isbn)\n if mi.series and docase:\n mi.series = fixcase(mi.series)\n if mi.title and mi.series:\n for pat in (r':\\s*Book\\s+\\d+\\s+of\\s+%s$', r'\\(%s\\)$', r':\\s*%s\\s+Book\\s+\\d+$'):\n pat = pat % re.escape(mi.series)\n q = re.sub(pat, '', mi.title, flags=re.I).strip()\n if q and q != mi.title:\n mi.title = q\n break\n\n def get_website_domain(self, domain):\n return {'uk': 'co.uk', 'jp': 'co.jp', 'br': 'com.br', 'au': 'com.au'}.get(domain, domain)\n\n def create_query(self, log, title=None, authors=None, identifiers={}, # {{{\n domain=None, for_amazon=True):\n try:\n from urllib.parse import unquote_plus, urlencode\n except ImportError:\n from urllib import unquote_plus, urlencode\n if domain is None:\n domain = self.domain\n\n idomain, asin = self.get_domain_and_asin(identifiers)\n if idomain is not None:\n domain = idomain\n\n # See the amazon detailed search page to get all options\n terms = []\n q = {'search-alias': 'aps',\n 'unfiltered': '1',\n }\n\n if domain == 'com':\n q['sort'] = 'relevanceexprank'\n else:\n q['sort'] = 'relevancerank'\n\n isbn = check_isbn(identifiers.get('isbn', None))\n\n if asin is not None:\n q['field-keywords'] = asin\n terms.append(asin)\n elif isbn is not None:\n q['field-isbn'] = isbn\n if len(isbn) == 13:\n terms.extend('({} OR {}-{})'.format(isbn, isbn[:3], isbn[3:]).split())\n else:\n terms.append(isbn)\n else:\n # Only return book results\n q['search-alias'] = {'br': 'digital-text',\n 'nl': 'aps'}.get(domain, 'stripbooks')\n if title:\n title_tokens = list(self.get_title_tokens(title))\n if title_tokens:\n q['field-title'] = ' '.join(title_tokens)\n terms.extend(title_tokens)\n if authors:\n author_tokens = list(self.get_author_tokens(authors,\n only_first_author=True))\n if author_tokens:\n q['field-author'] = ' '.join(author_tokens)\n terms.extend(author_tokens)\n\n if not ('field-keywords' in q or 'field-isbn' in q or\n ('field-title' in q)):\n # Insufficient metadata to make an identify query\n log.error('Insufficient metadata to construct query, none of title, ISBN or ASIN supplied')\n raise SearchFailed()\n\n if not for_amazon:\n return terms, domain\n\n if domain == 'nl':\n q['__mk_nl_NL'] = 'ÅMÅŽÕÑ'\n if 'field-keywords' not in q:\n q['field-keywords'] = ''\n for f in 'field-isbn field-title field-author'.split():\n q['field-keywords'] += ' ' + q.pop(f, '')\n q['field-keywords'] = q['field-keywords'].strip()\n\n encoded_q = {x.encode('utf-8', 'ignore'): y.encode('utf-8', 'ignore') for x, y in q.items()}\n url_query = urlencode(encoded_q)\n # amazon's servers want IRIs with unicode characters not percent esaped\n parts = []\n for x in url_query.split(b'&' if isinstance(url_query, bytes) else '&'):\n k, v = x.split(b'=' if isinstance(x, bytes) else '=', 1)\n parts.append('{}={}'.format(iri_quote_plus(unquote_plus(k)), iri_quote_plus(unquote_plus(v))))\n url_query = '&'.join(parts)\n url = 'https://www.amazon.%s/s/?' % self.get_website_domain(\n domain) + url_query\n return url, domain\n\n # }}}\n\n def get_cached_cover_url(self, identifiers): # {{{\n url = None\n domain, asin = self.get_domain_and_asin(identifiers)\n if asin is None:\n isbn = identifiers.get('isbn', None)\n if isbn is not None:\n asin = self.cached_isbn_to_identifier(isbn)\n if asin is not None:\n url = self.cached_identifier_to_cover_url(asin)\n\n return url\n # }}}\n\n def parse_results_page(self, root, domain): # {{{\n from lxml.html import tostring\n\n matches = []\n\n def title_ok(title):\n title = title.lower()\n bad = ['bulk pack', '[audiobook]', '[audio cd]',\n '(a book companion)', '( slipcase with door )', ': free sampler']\n if self.domain == 'com':\n bad.extend(['(%s edition)' % x for x in ('spanish', 'german')])\n for x in bad:\n if x in title:\n return False\n if title and title[0] in '[{' and re.search(r'\\(\\s*author\\s*\\)', title) is not None:\n # Bad entries in the catalog\n return False\n return True\n\n for query in (\n '//div[contains(@class, \"s-result-list\")]//h2/a[@href]',\n '//div[contains(@class, \"s-result-list\")]//div[@data-index]//h5//a[@href]',\n r'//li[starts-with(@id, \"result_\")]//a[@href and contains(@class, \"s-access-detail-page\")]',\n '//div[@data-cy=\"title-recipe\"]/a[@href]',\n ):\n result_links = root.xpath(query)\n if result_links:\n break\n for a in result_links:\n title = tostring(a, method='text', encoding='unicode')\n if title_ok(title):\n url = a.get('href')\n if url.startswith('/'):\n url = 'https://www.amazon.%s%s' % (\n self.get_website_domain(domain), url)\n matches.append(url)\n\n if not matches:\n # Previous generation of results page markup\n for div in root.xpath(r'//div[starts-with(@id, \"result_\")]'):\n links = div.xpath(r'descendant::a[@class=\"title\" and @href]')\n if not links:\n # New amazon markup\n links = div.xpath('descendant::h3/a[@href]')\n for a in links:\n title = tostring(a, method='text', encoding='unicode')\n if title_ok(title):\n url = a.get('href')\n if url.startswith('/'):\n url = 'https://www.amazon.%s%s' % (\n self.get_website_domain(domain), url)\n matches.append(url)\n break\n\n if not matches:\n # This can happen for some user agents that Amazon thinks are\n # mobile/less capable\n for td in root.xpath(\n r'//div[@id=\"Results\"]/descendant::td[starts-with(@id, \"search:Td:\")]'):\n for a in td.xpath(r'descendant::td[@class=\"dataColumn\"]/descendant::a[@href]/span[@class=\"srTitle\"]/..'):\n title = tostring(a, method='text', encoding='unicode')\n if title_ok(title):\n url = a.get('href')\n if url.startswith('/'):\n url = 'https://www.amazon.%s%s' % (\n self.get_website_domain(domain), url)\n matches.append(url)\n break\n if not matches and root.xpath('//form[@action=\"/errors/validateCaptcha\"]'):\n raise CaptchaError('Amazon returned a CAPTCHA page. Recently Amazon has begun using statistical'\n ' profiling to block access to its website. As such this metadata plugin is'\n ' unlikely to ever work reliably.')\n\n # Keep only the top 3 matches as the matches are sorted by relevance by\n # Amazon so lower matches are not likely to be very relevant\n return matches[:3]\n # }}}\n\n def search_amazon(self, br, testing, log, abort, title, authors, identifiers, timeout): # {{{\n from calibre.ebooks.chardet import xml_to_unicode\n from calibre.utils.cleantext import clean_ascii_chars\n matches = []\n query, domain = self.create_query(log, title=title, authors=authors,\n identifiers=identifiers)\n time.sleep(1)\n try:\n raw = br.open_novisit(query, timeout=timeout).read().strip()\n except Exception as e:\n if callable(getattr(e, 'getcode', None)) and \\\n e.getcode() == 404:\n log.error('Query malformed: %r' % query)\n raise SearchFailed()\n attr = getattr(e, 'args', [None])\n attr = attr if attr else [None]\n if isinstance(attr[0], socket.timeout):\n msg = _('Amazon timed out. Try again later.')\n log.error(msg)\n else:\n msg = 'Failed to make identify query: %r' % query\n log.exception(msg)\n raise SearchFailed()\n\n raw = clean_ascii_chars(xml_to_unicode(raw,\n strip_encoding_pats=True, resolve_entities=True)[0])\n\n if testing:\n import tempfile\n with tempfile.NamedTemporaryFile(prefix='amazon_results_',\n suffix='.html', delete=False) as f:\n f.write(raw.encode('utf-8'))\n print('Downloaded html for results page saved in', f.name)\n\n matches = []\n found = '404 - ' not in raw\n\n if found:\n try:\n root = parse_html(raw)\n except Exception:\n msg = 'Failed to parse amazon page for query: %r' % query\n log.exception(msg)\n raise SearchFailed()\n\n matches = self.parse_results_page(root, domain)\n\n return matches, query, domain, None\n # }}}\n\n def search_search_engine(self, br, testing, log, abort, title, authors, identifiers, timeout, override_server=None): # {{{\n from calibre.ebooks.metadata.sources.update import search_engines_module\n se = search_engines_module()\n terms, domain = self.create_query(log, title=title, authors=authors,\n identifiers=identifiers, for_amazon=False)\n site = self.referrer_for_domain(\n domain)[len('https://'):].partition('/')[0]\n matches = []\n server = override_server or self.server\n if server == 'bing':\n urlproc, sfunc = se.bing_url_processor, se.bing_search\n elif server == 'wayback':\n urlproc, sfunc = se.wayback_url_processor, se.ddg_search\n elif server == 'ddg':\n urlproc, sfunc = se.ddg_url_processor, se.ddg_search\n elif server == 'google':\n urlproc, sfunc = se.google_url_processor, se.google_search\n else: # auto or unknown\n urlproc, sfunc = se.google_url_processor, se.google_search\n # urlproc, sfunc = se.bing_url_processor, se.bing_search\n try:\n results, qurl = sfunc(terms, site, log=log, br=br, timeout=timeout)\n except HTTPError as err:\n if err.code == 429 and sfunc is se.google_search:\n log('Got too many requests error from Google, trying via DuckDuckGo')\n urlproc, sfunc = se.ddg_url_processor, se.ddg_search\n results, qurl = sfunc(terms, site, log=log, br=br, timeout=timeout)\n else:\n raise\n\n br.set_current_header('Referer', qurl)\n for result in results:\n if abort.is_set():\n return matches, terms, domain, None\n\n purl = urlparse(result.url)\n if '/dp/' in purl.path and site in purl.netloc:\n # We cannot use cached URL as wayback machine no longer caches\n # amazon and Google and Bing web caches are no longer\n # accessible.\n url = result.url\n if url not in matches:\n matches.append(url)\n if len(matches) >= 3:\n break\n else:\n log('Skipping non-book result:', result)\n if not matches:\n log('No search engine results for terms:', ' '.join(terms))\n if urlproc is se.google_url_processor:\n # Google does not cache adult titles\n log('Trying the bing search engine instead')\n return self.search_search_engine(br, testing, log, abort, title, authors, identifiers, timeout, 'bing')\n return matches, terms, domain, urlproc\n # }}}\n\n def identify(self, log, result_queue, abort, title=None, authors=None, # {{{\n identifiers={}, timeout=60):\n '''\n Note this method will retry without identifiers automatically if no\n match is found with identifiers.\n '''\n\n testing = getattr(self, 'running_a_test', False)\n\n udata = self._get_book_url(identifiers)\n br = self.browser\n log('User-agent:', br.current_user_agent())\n log('Server:', self.server)\n if testing:\n print('User-agent:', br.current_user_agent())\n if udata is not None and not self.use_search_engine:\n # Try to directly get details page instead of running a search\n # Cannot use search engine as the directly constructed URL is\n # usually redirected to a full URL by amazon, and is therefore\n # not cached\n domain, idtype, asin, durl = udata\n if durl is not None:\n preparsed_root = parse_details_page(\n durl, log, timeout, br, domain)\n if preparsed_root is not None:\n qasin = parse_asin(preparsed_root[1], log, durl)\n if qasin == asin:\n w = Worker(durl, result_queue, br, log, 0, domain,\n self, testing=testing, preparsed_root=preparsed_root, timeout=timeout)\n try:\n w.get_details()\n return\n except Exception:\n log.exception(\n 'get_details failed for url: %r' % durl)\n func = self.search_search_engine if self.use_search_engine else self.search_amazon\n try:\n matches, query, domain, cover_url_processor = func(\n br, testing, log, abort, title, authors, identifiers, timeout)\n except SearchFailed:\n return\n\n if abort.is_set():\n return\n\n if not matches:\n if identifiers and title and authors:\n log('No matches found with identifiers, retrying using only'\n ' title and authors. Query: %r' % query)\n time.sleep(1)\n return self.identify(log, result_queue, abort, title=title,\n authors=authors, timeout=timeout)\n log.error('No matches found with query: %r' % query)\n return\n\n if self.prefs['prefer_kindle_edition']:\n matches = sort_matches_preferring_kindle_editions(matches)\n\n workers = [Worker(\n url, result_queue, br, log, i, domain, self, testing=testing, timeout=timeout,\n cover_url_processor=cover_url_processor, filter_result=partial(\n self.filter_result, title, authors, identifiers)) for i, url in enumerate(matches)]\n\n for w in workers:\n # Don't send all requests at the same time\n time.sleep(1)\n w.start()\n if abort.is_set():\n return\n\n while not abort.is_set():\n a_worker_is_alive = False\n for w in workers:\n w.join(0.2)\n if abort.is_set():\n break\n if w.is_alive():\n a_worker_is_alive = True\n if not a_worker_is_alive:\n break\n\n return None\n # }}}\n\n def filter_result(self, title, authors, identifiers, mi, log): # {{{\n if not self.use_search_engine:\n return True\n if title is not None:\n import regex\n only_punctuation_pat = regex.compile(r'^\\p{P}+$')\n\n def tokenize_title(x):\n ans = icu_lower(x).replace(\"'\", '').replace('\"', '').rstrip(':')\n if only_punctuation_pat.match(ans) is not None:\n ans = ''\n return ans\n\n tokens = {tokenize_title(x) for x in title.split() if len(x) > 3}\n tokens.discard('')\n if tokens:\n result_tokens = {tokenize_title(x) for x in mi.title.split()}\n result_tokens.discard('')\n if not tokens.intersection(result_tokens):\n log('Ignoring result:', mi.title, 'as its title does not match')\n return False\n if authors:\n author_tokens = set()\n for author in authors:\n author_tokens |= {icu_lower(x) for x in author.split() if len(x) > 2}\n result_tokens = set()\n for author in mi.authors:\n result_tokens |= {icu_lower(x) for x in author.split() if len(x) > 2}\n if author_tokens and not author_tokens.intersection(result_tokens):\n log('Ignoring result:', mi.title, 'by', ' & '.join(mi.authors), 'as its author does not match')\n return False\n return True\n # }}}\n\n def download_cover(self, log, result_queue, abort, # {{{\n title=None, authors=None, identifiers={}, timeout=60, get_best_cover=False):\n cached_url = self.get_cached_cover_url(identifiers)\n if cached_url is None:\n log.info('No cached cover found, running identify')\n rq = Queue()\n self.identify(log, rq, abort, title=title, authors=authors,\n identifiers=identifiers)\n if abort.is_set():\n return\n results = []\n while True:\n try:\n results.append(rq.get_nowait())\n except Empty:\n break\n results.sort(key=self.identify_results_keygen(\n title=title, authors=authors, identifiers=identifiers))\n for mi in results:\n cached_url = self.get_cached_cover_url(mi.identifiers)\n if cached_url is not None:\n break\n if cached_url is None:\n log.info('No cover found')\n return\n\n if abort.is_set():\n return\n log('Downloading cover from:', cached_url)\n br = self.browser\n if self.use_search_engine:\n br = br.clone_browser()\n br.set_current_header('Referer', self.referrer_for_domain(self.domain))\n try:\n time.sleep(1)\n cdata = br.open_novisit(\n cached_url, timeout=timeout).read()\n result_queue.put((self, cdata))\n except Exception:\n log.exception('Failed to download cover from:', cached_url)\n # }}}\n\n\ndef manual_tests(domain, **kw): # {{{\n # To run these test use:\n # calibre-debug -c \"from calibre.ebooks.metadata.sources.amazon import *; manual_tests('com')\"\n from calibre.ebooks.metadata.sources.test import authors_test, comments_test, isbn_test, series_test, test_identify_plugin, title_test\n all_tests = {}\n all_tests['com'] = [ # {{{\n ( # in title\n {'title': 'Expert C# 2008 Business Objects',\n 'authors': ['Lhotka']},\n [title_test('Expert C#'),\n authors_test(['Rockford Lhotka'])\n ]\n ),\n\n ( # Paperback with series\n {'identifiers': {'amazon': '1423146786'}},\n [title_test('Heroes of Olympus', exact=False), series_test('The Heroes of Olympus', 5)]\n ),\n\n ( # Kindle edition with series\n {'identifiers': {'amazon': 'B0085UEQDO'}},\n [title_test('Three Parts Dead', exact=True),\n series_test('Craft Sequence', 1)]\n ),\n\n ( # + in title and uses id=\"main-image\" for cover\n {'identifiers': {'amazon': '1933988770'}},\n [title_test(\n 'C++ Concurrency in Action: Practical Multithreading', exact=True)]\n ),\n\n\n ( # Different comments markup, using Book Description section\n {'identifiers': {'amazon': '0982514506'}},\n [title_test(\n \"Griffin's Destiny\",\n exact=True),\n comments_test('Jelena'), comments_test('Ashinji'),\n ]\n ),\n\n ( # New search results page markup (Dec 2024)\n {'title': 'Come si scrive un articolo medico-scientifico'},\n [title_test('Come si scrive un articolo medico-scientifico', exact=True)]\n ),\n\n ( # No specific problems\n {'identifiers': {'isbn': '0743273567'}},\n [title_test('the great gatsby'),\n authors_test(['f. Scott Fitzgerald'])]\n ),\n\n ]\n\n # }}}\n\n all_tests['de'] = [ # {{{\n # series\n (\n {'identifiers': {'isbn': '3499275120'}},\n [title_test('Vespasian: Das Schwert des Tribuns: Historischer Roman',\n exact=False), authors_test(['Robert Fabbri']), series_test('Die Vespasian-Reihe', 1)\n ]\n\n ),\n\n ( # umlaut in title/authors\n {'title': 'Flüsternde Wälder',\n 'authors': ['Nicola Förg']},\n [title_test('Flüsternde Wälder'),\n authors_test(['Nicola Förg'], subset=True)\n ]\n ),\n\n (\n {'identifiers': {'isbn': '9783453314979'}},\n [title_test('Die letzten Wächter: Roman',\n exact=False), authors_test(['Sergej Lukianenko'])\n ]\n\n ),\n\n (\n {'identifiers': {'isbn': '3548283519'}},\n [title_test('Wer Wind Sät: Der Fünfte Fall Für Bodenstein Und Kirchhoff',\n exact=False), authors_test(['Nele Neuhaus'])\n ]\n\n ),\n ] # }}}\n\n all_tests['it'] = [ # {{{\n (\n {'identifiers': {'isbn': '8838922195'}},\n [title_test('La briscola in cinque',\n exact=True), authors_test(['Marco Malvaldi'])\n ]\n\n ),\n ] # }}}\n\n all_tests['fr'] = [ # {{{\n (\n {'identifiers': {'amazon_fr': 'B07L7ST4RS'}},\n [title_test('Le secret de Lola', exact=True),\n authors_test(['Amélie BRIZIO'])\n ]\n ),\n (\n {'identifiers': {'isbn': '2221116798'}},\n [title_test(\"L'étrange voyage de Monsieur Daldry\",\n exact=True), authors_test(['Marc Levy'])\n ]\n\n ),\n ] # }}}\n\n all_tests['es'] = [ # {{{\n (\n {'identifiers': {'isbn': '8483460831'}},\n [title_test('Tiempos Interesantes',\n exact=False), authors_test(['Terry Pratchett'])\n ]\n\n ),\n ] # }}}\n\n all_tests['se'] = [ # {{{\n (\n {'identifiers': {'isbn': '9780552140287'}},\n [title_test('Men At Arms: A Discworld Novel: 14',\n exact=False), authors_test(['Terry Pratchett'])\n ]\n\n ),\n ] # }}}\n\n all_tests['jp'] = [ # {{{\n ( # Adult filtering test\n {'identifiers': {'isbn': '4799500066'}},\n [title_test('Bitch Trap'), ]\n ),\n\n ( # isbn -> title, authors\n {'identifiers': {'isbn': '9784101302720'}},\n [title_test('精霊の守り人',\n exact=True), authors_test(['上橋 菜穂子'])\n ]\n ),\n ( # title, authors -> isbn (will use Shift_JIS encoding in query.)\n {'title': '考えない練習',\n 'authors': ['小池 龍之介']},\n [isbn_test('9784093881067'), ]\n ),\n ] # }}}\n\n all_tests['br'] = [ # {{{\n (\n {'title': 'A Ascensão da Sombra'},\n [title_test('A Ascensão da Sombra'), authors_test(['Robert Jordan'])]\n ),\n\n (\n {'title': 'Guerra dos Tronos'},\n [title_test('A Guerra dos Tronos. As Crônicas de Gelo e Fogo - Livro 1'), authors_test(['George R. R. Martin'])\n ]\n\n ),\n ] # }}}\n\n all_tests['nl'] = [ # {{{\n (\n {'title': 'Freakonomics'},\n [title_test('Freakonomics',\n exact=True), authors_test(['Steven Levitt & Stephen Dubner & R. Kuitenbrouwer & O. Brenninkmeijer & A. van Den Berg'])\n ]\n\n ),\n ] # }}}\n\n all_tests['cn'] = [ # {{{\n (\n {'identifiers': {'isbn': '9787115369512'}},\n [title_test('若为自由故 自由软件之父理查德斯托曼传', exact=True),\n authors_test(['[美]sam Williams', '邓楠,李凡希'])]\n ),\n (\n {'title': '爱上Raspberry Pi'},\n [title_test('爱上Raspberry Pi',\n exact=True), authors_test(['Matt Richardson', 'Shawn Wallace', '李凡希'])\n ]\n\n ),\n ] # }}}\n\n all_tests['ca'] = [ # {{{\n ( # Paperback with series\n {'identifiers': {'isbn': '9781623808747'}},\n [title_test('Parting Shot', exact=True),\n authors_test(['Mary Calmes'])]\n ),\n ( # in title\n {'title': 'Expert C# 2008 Business Objects',\n 'authors': ['Lhotka']},\n [title_test('Expert C# 2008 Business Objects'),\n authors_test(['Rockford Lhotka'])]\n ),\n ( # noscript description\n {'identifiers': {'amazon_ca': '162380874X'}},\n [title_test('Parting Shot', exact=True), authors_test(['Mary Calmes'])\n ]\n ),\n ] # }}}\n\n all_tests['in'] = [ # {{{\n ( # Paperback with series\n {'identifiers': {'amazon_in': '1423146786'}},\n [title_test('The Heroes of Olympus, Book Five The Blood of Olympus', exact=True)]\n ),\n ] # }}}\n\n def do_test(domain, start=0, stop=None, server='auto'):\n tests = all_tests[domain]\n if stop is None:\n stop = len(tests)\n tests = tests[start:stop]\n test_identify_plugin(Amazon.name, tests, modify_plugin=lambda p: (\n setattr(p, 'testing_domain', domain),\n setattr(p, 'touched_fields', p.touched_fields - {'tags'}),\n setattr(p, 'testing_server', server),\n ))\n\n do_test(domain, **kw)\n# }}}\n",
+ "big_book_search": "#!/usr/bin/env python\n# vim:fileencoding=UTF-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\n__license__ = 'GPL v3'\n__copyright__ = '2013, Kovid Goyal '\n__docformat__ = 'restructuredtext en'\n\nfrom calibre.ebooks.metadata.sources.base import Option, Source\n\n\ndef get_urls(br, tokens):\n from urllib.parse import quote_plus\n\n from html5_parser import parse\n escaped = (quote_plus(x) for x in tokens if x and x.strip())\n q = '+'.join(escaped)\n url = 'https://bigbooksearch.com/please-dont-scrape-my-site-you-will-put-my-api-key-over-the-usage-limit-and-the-site-will-break/books/'+q\n raw = br.open(url).read()\n root = parse(raw.decode('utf-8'))\n urls = [i.get('src') for i in root.xpath('//img[@src]')]\n return urls\n\n\nclass BigBookSearch(Source):\n\n name = 'Big Book Search'\n version = (1, 0, 1)\n minimum_calibre_version = (2, 80, 0)\n description = _('Downloads multiple book covers from Amazon. Useful to find alternate covers.')\n capabilities = frozenset(['cover'])\n can_get_multiple_covers = True\n options = (Option('max_covers', 'number', 5, _('Maximum number of covers to get'),\n _('The maximum number of covers to process from the search result')),\n )\n supports_gzip_transfer_encoding = True\n\n def download_cover(self, log, result_queue, abort,\n title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False):\n if not title:\n return\n br = self.browser\n tokens = tuple(self.get_title_tokens(title)) + tuple(self.get_author_tokens(authors))\n urls = get_urls(br, tokens)\n self.download_multiple_covers(title, authors, urls, get_best_cover, timeout, result_queue, abort, log)\n\n\ndef test():\n import pprint\n\n from calibre import browser\n br = browser()\n urls = get_urls(br, ['consider', 'phlebas', 'banks'])\n pprint.pprint(urls)\n\n\nif __name__ == '__main__':\n test()\n",
+ "edelweiss": "#!/usr/bin/env python\n# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\n__license__ = 'GPL v3'\n__copyright__ = '2013, Kovid Goyal '\n__docformat__ = 'restructuredtext en'\n\nimport re\nimport time\nfrom threading import Thread\n\ntry:\n from queue import Empty, Queue\nexcept ImportError:\n from Queue import Empty, Queue\n\nfrom calibre import as_unicode, random_user_agent\nfrom calibre.ebooks.metadata import check_isbn\nfrom calibre.ebooks.metadata.sources.base import Source\n\n\ndef clean_html(raw):\n from calibre.ebooks.chardet import xml_to_unicode\n from calibre.utils.cleantext import clean_ascii_chars\n return clean_ascii_chars(xml_to_unicode(raw, strip_encoding_pats=True,\n resolve_entities=True, assume_utf8=True)[0])\n\n\ndef parse_html(raw):\n raw = clean_html(raw)\n from html5_parser import parse\n return parse(raw)\n\n\ndef astext(node):\n from lxml import etree\n return etree.tostring(node, method='text', encoding='unicode',\n with_tail=False).strip()\n\n\nclass Worker(Thread): # {{{\n\n def __init__(self, basic_data, relevance, result_queue, br, timeout, log, plugin):\n Thread.__init__(self)\n self.daemon = True\n self.basic_data = basic_data\n self.br, self.log, self.timeout = br, log, timeout\n self.result_queue, self.plugin, self.sku = result_queue, plugin, self.basic_data['sku']\n self.relevance = relevance\n\n def run(self):\n url = ('https://www.edelweiss.plus/GetTreelineControl.aspx?controlName=/uc/product/two_Enhanced.ascx&'\n 'sku={0}&idPrefix=content_1_{0}&mode=0'.format(self.sku))\n try:\n raw = self.br.open_novisit(url, timeout=self.timeout).read()\n except Exception:\n self.log.exception('Failed to load comments page: %r'%url)\n return\n\n try:\n mi = self.parse(raw)\n mi.source_relevance = self.relevance\n self.plugin.clean_downloaded_metadata(mi)\n self.result_queue.put(mi)\n except Exception:\n self.log.exception('Failed to parse details for sku: %s'%self.sku)\n\n def parse(self, raw):\n from calibre.ebooks.metadata.book.base import Metadata\n from calibre.utils.date import UNDEFINED_DATE\n root = parse_html(raw)\n mi = Metadata(self.basic_data['title'], self.basic_data['authors'])\n\n # Identifiers\n if self.basic_data['isbns']:\n mi.isbn = self.basic_data['isbns'][0]\n mi.set_identifier('edelweiss', self.sku)\n\n # Tags\n if self.basic_data['tags']:\n mi.tags = self.basic_data['tags']\n mi.tags = [t[1:].strip() if t.startswith('&') else t for t in mi.tags]\n\n # Publisher\n mi.publisher = self.basic_data['publisher']\n\n # Pubdate\n if self.basic_data['pubdate'] and self.basic_data['pubdate'].year != UNDEFINED_DATE:\n mi.pubdate = self.basic_data['pubdate']\n\n # Rating\n if self.basic_data['rating']:\n mi.rating = self.basic_data['rating']\n\n # Comments\n comments = ''\n for cid in ('summary', 'contributorbio', 'quotes_reviews'):\n cid = 'desc_{}{}-content'.format(cid, self.sku)\n div = root.xpath('//*[@id=\"{}\"]'.format(cid))\n if div:\n comments += self.render_comments(div[0])\n if comments:\n mi.comments = comments\n\n mi.has_cover = self.plugin.cached_identifier_to_cover_url(self.sku) is not None\n return mi\n\n def render_comments(self, desc):\n from lxml import etree\n\n from calibre.library.comments import sanitize_comments_html\n for c in desc.xpath('descendant::noscript'):\n c.getparent().remove(c)\n for a in desc.xpath('descendant::a[@href]'):\n del a.attrib['href']\n a.tag = 'span'\n desc = etree.tostring(desc, method='html', encoding='unicode').strip()\n\n # remove all attributes from tags\n desc = re.sub(r'<([a-zA-Z0-9]+)\\s[^>]+>', r'<\\1>', desc)\n # Collapse whitespace\n # desc = re.sub(r'\\n+', '\\n', desc)\n # desc = re.sub(r' +', ' ', desc)\n # Remove comments\n desc = re.sub(r'(?s)', '', desc)\n return sanitize_comments_html(desc)\n# }}}\n\n\ndef get_basic_data(browser, log, *skus):\n from mechanize import Request\n\n from calibre.utils.date import parse_only_date\n zeroes = ','.join('0' for sku in skus)\n data = {\n 'skus': ','.join(skus),\n 'drc': zeroes,\n 'startPosition': '0',\n 'sequence': '1',\n 'selected': zeroes,\n 'itemID': '0',\n 'orderID': '0',\n 'mailingID': '',\n 'tContentWidth': '926',\n 'originalOrder': ','.join(type('')(i) for i in range(len(skus))),\n 'selectedOrderID': '0',\n 'selectedSortColumn': '0',\n 'listType': '1',\n 'resultType': '32',\n 'blockView': '1',\n }\n items_data_url = 'https://www.edelweiss.plus/GetTreelineControl.aspx?controlName=/uc/listviews/ListView_Title_Multi.ascx'\n req = Request(items_data_url, data)\n response = browser.open_novisit(req)\n raw = response.read()\n root = parse_html(raw)\n for item in root.xpath('//div[@data-priority]'):\n row = item.getparent().getparent()\n sku = item.get('id').split('-')[-1]\n isbns = [x.strip() for x in row.xpath('descendant::*[contains(@class, \"pev_sku\")]/text()')[0].split(',') if check_isbn(x.strip())]\n isbns.sort(key=len, reverse=True)\n try:\n tags = [x.strip() for x in astext(row.xpath('descendant::*[contains(@class, \"pev_categories\")]')[0]).split('/')]\n except IndexError:\n tags = []\n rating = 0\n for bar in row.xpath('descendant::*[contains(@class, \"bgdColorCommunity\")]/@style'):\n m = re.search(r'width: (\\d+)px;.*max-width: (\\d+)px', bar)\n if m is not None:\n rating = float(m.group(1)) / float(m.group(2))\n break\n try:\n pubdate = parse_only_date(astext(row.xpath('descendant::*[contains(@class, \"pev_shipDate\")]')[0]\n ).split(':')[-1].split(u'\\xa0')[-1].strip(), assume_utc=True)\n except Exception:\n log.exception('Error parsing published date')\n pubdate = None\n authors = []\n for x in [x.strip() for x in row.xpath('descendant::*[contains(@class, \"pev_contributor\")]/@title')]:\n authors.extend(a.strip() for a in x.split(','))\n entry = {\n 'sku': sku,\n 'cover': row.xpath('descendant::img/@src')[0].split('?')[0],\n 'publisher': astext(row.xpath('descendant::*[contains(@class, \"headerPublisher\")]')[0]),\n 'title': astext(row.xpath('descendant::*[@id=\"title_{}\"]'.format(sku))[0]),\n 'authors': authors,\n 'isbns': isbns,\n 'tags': tags,\n 'pubdate': pubdate,\n 'format': ' '.join(row.xpath('descendant::*[contains(@class, \"pev_format\")]/text()')).strip(),\n 'rating': rating,\n }\n if entry['cover'].startswith('/'):\n entry['cover'] = None\n yield entry\n\n\nclass Edelweiss(Source):\n\n name = 'Edelweiss'\n version = (2, 0, 1)\n minimum_calibre_version = (3, 6, 0)\n description = _('Downloads metadata and covers from Edelweiss - A catalog updated by book publishers')\n\n capabilities = frozenset(['identify', 'cover'])\n touched_fields = frozenset([\n 'title', 'authors', 'tags', 'pubdate', 'comments', 'publisher',\n 'identifier:isbn', 'identifier:edelweiss', 'rating'])\n supports_gzip_transfer_encoding = True\n has_html_comments = True\n\n @property\n def user_agent(self):\n # Pass in an index to random_user_agent() to test with a particular\n # user agent\n return random_user_agent(allow_ie=False)\n\n def _get_book_url(self, sku):\n if sku:\n return 'https://www.edelweiss.plus/#sku={}&page=1'.format(sku)\n\n def get_book_url(self, identifiers): # {{{\n sku = identifiers.get('edelweiss', None)\n if sku:\n return 'edelweiss', sku, self._get_book_url(sku)\n\n # }}}\n\n def get_cached_cover_url(self, identifiers): # {{{\n sku = identifiers.get('edelweiss', None)\n if not sku:\n isbn = identifiers.get('isbn', None)\n if isbn is not None:\n sku = self.cached_isbn_to_identifier(isbn)\n return self.cached_identifier_to_cover_url(sku)\n # }}}\n\n def create_query(self, log, title=None, authors=None, identifiers={}):\n try:\n from urllib.parse import urlencode\n except ImportError:\n from urllib import urlencode\n import time\n BASE_URL = ('https://www.edelweiss.plus/GetTreelineControl.aspx?'\n 'controlName=/uc/listviews/controls/ListView_data.ascx&itemID=0&resultType=32&dashboardType=8&itemType=1&dataType=products&keywordSearch&')\n keywords = []\n isbn = check_isbn(identifiers.get('isbn', None))\n if isbn is not None:\n keywords.append(isbn)\n elif title:\n title_tokens = list(self.get_title_tokens(title))\n if title_tokens:\n keywords.extend(title_tokens)\n author_tokens = self.get_author_tokens(authors, only_first_author=True)\n if author_tokens:\n keywords.extend(author_tokens)\n if not keywords:\n return None\n params = {\n 'q': (' '.join(keywords)).encode('utf-8'),\n '_': type('')(int(time.time()))\n }\n return BASE_URL+urlencode(params)\n\n # }}}\n\n def identify(self, log, result_queue, abort, title=None, authors=None, # {{{\n identifiers={}, timeout=30):\n import json\n\n br = self.browser\n br.addheaders = [\n ('Referer', 'https://www.edelweiss.plus/'),\n ('X-Requested-With', 'XMLHttpRequest'),\n ('Cache-Control', 'no-cache'),\n ('Pragma', 'no-cache'),\n ]\n if 'edelweiss' in identifiers:\n items = [identifiers['edelweiss']]\n else:\n log.error('Currently Edelweiss returns random books for search queries')\n return\n query = self.create_query(log, title=title, authors=authors,\n identifiers=identifiers)\n if not query:\n log.error('Insufficient metadata to construct query')\n return\n log('Using query URL:', query)\n try:\n raw = br.open(query, timeout=timeout).read().decode('utf-8')\n except Exception as e:\n log.exception('Failed to make identify query: %r'%query)\n return as_unicode(e)\n items = re.search(r'window[.]items\\s*=\\s*(.+?);', raw)\n if items is None:\n log.error('Failed to get list of matching items')\n log.debug('Response text:')\n log.debug(raw)\n return\n items = json.loads(items.group(1))\n\n if (not items and identifiers and title and authors and\n not abort.is_set()):\n return self.identify(log, result_queue, abort, title=title,\n authors=authors, timeout=timeout)\n\n if not items:\n return\n\n workers = []\n items = items[:5]\n for i, item in enumerate(get_basic_data(self.browser, log, *items)):\n sku = item['sku']\n for isbn in item['isbns']:\n self.cache_isbn_to_identifier(isbn, sku)\n if item['cover']:\n self.cache_identifier_to_cover_url(sku, item['cover'])\n fmt = item['format'].lower()\n if 'audio' in fmt or 'mp3' in fmt:\n continue # Audio-book, ignore\n workers.append(Worker(item, i, result_queue, br.clone_browser(), timeout, log, self))\n\n if not workers:\n return\n\n for w in workers:\n w.start()\n # Don't send all requests at the same time\n time.sleep(0.1)\n\n while not abort.is_set():\n a_worker_is_alive = False\n for w in workers:\n w.join(0.2)\n if abort.is_set():\n break\n if w.is_alive():\n a_worker_is_alive = True\n if not a_worker_is_alive:\n break\n\n # }}}\n\n def download_cover(self, log, result_queue, abort, # {{{\n title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False):\n cached_url = self.get_cached_cover_url(identifiers)\n if cached_url is None:\n log.info('No cached cover found, running identify')\n rq = Queue()\n self.identify(log, rq, abort, title=title, authors=authors,\n identifiers=identifiers)\n if abort.is_set():\n return\n results = []\n while True:\n try:\n results.append(rq.get_nowait())\n except Empty:\n break\n results.sort(key=self.identify_results_keygen(\n title=title, authors=authors, identifiers=identifiers))\n for mi in results:\n cached_url = self.get_cached_cover_url(mi.identifiers)\n if cached_url is not None:\n break\n if cached_url is None:\n log.info('No cover found')\n return\n\n if abort.is_set():\n return\n br = self.browser\n log('Downloading cover from:', cached_url)\n try:\n cdata = br.open_novisit(cached_url, timeout=timeout).read()\n result_queue.put((self, cdata))\n except Exception:\n log.exception('Failed to download cover from:', cached_url)\n # }}}\n\n\nif __name__ == '__main__':\n from calibre.ebooks.metadata.sources.test import authors_test, comments_test, pubdate_test, test_identify_plugin, title_test\n tests = [\n ( # A title and author search\n {'title': \"The Husband's Secret\", 'authors':['Liane Moriarty']},\n [title_test(\"The Husband's Secret\", exact=True),\n authors_test(['Liane Moriarty'])]\n ),\n\n ( # An isbn present in edelweiss\n {'identifiers':{'isbn': '9780312621360'}, },\n [title_test('Flame: A Sky Chasers Novel', exact=True),\n authors_test(['Amy Kathleen Ryan'])]\n ),\n\n # Multiple authors and two part title and no general description\n ({'identifiers':{'edelweiss':'0321180607'}},\n [title_test('XQuery From the Experts: A Guide to the W3C XML Query Language', exact=True),\n authors_test([\n 'Howard Katz', 'Don Chamberlin', 'Denise Draper', 'Mary Fernandez',\n 'Michael Kay', 'Jonathan Robie', 'Michael Rys', 'Jerome Simeon',\n 'Jim Tivy', 'Philip Wadler']),\n pubdate_test(2003, 8, 22),\n comments_test('Jérôme Siméon'), lambda mi: bool(mi.comments and 'No title summary' not in mi.comments)\n ]),\n ]\n start, stop = 0, len(tests)\n\n tests = tests[start:stop]\n test_identify_plugin(Edelweiss.name, tests)\n",
+ "google": "#!/usr/bin/env python\n# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai\n# License: GPLv3 Copyright: 2011, Kovid Goyal \nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport hashlib\nimport os\nimport re\nimport sys\nimport tempfile\nimport time\n\nimport regex\n\ntry:\n from queue import Empty, Queue\nexcept ImportError:\n from Queue import Empty, Queue\n\nfrom calibre import as_unicode, prepare_string_for_xml, replace_entities\nfrom calibre.ebooks.chardet import xml_to_unicode\nfrom calibre.ebooks.metadata import authors_to_string, check_isbn\nfrom calibre.ebooks.metadata.book.base import Metadata\nfrom calibre.ebooks.metadata.sources.base import Source\nfrom calibre.utils.cleantext import clean_ascii_chars\nfrom calibre.utils.localization import canonicalize_lang\n\nNAMESPACES = {\n 'openSearch': 'http://a9.com/-/spec/opensearchrss/1.0/',\n 'atom': 'http://www.w3.org/2005/Atom',\n 'dc': 'http://purl.org/dc/terms',\n 'gd': 'http://schemas.google.com/g/2005'\n}\n\n\ndef pretty_google_books_comments(raw):\n raw = replace_entities(raw)\n # Paragraphs in the comments are removed but whatever software googl uses\n # to do this does not insert a space so we often find the pattern\n # word.Capital in the comments which can be used to find paragraph markers.\n parts = []\n for x in re.split(r'([a-z)\"”])(\\.)([A-Z(\"“])', raw):\n if x == '.':\n parts.append('.
\\n\\n
')\n else:\n parts.append(prepare_string_for_xml(x))\n raw = '
' + ''.join(parts) + '
'\n return raw\n\n\ndef get_details(browser, url, timeout): # {{{\n try:\n raw = browser.open_novisit(url, timeout=timeout).read()\n except Exception as e:\n gc = getattr(e, 'getcode', lambda: -1)\n if gc() != 403:\n raise\n # Google is throttling us, wait a little\n time.sleep(2)\n raw = browser.open_novisit(url, timeout=timeout).read()\n\n return raw\n# }}}\n\n\nxpath_cache = {}\n\n\ndef XPath(x):\n ans = xpath_cache.get(x)\n if ans is None:\n from lxml import etree\n ans = xpath_cache[x] = etree.XPath(x, namespaces=NAMESPACES)\n return ans\n\n\ndef to_metadata(browser, log, entry_, timeout, running_a_test=False): # {{{\n from calibre.utils.xml_parse import safe_xml_fromstring\n # total_results = XPath('//openSearch:totalResults')\n # start_index = XPath('//openSearch:startIndex')\n # items_per_page = XPath('//openSearch:itemsPerPage')\n entry = XPath('//atom:entry')\n entry_id = XPath('descendant::atom:id')\n url = XPath('descendant::atom:link[@rel=\"self\"]/@href')\n creator = XPath('descendant::dc:creator')\n identifier = XPath('descendant::dc:identifier')\n title = XPath('descendant::dc:title')\n date = XPath('descendant::dc:date')\n publisher = XPath('descendant::dc:publisher')\n subject = XPath('descendant::dc:subject')\n description = XPath('descendant::dc:description')\n language = XPath('descendant::dc:language')\n\n # print(etree.tostring(entry_, pretty_print=True))\n\n def get_text(extra, x):\n try:\n ans = x(extra)\n if ans:\n ans = ans[0].text\n if ans and ans.strip():\n return ans.strip()\n except Exception:\n log.exception('Programming error:')\n return None\n\n def get_extra_details():\n raw = get_details(browser, details_url, timeout)\n if running_a_test:\n with open(os.path.join(tempfile.gettempdir(), 'Google-' + details_url.split('/')[-1] + '.xml'), 'wb') as f:\n f.write(raw)\n print('Book details saved to:', f.name, file=sys.stderr)\n feed = safe_xml_fromstring(xml_to_unicode(clean_ascii_chars(raw), strip_encoding_pats=True)[0])\n return entry(feed)[0]\n\n if isinstance(entry_, str):\n google_id = entry_\n details_url = 'https://www.google.com/books/feeds/volumes/' + google_id\n extra = get_extra_details()\n title_ = ': '.join([x.text for x in title(extra)]).strip()\n authors = [x.text.strip() for x in creator(extra) if x.text]\n else:\n id_url = entry_id(entry_)[0].text\n google_id = id_url.split('/')[-1]\n details_url = url(entry_)[0]\n title_ = ': '.join([x.text for x in title(entry_)]).strip()\n authors = [x.text.strip() for x in creator(entry_) if x.text]\n if not id_url or not title:\n # Silently discard this entry\n return None\n extra = None\n\n if not authors:\n authors = [_('Unknown')]\n if not title:\n return None\n if extra is None:\n extra = get_extra_details()\n mi = Metadata(title_, authors)\n mi.identifiers = {'google': google_id}\n mi.comments = get_text(extra, description)\n lang = canonicalize_lang(get_text(extra, language))\n if lang:\n mi.language = lang\n mi.publisher = get_text(extra, publisher)\n\n # ISBN\n isbns = []\n for x in identifier(extra):\n t = type('')(x.text).strip()\n if t[:5].upper() in ('ISBN:', 'LCCN:', 'OCLC:'):\n if t[:5].upper() == 'ISBN:':\n t = check_isbn(t[5:])\n if t:\n isbns.append(t)\n if isbns:\n mi.isbn = sorted(isbns, key=len)[-1]\n mi.all_isbns = isbns\n\n # Tags\n try:\n btags = [x.text for x in subject(extra) if x.text]\n tags = []\n for t in btags:\n atags = [y.strip() for y in t.split('/')]\n for tag in atags:\n if tag not in tags:\n tags.append(tag)\n except Exception:\n log.exception('Failed to parse tags:')\n tags = []\n if tags:\n mi.tags = [x.replace(',', ';') for x in tags]\n\n # pubdate\n pubdate = get_text(extra, date)\n if pubdate:\n from calibre.utils.date import parse_date, utcnow\n try:\n default = utcnow().replace(day=15)\n mi.pubdate = parse_date(pubdate, assume_utc=True, default=default)\n except Exception:\n log.error('Failed to parse pubdate %r' % pubdate)\n\n # Cover\n mi.has_google_cover = None\n for x in extra.xpath(\n '//*[@href and @rel=\"http://schemas.google.com/books/2008/thumbnail\"]'\n ):\n mi.has_google_cover = x.get('href')\n break\n\n return mi\n\n# }}}\n\n\nclass GoogleBooks(Source):\n\n name = 'Google'\n version = (1, 1, 3)\n minimum_calibre_version = (2, 80, 0)\n description = _('Downloads metadata and covers from Google Books')\n\n capabilities = frozenset({'identify'})\n touched_fields = frozenset({\n 'title', 'authors', 'tags', 'pubdate', 'comments', 'publisher',\n 'identifier:isbn', 'identifier:google', 'languages'\n })\n supports_gzip_transfer_encoding = True\n cached_cover_url_is_reliable = False\n\n GOOGLE_COVER = 'https://books.google.com/books?id=%s&printsec=frontcover&img=1'\n\n DUMMY_IMAGE_MD5 = frozenset(\n ('0de4383ebad0adad5eeb8975cd796657', 'a64fa89d7ebc97075c1d363fc5fea71f')\n )\n\n def get_book_url(self, identifiers): # {{{\n goog = identifiers.get('google', None)\n if goog is not None:\n return ('google', goog, 'https://books.google.com/books?id=%s' % goog)\n # }}}\n\n def id_from_url(self, url): # {{{\n from polyglot.urllib import parse_qs, urlparse\n purl = urlparse(url)\n if purl.netloc == 'books.google.com':\n q = parse_qs(purl.query)\n gid = q.get('id')\n if gid:\n return 'google', gid[0]\n # }}}\n\n def create_query(self, title=None, authors=None, identifiers={}, capitalize_isbn=False): # {{{\n try:\n from urllib.parse import urlencode\n except ImportError:\n from urllib import urlencode\n BASE_URL = 'https://books.google.com/books/feeds/volumes?'\n isbn = check_isbn(identifiers.get('isbn', None))\n q = ''\n if isbn is not None:\n q += ('ISBN:' if capitalize_isbn else 'isbn:') + isbn\n elif title or authors:\n\n def build_term(prefix, parts):\n return ' '.join('in' + prefix + ':' + x for x in parts)\n\n title_tokens = list(self.get_title_tokens(title))\n if title_tokens:\n q += build_term('title', title_tokens)\n author_tokens = list(self.get_author_tokens(authors, only_first_author=True))\n if author_tokens:\n q += ('+' if q else '') + build_term('author', author_tokens)\n\n if not q:\n return None\n if not isinstance(q, bytes):\n q = q.encode('utf-8')\n return BASE_URL + urlencode({\n 'q': q,\n 'max-results': 20,\n 'start-index': 1,\n 'min-viewability': 'none',\n })\n\n # }}}\n\n def download_cover( # {{{\n self,\n log,\n result_queue,\n abort,\n title=None,\n authors=None,\n identifiers={},\n timeout=30,\n get_best_cover=False\n ):\n cached_url = self.get_cached_cover_url(identifiers)\n if cached_url is None:\n log.info('No cached cover found, running identify')\n rq = Queue()\n self.identify(\n log,\n rq,\n abort,\n title=title,\n authors=authors,\n identifiers=identifiers\n )\n if abort.is_set():\n return\n results = []\n while True:\n try:\n results.append(rq.get_nowait())\n except Empty:\n break\n results.sort(\n key=self.identify_results_keygen(\n title=title, authors=authors, identifiers=identifiers\n )\n )\n for mi in results:\n cached_url = self.get_cached_cover_url(mi.identifiers)\n if cached_url is not None:\n break\n if cached_url is None:\n log.info('No cover found')\n return\n\n br = self.browser\n for candidate in (0, 1):\n if abort.is_set():\n return\n url = cached_url + '&zoom={}'.format(candidate)\n log('Downloading cover from:', cached_url)\n try:\n cdata = br.open_novisit(url, timeout=timeout).read()\n if cdata:\n if hashlib.md5(cdata).hexdigest() in self.DUMMY_IMAGE_MD5:\n log.warning('Google returned a dummy image, ignoring')\n else:\n result_queue.put((self, cdata))\n break\n except Exception:\n log.exception('Failed to download cover from:', cached_url)\n\n # }}}\n\n def get_cached_cover_url(self, identifiers): # {{{\n url = None\n goog = identifiers.get('google', None)\n if goog is None:\n isbn = identifiers.get('isbn', None)\n if isbn is not None:\n goog = self.cached_isbn_to_identifier(isbn)\n if goog is not None:\n url = self.cached_identifier_to_cover_url(goog)\n\n return url\n\n # }}}\n\n def postprocess_downloaded_google_metadata(self, ans, relevance=0): # {{{\n if not isinstance(ans, Metadata):\n return ans\n ans.source_relevance = relevance\n goog = ans.identifiers['google']\n for isbn in getattr(ans, 'all_isbns', []):\n self.cache_isbn_to_identifier(isbn, goog)\n if getattr(ans, 'has_google_cover', False):\n self.cache_identifier_to_cover_url(goog, self.GOOGLE_COVER % goog)\n if ans.comments:\n ans.comments = pretty_google_books_comments(ans.comments)\n self.clean_downloaded_metadata(ans)\n return ans\n # }}}\n\n def get_all_details( # {{{\n self,\n br,\n log,\n entries,\n abort,\n result_queue,\n timeout\n ):\n from lxml import etree\n for relevance, i in enumerate(entries):\n try:\n ans = self.postprocess_downloaded_google_metadata(to_metadata(br, log, i, timeout, self.running_a_test), relevance)\n if isinstance(ans, Metadata):\n result_queue.put(ans)\n except Exception:\n log.exception(\n 'Failed to get metadata for identify entry:', etree.tostring(i)\n )\n if abort.is_set():\n break\n\n # }}}\n\n def identify_via_web_search( # {{{\n self,\n log,\n result_queue,\n abort,\n title=None,\n authors=None,\n identifiers={},\n timeout=30\n ):\n from calibre.utils.filenames import ascii_text\n from polyglot.urllib import urlparse\n isbn = check_isbn(identifiers.get('isbn', None))\n q = []\n strip_punc_pat = regex.compile(r'[\\p{C}|\\p{M}|\\p{P}|\\p{S}|\\p{Z}]+', regex.UNICODE)\n google_ids = []\n check_tokens = set()\n has_google_id = 'google' in identifiers\n\n def to_check_tokens(*tokens):\n for t in tokens:\n if len(t) < 3:\n continue\n t = t.lower()\n if t in ('and', 'not', 'the'):\n continue\n yield ascii_text(strip_punc_pat.sub('', t))\n\n if has_google_id:\n google_ids.append(identifiers['google'])\n elif isbn is not None:\n q.append(isbn)\n elif title or authors:\n title_tokens = list(self.get_title_tokens(title))\n if title_tokens:\n q += title_tokens\n check_tokens |= set(to_check_tokens(*title_tokens))\n author_tokens = list(self.get_author_tokens(authors, only_first_author=True))\n if author_tokens:\n q += author_tokens\n check_tokens |= set(to_check_tokens(*author_tokens))\n if not q and not google_ids:\n return None\n from calibre.ebooks.metadata.sources.update import search_engines_module\n se = search_engines_module()\n br = se.google_specialize_browser(se.browser())\n if not has_google_id:\n url = se.google_format_query(q, site='books.google.com')\n log('Making query:', url)\n r = []\n root = se.query(br, url, 'google', timeout=timeout, save_raw=r.append)\n pat = re.compile(r'id=([^&]+)')\n for q in se.google_parse_results(root, r[0], log=log, ignore_uncached=False):\n m = pat.search(q.url)\n if m is None or not q.url:\n continue\n try:\n purl = urlparse(q.url)\n except Exception:\n continue\n if not purl.hostname.startswith('books.google'):\n continue\n google_ids.append(m.group(1))\n\n if not google_ids and isbn and (title or authors):\n return self.identify_via_web_search(log, result_queue, abort, title, authors, {}, timeout)\n found = False\n seen = set()\n for relevance, gid in enumerate(google_ids):\n if gid in seen:\n continue\n seen.add(gid)\n try:\n ans = to_metadata(br, log, gid, timeout, self.running_a_test)\n if isinstance(ans, Metadata):\n if isbn:\n if isbn not in ans.all_isbns:\n log('Excluding', ans.title, 'by', authors_to_string(ans.authors), 'as it does not match the ISBN:', isbn,\n 'not in', ' '.join(ans.all_isbns))\n continue\n elif check_tokens:\n candidate = set(to_check_tokens(*self.get_title_tokens(ans.title)))\n candidate |= set(to_check_tokens(*self.get_author_tokens(ans.authors)))\n if candidate.intersection(check_tokens) != check_tokens:\n log('Excluding', ans.title, 'by', authors_to_string(ans.authors), 'as it does not match the query')\n continue\n ans = self.postprocess_downloaded_google_metadata(ans, relevance)\n result_queue.put(ans)\n found = True\n except Exception:\n log.exception('Failed to get metadata for google books id:', gid)\n if abort.is_set():\n break\n if not found and isbn and (title or authors):\n return self.identify_via_web_search(log, result_queue, abort, title, authors, {}, timeout)\n # }}}\n\n def identify( # {{{\n self,\n log,\n result_queue,\n abort,\n title=None,\n authors=None,\n identifiers={},\n timeout=30\n ):\n from calibre.utils.xml_parse import safe_xml_fromstring\n entry = XPath('//atom:entry')\n identifiers = identifiers.copy()\n br = self.browser\n if 'google' in identifiers:\n try:\n ans = to_metadata(br, log, identifiers['google'], timeout, self.running_a_test)\n if isinstance(ans, Metadata):\n self.postprocess_downloaded_google_metadata(ans)\n result_queue.put(ans)\n return\n except Exception:\n log.exception('Failed to get metadata for Google identifier:', identifiers['google'])\n del identifiers['google']\n\n query = self.create_query(\n title=title, authors=authors, identifiers=identifiers\n )\n if not query:\n log.error('Insufficient metadata to construct query')\n return\n\n def make_query(query):\n log('Making query:', query)\n try:\n raw = br.open_novisit(query, timeout=timeout).read()\n except Exception as e:\n log.exception('Failed to make identify query: %r' % query)\n return False, as_unicode(e)\n\n try:\n feed = safe_xml_fromstring(xml_to_unicode(clean_ascii_chars(raw), strip_encoding_pats=True)[0])\n return True, entry(feed)\n except Exception as e:\n log.exception('Failed to parse identify results')\n return False, as_unicode(e)\n ok, entries = make_query(query)\n if not ok:\n return entries\n if not entries and not abort.is_set():\n log('No results found, doing a web search instead')\n return self.identify_via_web_search(log, result_queue, abort, title, authors, identifiers, timeout)\n\n # There is no point running these queries in threads as google\n # throttles requests returning 403 Forbidden errors\n self.get_all_details(br, log, entries, abort, result_queue, timeout)\n\n # }}}\n\n\nif __name__ == '__main__': # tests {{{\n # To run these test use:\n # calibre-debug src/calibre/ebooks/metadata/sources/google.py\n from calibre.ebooks.metadata.sources.test import authors_test, test_identify_plugin, title_test\n tests = [\n ({\n 'identifiers': {'google': 's7NIrgEACAAJ'},\n }, [title_test('Ride Every Stride', exact=False)]),\n\n ({\n 'identifiers': {'isbn': '0743273567'},\n 'title': 'Great Gatsby',\n 'authors': ['Fitzgerald']\n }, [\n title_test('The great gatsby', exact=True),\n authors_test(['F. Scott Fitzgerald'])\n ]),\n\n ({\n 'title': 'Flatland',\n 'authors': ['Abbott']\n }, [title_test('Flatland', exact=False)]),\n\n ({\n 'title': 'The Blood Red Indian Summer: A Berger and Mitry Mystery',\n 'authors': ['David Handler'],\n }, [title_test('The Blood Red Indian Summer: A Berger and Mitry Mystery')\n ]),\n\n ({\n # requires using web search to find the book, but web search is broken currently\n 'title': 'Dragon Done It',\n 'authors': ['Eric Flint'],\n }, [\n title_test('The dragon done it', exact=True),\n authors_test(['Eric Flint', 'Mike Resnick'])\n ]),\n\n ]\n test_identify_plugin(GoogleBooks.name, tests)\n\n# }}}\n",
+ "google_images": "#!/usr/bin/env python\n# vim:fileencoding=UTF-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\n__license__ = 'GPL v3'\n__copyright__ = '2013, Kovid Goyal '\n__docformat__ = 'restructuredtext en'\n\nfrom collections import OrderedDict\n\nfrom calibre import random_user_agent\nfrom calibre.ebooks.metadata.sources.base import Option, Source\n\n\ndef parse_html(raw):\n try:\n from html5_parser import parse\n except ImportError:\n # Old versions of calibre\n import html5lib\n return html5lib.parse(raw, treebuilder='lxml', namespaceHTMLElements=False)\n else:\n return parse(raw)\n\n\ndef imgurl_from_id(raw, tbnid):\n from json import JSONDecoder\n q = '\"{}\",['.format(tbnid)\n start_pos = raw.index(q)\n if start_pos < 100:\n return\n jd = JSONDecoder()\n data = jd.raw_decode('[' + raw[start_pos:])[0]\n # from pprint import pprint\n # pprint(data)\n url_num = 0\n for x in data:\n if isinstance(x, list) and len(x) == 3:\n q = x[0]\n if hasattr(q, 'lower') and q.lower().startswith('http'):\n url_num += 1\n if url_num > 1:\n return q\n\n\ndef parse_google_markup(raw):\n root = parse_html(raw)\n # newer markup pages use data-docid not data-tbnid\n results = root.xpath('//div/@data-tbnid') or root.xpath('//div/@data-docid')\n ans = OrderedDict()\n for tbnid in results:\n try:\n imgurl = imgurl_from_id(raw, tbnid)\n except Exception:\n continue\n if imgurl:\n ans[imgurl] = True\n return list(ans)\n\n\nclass GoogleImages(Source):\n\n name = 'Google Images'\n version = (1, 0, 6)\n minimum_calibre_version = (2, 80, 0)\n description = _('Downloads covers from a Google Image search. Useful to find larger/alternate covers.')\n capabilities = frozenset(['cover'])\n can_get_multiple_covers = True\n supports_gzip_transfer_encoding = True\n options = (Option('max_covers', 'number', 5, _('Maximum number of covers to get'),\n _('The maximum number of covers to process from the Google search result')),\n Option('size', 'choices', 'svga', _('Cover size'),\n _('Search for covers larger than the specified size'),\n choices=OrderedDict((\n ('any', _('Any size'),),\n ('l', _('Large'),),\n ('qsvga', _('Larger than %s')%'400x300',),\n ('vga', _('Larger than %s')%'640x480',),\n ('svga', _('Larger than %s')%'600x800',),\n ('xga', _('Larger than %s')%'1024x768',),\n ('2mp', _('Larger than %s')%'2 MP',),\n ('4mp', _('Larger than %s')%'4 MP',),\n ))),\n )\n\n def download_cover(self, log, result_queue, abort,\n title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False):\n if not title:\n return\n timeout = max(60, timeout) # Needs at least a minute\n title = ' '.join(self.get_title_tokens(title))\n author = ' '.join(self.get_author_tokens(authors))\n urls = self.get_image_urls(title, author, log, abort, timeout)\n self.download_multiple_covers(title, authors, urls, get_best_cover, timeout, result_queue, abort, log)\n\n @property\n def user_agent(self):\n return random_user_agent(allow_ie=False)\n\n def get_image_urls(self, title, author, log, abort, timeout):\n from calibre.utils.cleantext import clean_ascii_chars\n try:\n from urllib.parse import urlencode\n except ImportError:\n from urllib import urlencode\n br = self.browser\n q = urlencode({'as_q': ('%s %s'%(title, author)).encode('utf-8')})\n if isinstance(q, bytes):\n q = q.decode('utf-8')\n sz = self.prefs['size']\n if sz == 'any':\n sz = ''\n elif sz == 'l':\n sz = 'isz:l,'\n else:\n sz = 'isz:lt,islt:%s,' % sz\n # See https://www.google.com/advanced_image_search to understand this\n # URL scheme\n url = 'https://www.google.com/search?as_st=y&tbm=isch&{}&as_epq=&as_oq=&as_eq=&cr=&as_sitesearch=&safe=images&tbs={}iar:t,ift:jpg'.format(q, sz)\n log('Search URL: ' + url)\n # See https://github.com/benbusby/whoogle-search/pull/1054 for cookies\n br.set_simple_cookie('CONSENT', 'PENDING+987', '.google.com', path='/')\n template = b'\\x08\\x01\\x128\\x08\\x14\\x12+boq_identityfrontenduiserver_20231107.05_p0\\x1a\\x05en-US \\x03\\x1a\\x06\\x08\\x80\\xf1\\xca\\xaa\\x06'\n from base64 import standard_b64encode\n from datetime import date\n template.replace(b'20231107', date.today().strftime('%Y%m%d').encode('ascii'))\n br.set_simple_cookie('SOCS', standard_b64encode(template).decode('ascii').rstrip('='), '.google.com', path='/')\n # br.set_debug_http(True)\n raw = clean_ascii_chars(br.open(url).read().decode('utf-8'))\n # with open('/t/raw.html', 'w') as f:\n # f.write(raw)\n return parse_google_markup(raw)\n\n\ndef test_raw():\n import sys\n raw = open(sys.argv[-1]).read()\n for x in parse_google_markup(raw):\n print(x)\n\n\ndef test(title='Star Trek: Section 31: Control', authors=('David Mack',)):\n try:\n from queue import Queue\n except ImportError:\n from Queue import Queue\n from threading import Event\n\n from calibre.utils.logging import default_log\n p = GoogleImages(None)\n p.log = default_log\n rq = Queue()\n p.download_cover(default_log, rq, Event(), title=title, authors=authors)\n print('Downloaded', rq.qsize(), 'covers')\n\n\nif __name__ == '__main__':\n test()\n",
+ "hashes": {
+ "amazon": "3b8bf719310f23384f819213f656693baac2821f",
+ "big_book_search": "7a8b67c0f19ecbfe8a9d28b961aab1119f31c3e3",
+ "edelweiss": "640a39d0926dfdaa72f54160a1db5323b4d7c164",
+ "google": "e71e2242c2dababa10f73d9c2aee9b2864d4f80a",
+ "google_images": "4244dd8267cb6215c7dfd2da166c6e02b1db31ea",
+ "openlibrary": "239077a692701cbf0281e7a2e64306cd00217410",
+ "search_engines": "65c9081d21ecf31abf31ceb5f0d87870fcd943db"
+ },
+ "openlibrary": "#!/usr/bin/env python\n# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\n__license__ = 'GPL v3'\n__copyright__ = '2011, Kovid Goyal '\n__docformat__ = 'restructuredtext en'\n\nfrom calibre.ebooks.metadata.sources.base import Source\n\n\nclass OpenLibrary(Source):\n\n name = 'Open Library'\n version = (1, 0, 2)\n minimum_calibre_version = (2, 80, 0)\n description = _('Downloads covers from The Open Library')\n\n capabilities = frozenset(['cover'])\n\n OPENLIBRARY = 'https://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'\n\n def download_cover(self, log, result_queue, abort,\n title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False):\n if 'isbn' not in identifiers:\n return\n isbn = identifiers['isbn']\n br = self.browser\n try:\n ans = br.open_novisit(self.OPENLIBRARY%isbn, timeout=timeout).read()\n result_queue.put((self, ans))\n except Exception as e:\n if callable(getattr(e, 'getcode', None)) and e.getcode() == 404:\n log.error('No cover for ISBN: %r found'%isbn)\n else:\n log.exception('Failed to download cover for ISBN:', isbn)\n",
+ "search_engines": "#!/usr/bin/env python\n# vim:fileencoding=utf-8\n# License: GPLv3 Copyright: 2017, Kovid Goyal \n\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport json\nimport os\nimport re\nimport sys\nimport time\nfrom collections import namedtuple\nfrom contextlib import contextmanager\nfrom functools import partial\nfrom threading import Lock\n\ntry:\n from urllib.parse import parse_qs, quote, quote_plus, urlencode, urlparse\nexcept ImportError:\n from urllib import quote, quote_plus, urlencode\n\n from urlparse import parse_qs, urlparse\n\nfrom lxml import etree\n\nfrom calibre import browser as _browser\nfrom calibre import prints as safe_print\nfrom calibre import random_user_agent\nfrom calibre.constants import cache_dir\nfrom calibre.ebooks.chardet import xml_to_unicode\nfrom calibre.utils.lock import ExclusiveFile\nfrom calibre.utils.random_ua import accept_header_for_ua\n\ncurrent_version = (1, 2, 15)\nminimum_calibre_version = (2, 80, 0)\nwebcache = {}\nwebcache_lock = Lock()\nprints = partial(safe_print, file=sys.stderr)\n\n\nResult = namedtuple('Result', 'url title cached_url')\n\n\n@contextmanager\ndef rate_limit(name='test', time_between_visits=2, max_wait_seconds=5 * 60, sleep_time=0.2):\n lock_file = os.path.join(cache_dir(), 'search-engine.' + name + '.lock')\n with ExclusiveFile(lock_file, timeout=max_wait_seconds, sleep_time=sleep_time) as f:\n try:\n lv = float(f.read().decode('utf-8').strip())\n except Exception:\n lv = 0\n # we cannot use monotonic() as this is cross process and historical\n # data as well\n delta = time.time() - lv\n if delta < time_between_visits:\n time.sleep(time_between_visits - delta)\n try:\n yield\n finally:\n f.seek(0)\n f.truncate()\n f.write(repr(time.time()).encode('utf-8'))\n\n\ndef tostring(elem):\n return etree.tostring(elem, encoding='unicode', method='text', with_tail=False)\n\n\ndef browser():\n ua = random_user_agent(allow_ie=False)\n # ua = 'Mozilla/5.0 (Linux; Android 8.0.0; VTR-L29; rv:63.0) Gecko/20100101 Firefox/63.0'\n br = _browser(user_agent=ua)\n br.set_handle_gzip(True)\n br.addheaders += [\n ('Accept', accept_header_for_ua(ua)),\n ('Upgrade-insecure-requests', '1'),\n ]\n return br\n\n\ndef encode_query(**query):\n q = {k.encode('utf-8'): v.encode('utf-8') for k, v in query.items()}\n return urlencode(q).decode('utf-8')\n\n\ndef parse_html(raw):\n try:\n from html5_parser import parse\n except ImportError:\n # Old versions of calibre\n import html5lib\n return html5lib.parse(raw, treebuilder='lxml', namespaceHTMLElements=False)\n else:\n return parse(raw)\n\n\ndef query(br, url, key, dump_raw=None, limit=1, parser=parse_html, timeout=60, save_raw=None, simple_scraper=None):\n with rate_limit(key):\n if simple_scraper is None:\n raw = br.open_novisit(url, timeout=timeout).read()\n raw = xml_to_unicode(raw, strip_encoding_pats=True)[0]\n else:\n raw = simple_scraper(url, timeout=timeout)\n if dump_raw is not None:\n with open(dump_raw, 'w') as f:\n f.write(raw)\n if save_raw is not None:\n save_raw(raw)\n return parser(raw)\n\n\ndef quote_term(x):\n ans = quote_plus(x.encode('utf-8'))\n if isinstance(ans, bytes):\n ans = ans.decode('utf-8')\n return ans\n\n\n# DDG + Wayback machine DDG does a captcha after 2-3 requests {{{\n\ndef ddg_url_processor(url):\n return url\n\n\ndef ddg_term(t):\n t = t.replace('\"', '')\n if t.lower() in {'map', 'news'}:\n t = '\"' + t + '\"'\n if t in {'OR', 'AND', 'NOT'}:\n t = t.lower()\n return t\n\n\ndef ddg_href(url):\n if url.startswith('/'):\n q = url.partition('?')[2]\n url = parse_qs(q.encode('utf-8'))[b'uddg'][0].decode('utf-8')\n return url\n\n\ndef wayback_machine_cached_url(url, br=None, log=prints, timeout=60):\n q = quote_term(url)\n br = br or browser()\n try:\n data = query(br, 'https://archive.org/wayback/available?url=' +\n q, 'wayback', parser=json.loads, limit=0.25, timeout=timeout)\n except Exception as e:\n log('Wayback machine query failed for url: ' + url + ' with error: ' + str(e))\n return None\n try:\n closest = data['archived_snapshots']['closest']\n if closest['available']:\n ans = closest['url'].replace('http:', 'https:', 1)\n # get unmodified HTML\n ans = ans.replace(closest['timestamp'], closest['timestamp'] + 'id_', 1)\n return ans\n except Exception:\n pass\n from pprint import pformat\n log('Response from wayback machine:', pformat(data))\n\n\ndef wayback_url_processor(url):\n if url.startswith('/'):\n # Use original URL instead of absolutizing to wayback URL as wayback is\n # slow\n m = re.search(r'https?:', url)\n if m is None:\n url = 'https://web.archive.org' + url\n else:\n url = url[m.start():]\n return url\n\n\nddg_scraper_storage = []\n\n\ndef ddg_search(terms, site=None, br=None, log=prints, safe_search=False, dump_raw=None, timeout=60):\n # https://duck.co/help/results/syntax\n terms = [quote_term(ddg_term(t)) for t in terms]\n if site is not None:\n terms.append(quote_term(('site:' + site)))\n q = '+'.join(terms)\n url = 'https://duckduckgo.com/html/?q={q}&kp={kp}'.format(\n q=q, kp=1 if safe_search else -1)\n log('Making ddg query: ' + url)\n from calibre.scraper.simple import read_url\n br = br or browser()\n root = query(br, url, 'ddg', dump_raw, timeout=timeout, simple_scraper=partial(read_url, ddg_scraper_storage))\n ans = []\n for a in root.xpath('//*[@class=\"results\"]//*[@class=\"result__title\"]/a[@href and @class=\"result__a\"]'):\n try:\n ans.append(Result(ddg_href(a.get('href')), tostring(a), None))\n except KeyError:\n log('Failed to find ddg href in:', a.get('href'))\n return ans, url\n\n\ndef ddg_develop():\n br = browser()\n for result in ddg_search('heroes abercrombie'.split(), 'www.amazon.com', dump_raw='/t/raw.html', br=br)[0]:\n if '/dp/' in result.url:\n print(result.title)\n print(' ', result.url)\n print(' ', get_cached_url(result.url, br))\n print()\n# }}}\n\n\n# Bing uses a CAPTCHA {{{\n\ndef bing_term(t):\n t = t.replace('\"', '')\n if t in {'OR', 'AND', 'NOT'}:\n t = t.lower()\n return t\n\n\ndef bing_url_processor(url):\n return url\n\n\ndef resolve_bing_wrapper_page(url, br, log):\n raw = br.open_novisit(url).read().decode('utf-8', 'replace')\n m = re.search(r'var u = \"(.+)\"', raw)\n if m is None:\n log('Failed to resolve bing wrapper page for url: ' + url)\n return url\n log('Resolved bing wrapped URL: ' + url + ' to ' + m.group(1))\n return m.group(1)\n\n\nbing_scraper_storage = []\n\n\ndef bing_search(\n terms, site=None, br=None, log=prints, safe_search=False, dump_raw=None, timeout=60,\n show_user_agent=False, result_url_is_ok=lambda x: True\n):\n # http://vlaurie.com/computers2/Articles/bing_advanced_search.htm\n terms = [quote_term(bing_term(t)) for t in terms]\n if site is not None:\n terms.append(quote_term(('site:' + site)))\n q = '+'.join(terms)\n url = 'https://www.bing.com/search?q={q}'.format(q=q)\n log('Making bing query: ' + url)\n from calibre.scraper.simple import read_url\n root = query(br, url, 'bing', dump_raw, timeout=timeout, simple_scraper=partial(read_url, bing_scraper_storage))\n ans = []\n result_items = root.xpath('//*[@id=\"b_results\"]/li[@class=\"b_algo\"]')\n if not result_items:\n log('Bing returned no results')\n return ans, url\n for li in result_items:\n a = li.xpath('descendant::h2/a[@href]') or li.xpath('descendant::div[@class=\"b_algoheader\"]/a[@href]')\n a = a[0]\n title = tostring(a)\n ans_url = a.get('href')\n if ans_url.startswith('https://www.bing.com/'):\n ans_url = resolve_bing_wrapper_page(ans_url, br, log)\n if result_url_is_ok(ans_url):\n ans.append(Result(ans_url, title, None))\n if not ans:\n title = ' '.join(root.xpath('//title/text()'))\n log('Failed to find any results on results page, with title:', title)\n return ans, url\n\n\ndef bing_develop(terms='heroes abercrombie'):\n if isinstance(terms, str):\n terms = terms.split()\n for result in bing_search(terms, 'www.amazon.com', dump_raw='/t/raw.html', show_user_agent=True)[0]:\n if '/dp/' in result.url:\n print(result.title)\n print(' ', result.url)\n print(' ', result.cached_url)\n print()\n# }}}\n\n\n# Google only serves JS enabled search pages as of Sep 11, 2025 {{{\n\ndef google_term(t):\n t = t.replace('\"', '')\n if t in {'OR', 'AND', 'NOT'}:\n t = t.lower()\n return t\n\n\ndef google_url_processor(url):\n return url\n\n\ndef google_cache_url_for_url(url):\n if not isinstance(url, bytes):\n url = url.encode('utf-8')\n cu = quote(url, safe='')\n if isinstance(cu, bytes):\n cu = cu.decode('utf-8')\n return 'https://webcache.googleusercontent.com/search?q=cache:' + cu\n\n\ndef google_get_cached_url(url, br=None, log=prints, timeout=60):\n # Google's webcache was discontinued in september 2024\n cached_url = google_cache_url_for_url(url)\n br = google_specialize_browser(br or browser())\n try:\n raw = query(br, cached_url, 'google-cache', parser=lambda x: x.encode('utf-8'), timeout=timeout)\n except Exception as err:\n log('Failed to get cached URL from google for URL: {} with error: {}'.format(url, err))\n else:\n with webcache_lock:\n webcache[cached_url] = raw\n return cached_url\n\n\ndef canonicalize_url_for_cache_map(url):\n try:\n purl = urlparse(url)\n except Exception:\n return url\n if '.amazon.' in purl.netloc:\n url = url.split('&', 1)[0]\n return url\n\n\ndef google_parse_results(root, raw, log=prints, ignore_uncached=True):\n ans = []\n seen = set()\n for a in root.xpath('//a[@href]'):\n href = a.get('href')\n if not href.startswith('/url?q=http'):\n continue\n try:\n url = parse_qs(urlparse(href).query)['q'][0]\n purl = urlparse(url)\n except Exception:\n continue\n if purl.hostname.endswith('google.com'):\n continue\n try:\n title = tostring(next(a.iterchildren('span')))\n except StopIteration:\n continue\n curl = canonicalize_url_for_cache_map(url)\n if curl in seen:\n continue\n seen.add(curl)\n ans.append(Result(curl, title, None))\n if not ans:\n title = ' '.join(root.xpath('//title/text()'))\n log('Failed to find any results on results page, with title:', title)\n return ans\n\n\ndef google_consent_cookies():\n # See https://github.com/benbusby/whoogle-search/pull/1054 for cookies\n from base64 import standard_b64encode\n from datetime import date\n base = {'domain': '.google.com', 'path': '/'}\n b = base.copy()\n b['name'], b['value'] = 'CONSENT', 'PENDING+987'\n yield b\n template = b'\\x08\\x01\\x128\\x08\\x14\\x12+boq_identityfrontenduiserver_20231107.05_p0\\x1a\\x05en-US \\x03\\x1a\\x06\\x08\\x80\\xf1\\xca\\xaa\\x06'\n template.replace(b'20231107', date.today().strftime('%Y%m%d').encode('ascii'))\n b = base.copy()\n b['name'], b['value'] = 'SOCS', standard_b64encode(template).decode('ascii').rstrip('=')\n yield b\n\n\ndef google_specialize_browser(br):\n with webcache_lock:\n if not hasattr(br, 'google_consent_cookie_added'):\n for c in google_consent_cookies():\n br.set_simple_cookie(c['name'], c['value'], c['domain'], path=c['path'])\n br.google_consent_cookie_added = True\n # google serves JS based pages without the right user agent\n br.set_user_agent('L''y''nx''/2.''8.''6rel''.5 lib''ww''w-F''M/2.''1''4') # noqa\n return br\n\n\ndef is_probably_book_asin(t):\n return t and len(t) == 10 and t.startswith('B') and t.upper() == t\n\n\ndef is_asin_or_isbn(t):\n from calibre.ebooks.metadata import check_isbn\n return bool(check_isbn(t) or is_probably_book_asin(t))\n\n\ndef google_format_query(terms, site=None, tbm=None):\n prevent_spelling_correction = False\n for t in terms:\n if is_asin_or_isbn(t):\n prevent_spelling_correction = True\n break\n terms = [quote_term(google_term(t)) for t in terms]\n if site is not None:\n terms.append(quote_term(('site:' + site)))\n q = '+'.join(terms)\n url = 'https://www.google.com/search?q={q}'.format(q=q)\n # tbm causes 403 forbidden errors\n # if tbm:\n # url += '&tbm=' + tbm\n if prevent_spelling_correction:\n url += '&nfpr=1'\n return url\n\n\ndef google_search(terms, site=None, br=None, log=prints, safe_search=False, dump_raw=None, timeout=60):\n url = google_format_query(terms, site)\n log('Making google query: ' + url)\n br = google_specialize_browser(br or browser())\n r = []\n root = query(br, url, 'google', dump_raw, timeout=timeout, save_raw=r.append)\n return google_parse_results(root, r[0], log=log), url\n\n\ndef google_develop(search_terms='1423146786', raw_from=''):\n if raw_from:\n with open(raw_from, 'rb') as f:\n raw = f.read()\n results = google_parse_results(parse_html(raw), raw)\n else:\n br = browser()\n results = google_search(search_terms.split(), 'www.amazon.com', dump_raw='/t/raw.html', br=br)[0]\n for result in results:\n if '/dp/' in result.url:\n print(result.title)\n print(' ', result.url)\n print(' ', result.cached_url)\n print()\n# }}}\n\n\n# Yandex uses a CAPTCHA {{{\ndef yandex_term(t):\n t = t.replace('\"', '')\n if t in {'OR', 'AND', 'NOT'}:\n t = t.lower()\n return t\n\n\ndef yandex_format_query(terms, site=None):\n terms = [quote_term(yandex_term(t)) for t in terms]\n if site is not None:\n terms.append(quote_term(('site:' + site)))\n q = '+'.join(terms)\n url = 'https://yandex.com/search?text={q}'.format(q=q)\n return url\n\n\ndef yandex_parse_results(root, raw, log=prints, ignore_uncached=True):\n pass\n\n\nyandex_scraper_storage = []\n\n\ndef yandex_search(terms, site=None, br=None, dump_raw=None, log=prints, timeout=60):\n # Sadly yandex uses CAPTCHAs aggresively\n url = yandex_format_query(terms, site)\n br = browser()\n r = []\n from calibre.scraper.simple import read_url\n root = query(br, url, 'yandex', dump_raw, timeout=timeout, save_raw=r.append, simple_scraper=partial(read_url, yandex_scraper_storage))\n return yandex_parse_results(root, r[0], log=log), url\n\n\ndef yandex_develop(search_terms='1423146786', raw_from=''):\n if raw_from:\n with open(raw_from, 'rb') as f:\n raw = f.read()\n results = yandex_parse_results(parse_html(raw), raw)\n else:\n results = yandex_search(search_terms.split(), 'www.amazon.com', dump_raw='/t/raw.html')[0]\n for result in results:\n if '/dp/' in result.url:\n print(result.title)\n print(' ', result.url)\n print(' ', result.cached_url)\n print()\n\n# }}}\n\n\ndef get_cached_url(url, br=None, log=prints, timeout=60):\n from threading import Lock, Thread\n\n from polyglot.queue import Queue\n print_lock = Lock()\n q = Queue()\n\n def safe_print(*a):\n with print_lock:\n log(*a)\n\n def doit(func):\n try:\n q.put(func(url, br, safe_print, timeout))\n except Exception as e:\n safe_print(e)\n q.put(None)\n\n threads = []\n threads.append(Thread(target=doit, args=(wayback_machine_cached_url,), daemon=True).start())\n while threads:\n x = q.get()\n if x is not None:\n return x\n threads.pop()\n\n\ndef get_data_for_cached_url(url):\n with webcache_lock:\n return webcache.get(url)\n\n\ndef resolve_url(url):\n prefix, rest = url.partition(':')[::2]\n if prefix == 'bing':\n return bing_url_processor(rest)\n if prefix == 'wayback':\n return wayback_url_processor(rest)\n return url\n\n\n# if __name__ == '__main__':\n# import sys\n# func = sys.argv[-1]\n# globals()[func]()\n"
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/metadata_sources/global.json b/dotfiles/system/.config/calibre/metadata_sources/global.json
new file mode 100644
index 0000000..be7665d
--- /dev/null
+++ b/dotfiles/system/.config/calibre/metadata_sources/global.json
@@ -0,0 +1,23 @@
+{
+ "id_link_rules": {
+ "title": [
+ [
+ "Title",
+ "https://z-library.sk/s/?q=title%3A{title}&languages[]=english&extensions[]=EPUB&extensions[]=PDF&selected_content_types[]=book&order=bestmatch"
+ ]
+ ]
+ },
+ "ignore_fields": [
+ "rating",
+ "series"
+ ],
+ "tag_map_rules": [
+ {
+ "action": "remove",
+ "match_type": "not_one_of",
+ "query": "Art, Biography & Autobiography, Business, Chess, Computers, Cooking, Critical Theory, Design, Economics, French, History, Law, Linguistics, Literature, Magic, Mathematics, Music, Mythology, Non-Fiction, Philosophy, Poetry, Political Science, Politics, Psychology, Religion, Science, Social Critique, Sociology, Travel",
+ "replace": ""
+ }
+ ],
+ "txt_comments": true
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/mtp_devices.json b/dotfiles/system/.config/calibre/mtp_devices.json
new file mode 100644
index 0000000..274f3de
--- /dev/null
+++ b/dotfiles/system/.config/calibre/mtp_devices.json
@@ -0,0 +1,9 @@
+{
+ "blacklist": [],
+ "history": {
+ "G0W19E040464033L": [
+ "Fire",
+ "2021-01-28T21:54:04.815072+00:00"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/plugins/Apple Books covers.zip b/dotfiles/system/.config/calibre/plugins/Apple Books covers.zip
new file mode 100644
index 0000000..722e2c7
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Apple Books covers.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Barnes & Noble.zip b/dotfiles/system/.config/calibre/plugins/Barnes & Noble.zip
new file mode 100644
index 0000000..9c3932d
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Barnes & Noble.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Clean Comments.zip b/dotfiles/system/.config/calibre/plugins/Clean Comments.zip
new file mode 100644
index 0000000..224fcd7
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Clean Comments.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Extract ISBN.zip b/dotfiles/system/.config/calibre/plugins/Extract ISBN.zip
new file mode 100644
index 0000000..7214c0e
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Extract ISBN.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Favourites Menu.json b/dotfiles/system/.config/calibre/plugins/Favourites Menu.json
new file mode 100644
index 0000000..8f239ba
--- /dev/null
+++ b/dotfiles/system/.config/calibre/plugins/Favourites Menu.json
@@ -0,0 +1,140 @@
+{
+ "menus": [
+ {
+ "display": "Convert books",
+ "path": [
+ "Convert Books"
+ ]
+ },
+ null,
+ {
+ "display": "Start Content server",
+ "path": [
+ "Connect Share",
+ "Start Content server"
+ ]
+ },
+ {
+ "display": "Extract ISBN",
+ "path": [
+ "Extract ISBN"
+ ]
+ },
+ null,
+ {
+ "display": "Add to default list",
+ "path": [
+ "Reading List",
+ "Add to default list"
+ ]
+ },
+ {
+ "display": "Remove from default list",
+ "path": [
+ "Reading List",
+ "Remove from default list"
+ ]
+ },
+ {
+ "display": "View default list",
+ "path": [
+ "Reading List",
+ "View default list"
+ ]
+ },
+ null,
+ {
+ "display": "Find book duplicates...",
+ "path": [
+ "Find Duplicates",
+ "Find book duplicates..."
+ ]
+ },
+ {
+ "display": "Clean comments",
+ "path": [
+ "Clean Comments",
+ "Clean comments"
+ ]
+ },
+ {
+ "display": "Check library",
+ "path": [
+ "Choose Library",
+ "Library maintenance",
+ "Check library"
+ ]
+ },
+ {
+ "display": "Plugin updater",
+ "path": [
+ "Plugin Updater"
+ ]
+ },
+ null,
+ {
+ "display": "Email to...",
+ "path": [
+ "Connect Share",
+ "Email to..."
+ ]
+ },
+ {
+ "display": "email to Christine's Kindle",
+ "path": [
+ "Connect Share",
+ "Email to...",
+ "Christine's Kindle"
+ ]
+ },
+ {
+ "display": "email to Craig's Paperwhite",
+ "path": [
+ "Connect Share",
+ "Email to...",
+ "Paperwhite"
+ ]
+ },
+ {
+ "display": "email to Craig's Pixel6",
+ "path": [
+ "Connect Share",
+ "Email to...",
+ "Pixel6"
+ ]
+ },
+ null,
+ {
+ "display": "Start wireless device connection",
+ "path": [
+ "Connect Share",
+ "Start wireless device connection"
+ ]
+ },
+ {
+ "display": "Stop wireless device connection [192.168.86.26 or 172.17.0.1, port 9090]",
+ "path": [
+ "Connect Share",
+ "Stop wireless device connection [192.168.86.26 or 172.17.0.1, port 9090]"
+ ]
+ },
+ {
+ "display": "Manage collections",
+ "path": [
+ "Edit Collections"
+ ]
+ },
+ {
+ "display": "Get books",
+ "path": [
+ "Store"
+ ]
+ },
+ {
+ "display": "Kindle Collections",
+ "path": [
+ "Kindle Collections"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/plugins/Favourites Menu.zip b/dotfiles/system/.config/calibre/plugins/Favourites Menu.zip
new file mode 100644
index 0000000..767f621
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Favourites Menu.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Find Duplicates.json b/dotfiles/system/.config/calibre/plugins/Find Duplicates.json
new file mode 100644
index 0000000..9daa2b7
--- /dev/null
+++ b/dotfiles/system/.config/calibre/plugins/Find Duplicates.json
@@ -0,0 +1,13 @@
+{
+ "authorMatch": "identical",
+ "authorSoundexLength": 8,
+ "autoDeleteBinaryDups": false,
+ "identifierType": "isbn",
+ "includeLanguages": false,
+ "searchType": "binary",
+ "showAllGroups": true,
+ "showTagAuthor": true,
+ "sortGroupsByTitle": true,
+ "titleMatch": "identical",
+ "titleSoundexLength": 6
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/plugins/Find Duplicates.zip b/dotfiles/system/.config/calibre/plugins/Find Duplicates.zip
new file mode 100644
index 0000000..a6ce77a
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Find Duplicates.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/KePub Metadata Reader.zip b/dotfiles/system/.config/calibre/plugins/KePub Metadata Reader.zip
new file mode 100644
index 0000000..13394c9
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/KePub Metadata Reader.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/KePub Metadata Writer.zip b/dotfiles/system/.config/calibre/plugins/KePub Metadata Writer.zip
new file mode 100644
index 0000000..7ac4e55
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/KePub Metadata Writer.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Kindle hi-res covers.zip b/dotfiles/system/.config/calibre/plugins/Kindle hi-res covers.zip
new file mode 100644
index 0000000..40106fe
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Kindle hi-res covers.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Kobo Metadata.zip b/dotfiles/system/.config/calibre/plugins/Kobo Metadata.zip
new file mode 100644
index 0000000..aaf91cb
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Kobo Metadata.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Kobo Utilities.json b/dotfiles/system/.config/calibre/plugins/Kobo Utilities.json
new file mode 100644
index 0000000..4d9121a
--- /dev/null
+++ b/dotfiles/system/.config/calibre/plugins/Kobo Utilities.json
@@ -0,0 +1,122 @@
+{
+ "BookmarkOptions": {
+ "backgroundJob": false,
+ "clearIfUnread": false,
+ "doNotStoreIfReopened": false,
+ "rating": true,
+ "readingStatus": true,
+ "setDateToNow": true,
+ "storeBookmarks": true,
+ "storeIfMoreRecent": false
+ },
+ "Devices": {
+ "N4181C1037466": {
+ "active": true,
+ "backupOptionsStore": {
+ "backupCopiesToKeepSpin": 10,
+ "backupDestDirectory": "/home/cjennings/documents/kobo",
+ "backupEachCOnnection": true,
+ "backupZipDatabase": true,
+ "doDailyBackp": false
+ },
+ "location_code": "main",
+ "name": "Kobo Libra 2",
+ "serial_no": "N4181C1037466",
+ "type": "Kobo Libra 2",
+ "updateOptionsStore": {
+ "doEarlyFirmwareUpdate": false,
+ "doFirmwareUpdateCheck": true,
+ "firmwareUpdateCheckLastTime": 0
+ },
+ "uuid": "8de75c8a-f9b6-405c-86a3-515afd1e71fa"
+ }
+ },
+ "MetadataOptions": {
+ "author": false,
+ "authourSort": false,
+ "description": false,
+ "descriptionTemplate": "",
+ "descriptionUseTemplate": false,
+ "isbn": false,
+ "language": false,
+ "published_date": false,
+ "publisher": false,
+ "rating": false,
+ "readingStatus": -1,
+ "reading_direction": "Default",
+ "resetPosition": false,
+ "series": false,
+ "setRreadingStatus": false,
+ "set_reading_direction": false,
+ "set_sync_date": false,
+ "subtitle": false,
+ "subtitleTemplate": "",
+ "sync_date_library_date": "timestamp",
+ "title": false,
+ "titleSort": false,
+ "update_KoboEpubs": false,
+ "usePlugboard": false
+ },
+ "ReadingOptions": {
+ "doNotUpdateIfSet": false,
+ "lockMargins": false,
+ "readingAlignment": "Off",
+ "readingFontFamily": "Georgia",
+ "readingFontSize": 22,
+ "readingLeftMargin": 3,
+ "readingLineHeight": 1.3,
+ "readingRightMargin": 3,
+ "updateConfigFile": false
+ },
+ "_version": 2,
+ "backupAnnotations": {
+ "dest_directory": ""
+ },
+ "backupOptionsStore": {
+ "backupCopiesToKeepSpin": 5,
+ "backupDestDirectory": "",
+ "backupEachCOnnection": false,
+ "backupZipDatabase": true,
+ "doDailyBackp": false
+ },
+ "cleanImagesDir": {
+ "delete_extra_covers": false
+ },
+ "commonOptionsStore": {
+ "buttonActionDevice": "",
+ "buttonActionLibrary": "",
+ "individualDeviceOptions": true
+ },
+ "coverUpload": {
+ "blackandwhite": false,
+ "dithered_covers": false,
+ "keep_cover_aspect": false,
+ "kepub_covers": false,
+ "letterbox": false,
+ "letterbox_color": "#000000",
+ "png_covers": false
+ },
+ "fixDuplicatesOptionsStore": {
+ "keepNewestShelf": true,
+ "purgeShelves": false
+ },
+ "getShelvesOptionStore": {
+ "allBooks": true,
+ "replaceShelves": true
+ },
+ "removeAnnotations": {
+ "removeAnnotAction": 0
+ },
+ "removeCovers": {
+ "kepub_covers": false,
+ "remove_fullsize_covers": false
+ },
+ "setRelatedBooksOptionsStore": {
+ "relatedBooksType": 0
+ },
+ "updateOptionsStore": {
+ "doEarlyFirmwareUpdate": false,
+ "doFirmwareUpdateCheck": false,
+ "firmwareUpdateCheckLastTime": 1656213583
+ }
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/plugins/Kobo Utilities.zip b/dotfiles/system/.config/calibre/plugins/Kobo Utilities.zip
new file mode 100644
index 0000000..4d62c4b
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Kobo Utilities.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/KoboTouchExtended.zip b/dotfiles/system/.config/calibre/plugins/KoboTouchExtended.zip
new file mode 100644
index 0000000..3640da2
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/KoboTouchExtended.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Open With.json b/dotfiles/system/.config/calibre/plugins/Open With.json
new file mode 100644
index 0000000..81eaeb8
--- /dev/null
+++ b/dotfiles/system/.config/calibre/plugins/Open With.json
@@ -0,0 +1,61 @@
+{
+ "OpenWithMenus": {
+ "Menus": [
+ {
+ "active": false,
+ "appArgs": "",
+ "appPath": "firefox",
+ "format": "EPUB",
+ "image": "owp_firefox.png",
+ "menuText": "EPUBReader (EPUB)",
+ "subMenu": ""
+ },
+ {
+ "active": false,
+ "appArgs": "-c",
+ "appPath": "/usr/bin/emacsclient",
+ "format": "PDF",
+ "image": "reader.png",
+ "menuText": "Emacsclient",
+ "subMenu": ""
+ },
+ {
+ "active": true,
+ "appArgs": "",
+ "appPath": "/usr/bin/zathura",
+ "format": "EPUB",
+ "image": "edit_book.png",
+ "menuText": "Zathura (EPUB)",
+ "subMenu": ""
+ },
+ {
+ "active": true,
+ "appArgs": "",
+ "appPath": "/usr/bin/zathura",
+ "format": "PDF",
+ "image": "PDF.png",
+ "menuText": "Zathura (PDF)",
+ "subMenu": ""
+ },
+ {
+ "active": false,
+ "appArgs": "-c",
+ "appPath": "/usr/bin/emacsclient",
+ "format": "EPUB",
+ "image": "PDF.png",
+ "menuText": "Emacsclient",
+ "subMenu": ""
+ },
+ {
+ "active": false,
+ "appArgs": "",
+ "appPath": "gimp",
+ "format": "COVER",
+ "image": "owp_gimp.png",
+ "menuText": "Gimp (Cover)",
+ "subMenu": ""
+ }
+ ],
+ "UrlColWidth": 202
+ }
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/plugins/Open With.zip b/dotfiles/system/.config/calibre/plugins/Open With.zip
new file mode 100644
index 0000000..548c8ed
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Open With.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Reading List.json b/dotfiles/system/.config/calibre/plugins/Reading List.json
new file mode 100644
index 0000000..a348407
--- /dev/null
+++ b/dotfiles/system/.config/calibre/plugins/Reading List.json
@@ -0,0 +1,8 @@
+{
+ "Devices": {},
+ "Options": {
+ "quickAccess": false,
+ "removeDialog": true
+ },
+ "SchemaVersion": 1.65
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/plugins/Reading List.zip b/dotfiles/system/.config/calibre/plugins/Reading List.zip
new file mode 100644
index 0000000..3a46edf
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Reading List.zip differ
diff --git a/dotfiles/system/.config/calibre/plugins/Search The Internet.json b/dotfiles/system/.config/calibre/plugins/Search The Internet.json
new file mode 100644
index 0000000..21f6786
--- /dev/null
+++ b/dotfiles/system/.config/calibre/plugins/Search The Internet.json
@@ -0,0 +1,1097 @@
+{
+ "SearchMenus": {
+ "Menus": [
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_audible.png",
+ "menuText": "Audible for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.audible.com/search?keywords={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_audible.png",
+ "menuText": "Audible for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.audible.com/search?keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": true,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.com for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.com/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.co.uk for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.co.uk/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.com.au for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.com.au/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_bn.png",
+ "menuText": "Barnes and Noble for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.barnesandnoble.com/s/{author}/_/M-8q8"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_bn.png",
+ "menuText": "Barnes and Noble for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.barnesandnoble.com/s/{author}+{title}/_/M-8q8"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_bn.png",
+ "menuText": "Barnes and Noble for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.barnesandnoble.com/s/{isbn}/_/M-8q8"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_bn.png",
+ "menuText": "Barnes and Noble for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.barnesandnoble.com/s/{title}/_/M-8q8"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_ebay.png",
+ "menuText": "EBay US",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.ebay.com/sch/i.html?_nkw={author}+{title}&_sacat=267"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_ebay.png",
+ "menuText": "EBay UK",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.ebay.co.uk/sch/i.html?_nkw={author}+{title}&_sacat=267"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_ebooks.png",
+ "menuText": "EBooks for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.ebooks.com/SearchApp/SearchResults.net?term={author}&RestrictBy=author"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_ebooks.png",
+ "menuText": "EBooks for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.ebooks.com/SearchApp/SearchResults.net?term={author}+{title}"
+ },
+ {
+ "active": true,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": true,
+ "encoding": "utf-8",
+ "image": "stip_ff.png",
+ "menuText": "FantasticFiction for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.fantasticfiction.com/search/?searchfor=author&keywords={author}"
+ },
+ {
+ "active": true,
+ "encoding": "utf-8",
+ "image": "stip_ff.png",
+ "menuText": "FantasticFiction for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.fantasticfiction.com/search/?searchfor=book&keywords={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_fictiondb.png",
+ "menuText": "FictionDB for Author",
+ "method": "POST",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.fictiondb.com/search/searchresults.htm?styp=5&srchtxt={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_fictiondb.png",
+ "menuText": "FictionDB for Book",
+ "method": "POST",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.fictiondb.com/search/searchresults.htm?styp=5&author={author}&title={title}&srchtxt=multi&sgcode=0&tpcode=0&imprint=0&pubgroup=0&genretype=--&rating=-&myrating=-&status=-"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_fictiondb.png",
+ "menuText": "FictionDB for ISBN",
+ "method": "POST",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.fictiondb.com/search/searchresults.htm?styp=5&srchtxt={isbn}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_fictiondb.png",
+ "menuText": "FictionDB for Title",
+ "method": "POST",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.fictiondb.com/search/searchresults.htm?styp=5&srchtxt={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_goodreads.png",
+ "menuText": "Goodreads for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.goodreads.com/search/search?q={author}&search_type=books"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_goodreads.png",
+ "menuText": "Goodreads for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.goodreads.com/search/search?q={author}+{title}&search_type=books"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_goodreads.png",
+ "menuText": "Goodreads for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.goodreads.com/search/search?q={isbn}&search_type=books"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_goodreads.png",
+ "menuText": "Goodreads for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.goodreads.com/search/search?q={title}&search_type=books"
+ },
+ {
+ "active": true,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": true,
+ "encoding": "utf-8",
+ "image": "stip_google.png",
+ "menuText": "Google images for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.google.com/images?q=%22{author}%22+%22{title}%22"
+ },
+ {
+ "active": true,
+ "encoding": "utf-8",
+ "image": "stip_google.png",
+ "menuText": "Google images 400x300",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.google.com/images?as_q={author}+%22{title}%22&tbs=isch:1,isz:lt,islt:qsvga,imgo:1&safe=off"
+ },
+ {
+ "active": true,
+ "encoding": "utf-8",
+ "image": "stip_google.png",
+ "menuText": "Google.com for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.google.com/#sclient=psy&q=%22{author}%22+%22{title}%22"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_isfdb.png",
+ "menuText": "isfdb for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.isfdb.org/cgi-bin/se.cgi?type=Name&arg={author}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_isfdb.png",
+ "menuText": "isfdb for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.isfdb.org/cgi-bin/edit/tp_search.cgi?TERM_1={title}&USE_1=title&OPERATOR_1=AND&TERM_2={author}&USE_2=author&OPERATOR_2=AND"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_isfdb.png",
+ "menuText": "isfdb for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.isfdb.org/cgi-bin/se.cgi?type=ISBN&arg={isbn}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_isfdb.png",
+ "menuText": "isfdb for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.isfdb.org/cgi-bin/se.cgi?type=Fiction+Titles&arg={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_kobo.png",
+ "menuText": "Kobo for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.kobobooks.com/search/search.html?q={author}&f=author"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_kobo.png",
+ "menuText": "Kobo for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.kobobooks.com/search/search.html?q={author}+{title}&f=author"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_kobo.png",
+ "menuText": "Kobo for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.kobobooks.com/search/search.html?q={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_loc.png",
+ "menuText": "Library of Congress for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://catalog.loc.gov/cgi-bin/Pwebrecon.cgi?DB=local&Search_Arg={author}&Search_Code=NAME%40&CNT=100&hist=1&type=quick"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_loc.png",
+ "menuText": "Library of Congress for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://catalog.loc.gov/cgi-bin/Pwebrecon.cgi?DB=local&Search_Arg={isbn}&Search_Code=STNO^*&CNT=100&hist=1&type=quick"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_loc.png",
+ "menuText": "Library of Congress for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://catalog.loc.gov/cgi-bin/Pwebrecon.cgi?DB=local&Search_Arg={title}&Search_Code=TKEY^*&CNT=100&hist=1&type=quick"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_lthing.png",
+ "menuText": "LibraryThing for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.librarything.com/search.php?search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_lthing.png",
+ "menuText": "LibraryThing for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.librarything.com/search.php?search={title}+{author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_lthing.png",
+ "menuText": "LibraryThing for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.librarything.com/search.php?search={isbn}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_lthing.png",
+ "menuText": "LibraryThing for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.librarything.com/search.php?search={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_litmap.png",
+ "menuText": "Literature-Map like Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.literature-map.com/{author}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_loveread.png",
+ "menuText": "Lovereading for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.lovereading.co.uk/search?s={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_loveread.png",
+ "menuText": "Lovereading for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.lovereading.co.uk/search?s={author}+{title}&view=book-results"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_loveread.png",
+ "menuText": "Lovereading for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.lovereading.co.uk/search?s={title}&view=book-results"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_manybooks.png",
+ "menuText": "ManyBooks for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://manybooks.net/search-book?search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_manybooks.png",
+ "menuText": "ManyBooks for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://manybooks.net/search-book?search={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "stip_nyt.png",
+ "menuText": "NYTimes for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://nytimes.com/search?query={author}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "stip_nyt.png",
+ "menuText": "NYTimes for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://nytimes.com/search?query={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "stip_nyt.png",
+ "menuText": "NYTimes for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://nytimes.com/search?query={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wstones.png",
+ "menuText": "Waterstones for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.waterstones.com/books/search/term/{author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wstones.png",
+ "menuText": "Waterstones for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.waterstones.com/books/search/term/{title}+{author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wstones.png",
+ "menuText": "Waterstones for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.waterstones.com/books/search/term/{isbn}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wstones.png",
+ "menuText": "Waterstones for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.waterstones.com/books/search/term/{title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wsirn.png",
+ "menuText": "WhatShouldIReadNext for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.whatshouldireadnext.com/isbn/{isbn}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://en.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://en.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://en.wikipedia.org/w/index.php?title=Special%3ASearch&search={title}"
+ },
+ {
+ "active": false,
+ "encoding": "",
+ "image": "",
+ "menuText": "",
+ "method": "",
+ "openGroup": false,
+ "subMenu": "",
+ "url": ""
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.ca for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.ca/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.cn for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.cn/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.co.jp for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.co.jp/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.de for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.de/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.it for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.it/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "latin-1",
+ "image": "stip_amazon.png",
+ "menuText": "Amazon.fr for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.amazon.fr/s/ref=nb_sb_noss?url=search-alias%3Dstripbooks&field-keywords={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_chapters.png",
+ "menuText": "Chapters.ca for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://indigo.ca/en-ca/search/?q={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_chapters.png",
+ "menuText": "Chapters.ca for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://indigo.ca/en-ca/search/?q={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_chapters.png",
+ "menuText": "Chapters.ca for ISBN",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://indigo.ca/en-ca/search/?q={isbn}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_chapters.png",
+ "menuText": "Chapters.ca for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://indigo.ca/en-ca/search/?q={title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_fnac.png",
+ "menuText": "Fnac for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://recherche.fnac.com/Search/SearchResult.aspx?SCat=2&Search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_fnac.png",
+ "menuText": "Fnac for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://recherche.fnac.com/Search/SearchResult.aspx?SCat=2&Search={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_fnac.png",
+ "menuText": "Fnac for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://recherche.fnac.com/Search/SearchResult.aspx?SCat=2&Search={title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_google.png",
+ "menuText": "Google.de for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.google.de/#sclient=psy&q=%22{author}%22+%22{title}%22"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_google.png",
+ "menuText": "Google.es for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.google.es/#sclient=psy&q=%22{author}%22+%22{title}%22"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_google.png",
+ "menuText": "Google.fr for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.google.fr/#sclient=psy&q=%22{author}%22+%22{title}%22"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_google.png",
+ "menuText": "Google.it for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.google.it/#sclient=psy&q=%22{author}%22+%22{title}%22"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_libri.png",
+ "menuText": "libri.de for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.libri.de/shop/action/advancedSearch?action=search&nodeId=-1&binderType=Alle&languageCode=DE&person={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_libri.png",
+ "menuText": "libri.de for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.libri.de/shop/action/advancedSearch?action=search&nodeId=-1&binderType=Alle&languageCode=DE&title={title}&person={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_libri.png",
+ "menuText": "libri.de for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://www.libri.de/shop/action/advancedSearch?action=search&nodeId=-1&binderType=Alle&languageCode=DE&title={title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.de for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://de.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.de for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://de.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.de for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://de.wikipedia.org/w/index.php?title=Special%3ASearch&search={title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.es for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://es.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.es for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://es.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.es for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://es.wikipedia.org/w/index.php?title=Special%3ASearch&search={title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.fr for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://fr.wikipedia.org/w/index.php?title=Sp%E9cial%3ARecherche&search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.fr for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://fr.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.fr for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://fr.wikipedia.org/w/index.php?title=Sp%E9cial%3ARecherche&search={title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.it for Author",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://it.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.it for Book",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://it.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}+{title}"
+ },
+ {
+ "active": false,
+ "encoding": "utf-8",
+ "image": "stip_wikipedia.png",
+ "menuText": "Wikipedia.it for Title",
+ "method": "GET",
+ "openGroup": false,
+ "subMenu": "",
+ "url": "https://it.wikipedia.org/w/index.php?title=Special%3ASearch&search={title}"
+ }
+ ],
+ "UrlColWidth": 1543
+ }
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/plugins/Wikidata.zip b/dotfiles/system/.config/calibre/plugins/Wikidata.zip
new file mode 100644
index 0000000..61f919b
Binary files /dev/null and b/dotfiles/system/.config/calibre/plugins/Wikidata.zip differ
diff --git a/dotfiles/system/.config/calibre/save_to_disk.py.json b/dotfiles/system/.config/calibre/save_to_disk.py.json
new file mode 100644
index 0000000..bdf4e57
--- /dev/null
+++ b/dotfiles/system/.config/calibre/save_to_disk.py.json
@@ -0,0 +1,15 @@
+{
+ "asciiize": false,
+ "formats": "all",
+ "replace_whitespace": false,
+ "save_cover": true,
+ "save_extra_files": false,
+ "send_template": "{author_sort}/{title} - {authors}",
+ "send_timefmt": "%b, %Y",
+ "single_dir": false,
+ "template": "{author_sort}/{title}/{title} - {authors}",
+ "timefmt": "%b, %Y",
+ "to_lowercase": false,
+ "update_metadata": false,
+ "write_opf": true
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/scheduler.xml b/dotfiles/system/.config/calibre/scheduler.xml
new file mode 100644
index 0000000..d145a64
--- /dev/null
+++ b/dotfiles/system/.config/calibre/scheduler.xml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 6:6:0
+
+
+
+
+
+
+
+ 24:6:0
+
+
+
+ 24:6:0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1:0:0
+
+
+
+
+
+
+
+ 30.000000
+
+
+
+ 5:6:0
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/server-config.txt b/dotfiles/system/.config/calibre/server-config.txt
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/calibre/server-custom-list-template.json b/dotfiles/system/.config/calibre/server-custom-list-template.json
new file mode 100644
index 0000000..f5cad2c
--- /dev/null
+++ b/dotfiles/system/.config/calibre/server-custom-list-template.json
@@ -0,0 +1,14 @@
+{
+ "comments_fields": [
+ "comments"
+ ],
+ "height": "auto",
+ "lines": [
+ "{title} by {authors}",
+ "{series_index} of {series}|||{rating}",
+ "{tags}",
+ "Date: {timestamp}|||Published: {pubdate}|||Publisher: {publisher}"
+ ],
+ "thumbnail": true,
+ "thumbnail_height": 140
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/server-search-the-net.json b/dotfiles/system/.config/calibre/server-search-the-net.json
new file mode 100644
index 0000000..89dbf5a
--- /dev/null
+++ b/dotfiles/system/.config/calibre/server-search-the-net.json
@@ -0,0 +1,7 @@
+[
+ {
+ "name": "Z-Library",
+ "url": "https://z-library.sk/s/?q=Title%3A{title}%20author%3A{author}&languages[]=english&extensions[]=EPUB&extensions[]=PDF&selected_content_types[]=book&order=bestmatch",
+ "type": "book"
+ }
+]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/server-users.sqlite b/dotfiles/system/.config/calibre/server-users.sqlite
new file mode 100644
index 0000000..c191559
Binary files /dev/null and b/dotfiles/system/.config/calibre/server-users.sqlite differ
diff --git a/dotfiles/system/.config/calibre/shortcuts/main.json b/dotfiles/system/.config/calibre/shortcuts/main.json
new file mode 100644
index 0000000..3af458c
--- /dev/null
+++ b/dotfiles/system/.config/calibre/shortcuts/main.json
@@ -0,0 +1,15 @@
+{
+ "map": {
+ "Interface Action: Edit Collections (Edit Collections) - qaction": [
+ "Ctrl+Shift+C"
+ ],
+ "Interface Action: Extract ISBN (Extract ISBN) - qaction": [
+ "Ctrl+I"
+ ],
+ "Interface Action: Quickview (Quickview) - qaction": [],
+ "quit calibre": [
+ "Q"
+ ]
+ },
+ "options_map": {}
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/smtp.py.json b/dotfiles/system/.config/calibre/smtp.py.json
new file mode 100644
index 0000000..9e9ebb8
--- /dev/null
+++ b/dotfiles/system/.config/calibre/smtp.py.json
@@ -0,0 +1,77 @@
+{
+ "accounts": {
+ "c@cjennings.net": [
+ "EPUB,PDF",
+ false,
+ false
+ ],
+ "cciarmello@gmail.com": [
+ "PDF",
+ false,
+ false
+ ],
+ "cjennings_oasis@kindle.com": [
+ "EPUB",
+ false,
+ false
+ ],
+ "cjennings_paperwhite@kindle.com": [
+ "EPUB",
+ false,
+ true
+ ],
+ "cjennings_pixel6@kindle.com": [
+ "EPUB",
+ false,
+ false
+ ],
+ "laurajsmetanick@gmail.com": [
+ "PDF",
+ false,
+ false
+ ],
+ "laurajsmetanick@kindle.com": [
+ "EPUB, TPZ",
+ false,
+ false
+ ],
+ "lugrad2012_VxFH2q@kindle.com": [
+ "EPUB, TPZ",
+ false,
+ false
+ ],
+ "rubyblu@kindle.com": [
+ "EPUB,AZW3,MOBI",
+ false,
+ false
+ ]
+ },
+ "aliases": {
+ "c@cjennings.net": "c@cjennings.net",
+ "cjennings_oasis@kindle.com": "Oasis",
+ "cjennings_paperwhite@kindle.com": "Paperwhite",
+ "cjennings_pixel6@kindle.com": "Pixel6",
+ "laurajsmetanick@gmail.com": "Laura's Direct Email",
+ "laurajsmetanick@kindle.com": "Laura's Kindle",
+ "lugrad2012_VxFH2q@kindle.com": "Laura's iPad Kindle",
+ "rubyblu@kindle.com": "Christine's Kindle"
+ },
+ "encryption": "TLS",
+ "from_": "c@cjennings.net",
+ "relay_host": "127.0.0.1",
+ "relay_password": "306c5275546b4e3766396f556d4d4342584d65786567",
+ "relay_port": 1025,
+ "relay_username": "c@cjennings.net",
+ "subjects": {
+ "c@cjennings.net": "Book: {title} - {author}",
+ "cciarmello@gmail.com": "{title}",
+ "cjennings_oasis@kindle.com": "{title}",
+ "cjennings_paperwhite@kindle.com": "{title}",
+ "cjennings_pixel6@kindle.com": "{title}",
+ "laurajsmetanick@gmail.com": "{title}",
+ "laurajsmetanick@kindle.com": "Book: {title} - {author}",
+ "lugrad2012_VxFH2q@kindle.com": "Book: {title} - {author}",
+ "rubyblu@kindle.com": "Book: {title} - {author}"
+ },
+ "tags": {}
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/tag-map-rules.json b/dotfiles/system/.config/calibre/tag-map-rules.json
new file mode 100644
index 0000000..7238834
--- /dev/null
+++ b/dotfiles/system/.config/calibre/tag-map-rules.json
@@ -0,0 +1,10 @@
+{
+ "default": [
+ {
+ "action": "remove",
+ "match_type": "not_one_of",
+ "query": "Art, Biography & Autobiography, Business, Chess, Comics, Computer, Cooking, Design, Economics, Fiction, Finance, Fitness, Games, Gardening, History, Latin, Law, Linguistics, Literary Critique, Literature, Magic, Mathematics, Music, Mythology, Non-Fiction, Philosophy, Poetry, Political Science, Politics, Psychology, Religion, Science, Social Critique, Sociology, Travel, Zen",
+ "replace": ""
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer-webengine.json b/dotfiles/system/.config/calibre/viewer-webengine.json
new file mode 100644
index 0000000..dcd1405
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer-webengine.json
@@ -0,0 +1,327 @@
+{
+ "geometry-of-main_window_geometry": {
+ "frame_geometry": {
+ "height": 981,
+ "width": 1504,
+ "x": 0,
+ "y": 22
+ },
+ "full_screened": false,
+ "geometry": {
+ "height": 981,
+ "width": 1504,
+ "x": 0,
+ "y": 22
+ },
+ "maximized": false,
+ "normal_geometry": {
+ "height": 981,
+ "width": 1504,
+ "x": 0,
+ "y": 22
+ },
+ "qt": {
+ "__class__": "bytearray",
+ "__value__": "AdnQywADAAAAAAAAAAAAFgAABd8AAAPqAAAAAAAAABYAAAXfAAAD6gAAAAAAAAAABeAAAAAAAAAAFgAABd8AAAPq"
+ },
+ "screen": {
+ "depth": 24,
+ "device_pixel_ratio": 1.5,
+ "geometry_in_logical_pixels": {
+ "height": 1003,
+ "width": 1504,
+ "x": 0,
+ "y": 0
+ },
+ "index_in_screens_list": 0,
+ "manufacturer": "",
+ "model": "",
+ "name": "eDP-1",
+ "serial": "",
+ "size_in_logical_pixels": {
+ "height": 1003,
+ "width": 1504
+ },
+ "virtual_geometry": {
+ "height": 1003,
+ "width": 1504,
+ "x": 0,
+ "y": 0
+ }
+ }
+ },
+ "local_storage": {
+ "search-bar-history-search-for-sc": [
+ "black",
+ "dark",
+ "reverse",
+ "invert",
+ "quit"
+ ]
+ },
+ "lookup_location": "Google dictionary",
+ "main_window_geometry": {
+ "__class__": "bytearray",
+ "__value__": "AdnQywADAAAAAAAAAAAAEwAABd8AAAPqAAAAAAAAABMAAAXfAAAD6gAAAAAAAAAABeAAAAAAAAAAEwAABd8AAAPq"
+ },
+ "main_window_state": {
+ "__class__": "bytearray",
+ "__value__": "AAAA/wAAAAH9AAAAAgAAAAAAAAAAAAAAAPwCAAAAAvsAAAAQAHQAbwBjAC0AZABvAGMAawAAAAAA/////wAAAIsA////+wAAABYAcwBlAGEAcgBjAGgALQBkAG8AYwBrAAAAAAD/////AAAAnAD///8AAAABAAABeAAAA9X8AgAAAAT7AAAAFgBsAG8AbwBrAHUAcAAtAGQAbwBjAGsAAAAAAAAAA9UAAACGAP////sAAAAcAGIAbwBvAGsAbQBhAHIAawBzAC0AZABvAGMAawAAAAAAAAAD1QAAAPcA////+wAAABwAaQBuAHMAcABlAGMAdABvAHIALQBkAG8AYwBrAAAAAAD/////AAAAFAD////7AAAAHgBoAGkAZwBoAGwAaQBnAGgAdABzAC0AZABvAGMAawAAAAAA/////wAAANoA////AAAF4AAAA9UAAAAEAAAABAAAAAgAAAAI/AAAAAEAAAAAAAAAAQAAAB4AYQBjAHQAaQBvAG4AcwBfAHQAbwBvAGwAYgBhAHICAAAAAP////8AAAAAAAAAAA=="
+ },
+ "old_prefs_migrated": true,
+ "session_data": {
+ "base_font_size": 28,
+ "columns_per_screen": {
+ "landscape": 1,
+ "portrait": 1
+ },
+ "controls_help_shown_count": 2,
+ "current_color_scheme": "black",
+ "keyboard_shortcuts": {
+ "quit": [
+ {
+ "altKey": false,
+ "ctrlKey": false,
+ "key": "q",
+ "metaKey": false,
+ "shiftKey": false
+ }
+ ]
+ },
+ "margin_bottom": 100,
+ "margin_left": 100,
+ "margin_right": 100,
+ "margin_top": 100,
+ "read_mode": "flow",
+ "standalone_font_settings": {
+ "minimum_font_size": 12,
+ "mono_family": "Berkeley Mono",
+ "sans_family": "Verdana",
+ "serif_family": "Merriweather"
+ },
+ "standalone_misc_settings": {
+ "remember_last_read": true,
+ "remember_window_geometry": false,
+ "save_annotations_in_ebook": true,
+ "singleinstance": false
+ },
+ "standalone_recently_opened": [
+ {
+ "authors": [
+ "Richard Polt"
+ ],
+ "key": "/home/cjennings/sync/books/Richard Polt/Heidegger_ An Introduction (44728)/Heidegger_ An Introduction - Richard Polt.azw3",
+ "pathtoebook": "/home/cjennings/sync/books/Richard Polt/Heidegger_ An Introduction (44728)/Heidegger_ An Introduction - Richard Polt.azw3",
+ "timestamp": "2025-09-10T01:03:46.282Z",
+ "title": "Heidegger: An Introduction"
+ },
+ {
+ "authors": [
+ "David Harvey"
+ ],
+ "key": "/home/cjennings/sync/books/David Harvey/A Companion to Marx's Capital_ Volume 1 (44747)/A Companion to Marx's Capital_ Volume 1 - David Harvey.mobi",
+ "pathtoebook": "/home/cjennings/sync/books/David Harvey/A Companion to Marx's Capital_ Volume 1 (44747)/A Companion to Marx's Capital_ Volume 1 - David Harvey.mobi",
+ "timestamp": "2025-09-10T01:01:01.569Z",
+ "title": "A Companion to Marx's Capital: Volume 1"
+ },
+ {
+ "authors": [
+ "Quentin Skinner"
+ ],
+ "key": "/home/cjennings/sync/books/Quentin Skinner/Liberty before Liberalism (44222)/Liberty before Liberalism - Quentin Skinner.mobi",
+ "pathtoebook": "/home/cjennings/sync/books/Quentin Skinner/Liberty before Liberalism (44222)/Liberty before Liberalism - Quentin Skinner.mobi",
+ "timestamp": "2025-08-03T23:22:50.318Z",
+ "title": "Liberty before Liberalism"
+ },
+ {
+ "authors": [
+ "Saint Thomas Aquinas"
+ ],
+ "key": "/home/cjennings/sync/books/Thomas Aquinas/Commentary on Aristotle's Metaphysics (43871)/Commentary on Aristotle's Metaphysics - Thomas Aquinas.epub",
+ "pathtoebook": "/home/cjennings/sync/books/Thomas Aquinas/Commentary on Aristotle's Metaphysics (43871)/Commentary on Aristotle's Metaphysics - Thomas Aquinas.epub",
+ "timestamp": "2025-07-30T21:29:13.047Z",
+ "title": "Commentary on Aristotle's Metaphysics"
+ },
+ {
+ "authors": [
+ "Saint Thomas Aquinas"
+ ],
+ "key": "/home/cjennings/sync/books/Saint Thomas Aquinas/Commentary on Aristotle's Metaphysics (43871)/Commentary on Aristotle's Metaphysics - Saint Thomas Aquinas.epub",
+ "pathtoebook": "/home/cjennings/sync/books/Saint Thomas Aquinas/Commentary on Aristotle's Metaphysics (43871)/Commentary on Aristotle's Metaphysics - Saint Thomas Aquinas.epub",
+ "timestamp": "2025-07-30T08:21:23.202Z",
+ "title": "Commentary on Aristotle's Metaphysics"
+ },
+ {
+ "authors": [
+ "Desconocido"
+ ],
+ "key": "/home/cjennings/sync/books/Jill Vance Buroker/Kant's 'Critique of Pure Reason'_ An Introduction (43864)/Kant's 'Critique of Pure Reason'_ An Intro - Jill Vance Buroker.mobi",
+ "pathtoebook": "/home/cjennings/sync/books/Jill Vance Buroker/Kant's 'Critique of Pure Reason'_ An Introduction (43864)/Kant's 'Critique of Pure Reason'_ An Intro - Jill Vance Buroker.mobi",
+ "timestamp": "2025-07-18T04:13:33.770Z",
+ "title": "Kants Critique of Pure Reason An Introduction Cambridge Introductions to Key Philosophical Texts Cambridge"
+ },
+ {
+ "authors": [
+ "Leszek Kolakowski"
+ ],
+ "key": "/home/cjennings/sync/books/Leszek Kolakowski/Is God Happy__ Selected Essays (43040)/Is God Happy__ Selected Essays - Leszek Kolakowski.azw3",
+ "pathtoebook": "/home/cjennings/sync/books/Leszek Kolakowski/Is God Happy__ Selected Essays (43040)/Is God Happy__ Selected Essays - Leszek Kolakowski.azw3",
+ "timestamp": "2025-07-13T16:19:13.806Z",
+ "title": "Is God Happy?: Selected Essays (Penguin Modern Classics)"
+ },
+ {
+ "authors": [
+ "Desconocido"
+ ],
+ "key": "/home/cjennings/sync/books/Desconocido/Routledge Aristotle And The Metaphysics (43652)/Routledge Aristotle And The Metaphysics - Desconocido.azw3",
+ "pathtoebook": "/home/cjennings/sync/books/Desconocido/Routledge Aristotle And The Metaphysics (43652)/Routledge Aristotle And The Metaphysics - Desconocido.azw3",
+ "timestamp": "2025-07-06T21:55:18.416Z",
+ "title": "Routledge Aristotle And The Metaphysics"
+ },
+ {
+ "authors": [
+ "Habermas, Jürgen"
+ ],
+ "key": "/home/cjennings/sync/books/Habermas, Jurgen/The Philosophical Discourse of Modernity (40589)/The Philosophical Discourse of Modernity - Habermas, Jurgen.epub",
+ "pathtoebook": "/home/cjennings/sync/books/Habermas, Jurgen/The Philosophical Discourse of Modernity (40589)/The Philosophical Discourse of Modernity - Habermas, Jurgen.epub",
+ "timestamp": "2024-12-13T02:38:28.792Z",
+ "title": "The Philosophical Discourse of Modernity"
+ },
+ {
+ "authors": [
+ "Tamsyn Muir"
+ ],
+ "key": "/home/cjennings/sync/books/Tamsyn Muir/Gideon the Ninth (40289)/Gideon the Ninth - Tamsyn Muir.epub",
+ "pathtoebook": "/home/cjennings/sync/books/Tamsyn Muir/Gideon the Ninth (40289)/Gideon the Ninth - Tamsyn Muir.epub",
+ "timestamp": "2024-11-15T19:06:33.047Z",
+ "title": "Gideon the Ninth"
+ },
+ {
+ "key": "/home/cjennings/.local/opt/tor-browser/app/Browser/downloads/Love and Rockets #1 (1981) [Pyramid].cbz",
+ "pathtoebook": "/home/cjennings/.local/opt/tor-browser/app/Browser/downloads/Love and Rockets #1 (1981) [Pyramid].cbz",
+ "timestamp": "2022-08-23T16:40:22.898Z",
+ "title": "Love and Rockets #1 (1981) [Pyramid]"
+ },
+ {
+ "key": "/home/cjennings/.local/opt/tor-browser/app/Browser/downloads/Love & Rockets v1 #05 (March 1984) [Cclay].cbr",
+ "pathtoebook": "/home/cjennings/.local/opt/tor-browser/app/Browser/downloads/Love & Rockets v1 #05 (March 1984) [Cclay].cbr",
+ "timestamp": "2022-08-23T16:40:04.599Z",
+ "title": "Love & Rockets v1 #05 (March 1984) [Cclay]"
+ },
+ {
+ "key": "/tmp/mozilla_cjennings0/Love & Rockets v1 #05 (March 1984) [Cclay].cbr",
+ "pathtoebook": "/tmp/mozilla_cjennings0/Love & Rockets v1 #05 (March 1984) [Cclay].cbr",
+ "timestamp": "2022-08-23T16:31:27.722Z",
+ "title": "Love & Rockets v1 #05 (March 1984) [Cclay]"
+ },
+ {
+ "authors": [
+ "George Grätzer"
+ ],
+ "key": "/home/cjennings/Library/George Gratzer/More Math Into LaTeX (27737)/More Math Into LaTeX - George Gratzer.mobi",
+ "pathtoebook": "/home/cjennings/Library/George Gratzer/More Math Into LaTeX (27737)/More Math Into LaTeX - George Gratzer.mobi",
+ "timestamp": "2022-01-14T10:36:05.803Z",
+ "title": "More Math Into LaTeX"
+ },
+ {
+ "authors": [
+ "Simenon Georges"
+ ],
+ "key": "/home/cjennings/Library/Simenon Georges/050 Maigret's Little Joke (27730)/050 Maigret's Little Joke - Simenon Georges.mobi",
+ "pathtoebook": "/home/cjennings/Library/Simenon Georges/050 Maigret's Little Joke (27730)/050 Maigret's Little Joke - Simenon Georges.mobi",
+ "timestamp": "2022-01-10T12:32:52.530Z",
+ "title": "050 Maigret's Little Joke"
+ },
+ {
+ "authors": [
+ "Will Durant"
+ ],
+ "key": "/home/cjennings/Library/Will Durant/Story of Philosophy (3224)/Story of Philosophy - Will Durant.azw3",
+ "pathtoebook": "/home/cjennings/Library/Will Durant/Story of Philosophy (3224)/Story of Philosophy - Will Durant.azw3",
+ "timestamp": "2022-01-05T19:33:13.710Z",
+ "title": "Story of Philosophy"
+ },
+ {
+ "authors": [
+ "P G Wodehouse"
+ ],
+ "key": "/home/cjennings/Library/P. G. Wodehouse/Laughing Gas (24469)/Laughing Gas - P. G. Wodehouse.mobi",
+ "pathtoebook": "/home/cjennings/Library/P. G. Wodehouse/Laughing Gas (24469)/Laughing Gas - P. G. Wodehouse.mobi",
+ "timestamp": "2022-01-03T00:51:21.126Z",
+ "title": "Laughing Gas"
+ },
+ {
+ "authors": [
+ "Peter Seibel"
+ ],
+ "key": "/home/cjennings/Library/Peter Seibel/Coders at Work_ Reflections on the Craft of Programming (316)/Coders at Work_ Reflections on the Craft o - Peter Seibel.htmlz",
+ "pathtoebook": "/home/cjennings/Library/Peter Seibel/Coders at Work_ Reflections on the Craft of Programming (316)/Coders at Work_ Reflections on the Craft o - Peter Seibel.htmlz",
+ "timestamp": "2022-01-03T00:38:17.903Z",
+ "title": "Coders at Work"
+ },
+ {
+ "authors": [
+ "by Mike Gancarz"
+ ],
+ "key": "/home/cjennings/Downloads/torrents/files/Linux and the Unix Philosophy by Mike Gancarz (z-lib.org).epub",
+ "pathtoebook": "/home/cjennings/Downloads/torrents/files/Linux and the Unix Philosophy by Mike Gancarz (z-lib.org).epub",
+ "timestamp": "2022-01-02T23:44:59.829Z",
+ "title": "4362"
+ },
+ {
+ "authors": [
+ "Margaret Dauler Wilson"
+ ],
+ "key": "/home/cjennings/Library/Margaret Dauler Wilson/Descartes (86)/Descartes - Margaret Dauler Wilson.mobi",
+ "pathtoebook": "/home/cjennings/Library/Margaret Dauler Wilson/Descartes (86)/Descartes - Margaret Dauler Wilson.mobi",
+ "timestamp": "2022-01-02T14:20:51.792Z",
+ "title": "Descartes (Arguments of the Philosophers)"
+ },
+ {
+ "authors": [
+ "Alexander Tarlinder"
+ ],
+ "key": "/home/cjennings/Library/Alexander Tarlinder/Developer Testing_ Building Quality Into Software (26)/Developer Testing_ Building Quality Into S - Alexander Tarlinder.azw3",
+ "pathtoebook": "/home/cjennings/Library/Alexander Tarlinder/Developer Testing_ Building Quality Into Software (26)/Developer Testing_ Building Quality Into S - Alexander Tarlinder.azw3",
+ "timestamp": "2022-01-02T03:53:52.454Z",
+ "title": "Developer Testing: Building Quality into Software (Addison-Wesley Signature Series (Cohn))"
+ },
+ {
+ "authors": [
+ "Dieter Lohmar, Jagna Brudzinska"
+ ],
+ "key": "/home/cjennings/Library/Dieter Lohmar/Founding Psychoanalysis Phenomenologically_ Phenomenological Theory of Subjectivity and the Ps (17064)/Founding Psychoanalysis Phenomenologically - Dieter Lohmar.pdf",
+ "pathtoebook": "/home/cjennings/Library/Dieter Lohmar/Founding Psychoanalysis Phenomenologically_ Phenomenological Theory of Subjectivity and the Ps (17064)/Founding Psychoanalysis Phenomenologically - Dieter Lohmar.pdf",
+ "timestamp": "2022-01-01T22:55:44.420Z",
+ "title": "Founding Psychoanalysis Phenomenologically: Phenomenological Theory of Subjectivity and the Psychoanalytic Experience (Phaenomenologica, 199)"
+ },
+ {
+ "authors": [
+ "Kevin Passmore"
+ ],
+ "key": "/home/cjennings/Library/Kevin Passmore/Fascism_ A Very Short Introduction (5508)/Fascism_ A Very Short Introduction - Kevin Passmore.mobi",
+ "pathtoebook": "/home/cjennings/Library/Kevin Passmore/Fascism_ A Very Short Introduction (5508)/Fascism_ A Very Short Introduction - Kevin Passmore.mobi",
+ "timestamp": "2021-11-01T00:49:09.044Z",
+ "title": "Fascism: A Very Short Introduction (Very Short Introductions)"
+ },
+ {
+ "authors": [
+ "Lewis Carroll"
+ ],
+ "key": "/home/cjennings/Library/Lewis Carroll/Alice's Adventures in Wonderland_ &, Through the Looking-Glass (784)/Alice's Adventures in Wonderland_ &, Throu - Lewis Carroll.mobi",
+ "pathtoebook": "/home/cjennings/Library/Lewis Carroll/Alice's Adventures in Wonderland_ &, Through the Looking-Glass (784)/Alice's Adventures in Wonderland_ &, Throu - Lewis Carroll.mobi",
+ "timestamp": "2021-11-01T00:48:02.197Z",
+ "title": "Alice's Adventures in Wonderland and Through the Looking-Glass"
+ },
+ {
+ "authors": [
+ "Timothy Snyder"
+ ],
+ "key": "/home/cjennings/Library/Timothy Snyder/On Tyranny_ Twenty Lessons From the Twentieth Century (635)/On Tyranny_ Twenty Lessons From the Twenti - Timothy Snyder.azw3",
+ "pathtoebook": "/home/cjennings/Library/Timothy Snyder/On Tyranny_ Twenty Lessons From the Twentieth Century (635)/On Tyranny_ Twenty Lessons From the Twenti - Timothy Snyder.azw3",
+ "timestamp": "2021-10-31T22:46:48.986Z",
+ "title": "On Tyranny: Twenty Lessons from the Twentieth Century"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer.json b/dotfiles/system/.config/calibre/viewer.json
new file mode 100644
index 0000000..ecc631e
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer.json
@@ -0,0 +1,13 @@
+{
+ "print-to-pdf-bottom-margin": 1.0,
+ "print-to-pdf-geometry": {
+ "__class__": "bytearray",
+ "__value__": "AdnQywADAAAAAAEjAAAA7AAAAyQAAAIpAAABJQAAAO4AAAMiAAACJwAAAAAAAAAABVYAAAElAAAA7gAAAyIAAAIn"
+ },
+ "print-to-pdf-left-margin": 1.0,
+ "print-to-pdf-page-numbers": false,
+ "print-to-pdf-page-size": "letter",
+ "print-to-pdf-right-margin": 1.0,
+ "print-to-pdf-show-file": true,
+ "print-to-pdf-top-margin": 1.0
+}
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/19f02e8b622152fd5d7c642d30ecac05080ddf3e9e288a22c4f49866ba57c8b2.json b/dotfiles/system/.config/calibre/viewer/annots/19f02e8b622152fd5d7c642d30ecac05080ddf3e9e288a22c4f49866ba57c8b2.json
new file mode 100644
index 0000000..6ecdf09
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/19f02e8b622152fd5d7c642d30ecac05080ddf3e9e288a22c4f49866ba57c8b2.json
@@ -0,0 +1 @@
+[{"pos": "epubcfi(/10/2/4/2[sbo-rt-content]/2/2[idm45611906833112]/16/1:266)", "pos_type": "epubcfi", "timestamp": "2022-07-09T18:01:11.603570+00:00", "type": "last-read"}]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/33083ace2855943c7e4d7d188c47051f047f05e84d828fca5e5545396b94f14c.json b/dotfiles/system/.config/calibre/viewer/annots/33083ace2855943c7e4d7d188c47051f047f05e84d828fca5e5545396b94f14c.json
new file mode 100644
index 0000000..0317109
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/33083ace2855943c7e4d7d188c47051f047f05e84d828fca5e5545396b94f14c.json
@@ -0,0 +1 @@
+[{"pos": "epubcfi(/2/2/4/2@50:49.93)", "pos_type": "epubcfi", "timestamp": "2025-07-18T04:14:04.406842+00:00", "type": "last-read"}]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/5856c3e5aa41dd1b47711fa2b70e5ba9a2f61369f97c7fcc415321753e7c8bea.json b/dotfiles/system/.config/calibre/viewer/annots/5856c3e5aa41dd1b47711fa2b70e5ba9a2f61369f97c7fcc415321753e7c8bea.json
new file mode 100644
index 0000000..a44655c
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/5856c3e5aa41dd1b47711fa2b70e5ba9a2f61369f97c7fcc415321753e7c8bea.json
@@ -0,0 +1 @@
+[{"pos": "epubcfi(/2/2/4/2[page_1]@50:50)", "pos_type": "epubcfi", "timestamp": "2022-08-23T16:40:12.749665+00:00", "type": "last-read"}]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/5d4b018509f9383872d23f1c4a0652d20e908edc16409bc7697635a28f96478e.json b/dotfiles/system/.config/calibre/viewer/annots/5d4b018509f9383872d23f1c4a0652d20e908edc16409bc7697635a28f96478e.json
new file mode 100644
index 0000000..1dfa74a
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/5d4b018509f9383872d23f1c4a0652d20e908edc16409bc7697635a28f96478e.json
@@ -0,0 +1 @@
+[{"pos": "epubcfi(/2/2/4/12[page_6]@50:50)", "pos_type": "epubcfi", "timestamp": "2022-08-23T16:41:02.476450+00:00", "type": "last-read"}]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/6fd06a181469267e9c09d240ef2d3cca061e54ce37143a9e142524f61028cdd9.json b/dotfiles/system/.config/calibre/viewer/annots/6fd06a181469267e9c09d240ef2d3cca061e54ce37143a9e142524f61028cdd9.json
new file mode 100644
index 0000000..2579467
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/6fd06a181469267e9c09d240ef2d3cca061e54ce37143a9e142524f61028cdd9.json
@@ -0,0 +1 @@
+[{"pos": "epubcfi(/2/2/4/6[page_3]@50:50)", "pos_type": "epubcfi", "timestamp": "2022-08-23T16:31:51.861250+00:00", "type": "last-read"}]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/90922c33b4cfd6cdf2f2f462bc5f6e6b0f18bdb829384144fdd13cc3b487deb1.json b/dotfiles/system/.config/calibre/viewer/annots/90922c33b4cfd6cdf2f2f462bc5f6e6b0f18bdb829384144fdd13cc3b487deb1.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/90922c33b4cfd6cdf2f2f462bc5f6e6b0f18bdb829384144fdd13cc3b487deb1.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/ab0b0aa00cc90f53470da2761ea678a4ccacef1f5002917bda43970cd6096b19.json b/dotfiles/system/.config/calibre/viewer/annots/ab0b0aa00cc90f53470da2761ea678a4ccacef1f5002917bda43970cd6096b19.json
new file mode 100644
index 0000000..ac7dcad
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/ab0b0aa00cc90f53470da2761ea678a4ccacef1f5002917bda43970cd6096b19.json
@@ -0,0 +1 @@
+[{"pos": "epubcfi(/2/2/4/2@50:49.93)", "pos_type": "epubcfi", "timestamp": "2025-07-13T16:19:15.135276+00:00", "type": "last-read"}]
\ No newline at end of file
diff --git a/dotfiles/system/.config/calibre/viewer/annots/c5a80ad08eb5ae859fefd73672b6a7cddc243254b55897adfdd5671fe7b2aacf.json b/dotfiles/system/.config/calibre/viewer/annots/c5a80ad08eb5ae859fefd73672b6a7cddc243254b55897adfdd5671fe7b2aacf.json
new file mode 100644
index 0000000..098752f
--- /dev/null
+++ b/dotfiles/system/.config/calibre/viewer/annots/c5a80ad08eb5ae859fefd73672b6a7cddc243254b55897adfdd5671fe7b2aacf.json
@@ -0,0 +1 @@
+[{"pos": "epubcfi(/4/2/4/128/1:16)", "pos_type": "epubcfi", "timestamp": "2025-07-06T21:55:27.324496+00:00", "type": "last-read"}]
\ No newline at end of file
diff --git a/dotfiles/system/.config/conky/conky.conf b/dotfiles/system/.config/conky/conky.conf
new file mode 100644
index 0000000..09ee8ea
--- /dev/null
+++ b/dotfiles/system/.config/conky/conky.conf
@@ -0,0 +1,24 @@
+conky.config = {
+out_to_console = true,
+out_to_x = false,
+background = false,
+update_interval = 30,
+total_run_times = 0,
+};
+conky.text = [[ \
+${if_existing /sys/class/power_supply/BAT0}\
+ \
+${battery_percent BAT0}% \
+${if_existing /sys/class/power_supply/BAT0/status Charging} ${endif}\
+${if_existing /sys/class/power_supply/BAT0/status Discharging} ${endif}\
+${endif}\
+${if_existing /sys/class/power_supply/BAT1}\
+ \
+${battery_percent BAT1}% \
+${if_existing /sys/class/power_supply/BAT1/status Charging} ${endif}\
+${if_existing /sys/class/power_supply/BAT1/status Discharging} ${endif}\
+${endif}\
+ ${fs_used}/${fs_size} \
+ ${time %a %B %d} \
+ ${time %I:%M %p %Z}
+]];
diff --git a/dotfiles/system/.config/dunst/dunstrc b/dotfiles/system/.config/dunst/dunstrc
new file mode 100644
index 0000000..d804fc8
--- /dev/null
+++ b/dotfiles/system/.config/dunst/dunstrc
@@ -0,0 +1,201 @@
+[global]
+
+ ### Display ###
+
+ # Display notifications on monitor with mouse focus
+ follow = mouse
+ # Set {width}x{height} and {x_pos}+{y_pos}
+ geometry = "300x30-5+60"
+ # Show number of hidden notifications
+ indicate_hidden = yes
+ # Shrink window if smaller than width
+ shrink = no
+ # Set transparency of notifications
+ transparency = 0
+ # Height of whole notification
+ notification_height = 0
+ # Height of seperators
+ separator_height = 2
+ # Text and seperator padding
+ padding = 8
+ # Horizontal padding
+ horizontal_padding = 8
+ # Width of frame around window
+ frame_width = 1
+ # Color of frame around window
+ frame_color = "#4287f5"
+ # Sort messages by urgency
+ sort = yes
+ # Idle seconds
+ idle_threshold = 0
+
+ ### Text ###
+
+ # Set font of notifications
+ font = FiraCode Nerd Font Mono, 10
+ # Spacing between lines
+ line-height = 0
+ # Markup parsing
+ markup = full
+ # Message format:
+ # %a - appname
+ # %s - summary
+ # %b - body
+ # %i - iconname (with path)
+ # %I - iconname (without path)
+ # %p - progress value (if set)
+ # %n - progress value no extra characters
+ # %% - literal %
+ format = "%a\n%s\n%b"
+ # Align message text horizontally
+ alignment = center
+ # Align message text vertically
+ vertical_alignment = center
+ # Show age of message if message is older than x seconds
+ show_age_threshold = -1
+ # Split notifications into multiple lines
+ word_wrap = yes
+ # If message too long, add ellipsize to...
+ ellipsize = middle
+ # Ignore newlines in notifications
+ ignore_newline = no
+ # Stack duplicate notifications
+ stack_duplicates = true
+ # Hide number of duplicate notifications
+ hide_duplicate_count = true
+ # Show indicatiors for urls and actions
+ show_indicators = no
+
+ ### Icons ###
+
+ # icon_position = off
+
+ ### History ###
+
+ # Length of history
+ history_length = 20
+
+ ### Misc ###
+
+ # Dmenu path
+ dmenu = /usr/bin/dmenu -p dunst:
+ # Browser
+ browser = /usr/bin/firefox -new-tab
+ # Always run scripts
+ always_run_script = true
+ # Title of notification
+ title = Message
+ # Notification class
+ class = Dunst
+ # Print notification on startup
+ startup_notification = false
+ # Dunst verbosity
+ verbosity = mesg
+ # Corner radius of dunst
+ corner_radius = 15
+ # Ignore dbus closeNotification message
+ ignore_dbusclose = false
+
+ ### Mouse ###
+
+ # Left click
+ mouse_left_click = close_current
+ # Middle click
+ mouse_middle_click = do_action
+ # Right click
+ mouse_right_click = do_action
+
+[shortcuts]
+
+ # Close one notification
+ close = ctrl+space
+ # Close all notifications
+ close_all = ctrl+shift+space
+
+[urgency_normal]
+
+ # Normal urgency notifications
+ background = "#202632"
+ foreground = "#ffffff"
+ timeout = 5
+
+[urgency_critical]
+
+ # High urgency notifications
+ background = "#ffffff"
+ foreground = "#db0101"
+ timeout = 0
+# For defaults and description of each option, see the link bellow
+# https://github.com/dunst-project/dunst/blob/master/dunstrc
+
+[global]
+# Display
+follow = mouse
+width = 350
+height = (0, 300)
+origin = top-right
+offset = (35, 35)
+indicate_hidden = yes
+notification_limit = 5
+gap_size = 12
+padding = 12
+horizontal_padding = 20
+frame_width = 1
+sort = no
+
+# Progress bar
+progress_bar_frame_width = 0
+progress_bar_corner_radius = 3
+
+# Colors
+foreground = "#cdd1dc"
+frame_color = "#2d303c"
+highlight = "#2274d5, #82aad9"
+
+# Text
+font = FiraCode Nerd Font Mono, 10
+markup = full
+format = "%a\n%s\n%b"
+alignment = left
+vertical_alignment = center
+show_age_threshold = -1
+hide_duplicate_count = false
+
+# Icon
+icon_position = left
+min_icon_size = 54
+max_icon_size = 80
+icon_path = /usr/share/icons/Arc/status/96:/usr/share/icons/Arc/status/symbolic
+icon_corner_radius = 4
+
+# Misc/Advanced
+dmenu = wofi --show drun --prompt 'Open with'
+corner_radius = 10
+
+# Mouse
+mouse_left_click = close_current
+mouse_middle_click = do_action, close_current
+mouse_right_click = close_all
+
+[urgency_low]
+background = "#383c4af0"
+timeout = 3
+
+[urgency_normal]
+background = "#383c4af0"
+timeout = 8
+
+[urgency_critical]
+background = "#9b4d4bf0"
+frame_color = "#ab6d6b"
+highlight = "#eb4d4b"
+foreground = "#ffffff"
+timeout = 0
+
+# Rules
+[fullscreen_delay_everything]
+fullscreen = delay
+
+[fullscreen_show_critical]
+msg_urgency = critical
+fullscreen = show
\ No newline at end of file
diff --git a/dotfiles/system/.config/environment.d/envvars.conf b/dotfiles/system/.config/environment.d/envvars.conf
new file mode 100644
index 0000000..f937aab
--- /dev/null
+++ b/dotfiles/system/.config/environment.d/envvars.conf
@@ -0,0 +1 @@
+PATH=/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin:/usr/sbin:$HOME/.config/rofi/scripts
diff --git a/dotfiles/system/.config/flameshot/flameshot.ini b/dotfiles/system/.config/flameshot/flameshot.ini
new file mode 100644
index 0000000..8874b3b
--- /dev/null
+++ b/dotfiles/system/.config/flameshot/flameshot.ini
@@ -0,0 +1,11 @@
+[General]
+contrastOpacity=216
+copyOnDoubleClick=true
+copyPathAfterSave=true
+saveAfterCopy=true
+saveAsFileExtension=jpg
+savePath=/home/cjennings/pictures/screenshots
+savePathFixed=true
+showStartupLaunchMessage=false
+uiColor=#2587e2
+useJpgForClipboard=true
diff --git a/dotfiles/system/.config/fontconfig/fonts.conf b/dotfiles/system/.config/fontconfig/fonts.conf
new file mode 100644
index 0000000..6a27675
--- /dev/null
+++ b/dotfiles/system/.config/fontconfig/fonts.conf
@@ -0,0 +1,27 @@
+
+
+
+
+
+ true
+
+
+ true
+
+
+ hintslight
+
+
+ none
+
+
+ true
+
+
+ lcdnone
+
+
+ 144
+
+
+
diff --git a/dotfiles/system/.config/ghostty/config b/dotfiles/system/.config/ghostty/config
new file mode 100644
index 0000000..68f8a15
--- /dev/null
+++ b/dotfiles/system/.config/ghostty/config
@@ -0,0 +1,44 @@
+# Ghostty configuration to match st terminal appearance
+
+# Font configuration (matching st)
+font-family = "Berkeley Mono"
+font-size = 12
+font-feature = ss01
+font-feature = ss02
+font-feature = ss03
+
+# Window appearance
+window-padding-x = 2
+window-padding-y = 2
+background-opacity = 0.8
+gtk-titlebar = false
+
+# Colors (matching st's color scheme)
+# Foreground: gray90, Background: black
+foreground = d9d9d9
+background = 000000
+
+# 16 ANSI colors (matching st config.def.h)
+palette = 0=#000000
+palette = 1=#cd0000
+palette = 2=#00cd00
+palette = 3=#cdcd00
+palette = 4=#0000ee
+palette = 5=#cd00cd
+palette = 6=#00cdcd
+palette = 7=#e5e5e5
+palette = 8=#7f7f7f
+palette = 9=#ff0000
+palette = 10=#00ff00
+palette = 11=#ffff00
+palette = 12=#5c5cff
+palette = 13=#ff00ff
+palette = 14=#00ffff
+palette = 15=#ffffff
+
+# Cursor configuration
+cursor-color = cccccc
+cursor-style = block
+
+# Keybindings
+keybind = shift+enter=text:\x1b\r
diff --git a/dotfiles/system/.config/gtk-3.0/gtk.css b/dotfiles/system/.config/gtk-3.0/gtk.css
new file mode 100644
index 0000000..a1d4c13
--- /dev/null
+++ b/dotfiles/system/.config/gtk-3.0/gtk.css
@@ -0,0 +1,6 @@
+.window-frame, .window-frame:backdrop {
+ box-shadow: 0 0 0 black; /* removes shadow completely */
+ border-style: none;
+ margin: 1; /* this retains the ability to resize with the mouse, if 1px is too narrow, set some higher values */
+ border-radius: 0;
+}
diff --git a/dotfiles/system/.config/gtk-3.0/settings.ini b/dotfiles/system/.config/gtk-3.0/settings.ini
new file mode 100644
index 0000000..2023ae6
--- /dev/null
+++ b/dotfiles/system/.config/gtk-3.0/settings.ini
@@ -0,0 +1,18 @@
+[Settings]
+gtk-print-backends=file,cups,pdf
+gtk-theme-name=Adwaita-dark
+gtk-icon-theme-name=Papirus-Dark
+gtk-font-name=Cantarell 11
+gtk-cursor-theme-name=Vimix-white-cursors
+gtk-cursor-theme-size=0
+gtk-toolbar-style=GTK_TOOLBAR_BOTH
+gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
+gtk-button-images=1
+gtk-menu-images=1
+gtk-enable-event-sounds=1
+gtk-enable-input-feedback-sounds=1
+gtk-xft-antialias=1
+gtk-xft-hinting=1
+gtk-xft-hintstyle=hintfull
+gtk-xft-rgba=rgb
+gtk-application-prefer-dark-theme=1
\ No newline at end of file
diff --git a/dotfiles/system/.config/htop/htoprc b/dotfiles/system/.config/htop/htoprc
new file mode 100644
index 0000000..06b580d
--- /dev/null
+++ b/dotfiles/system/.config/htop/htoprc
@@ -0,0 +1,63 @@
+# Beware! This file is rewritten by htop when settings are changed in the interface.
+# The parser is also very primitive, and not human-friendly.
+htop_version=3.2.2
+config_reader_min_version=3
+fields=0 48 17 18 38 39 40 2 46 47 49 1
+hide_kernel_threads=1
+hide_userland_threads=0
+hide_running_in_container=0
+shadow_other_users=0
+show_thread_names=0
+show_program_path=1
+highlight_base_name=0
+highlight_deleted_exe=1
+shadow_distribution_path_prefix=0
+highlight_megabytes=1
+highlight_threads=1
+highlight_changes=0
+highlight_changes_delay_secs=5
+find_comm_in_cmdline=1
+strip_exe_from_cmdline=1
+show_merged_command=0
+header_margin=1
+screen_tabs=1
+detailed_cpu_time=0
+cpu_count_from_one=0
+show_cpu_usage=1
+show_cpu_frequency=0
+show_cpu_temperature=0
+degree_fahrenheit=0
+update_process_names=0
+account_guest_in_cpu_meter=0
+color_scheme=0
+enable_mouse=1
+delay=15
+hide_function_bar=0
+header_layout=two_50_50
+column_meters_0=AllCPUs Memory Swap
+column_meter_modes_0=1 1 1
+column_meters_1=Tasks LoadAverage Uptime
+column_meter_modes_1=2 2 2
+tree_view=0
+sort_key=46
+tree_sort_key=0
+sort_direction=-1
+tree_sort_direction=1
+tree_view_always_by_pid=0
+all_branches_collapsed=0
+screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command
+.sort_key=PERCENT_CPU
+.tree_sort_key=PID
+.tree_view=0
+.tree_view_always_by_pid=0
+.sort_direction=-1
+.tree_sort_direction=1
+.all_branches_collapsed=0
+screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command
+.sort_key=IO_RATE
+.tree_sort_key=PID
+.tree_view=0
+.tree_view_always_by_pid=0
+.sort_direction=-1
+.tree_sort_direction=1
+.all_branches_collapsed=0
diff --git a/dotfiles/system/.config/lf/cleaner b/dotfiles/system/.config/lf/cleaner
new file mode 100755
index 0000000..a184d84
--- /dev/null
+++ b/dotfiles/system/.config/lf/cleaner
@@ -0,0 +1,4 @@
+#!/bin/sh
+if [ -n "$FIFO_UEBERZUG" ]; then
+ printf '{"action": "remove", "identifier": "PREVIEW"}\n' > "$FIFO_UEBERZUG"
+fi
diff --git a/dotfiles/system/.config/lf/draw_img b/dotfiles/system/.config/lf/draw_img
new file mode 100755
index 0000000..5a70d5e
--- /dev/null
+++ b/dotfiles/system/.config/lf/draw_img
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+
+clear_screen() {
+ printf '\e[%sH\e[9999C\e[1J%b\e[1;%sr' \
+ "$((LINES-2))" "${TMUX:+\e[2J}" "$max_items"
+}
+
+# Get a file's mime_type.
+mime_type=$(file -bi "$1")
+
+# File isn't an image file, give warning.
+if [[ $mime_type != image/* ]]; then
+ lf -remote "send $id echoerr 'Not an image'"
+ exit
+fi
+
+w3m_paths=(/usr/{local/,}{lib,libexec,lib64,libexec64}/w3m/w3mi*)
+read -r w3m _ < <(type -p w3mimgdisplay "${w3m_paths[@]}")
+read -r LINES COLUMNS < <(stty size)
+
+# Get terminal window size in pixels and set it to WIDTH and HEIGHT.
+export $(xdotool getactivewindow getwindowgeometry --shell)
+
+# Get the image size in pixels.
+read -r img_width img_height < <("$w3m" <<< "5;${CACHE:-$1}")
+
+((img_width > WIDTH)) && {
+ ((img_height=img_height*WIDTH/img_width))
+ ((img_width=WIDTH))
+}
+
+((img_height > HEIGHT)) && {
+ ((img_width=img_width*HEIGHT/img_height))
+ ((img_height=HEIGHT))
+}
+
+# Variable needed for centering image.
+HALF_HEIGHT=$(expr $HEIGHT / 2)
+HALF_WIDTH=$(expr $WIDTH / 2)
+HALF_IMG_HEIGHT=$(expr $img_height / 2)
+HALF_IMG_WIDTH=$(expr $img_width / 2)
+X_POS=$(expr $HALF_WIDTH - $HALF_IMG_WIDTH)
+Y_POS=$(expr $HALF_HEIGHT - $HALF_IMG_HEIGHT)
+
+clear_screen
+# Hide the cursor.
+printf '\e[?25l'
+
+# Display the image.
+printf '0;1;%s;%s;%s;%s;;;;;%s\n3;\n4\n' \
+ ${X_POS:-0} \
+ ${Y_POS:-0} \
+ "$img_width" \
+ "$img_height" \
+ "${CACHE:-$1}" | "$w3m" &>/dev/null
+
+# Wait for user input.
+read -ern 1
+
+# Clear the image.
+printf '6;%s;%s;%s;%s\n3;' \
+ "${X_POS:-0}" \
+ "${Y_POS:-0}" \
+ "$WIDTH" \
+ "$HEIGHT" | "$w3m" &>/dev/null
+
+clear_screen
diff --git a/dotfiles/system/.config/lf/image b/dotfiles/system/.config/lf/image
new file mode 100755
index 0000000..77ddb5b
--- /dev/null
+++ b/dotfiles/system/.config/lf/image
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+readonly ID_PREVIEW="preview"
+main() {
+ case "$1" in
+ "clear")
+ declare -p -A cmd=([action]=remove [identifier]="$ID_PREVIEW") \
+ > "$FIFO_UEBERZUG"
+ ;;
+ "draw")
+ declare -p -A cmd=([action]=add [identifier]="$ID_PREVIEW" \
+ [x]="$3" [y]="$4" [max_width]="$5" [max_height]="$6" \
+ [path]="$2") > "$FIFO_UEBERZUG"
+ ;;
+ "*") echo "Unknown command: '$1', '$2'" ;;
+ esac
+}
+main "$@"
+
diff --git a/dotfiles/system/.config/lf/lfrc b/dotfiles/system/.config/lf/lfrc
new file mode 100644
index 0000000..50f5af9
--- /dev/null
+++ b/dotfiles/system/.config/lf/lfrc
@@ -0,0 +1,333 @@
+# lffc
+# Craig Jennings
+#
+
+
+##########################################################################
+# BASIC SETTINGS #
+##########################################################################
+
+set ratios 1:2:3
+set cleaner ~/.config/lf/cleaner # path to cleaner script
+set previewer ~/.config/lf/preview # path to preview script
+set preview # turn on previews
+
+set nohidden # don't show hidden files. '.' toggles
+set incsearch true # incremental searching
+set drawbox # draw boxes around panes
+set noicons # turn on icons
+set ignorecase # ignore case in sorting & searching
+set filesep " " # separate files w/ space not newline
+
+set shell sh
+set shellopts '-eu'
+
+##########################################################################
+# REMOVE SOME DEFAULT BINDINGS #
+##########################################################################
+
+map m
+map o
+map n
+map "'"
+map '"'
+map d
+map c
+map e
+map f
+
+##########################################################################
+# BASIC COMMANDS #
+##########################################################################
+
+map . set hidden! # toggle hidden files
+map p paste
+map x cut
+map y copy
+map H top
+map L bottom
+map R reload
+map C clear
+map U unselect
+
+##########################################################################
+# LF CONFIG EDIT/NAV
+##########################################################################
+
+# LF CONFIG
+#edit lfrc
+map elf $$EDITOR ~/.config/lf/lfrc &!
+
+# goto lf dir
+map glf cd ~/.config/lf/
+
+# reload lfrc
+map push :source~/.config/lf/lfrc
+
+##########################################################################
+# CUSTOM COMMANDS #
+##########################################################################
+
+# SET WALLPAPER BACKGROUND
+map bg $feh --bg-fill "$f"
+
+# ROTATE IMAGE 90 degrees clockwise
+map 90 mogrify -rotate 90 "$f"
+
+# DETOX FILENAME
+map dtx $detox "$f"
+
+# COPY FILE PATH
+map Y $echo "$fx" | clip
+
+# ADD TO DOTFILES REPO
+map atd /usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME add "$f"
+
+
+##########################################################################
+# NAVIGATION / FILE MANAGEMENT #
+##########################################################################
+
+### MAIN
+map mh. $mv "$f" ~
+map ch. $cp "$f" ~
+map gh. cd ~
+
+map mdx $mv "$f" ~/documents
+map cdx $cp "$f" ~/documents
+map gdx cd ~/documents
+
+map mdl $mv "$f" ~/downloads
+map cdl $cp "$f" ~/downloads
+map gdl cd ~/downloads
+
+### PICTURES
+map mpx $mv "$f" ~/pictures
+map cpx $cp "$f" ~/pictures
+map gpx cd ~/pictures
+
+map mps $mv "$f" ~/pictures/screenshots
+map cps $cp "$f" ~/pictures/screenshots
+map gps cd ~/pictures/screenshots
+
+map mpw $mv "$f" ~/pictures/wallpaper
+map cpw $cp "$f" ~/pictures/wallpaper
+map gpw cd ~/pictures/wallpaper
+
+### MAME
+
+map mmr $mv "$f" ~/.mame/roms
+map cmr $cp "$f" ~/.mame/roms
+map gmr cd ~/.mame/roms
+map owm /usr/bin/mame "$f"
+
+### MISC
+map gtc cd ~/downloads/torrents/complete
+map gulb cd /usr/local/bin
+map gp0 cd ~/.vids
+map mp0 $mv "$f" ~/.vids
+
+map gmv cd ~/movies
+map mmv $mv "$f" ~/movies
+
+##########################################################################
+# OPEN WITH COMMANDS #
+##########################################################################
+
+# open with vlc video player (default: mpv)
+map owv $vlc "$f"
+
+# open with gimp (default: nsxiv)
+map owg $gimp "$f"
+
+# open with zathura (default emacs pdf-tools)
+map owz $zathura "$f"
+
+# open with audacious
+map owa $audacious "$f"
+
+##########################################################################
+# FILE OPERATION #
+##########################################################################
+
+
+# RENAME
+#
+cmd rename %[ -e $1 ] && printf "file exists" || mv "$f" $1
+map r push :rename
+
+
+# OPEN
+#
+# Called when current file is not a directory.
+cmd open ${{
+ # if text or json file
+ case $(file --mime-type "$f" -bL) in
+ text/*|application/json) $EDITOR "$f";;
+
+ *) xdg-open "$f" ;;
+ esac
+}}
+map open
+
+
+# DELETE
+#
+cmd delete $rm -rf "$fx"
+map dd delete
+
+map delete
+
+# MKDIR
+#
+cmd mkdir ${{
+ printf "Directory Name: "
+ read ans
+ mkdir $ans
+}}
+map md mkdir
+
+
+# MKFILE
+#
+cmd mkfile ${{
+ printf "File Name: "
+ read ans
+ $EDITOR $ans
+}}
+map mf mkfile
+
+
+# SUDO MKFILE
+#
+cmd sudomkfile ${{
+ printf "File Name: "
+ read ans
+ sudo $EDITOR $ans
+}}
+map mr sudomkfile
+
+
+# CHMOD
+#
+cmd chmod ${{
+ printf "Mode Bits: "
+ read ans
+ for file in "$fx"
+ do
+ chmod $ans $file
+ done
+ lf -remote 'send reload'
+}}
+map ch chmod
+
+
+######################################################################## #
+# COMPRESSION FUNCTIONS #
+######################################################################## #
+
+# EXTRACT
+cmd extract ${{
+ case "$f" in
+ *.tar.bz2) tar xjf "$f" ;;
+ *.tar.gz) tar xzf "$f" ;;
+ *.bz2) bunzip2 "$f" ;;
+ *.rar) rar x "$f" ;;
+ *.gz) gunzip "$f" ;;
+ *.tar) tar xf "$f" ;;
+ *.tbz2) tar xjf "$f" ;;
+ *.tgz) tar xzf "$f" ;;
+ *.zip) unzip "$f" ;;
+ *.Z) uncompress "$f" ;;
+ *) echo "Unsupported format" ;;
+ esac
+}}
+map ex extract
+
+
+# TARGZ
+# tar.gz current or selected files
+#
+cmd targz ${{
+ set -f
+ mkdir $1
+ cp -r "$fx" $1
+ tar czf $1.tar.gz $1
+ rm -rf $1
+}}
+map tgz targz
+
+
+# ZIP
+# zip current file or selected files
+cmd zip ${{
+ set -f
+ mkdir $1
+ cp -r "$fx" $1
+ zip -r $1.zip $1
+ rm -rf $1
+}}
+map zip zip
+
+
+######################################################################## #
+# MISCELLANEOUS CONVENIENCE COMMANDS #
+######################################################################## #
+
+
+# PACMAN INSTALL
+#
+cmd pacman_install ${{
+ case "$f" in
+ *.pkg.tar.xz|*.pkg.tar.gz|*.pkg.tar.zst) sudo pacman -U "$f" ;;
+ *) echo "This doesn't look like an Arch package, so not installing."
+}}
+
+
+# MP3
+# convert audio file to mp3
+#
+cmd mp3 ${{
+ set -f
+ outname=$(echo "$f" | cut -f 1 -d '.')
+ lame -V --preset extreme $f "${outname}.mp3"
+}}
+
+
+######################################################################## #
+# FZF HELPER FUNCTIONS #
+######################################################################## #
+
+
+# FZF-JUMP
+#
+# jump to file or directory with c-f
+cmd fzf_jump ${{
+ res="$(find . -maxdepth 1 | fzf --reverse --header='Jump to location' | sed 's/\\/\\\\/g;s/"/\\"/g')"
+ if [ -d "$res" ] ; then
+ cmd="cd"
+ elif [ -f "$res" ] ; then
+ cmd="select"
+ else
+ exit 0
+ fi
+ lf -remote "send $id $cmd \"$res\""
+}}
+map :fzf_jump
+
+
+# FZF-SEARCH
+#
+# search contents of files in current directory, then select a file
+cmd fzf_search ${{
+ res="$( \
+ RG_PREFIX="rg --column --line-number --no-heading --color=always \
+ --smart-case "
+ FZF_DEFAULT_COMMAND="$RG_PREFIX ''" \
+ fzf --bind "change:reload:$RG_PREFIX {q} || true" \
+ --ansi --layout=reverse --header 'Search in files' \
+ | cut -d':' -f1
+ )"
+ [ ! -z "$res" ] && lf -remote "send $id select \"$res\""
+}}
+map gs :fzf_search
diff --git a/dotfiles/system/.config/lf/preview b/dotfiles/system/.config/lf/preview
new file mode 100755
index 0000000..68cda52
--- /dev/null
+++ b/dotfiles/system/.config/lf/preview
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+image() {
+ geometry="$(($2-2))x$3"
+ chafa "$1" -f sixel -s "$geometry" --animate false
+}
+
+batorcat() {
+ file="$1"
+ shift
+ if command -v bat > /dev/null 2>&1
+ then
+ bat --color=always --style=plain --pager=never "$file" "$@"
+ else
+ cat "$file"
+ fi
+}
+
+glowormdcat() {
+ file="$1"
+ shift
+ if command -v glow > /dev/null 2>&1
+ then
+ glow "$file"
+ else
+ mdcat "$file"
+ fi
+}
+
+CACHE="$HOME/.cache/lf/thumbnail.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | awk '{print $1}'))"
+
+case "$(printf "%s\n" "$(readlink -f "$1")" | awk '{print tolower($0)}')" in
+ *.tgz|*.tar.gz) tar tzf "$1" ;;
+ *.tar.bz2|*.tbz2) tar tjf "$1" ;;
+ *.tar.txz|*.txz) xz --list "$1" ;;
+ *.tar) tar tf "$1" ;;
+ *.zip|*.jar|*.war|*.ear|*.oxt) unzip -l "$1" ;;
+ *.rar) unrar l "$1" ;;
+ *.md)
+ glowormdcat "$1";;
+ *.7z) 7z l "$1" ;;
+ *.[1-8]) man "$1" | col -b ;;
+ *.o) nm "$1";;
+ *.torrent) transmission-show "$1" ;;
+ *.iso) iso-info --no-header -l "$1" ;;
+ *.odt|*.ods|*.odp|*.sxw) odt2txt "$1" ;;
+ *.doc) catdoc "$1" ;;
+ *.docx) docx2txt "$1" ;;
+ *.xml|*.html) w3m -dump "$1";;
+ *.xls|*.xlsx)
+ ssconvert --export-type=Gnumeric_stf:stf_csv "$1" "fd://1" | batorcat --language=csv
+ ;;
+ *.wav|*.mp3|*.flac|*.m4a|*.wma|*.ape|*.ac3|*.og[agx]|*.spx|*.opus|*.as[fx]|*.mka)
+ exiftool "$1"
+ ;;
+ *.pdf)
+ [ ! -f "${CACHE}.jpg" ] && \
+ pdftoppm -jpeg -f 1 -singlefile "$1" "$CACHE"
+ image "${CACHE}.jpg" "$2" "$3" "$4" "$5"
+ ;;
+ *.epub)
+ [ ! -f "$CACHE" ] && \
+ epub-thumbnailer "$1" "$CACHE" 1024
+ image "$CACHE" "$2" "$3" "$4" "$5"
+ ;;
+ *.cbz|*.cbr|*.cbt)
+ [ ! -f "$CACHE" ] && \
+ comicthumb "$1" "$CACHE" 1024
+ image "$CACHE" "$2" "$3" "$4" "$5"
+ ;;
+ *.avi|*.mp4|*.wmv|*.dat|*.3gp|*.ogv|*.mkv|*.mpg|*.mpeg|*.vob|*.fl[icv]|*.m2v|*.mov|*.webm|*.ts|*.mts|*.m4v|*.r[am]|*.qt|*.divx)
+ [ ! -f "${CACHE}.jpg" ] && \
+ ffmpegthumbnailer -i "$1" -o "${CACHE}.jpg" -s 0 -q 5
+ image "${CACHE}.jpg" "$2" "$3" "$4" "$5"
+ ;;
+ *.bmp|*.jpg|*.jpeg|*.png|*.xpm|*.webp|*.tiff|*.gif|*.jfif|*.ico)
+ image "$1" "$2" "$3" "$4" "$5"
+ ;;
+ *.svg)
+ [ ! -f "${CACHE}.jpg" ] && \
+ convert "$1" "${CACHE}.jpg"
+ image "${CACHE}.jpg" "$2" "$3" "$4" "$5"
+ ;;
+ *.ino)
+ batorcat --language=cpp "$1"
+ ;;
+ *)
+ batorcat "$1"
+ ;;
+esac
+exit 0
diff --git a/dotfiles/system/.config/mc/panels.ini b/dotfiles/system/.config/mc/panels.ini
new file mode 100644
index 0000000..e69de29
diff --git a/dotfiles/system/.config/mopidy/mopidy.conf b/dotfiles/system/.config/mopidy/mopidy.conf
new file mode 100644
index 0000000..a386dbd
--- /dev/null
+++ b/dotfiles/system/.config/mopidy/mopidy.conf
@@ -0,0 +1,91 @@
+# Mopidy configuration for cjennings
+# See https://docs.mopidy.com/en/latest/config/ for documentation
+
+[core]
+cache_dir = $XDG_CACHE_DIR/mopidy
+config_dir = $XDG_CONFIG_DIR/mopidy
+data_dir = $XDG_DATA_DIR/mopidy
+max_tracklist_length = 10000
+restore_state = false
+
+[logging]
+verbosity = 0
+format = %(levelname)-8s %(asctime)s [%(process)d:%(threadName)s] %(name)s\n %(message)s
+color = true
+
+[audio]
+mixer = software
+mixer_volume =
+output = pulsesink device=alsa_output.pci-0000_00_1f.3.analog-stereo
+# Buffer time in milliseconds - 1000ms works well for local files
+# Explicitly using analog output to avoid Bluetooth latency/jumpiness issues
+buffer_time = 1000
+
+[proxy]
+scheme =
+hostname =
+port =
+username =
+password =
+
+[file]
+enabled = true
+# Point to Craig's music directory
+media_dirs =
+ /home/cjennings/music|Music
+excluded_file_extensions =
+ .directory
+ .html
+ .jpeg
+ .jpg
+ .log
+ .nfo
+ .pdf
+ .png
+ .txt
+ .zip
+show_dotfiles = false
+follow_symlinks = false
+metadata_timeout = 1000
+
+[http]
+enabled = true
+hostname = 127.0.0.1
+port = 6680
+zeroconf = Mopidy HTTP server on $hostname
+allowed_origins =
+csrf_protection = true
+default_app = mopidy
+
+[m3u]
+enabled = true
+base_dir = /home/cjennings/music
+default_encoding = latin-1
+default_extension = .m3u
+playlists_dir = /home/cjennings/music
+
+[softwaremixer]
+enabled = true
+
+[stream]
+enabled = true
+protocols =
+ http
+ https
+ mms
+ rtmp
+ rtmps
+ rtsp
+metadata_blacklist =
+# Increase timeout for slow/distant streams (30 seconds)
+timeout = 30000
+
+# MPD frontend - enables MPD protocol compatibility (port 6600)
+# This allows MPD clients like mpc, ncmpcpp to work with Mopidy
+[mpd]
+enabled = true
+hostname = 127.0.0.1
+port = 6600
+password =
+max_connections = 20
+connection_timeout = 60
\ No newline at end of file
diff --git a/dotfiles/system/.config/mpd/mpd.conf b/dotfiles/system/.config/mpd/mpd.conf
new file mode 100644
index 0000000..d084cb2
--- /dev/null
+++ b/dotfiles/system/.config/mpd/mpd.conf
@@ -0,0 +1,437 @@
+# An example configuration file for MPD.
+# Read the user manual for documentation: http://www.musicpd.org/doc/user/
+# or /usr/share/doc/mpd/user-manual.html
+
+
+# Files and directories #######################################################
+#
+# This setting controls the top directory which MPD will search to discover the
+# available audio files and add them to the daemon's online database. This
+# setting defaults to the XDG directory, otherwise the music directory will be
+# be disabled and audio files will only be accepted over ipc socket (using
+# file:// protocol) or streaming files over an accepted protocol.
+#
+music_directory "/home/cjennings/music"
+#
+# This setting sets the MPD internal playlist directory. The purpose of this
+# directory is storage for playlists created by MPD. The server will use
+# playlist files not created by the server but only if they are in the MPD
+# format. This setting defaults to playlist saving being disabled.
+#
+playlist_directory "/home/cjennings/music"
+#
+# This setting sets the location of the MPD database. This file is used to
+# load the database at server start up and store the database while the
+# server is not up. This setting defaults to disabled which will allow
+# MPD to accept files over ipc socket (using file:// protocol) or streaming
+# files over an accepted protocol.
+#
+db_file "/home/cjennings/.config/mpd/database"
+#
+# These settings are the locations for the daemon log files for the daemon.
+# These logs are great for troubleshooting, depending on your log_level
+# settings.
+#
+# The special value "syslog" makes MPD use the local syslog daemon. This
+# setting defaults to logging to syslog, otherwise logging is disabled.
+#
+log_file "/home/cjennings/.config/mpd/log"
+#
+# This setting sets the location of the file which stores the process ID
+# for use of mpd --kill and some init scripts. This setting is disabled by
+# default and the pid file will not be stored.
+#
+pid_file "/home/cjennings/.config/mpd/pid"
+#
+# This setting sets the location of the file which contains information about
+# most variables to get MPD back into the same general shape it was in before
+# it was brought down. This setting is disabled by default and the server
+# state will be reset on server start up.
+#
+state_file "/home/cjennings/.config/mpd/state"
+#
+# The location of the sticker database. This is a database which
+# manages dynamic information attached to songs.
+#
+sticker_file "/home/cjennings/.config/mpd/sticker.sql"
+#
+###############################################################################
+
+
+# General music daemon options ################################################
+#
+# This setting specifies the user that MPD will run as. MPD should never run as
+# root and you may use this setting to make MPD change its user ID after
+# initialization. This setting is disabled by default and MPD is run as the
+# current user.
+#
+user "cjennings"
+#
+# This setting specifies the group that MPD will run as. If not specified
+# primary group of user specified with "user" setting will be used (if set).
+# This is useful if MPD needs to be a member of group such as "audio" to
+# have permission to use sound card.
+#
+#group "nogroup"
+#
+# This setting sets the address for the daemon to listen on. Careful attention
+# should be paid if this is assigned to anything other then the default, any.
+# This setting can deny access to control of the daemon. Choose any if you want
+# to have mpd listen on every address. Not effective if systemd socket
+# activation is in use.
+#
+# For network
+# bind_to_address "0.0.0.0"
+#
+# And for Unix Socket
+# bind_to_address "/home/cjennings/.config/mpd/socket"
+bind_to_address "127.0.0.1"
+#
+# This setting is the TCP port that is desired for the daemon to get assigned
+# to.
+#
+port "6600"
+#
+# This setting controls the type of information which is logged. Available
+# setting arguments are "default", "secure" or "verbose". The "verbose" setting
+# argument is recommended for troubleshooting, though can quickly stretch
+# available resources on limited hardware storage.
+#
+log_level "default"
+#
+# If you have a problem with your MP3s ending abruptly it is recommended that
+# you set this argument to "no" to attempt to fix the problem. If this solves
+# the problem, it is highly recommended to fix the MP3 files with vbrfix
+# (available as vbrfix in the debian archive), at which
+# point gapless MP3 playback can be enabled.
+#
+#gapless_mp3_playback "yes"
+#
+# Setting "restore_paused" to "yes" puts MPD into pause mode instead
+# of starting playback after startup.
+#
+restore_paused "yes"
+#
+# This setting enables MPD to create playlists in a format usable by other
+# music players.
+#
+save_absolute_paths_in_playlists "yes"
+#
+# This setting defines a list of tag types that will be extracted during the
+# audio file discovery process. The complete list of possible values can be
+# found in the mpd.conf man page.
+#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
+#
+# This setting enables automatic update of MPD's database when files in
+# music_directory are changed.
+#
+auto_update "yes"
+#
+# Limit the depth of the directories being watched, 0 means only watch
+# the music directory itself. There is no limit by default.
+#
+#auto_update_depth "3"
+#
+# Buffer settings for smooth streaming
+audio_buffer_size "16384" # 16 MB buffer (default 4096 KB)
+#
+###############################################################################
+
+
+# Symbolic link behavior ######################################################
+#
+# If this setting is set to "yes", MPD will discover audio files by following
+# symbolic links outside of the configured music_directory.
+#
+#follow_outside_symlinks "yes"
+#
+# If this setting is set to "yes", MPD will discover audio files by following
+# symbolic links inside of the configured music_directory.
+#
+#follow_inside_symlinks "yes"
+#
+###############################################################################
+
+
+# Zeroconf / Avahi Service Discovery ##########################################
+#
+# If this setting is set to "yes", service information will be published with
+# Zeroconf / Avahi.
+#
+# zeroconf_enabled "yes"
+#
+# The argument to this setting will be the Zeroconf / Avahi unique name for
+# this MPD server on the network.
+#
+# zeroconf_name "Music Player Daemon"
+#
+###############################################################################
+
+
+# Permissions #################################################################
+#
+# If this setting is set, MPD will require password authorization. The password
+# can setting can be specified multiple times for different password profiles.
+#
+#password "password@read,add,control,admin"
+#
+# This setting specifies the permissions a user has who has not yet logged in.
+#
+#default_permissions "read,add,control,admin"
+#
+###############################################################################
+
+
+# Database #######################################################################
+#
+
+#database {
+# plugin "proxy"
+# host "other.mpd.host"
+# port "6600"
+#}
+
+# Input #######################################################################
+#
+
+input {
+ plugin "curl"
+ timeout "30000" # 30 second timeout for connections
+# proxy "proxy.isp.com:8080"
+# proxy_user "user"
+# proxy_password "password"
+}
+
+#
+###############################################################################
+
+# Audio Output ################################################################
+#
+# MPD supports various audio output types, as well as playing through multiple
+# audio outputs at the same time, through multiple audio_output settings
+# blocks. Setting this block is optional, though the server will only attempt
+# autodetection for one sound card.
+#
+# An example of an ALSA output:
+#
+#audio_output {
+# type "alsa"
+# name "My ALSA Device"
+# device "hw:0,0" # optional
+# mixer_type "hardware" # optional
+# mixer_device "default" # optional
+# mixer_control "PCM" # optional
+# mixer_index "0" # optional
+#}
+#
+# An example of an OSS output:
+#
+#audio_output {
+# type "oss"
+# name "My OSS Device"
+# device "/dev/dsp" # optional
+# mixer_type "hardware" # optional
+# mixer_device "/dev/mixer" # optional
+# mixer_control "PCM" # optional
+#}
+#
+# An example of a shout output (for streaming to Icecast):
+#
+#audio_output {
+# type "shout"
+# encoding "ogg" # optional
+# name "My Shout Stream"
+# host "localhost"
+# port "8000"
+# mount "/mpd.ogg"
+# password "hackme"
+# quality "5.0"
+# bitrate "128"
+# format "44100:16:1"
+# protocol "icecast2" # optional
+# user "source" # optional
+# description "My Stream Description" # optional
+# url "http://example.com" # optional
+# genre "jazz" # optional
+# public "no" # optional
+# timeout "2" # optional
+# mixer_type "software" # optional
+#}
+#
+# An example of a recorder output:
+#
+#audio_output {
+# type "recorder"
+# name "My recorder"
+# encoder "vorbis" # optional, vorbis or lame
+# path "/var/lib/mpd/recorder/mpd.ogg"
+## quality "5.0" # do not define if bitrate is defined
+# bitrate "128" # do not define if quality is defined
+# format "44100:16:1"
+#}
+#
+# An example of a httpd output (built-in HTTP streaming server):
+#
+#audio_output {
+# type "httpd"
+# name "My HTTP Stream"
+# encoder "vorbis" # optional, vorbis or lame
+# port "8000"
+# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6
+# quality "5.0" # do not define if bitrate is defined
+# bitrate "128" # do not define if quality is defined
+# format "44100:16:1"
+# max_clients "0" # optional 0=no limit
+#}
+#
+## cjennings 2021-06-26
+
+audio_output {
+ type "pulse"
+ name "pulse audio"
+}
+
+audio_output {
+ type "fifo"
+ name "my_fifo"
+ path "/tmp/mpd.fifo"
+ format "44100:16:2"
+}
+# An example of a pulseaudio output (streaming to a remote pulseaudio server)
+# Please see README.Debian if you want mpd to play through the pulseaudio
+# daemon started as part of your graphical desktop session!
+#
+#audio_output {
+# type "pulse"
+# name "My Pulse Output"
+# server "remote_server" # optional
+# sink "remote_server_sink" # optional
+#}
+#
+# An example of a winmm output (Windows multimedia API).
+#
+#audio_output {
+# type "winmm"
+# name "My WinMM output"
+# device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
+# or
+# device "0" # optional
+# mixer_type "hardware" # optional
+#}
+#
+# An example of an openal output.
+#
+#audio_output {
+# type "openal"
+# name "My OpenAL output"
+# device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
+#}
+#
+## Example "pipe" output:
+#
+#audio_output {
+# type "pipe"
+# name "my pipe"
+# command "aplay -f cd 2>/dev/null"
+## Or if you're want to use AudioCompress
+# command "AudioCompress -m | aplay -f cd 2>/dev/null"
+## Or to send raw PCM stream through PCM:
+# command "nc example.org 8765"
+# format "44100:16:2"
+#}
+#
+## An example of a null output (for no audio output):
+#
+#audio_output {
+# type "null"
+# name "My Null Output"
+# mixer_type "none" # optional
+#}
+#
+# If MPD has been compiled with libsamplerate support, this setting specifies
+# the sample rate converter to use. Possible values can be found in the
+# mpd.conf man page or the libsamplerate documentation. By default, this is
+# setting is disabled.
+#
+#samplerate_converter "Fastest Sinc Interpolator"
+#
+###############################################################################
+
+
+# Normalization automatic volume adjustments ##################################
+#
+# This setting specifies the type of ReplayGain to use. This setting can have
+# the argument "off", "album", "track" or "auto". "auto" is a special mode that
+# chooses between "track" and "album" depending on the current state of
+# random playback. If random playback is enabled then "track" mode is used.
+# See for more details about ReplayGain.
+# This setting is off by default.
+#
+replaygain "track"
+#
+# This setting sets the pre-amp used for files that have ReplayGain tags. By
+# default this setting is disabled.
+#
+#replaygain_preamp "0"
+#
+# This setting sets the pre-amp used for files that do NOT have ReplayGain tags.
+# By default this setting is disabled.
+#
+#replaygain_missing_preamp "0"
+#
+# This setting enables or disables ReplayGain limiting.
+# MPD calculates actual amplification based on the ReplayGain tags
+# and replaygain_preamp / replaygain_missing_preamp setting.
+# If replaygain_limit is enabled MPD will never amplify audio signal
+# above its original level. If replaygain_limit is disabled such amplification
+# might occur. By default this setting is enabled.
+#
+#replaygain_limit "yes"
+#
+# This setting enables on-the-fly normalization volume adjustment. This will
+# result in the volume of all playing audio to be adjusted so the output has
+# equal "loudness". This setting is disabled by default.
+#
+volume_normalization "yes"
+#
+###############################################################################
+
+
+# Character Encoding ##########################################################
+#
+# If file or directory names do not display correctly for your locale then you
+# may need to modify this setting.
+#
+filesystem_charset "UTF-8"
+#
+# This setting controls the encoding that ID3v1 tags should be converted from.
+#
+# id3v1_encoding "UTF-8" (this is now deprecated)
+#
+###############################################################################
+
+
+# SIDPlay decoder #############################################################
+#
+# songlength_database:
+# Location of your songlengths file, as distributed with the HVSC.
+# The sidplay plugin checks this for matching MD5 fingerprints.
+# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq
+#
+# default_songlength:
+# This is the default playing time in seconds for songs not in the
+# songlength database, or in case you're not using a database.
+# A value of 0 means play indefinitely.
+#
+# filter:
+# Turns the SID filter emulation on or off.
+#
+#decoder {
+# plugin "sidplay"
+# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt"
+# default_songlength "120"
+# filter "true"
+#}
+#
+###############################################################################
+
diff --git a/dotfiles/system/.config/mpd/musicpd.conf b/dotfiles/system/.config/mpd/musicpd.conf
new file mode 100644
index 0000000..9f34c44
--- /dev/null
+++ b/dotfiles/system/.config/mpd/musicpd.conf
@@ -0,0 +1,436 @@
+# An example configuration file for MPD.
+# Read the user manual for documentation: http://www.musicpd.org/doc/user/
+# or /usr/share/doc/mpd/user-manual.html
+
+
+# Files and directories #######################################################
+#
+# This setting controls the top directory which MPD will search to discover the
+# available audio files and add them to the daemon's online database. This
+# setting defaults to the XDG directory, otherwise the music directory will be
+# be disabled and audio files will only be accepted over ipc socket (using
+# file:// protocol) or streaming files over an accepted protocol.
+#
+music_directory "~cjennings/music"
+#
+# This setting sets the MPD internal playlist directory. The purpose of this
+# directory is storage for playlists created by MPD. The server will use
+# playlist files not created by the server but only if they are in the MPD
+# format. This setting defaults to playlist saving being disabled.
+#
+playlist_directory "~cjennings/music"
+#
+# This setting sets the location of the MPD database. This file is used to
+# load the database at server start up and store the database while the
+# server is not up. This setting defaults to disabled which will allow
+# MPD to accept files over ipc socket (using file:// protocol) or streaming
+# files over an accepted protocol.
+#
+db_file "~cjennings/.config/mpd/database"
+#
+# These settings are the locations for the daemon log files for the daemon.
+# These logs are great for troubleshooting, depending on your log_level
+# settings.
+#
+# The special value "syslog" makes MPD use the local syslog daemon. This
+# setting defaults to logging to syslog, otherwise logging is disabled.
+#
+log_file "~cjennings/.config/mpd/mpd.log"
+#
+# This setting sets the location of the file which stores the process ID
+# for use of mpd --kill and some init scripts. This setting is disabled by
+# default and the pid file will not be stored.
+#
+pid_file "~cjennings/.config/mpd/pid"
+#
+# This setting sets the location of the file which contains information about
+# most variables to get MPD back into the same general shape it was in before
+# it was brought down. This setting is disabled by default and the server
+# state will be reset on server start up.
+#
+state_file "~cjennings/.config/mpd/state"
+#
+# The location of the sticker database. This is a database which
+# manages dynamic information attached to songs.
+#
+sticker_file "~cjennings/.config/mpd/sticker.sql"
+#
+###############################################################################
+
+
+# General music daemon options ################################################
+#
+# This setting specifies the user that MPD will run as. MPD should never run as
+# root and you may use this setting to make MPD change its user ID after
+# initialization. This setting is disabled by default and MPD is run as the
+# current user.
+#
+user "cjennings"
+#
+# This setting specifies the group that MPD will run as. If not specified
+# primary group of user specified with "user" setting will be used (if set).
+# This is useful if MPD needs to be a member of group such as "audio" to
+# have permission to use sound card.
+#
+#group "nogroup"
+#
+# This setting sets the address for the daemon to listen on. Careful attention
+# should be paid if this is assigned to anything other then the default, any.
+# This setting can deny access to control of the daemon. Choose any if you want
+# to have mpd listen on every address. Not effective if systemd socket
+# activation is in use.
+#
+# For network
+bind_to_address "0.0.0.0"
+#
+# And for Unix Socket
+#bind_to_address "/run/mpd/socket"
+#
+# This setting is the TCP port that is desired for the daemon to get assigned
+# to.
+#
+#port "6600"
+#
+# This setting controls the type of information which is logged. Available
+# setting arguments are "default", "secure" or "verbose". The "verbose" setting
+# argument is recommended for troubleshooting, though can quickly stretch
+# available resources on limited hardware storage.
+#
+#log_level "default"
+#
+# If you have a problem with your MP3s ending abruptly it is recommended that
+# you set this argument to "no" to attempt to fix the problem. If this solves
+# the problem, it is highly recommended to fix the MP3 files with vbrfix
+# (available as vbrfix in the debian archive), at which
+# point gapless MP3 playback can be enabled.
+#
+#gapless_mp3_playback "yes"
+#
+# Setting "restore_paused" to "yes" puts MPD into pause mode instead
+# of starting playback after startup.
+#
+restore_paused "yes"
+#
+# This setting enables MPD to create playlists in a format usable by other
+# music players.
+#
+save_absolute_paths_in_playlists "yes"
+#
+# This setting defines a list of tag types that will be extracted during the
+# audio file discovery process. The complete list of possible values can be
+# found in the mpd.conf man page.
+#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
+#
+# This setting enables automatic update of MPD's database when files in
+# music_directory are changed.
+#
+auto_update "yes"
+#
+# Limit the depth of the directories being watched, 0 means only watch
+# the music directory itself. There is no limit by default.
+#
+#auto_update_depth "3"
+#
+###############################################################################
+
+
+# Symbolic link behavior ######################################################
+#
+# If this setting is set to "yes", MPD will discover audio files by following
+# symbolic links outside of the configured music_directory.
+#
+#follow_outside_symlinks "yes"
+#
+# If this setting is set to "yes", MPD will discover audio files by following
+# symbolic links inside of the configured music_directory.
+#
+#follow_inside_symlinks "yes"
+#
+###############################################################################
+
+
+# Zeroconf / Avahi Service Discovery ##########################################
+#
+# If this setting is set to "yes", service information will be published with
+# Zeroconf / Avahi.
+#
+# zeroconf_enabled "yes"
+#
+# The argument to this setting will be the Zeroconf / Avahi unique name for
+# this MPD server on the network.
+#
+# zeroconf_name "Music Player Daemon"
+#
+###############################################################################
+
+
+# Permissions #################################################################
+#
+# If this setting is set, MPD will require password authorization. The password
+# can setting can be specified multiple times for different password profiles.
+#
+#password "password@read,add,control,admin"
+#
+# This setting specifies the permissions a user has who has not yet logged in.
+#
+#default_permissions "read,add,control,admin"
+#
+###############################################################################
+
+
+# Database #######################################################################
+#
+
+#database {
+# plugin "proxy"
+# host "other.mpd.host"
+# port "6600"
+#}
+
+# Input #######################################################################
+#
+
+input {
+ plugin "curl"
+# proxy "proxy.isp.com:8080"
+# proxy_user "user"
+# proxy_password "password"
+}
+
+#
+###############################################################################
+
+# Audio Output ################################################################
+#
+# MPD supports various audio output types, as well as playing through multiple
+# audio outputs at the same time, through multiple audio_output settings
+# blocks. Setting this block is optional, though the server will only attempt
+# autodetection for one sound card.
+#
+# An example of an ALSA output:
+#
+#audio_output {
+# type "alsa"
+# name "My ALSA Device"
+# device "hw:0,0" # optional
+# mixer_type "hardware" # optional
+# mixer_device "default" # optional
+# mixer_control "PCM" # optional
+# mixer_index "0" # optional
+#}
+#
+# An example of an OSS output:
+#
+#audio_output {
+# type "oss"
+# name "My OSS Device"
+# device "/dev/dsp" # optional
+# mixer_type "hardware" # optional
+# mixer_device "/dev/mixer" # optional
+# mixer_control "PCM" # optional
+#}
+#
+# An example of a shout output (for streaming to Icecast):
+#
+#audio_output {
+# type "shout"
+# encoding "ogg" # optional
+# name "My Shout Stream"
+# host "localhost"
+# port "8000"
+# mount "/mpd.ogg"
+# password "hackme"
+# quality "5.0"
+# bitrate "128"
+# format "44100:16:1"
+# protocol "icecast2" # optional
+# user "source" # optional
+# description "My Stream Description" # optional
+# url "http://example.com" # optional
+# genre "jazz" # optional
+# public "no" # optional
+# timeout "2" # optional
+# mixer_type "software" # optional
+#}
+#
+# An example of a recorder output:
+#
+#audio_output {
+# type "recorder"
+# name "My recorder"
+# encoder "vorbis" # optional, vorbis or lame
+# path "/var/lib/mpd/recorder/mpd.ogg"
+## quality "5.0" # do not define if bitrate is defined
+# bitrate "128" # do not define if quality is defined
+# format "44100:16:1"
+#}
+#
+# An example of a httpd output (built-in HTTP streaming server):
+#
+#audio_output {
+# type "httpd"
+# name "My HTTP Stream"
+# encoder "vorbis" # optional, vorbis or lame
+# port "8000"
+# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6
+# quality "5.0" # do not define if bitrate is defined
+# bitrate "128" # do not define if quality is defined
+# format "44100:16:1"
+# max_clients "0" # optional 0=no limit
+#}
+#
+## cjennings 2021-06-26
+
+audio_output {
+ type "oss"
+ name "OSS Audio"
+ device "/dev/dsp" # optional
+ mixer_type "hardware" # optional
+ mixer_device "/dev/mixer" # optional
+ mixer_control "vol" # optional
+}
+
+audio_output {
+ type "fifo"
+ name "my_fifo"
+ path "/tmp/mpd.fifo"
+ format "44100:16:2"
+}
+# An example of a pulseaudio output (streaming to a remote pulseaudio server)
+# Please see README.Debian if you want mpd to play through the pulseaudio
+# daemon started as part of your graphical desktop session!
+#
+#audio_output {
+# type "pulse"
+# name "My Pulse Output"
+# server "remote_server" # optional
+# sink "remote_server_sink" # optional
+#}
+#
+# An example of a winmm output (Windows multimedia API).
+#
+#audio_output {
+# type "winmm"
+# name "My WinMM output"
+# device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
+# or
+# device "0" # optional
+# mixer_type "hardware" # optional
+#}
+#
+# An example of an openal output.
+#
+#audio_output {
+# type "openal"
+# name "My OpenAL output"
+# device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
+#}
+#
+## Example "pipe" output:
+#
+#audio_output {
+# type "pipe"
+# name "my pipe"
+# command "aplay -f cd 2>/dev/null"
+## Or if you're want to use AudioCompress
+# command "AudioCompress -m | aplay -f cd 2>/dev/null"
+## Or to send raw PCM stream through PCM:
+# command "nc example.org 8765"
+# format "44100:16:2"
+#}
+#
+## An example of a null output (for no audio output):
+#
+#audio_output {
+# type "null"
+# name "My Null Output"
+# mixer_type "none" # optional
+#}
+#
+# If MPD has been compiled with libsamplerate support, this setting specifies
+# the sample rate converter to use. Possible values can be found in the
+# mpd.conf man page or the libsamplerate documentation. By default, this is
+# setting is disabled.
+#
+#samplerate_converter "Fastest Sinc Interpolator"
+#
+###############################################################################
+
+
+# Normalization automatic volume adjustments ##################################
+#
+# This setting specifies the type of ReplayGain to use. This setting can have
+# the argument "off", "album", "track" or "auto". "auto" is a special mode that
+# chooses between "track" and "album" depending on the current state of
+# random playback. If random playback is enabled then "track" mode is used.
+# See for more details about ReplayGain.
+# This setting is off by default.
+#
+replaygain "album"
+#
+# This setting sets the pre-amp used for files that have ReplayGain tags. By
+# default this setting is disabled.
+#
+#replaygain_preamp "0"
+#
+# This setting sets the pre-amp used for files that do NOT have ReplayGain tags.
+# By default this setting is disabled.
+#
+#replaygain_missing_preamp "0"
+#
+# This setting enables or disables ReplayGain limiting.
+# MPD calculates actual amplification based on the ReplayGain tags
+# and replaygain_preamp / replaygain_missing_preamp setting.
+# If replaygain_limit is enabled MPD will never amplify audio signal
+# above its original level. If replaygain_limit is disabled such amplification
+# might occur. By default this setting is enabled.
+#
+#replaygain_limit "yes"
+#
+# This setting enables on-the-fly normalization volume adjustment. This will
+# result in the volume of all playing audio to be adjusted so the output has
+# equal "loudness". This setting is disabled by default.
+#
+volume_normalization "yes"
+#
+###############################################################################
+
+
+# Character Encoding ##########################################################
+#
+# If file or directory names do not display correctly for your locale then you
+# may need to modify this setting.
+#
+filesystem_charset "UTF-8"
+#
+# This setting controls the encoding that ID3v1 tags should be converted from.
+#
+# id3v1_encoding "UTF-8"
+#
+###############################################################################
+
+
+# SIDPlay decoder #############################################################
+#
+# songlength_database:
+# Location of your songlengths file, as distributed with the HVSC.
+# The sidplay plugin checks this for matching MD5 fingerprints.
+# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq
+#
+# default_songlength:
+# This is the default playing time in seconds for songs not in the
+# songlength database, or in case you're not using a database.
+# A value of 0 means play indefinitely.
+#
+# filter:
+# Turns the SID filter emulation on or off.
+#
+#decoder {
+# plugin "sidplay"
+# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt"
+# default_songlength "120"
+# filter "true"
+#}
+#
+###############################################################################
+
diff --git a/dotfiles/system/.config/mpv/input.conf b/dotfiles/system/.config/mpv/input.conf
new file mode 100644
index 0000000..937534c
--- /dev/null
+++ b/dotfiles/system/.config/mpv/input.conf
@@ -0,0 +1,4 @@
+UP add volume +5
+DOWN add volume -5
+- add volume -5
+= add volume 5
diff --git a/dotfiles/system/.config/mpv/mpv.conf b/dotfiles/system/.config/mpv/mpv.conf
new file mode 100644
index 0000000..52a4082
--- /dev/null
+++ b/dotfiles/system/.config/mpv/mpv.conf
@@ -0,0 +1 @@
+volume=75
diff --git a/dotfiles/system/.config/ncmpcpp/bindings b/dotfiles/system/.config/ncmpcpp/bindings
new file mode 100644
index 0000000..a7ca6c0
--- /dev/null
+++ b/dotfiles/system/.config/ncmpcpp/bindings
@@ -0,0 +1,551 @@
+##############################################################
+## This is the example bindings file. Copy it to ##
+## $XDG_CONFIG_HOME/ncmpcpp/bindings or ~/.ncmpcpp/bindings ##
+## and set up your preferences. ##
+##############################################################
+##
+##### General rules #####
+##
+## 1) Because each action has runtime checks whether it's
+## ok to run it, a few actions can be bound to one key.
+## Actions will be bound in order given in configuration
+## file. When a key is pressed, first action in order
+## will test itself whether it's possible to run it. If
+## test succeeds, action is executed and other actions
+## bound to this key are ignored. If it doesn't, next
+## action in order tests itself etc.
+##
+## 2) It's possible to bind more that one action at once
+## to a key. It can be done using the following syntax:
+##
+## def_key "key"
+## action1
+## action2
+## ...
+##
+## This creates a chain of actions. When such chain is
+## executed, each action in chain is run until the end of
+## chain is reached or one of its actions fails to execute
+## due to its requirements not being met. If multiple actions
+## and/or chains are bound to the same key, they will be
+## consecutively run until one of them gets fully executed.
+##
+## 3) When ncmpcpp starts, bindings configuration file is
+## parsed and then ncmpcpp provides "missing pieces"
+## of default keybindings. If you want to disable some
+## bindings, there is a special action called 'dummy'
+## for that purpose. Eg. if you want to disable ability
+## to crop playlists, you need to put the following
+## into configuration file:
+##
+## def_key "C"
+## dummy
+##
+## After that ncmpcpp will not bind any default action
+## to this key.
+##
+## 4) To let you write simple macros, the following special
+## actions are provided:
+##
+## - push_character "character" - pushes given special
+## character into input queue, so it will be immediately
+## picked by ncmpcpp upon next call to readKey function.
+## Accepted values: mouse, up, down, page_up, page_down,
+## home, end, space, enter, insert, delete, left, right,
+## tab, ctrl-a, ctrl-b, ..., ctrl-z, ctrl-[, ctrl-\\,
+## ctrl-], ctrl-^, ctrl-_, f1, f2, ..., f12, backspace.
+## In addition, most of these names can be prefixed with
+## alt-/ctrl-/shift- to be recognized with the appropriate
+## modifier key(s).
+##
+## - push_characters "string" - pushes given string into
+## input queue.
+##
+## - require_runnable "action" - checks whether given action
+## is runnable and fails if it isn't. This is especially
+## useful when mixed with previous two functions. Consider
+## the following macro definition:
+##
+## def_key "key"
+## push_characters "custom_filter"
+## apply_filter
+##
+## If apply_filter can't be currently run, we end up with
+## sequence of characters in input queue which will be
+## treated just as we typed them. This may lead to unexpected
+## results (in this case 'c' will most likely clear current
+## playlist, 'u' will trigger database update, 's' will stop
+## playback etc.). To prevent such thing from happening, we
+## need to change above definition to this one:
+##
+## def_key "key"
+## require_runnable "apply_filter"
+## push_characters "custom_filter"
+## apply_filter
+##
+## Here, first we test whether apply_filter can be actually run
+## before we stuff characters into input queue, so if condition
+## is not met, whole chain is aborted and we're fine.
+##
+## - require_screen "screen" - checks whether given screen is
+## currently active. accepted values: browser, clock, help,
+## media_library, outputs, playlist, playlist_editor,
+## search_engine, tag_editor, visualizer, last_fm, lyrics,
+## selected_items_adder, server_info, song_info,
+## sort_playlist_dialog, tiny_tag_editor.
+##
+## - run_external_command "command" - runs given command using
+## system() function.
+##
+## - run_external_console_command "command" - runs given console
+## command using system() function.
+##
+##
+## 5) In addition to binding to a key, you can also bind actions
+## or chains of actions to a command. If it comes to commands,
+## syntax is very similar to defining keys. Here goes example
+## definition of a command:
+##
+## def_command "quit" [deferred]
+## stop
+## quit
+##
+## If you execute the above command (which can be done by
+## invoking action execute_command, typing 'quit' and pressing
+## enter), ncmpcpp will stop the player and then quit. Note the
+## presence of word 'deferred' enclosed in square brackets. It
+## tells ncmpcpp to wait for confirmation (ie. pressing enter)
+## after you typed quit. Instead of 'deferred', 'immediate'
+## could be used. Then ncmpcpp will not wait for confirmation
+## (enter) and will execute the command the moment it sees it.
+##
+## Note: while command chains are executed, internal environment
+## update (which includes current window refresh and mpd status
+## update) is not performed for performance reasons. However, it
+## may be desirable to do so in some situration. Therefore it's
+## possible to invoke by hand by performing 'update enviroment'
+## action.
+##
+## Note: There is a difference between:
+##
+## def_key "key"
+## action1
+##
+## def_key "key"
+## action2
+##
+## and
+##
+## def_key "key"
+## action1
+## action2
+##
+## First one binds two single actions to the same key whilst
+## second one defines a chain of actions. The behavior of
+## these two is different and is described in (1) and (2).
+##
+## Note: Function def_key accepts non-ascii characters.
+##
+##### List of unbound actions #####
+##
+## The following actions are not bound to any key/command:
+##
+## - set_volume
+## - load
+##
+#
+#def_key "mouse"
+# mouse_event
+#
+#def_key "up"
+# scroll_up
+#
+#def_key "shift-up"
+# select_item
+# scroll_up
+#
+#def_key "down"
+# scroll_down
+#
+#def_key "shift-down"
+# select_item
+# scroll_down
+#
+#def_key "["
+# scroll_up_album
+#
+#def_key "]"
+# scroll_down_album
+#
+#def_key "{"
+# scroll_up_artist
+#
+#def_key "}"
+# scroll_down_artist
+#
+#def_key "page_up"
+# page_up
+#
+#def_key "page_down"
+# page_down
+#
+#def_key "home"
+# move_home
+#
+#def_key "end"
+# move_end
+#
+#def_key "insert"
+# select_item
+#
+#def_key "enter"
+# enter_directory
+#
+#def_key "enter"
+# toggle_output
+#
+#def_key "enter"
+# run_action
+#
+#def_key "enter"
+# play_item
+#
+#def_key "space"
+# add_item_to_playlist
+#
+#def_key "space"
+# toggle_lyrics_update_on_song_change
+#
+#def_key "space"
+# toggle_visualization_type
+#
+#def_key "delete"
+# delete_playlist_items
+#
+#def_key "delete"
+# delete_browser_items
+#
+#def_key "delete"
+# delete_stored_playlist
+#
+#def_key "right"
+# next_column
+#
+#def_key "right"
+# slave_screen
+#
+#def_key "right"
+# volume_up
+#
+#def_key "+"
+# volume_up
+#
+#def_key "left"
+# previous_column
+#
+#def_key "left"
+# master_screen
+#
+#def_key "left"
+# volume_down
+#
+#def_key "-"
+# volume_down
+#
+#def_key ":"
+# execute_command
+#
+#def_key "tab"
+# next_screen
+#
+#def_key "shift-tab"
+# previous_screen
+#
+#def_key "f1"
+# show_help
+#
+#def_key "1"
+# show_playlist
+#
+#def_key "2"
+# show_browser
+#
+#def_key "2"
+# change_browse_mode
+#
+#def_key "3"
+# show_search_engine
+#
+#def_key "3"
+# reset_search_engine
+#
+#def_key "4"
+# show_media_library
+#
+#def_key "4"
+# toggle_media_library_columns_mode
+#
+#def_key "5"
+# show_playlist_editor
+#
+#def_key "6"
+# show_tag_editor
+#
+#def_key "7"
+# show_outputs
+#
+#def_key "8"
+# show_visualizer
+#
+def_key "="
+ show_clock
+#
+#def_key "@"
+# show_server_info
+#
+#def_key "s"
+# stop
+#
+#def_key "p"
+# pause
+#
+#def_key ">"
+# next
+#
+#def_key "<"
+# previous
+#
+#def_key "ctrl-h"
+# jump_to_parent_directory
+#
+#def_key "ctrl-h"
+# replay_song
+#
+#def_key "backspace"
+# jump_to_parent_directory
+#
+#def_key "backspace"
+# replay_song
+#
+#def_key "backspace"
+# play
+#
+#def_key "f"
+# seek_forward
+#
+#def_key "b"
+# seek_backward
+#
+#def_key "r"
+# toggle_repeat
+#
+#def_key "z"
+# toggle_random
+#
+#def_key "y"
+# save_tag_changes
+#
+#def_key "y"
+# start_searching
+#
+def_key "t"
+ toggle_single
+#
+#def_key "R"
+# toggle_consume
+#
+#def_key "Y"
+# toggle_replay_gain_mode
+#
+#def_key "T"
+# toggle_add_mode
+#
+#def_key "|"
+# toggle_mouse
+#
+#def_key "#"
+# toggle_bitrate_visibility
+#
+#def_key "Z"
+# shuffle
+#
+#def_key "x"
+# toggle_crossfade
+#
+#def_key "X"
+# set_crossfade
+#
+#def_key "u"
+# update_database
+#
+#def_key "ctrl-s"
+# sort_playlist
+#
+#def_key "ctrl-s"
+# toggle_browser_sort_mode
+#
+#def_key "ctrl-s"
+# toggle_media_library_sort_mode
+#
+#def_key "ctrl-r"
+# reverse_playlist
+#
+#def_key "ctrl-f"
+# apply_filter
+#
+#def_key "ctrl-_"
+# select_found_items
+#
+#def_key "/"
+# find
+#
+#def_key "/"
+# find_item_forward
+#
+#def_key "?"
+# find
+#
+#def_key "?"
+# find_item_backward
+#
+#def_key "."
+# next_found_item
+#
+#def_key ","
+# previous_found_item
+#
+#def_key "w"
+# toggle_find_mode
+#
+#def_key "e"
+# edit_song
+#
+#def_key "e"
+# edit_library_tag
+#
+#def_key "e"
+# edit_library_album
+#
+#def_key "e"
+# edit_directory_name
+#
+#def_key "e"
+# edit_playlist_name
+#
+#def_key "e"
+# edit_lyrics
+#
+def_key "i"
+ show_song_info
+#
+#def_key "I"
+# show_artist_info
+#
+#def_key "g"
+# jump_to_position_in_song
+#
+def_key "l"
+ show_lyrics
+#
+#def_key "ctrl-v"
+# select_range
+#
+#def_key "v"
+# reverse_selection
+#
+#def_key "V"
+# remove_selection
+#
+#def_key "B"
+# select_album
+#
+#def_key "a"
+# add_selected_items
+#
+#def_key "c"
+# clear_playlist
+#
+#def_key "c"
+# clear_main_playlist
+#
+#def_key "C"
+# crop_playlist
+#
+#def_key "C"
+# crop_main_playlist
+#
+#def_key "m"
+# move_sort_order_up
+#
+def_key "shift-up"
+ move_selected_items_up
+#
+#def_key "n"
+# move_sort_order_down
+#
+def_key "shift-down"
+ move_selected_items_down
+#
+#def_key "M"
+# move_selected_items_to
+#
+#def_key "A"
+# add
+#
+def_key "S"
+ save_playlist
+#
+#def_key "o"
+# jump_to_playing_song
+#
+#def_key "G"
+# jump_to_browser
+#
+#def_key "G"
+# jump_to_playlist_editor
+#
+#def_key "~"
+# jump_to_media_library
+#
+#def_key "E"
+# jump_to_tag_editor
+#
+#def_key "U"
+# toggle_playing_song_centering
+#
+#def_key "P"
+# toggle_display_mode
+#
+#def_key "\\"
+# toggle_interface
+#
+#def_key "!"
+# toggle_separators_between_albums
+#
+#def_key "L"
+# toggle_lyrics_fetcher
+#
+#def_key "F"
+# fetch_lyrics_in_background
+#
+#def_key "alt-l"
+# toggle_fetching_lyrics_in_background
+#
+#def_key "ctrl-l"
+# toggle_screen_lock
+#
+#def_key "`"
+# toggle_library_tag_type
+#
+#def_key "`"
+# refetch_lyrics
+#
+#def_key "`"
+# add_random_items
+#
+#def_key "ctrl-p"
+# set_selected_items_priority
+#
+#def_key "q"
+# quit
+#
diff --git a/dotfiles/system/.config/ncmpcpp/config b/dotfiles/system/.config/ncmpcpp/config
new file mode 100644
index 0000000..6520d5a
--- /dev/null
+++ b/dotfiles/system/.config/ncmpcpp/config
@@ -0,0 +1,71 @@
+# Connection
+mpd_host = "127.0.0.1"
+# mpd_host = "/home/cjennings/.config/mpd/socket"
+mpd_port = "6600"
+mpd_music_dir = "/home/cjennings/music"
+mpd_connection_timeout = "10"
+mpd_crossfade_time = "1"
+
+# Visualizer
+visualizer_data_source = "/tmp/mpd.fifo"
+visualizer_output_name = "FIFO"
+visualizer_in_stereo = "yes"
+visualizer_type = "wave_filled"
+visualizer_color = 246,245,244,243,242,241,240,239,238,237,236,235
+visualizer_look = "|○"
+
+# Columns
+song_columns_list_format = "(3f)[239]{} (35)[246]{t|f} (30)[blue]{a} (30)[green]{b} (5f)[240]{l}"
+song_list_format = "{$5 %a$9 $1│$9 $8%t$9 }|{ $8%f$9}$R{$5%b $7}"
+song_status_format = "{{{$5%a$9}} $8-$9 {$2%t$9}|{$0%f$9}{ $8-$9 $3%b$9{ $8-$9 $5%y$9}}}"
+song_library_format = "{%n $8-$9 }{%t}|{%f}"
+now_playing_prefix = "$8$b ➤ "
+browser_playlist_prefix = "playlist"
+selected_item_prefix = "$5"
+selected_item_suffix = "$9"
+song_window_title_format = "{%t}|{%f} - {%a}"
+
+# Various
+playlist_show_remaining_time = "no"
+playlist_shorten_total_times = "yes"
+playlist_separate_albums = "no"
+playlist_display_mode = "columns"
+browser_display_mode = "columns"
+search_engine_display_mode = "columns"
+discard_colors_if_item_is_selected = "no"
+incremental_seeking = "yes"
+seek_time = "1"
+autocenter_mode = "yes"
+centered_cursor = "yes"
+progressbar_look = "─╼─"
+progressbar_color = 240
+progressbar_elapsed_color = white
+user_interface = "classic"
+header_visibility = "no"
+titles_visibility = "no"
+header_text_scrolling = "yes"
+cyclic_scrolling = "no"
+lines_scrolled = "2"
+follow_now_playing_lyrics = "yes"
+show_hidden_files_in_local_browser = "no"
+jump_to_now_playing_song_at_start = "yes"
+clock_display_seconds = "no"
+display_volume_level = "no"
+display_bitrate = "yes"
+display_remaining_time = "no"
+regular_expressions = "extended"
+ignore_leading_the = "no"
+block_search_constraints_change_if_items_found = "yes"
+mouse_support = "yes"
+mouse_list_scroll_whole_page = "yes"
+external_editor = "vim"
+use_console_editor = "yes"
+colors_enabled = "yes"
+empty_tag_color = "white"
+header_window_color = "yellow"
+state_line_color = "black"
+state_flags_color = "black"
+main_window_color = 243
+statusbar_color = "yellow"
+active_window_border = "yellow"
+
diff --git a/dotfiles/system/.config/nitrogen/bg-saved.cfg b/dotfiles/system/.config/nitrogen/bg-saved.cfg
new file mode 100644
index 0000000..eccaecb
--- /dev/null
+++ b/dotfiles/system/.config/nitrogen/bg-saved.cfg
@@ -0,0 +1,4 @@
+[xin_-1]
+file=/home/cjennings/pictures/wallpaper/zendopeak.jpg
+mode=5
+bgcolor=#000000
diff --git a/dotfiles/system/.config/picom.conf b/dotfiles/system/.config/picom.conf
new file mode 100644
index 0000000..0b65df7
--- /dev/null
+++ b/dotfiles/system/.config/picom.conf
@@ -0,0 +1,56 @@
+# opacity-rule = [
+# "85:class_g = 'XTerm'",
+# "85:class_g = 'Alacritty'",
+# "85:class_g = 'xterm-kitty'",
+# "85:class_g = 'URxvt'",
+# "85:class_g = 'tabbed'",
+# ];
+# "85:class_g = 'st-256color'",
+# "85:class_g = 'Emacs'",
+
+# Blur
+blur:
+{
+ method = "dual_kawase";
+ strength = 2;
+}
+
+wintypes:
+{
+ normal = { blur-background = true; };
+ splash = { blur-background = false; };
+};
+
+# Fading
+fading = false;
+fade-in-step = 0.07;
+fade-out-step = 0.07;
+fade-exclude = [ ];
+
+# Other
+corner-radius = 10.0;
+round-borders = 1;
+mark-wmwin-focused = true;
+mark-ovredir-focused = true;
+detect-rounded-corners = true;
+rounded-corners-exclude = [
+ "class_g = 'dwm'",
+ "class_g = 'dwmsystray'",
+ "window_type = 'dock'"
+ ];
+detect-client-opacity = true;
+
+vsync = true;
+dbe = false;
+unredir-if-possible = true;
+detect-transient = true;
+detect-client-leader = true;
+invert-color-include = [ ];
+
+# GLX backend
+backend = "glx";
+glx-no-stencil = true;
+glx-copy-from-front = false;
+use-damage = true
+glx-no-rebind-pixmap = true;
+
diff --git a/dotfiles/system/.config/pychess/config b/dotfiles/system/.config/pychess/config
new file mode 100644
index 0000000..694298a
--- /dev/null
+++ b/dotfiles/system/.config/pychess/config
@@ -0,0 +1,213 @@
+[General]
+soundcombo0 = 3
+soundcombo1 = 3
+soundcombo2 = 3
+soundcombo3 = 3
+soundcombo4 = 3
+soundcombo5 = 3
+soundcombo6 = 3
+soundcombo7 = 3
+soundcombo8 = 3
+soundcombo9 = 3
+soundcombo10 = 3
+soundcombo11 = 3
+soundcombo12 = 3
+sounduri0 = file:/usr/share/pychess/sounds/move1.ogg
+sounduri1 = file:/usr/share/pychess/sounds/check1.ogg
+sounduri2 = file:/usr/share/pychess/sounds/capture1.ogg
+sounduri3 = file:/usr/share/pychess/sounds/start1.ogg
+sounduri4 = file:/usr/share/pychess/sounds/win1.ogg
+sounduri5 = file:/usr/share/pychess/sounds/lose1.ogg
+sounduri6 = file:/usr/share/pychess/sounds/draw1.ogg
+sounduri7 = file:/usr/share/pychess/sounds/obs_mov.ogg
+sounduri8 = file:/usr/share/pychess/sounds/obs_end.ogg
+sounduri9 = file:/usr/share/pychess/sounds/alarm.ogg
+sounduri10 = file:/usr/share/pychess/sounds/invalid.ogg
+sounduri11 = file:/usr/share/pychess/sounds/success.ogg
+sounduri12 = file:/usr/share/pychess/sounds/choice.ogg
+ics_combo = 0
+autologin = False
+categorycombo = 0
+max_log_files = 10
+hint_mode = False
+spy_mode = False
+show_sidepanels = True
+autocallflag = True
+tips_seed = 468535751
+tips_index = 3
+firstname = Craig Jennings
+secondname = Whomever
+showemt = False
+showeval = True
+showblunder = True
+hidetabs = True
+closeall = False
+facetoface = False
+scorelinearscale = False
+showcaptured = True
+figuresinnotation = False
+moveanimation = True
+noanimation = False
+autopromote = False
+autorotate = False
+showficsgameno = False
+fullanimation = False
+showcords = False
+drawgrid = False
+activatesupportalgorithm = False
+autosave = True
+autosaveformat = cjennings
+saveemt = True
+saveeval = True
+saveratingchange = True
+indentpgn = True
+saveowngames = False
+max_analysis_spin = 3
+max_depth_spin = 20
+infinite_analysis = False
+infinite_depth = True
+opening_check = False
+book_depth_max = 13
+endgame_check = False
+online_egtb_check = True
+analyzer_check = True
+inv_analyzer_check = False
+alarm_spin = 15
+usesounds = False
+newgametasker_playercombo = 1
+taskerskillslider = 2.0
+ana_combobox = c72ee03eee276a3a8e142ed144b4c77b
+inv_ana_combobox = c72ee03eee276a3a8e142ed144b4c77b
+tipofthedaywindow_width = 693
+tipofthedaywindow_height = 373
+tipofthedaywindow_x = 778
+tipofthedaywindow_y = 562
+externalsdialogwindow_width = 1193
+externalsdialogwindow_height = 371
+externalsdialogwindow_x = 528
+externalsdialogwindow_y = 563
+mainwindow_width = 2256
+mainwindow_height = 1471
+mainwindow_x = 0
+mainwindow_y = 33
+download_scoutfish = True
+opening_file_entry = /usr/share/pychess/pychess_book.bin
+book_exact_match = True
+egtb_path = /usr/share/pychess
+welcome_image = /usr/share/pychess/glade/background.jpg
+board_style = 2
+board_frame = 5
+lightcolour = #444444
+darkcolour = #252525
+piecetheme = Merida_new
+autosavepath = /home/cjennings/documents/chessdata
+preferencesdialogwindow_width = 1102
+preferencesdialogwindow_height = 1031
+preferencesdialogwindow_x = 574
+preferencesdialogwindow_y = 233
+show_tip_at_startup = False
+dont_show_externals_at_startup = False
+ngblitz min = 5
+ngblitz moves = 0
+ngblitz gain = 0
+ngrapid min = 15
+ngrapid moves = 0
+ngrapid gain = 5
+ngnormal min = 45
+ngnormal moves = 0
+ngnormal gain = 15
+ngclassical min = 3
+ngclassical moves = 40
+ngclassical gain = 0
+ngvariant1 = 3
+ngvariant2 = 23
+whiteplayercombobox = 0
+blackplayercombobox = 1
+skillslider1 = 20
+skillslider2 = 4.0
+notimeradio = True
+blitzradio = False
+rapidradio = 0
+normalradio = False
+classicalradio = False
+playnormalradio = 0
+playvariant1radio = 0
+playvariant2radio = 0
+numberoffingers = 2
+seek1radio = 0
+seek2radio = 0
+seek3radio = 0
+challenge1radio = 0
+challenge2radio = 0
+challenge3radio = 0
+untimedcheck-1 = False
+minutesspin-1 = 15
+gainspin-1 = 10
+strengthcheck-1 = True
+chainalignment-1 = True
+ratingcenterslider-1 = 40
+toleranceslider-1 = 8
+tolerancehbox-1 = False
+nocolorradio-1 = True
+whitecolorradio-1 = False
+blackcolorradio-1 = False
+variantcombo-1 = 4
+novariantradio-1 = True
+variantradio-1 = False
+ratedgamecheck-1 = False
+manualacceptcheck-1 = False
+untimedcheck-2 = False
+minutesspin-2 = 10
+gainspin-2 = 0
+strengthcheck-2 = True
+chainalignment-2 = True
+ratingcenterslider-2 = 40
+toleranceslider-2 = 8
+tolerancehbox-2 = False
+nocolorradio-2 = True
+whitecolorradio-2 = False
+blackcolorradio-2 = False
+variantcombo-2 = 3
+novariantradio-2 = True
+variantradio-2 = False
+ratedgamecheck-2 = False
+manualacceptcheck-2 = False
+untimedcheck-3 = False
+minutesspin-3 = 2
+gainspin-3 = 12
+strengthcheck-3 = True
+chainalignment-3 = True
+ratingcenterslider-3 = 40
+toleranceslider-3 = 8
+tolerancehbox-3 = False
+nocolorradio-3 = True
+whitecolorradio-3 = False
+blackcolorradio-3 = False
+variantcombo-3 = 23
+novariantradio-3 = True
+variantradio-3 = False
+ratedgamecheck-3 = False
+manualacceptcheck-3 = False
+chat_paned_position = 306
+standard_toggle = True
+blitz_toggle = True
+lightning_toggle = True
+variant_toggle = True
+registered_toggle = True
+guest_toggle = True
+computer_toggle = True
+titled_toggle = True
+standard_toggle1 = True
+blitz_toggle1 = True
+lightning_toggle1 = True
+variant_toggle1 = True
+computer_toggle1 = True
+download_chess_db = True
+download_timestamp = True
+
+[FICS]
+asguestcheck = True
+timesealcheck = True
+
+[ICC]
+
diff --git a/dotfiles/system/.config/pychess/engines.json b/dotfiles/system/.config/pychess/engines.json
new file mode 100644
index 0000000..913a9be
--- /dev/null
+++ b/dotfiles/system/.config/pychess/engines.json
@@ -0,0 +1,526 @@
+[
+ {
+ "analyze": true,
+ "command": "/usr/lib/python3.13/site-packages/pychess/Players/PyChess.py",
+ "comment": "",
+ "country": "dk",
+ "level": 5,
+ "md5": "be6e594623ce6b20024914bcffba3f66",
+ "name": "PyChess.py",
+ "options": [
+ {
+ "default": false,
+ "name": "Ponder",
+ "type": "check",
+ "value": false
+ },
+ {
+ "default": 0,
+ "max": 100,
+ "min": 0,
+ "name": "skipPruneChance",
+ "type": "spin",
+ "value": 0
+ }
+ ],
+ "protocol": "xboard",
+ "recheck": false,
+ "variants": [
+ "normal",
+ "wildcastle",
+ "nocastle",
+ "fischerandom",
+ "crazyhouse",
+ "light-brigade",
+ "losers",
+ "suicide",
+ "giveaway",
+ "horde",
+ "atomic",
+ "racingkings",
+ "seirawan",
+ "kingofthehill",
+ "3check",
+ "placement",
+ "asean",
+ "cambodian",
+ "makruk",
+ "sittuyin"
+ ],
+ "vm_args": [
+ "-u"
+ ],
+ "vm_command": "/usr/bin/python",
+ "vm_name": "python"
+ },
+ {
+ "analyze": true,
+ "command": "/usr/bin/critter",
+ "comment": "",
+ "country": "sk",
+ "level": 20,
+ "md5": "854b376c8e7b3405b5e4f7cef8420204",
+ "name": "critter",
+ "options": [
+ {
+ "default": 64,
+ "max": 8192,
+ "min": 8,
+ "name": "Hash",
+ "type": "spin"
+ },
+ {
+ "default": false,
+ "name": "Ponder",
+ "type": "check"
+ },
+ {
+ "default": true,
+ "name": "OwnBook",
+ "type": "check"
+ },
+ {
+ "default": "book.cbk",
+ "name": "Book File",
+ "type": "text"
+ },
+ {
+ "default": false,
+ "name": "UCI_Chess960",
+ "type": "check"
+ },
+ {
+ "default": 1,
+ "max": 100,
+ "min": 1,
+ "name": "MultiPV",
+ "type": "spin"
+ },
+ {
+ "default": 20,
+ "max": 32,
+ "min": 1,
+ "name": "Threads",
+ "type": "spin"
+ },
+ {
+ "default": 5,
+ "max": 15,
+ "min": 4,
+ "name": "Split Depth",
+ "type": "spin"
+ },
+ {
+ "default": "/gtb",
+ "name": "GaviotaTbPath",
+ "type": "text"
+ },
+ {
+ "default": 32,
+ "max": 256,
+ "min": 4,
+ "name": "GaviotaTbCache",
+ "type": "spin"
+ },
+ {
+ "choices": [
+ "uncompressed",
+ "cp1",
+ "cp2",
+ "cp3",
+ "cp4"
+ ],
+ "default": "cp4",
+ "name": "GaviotaTbCompression",
+ "type": "combo"
+ },
+ {
+ "choices": [
+ "Disable",
+ "Only At Root",
+ "Everywhere"
+ ],
+ "default": "Only At Root",
+ "name": "Tablebase Usage",
+ "type": "combo"
+ },
+ {
+ "name": "Clear Hash",
+ "type": "button"
+ },
+ {
+ "default": false,
+ "name": "Use Session File",
+ "type": "check"
+ },
+ {
+ "default": "session.csf",
+ "name": "Session File",
+ "type": "text"
+ },
+ {
+ "default": 32,
+ "max": 1024,
+ "min": 2,
+ "name": "SF Size",
+ "type": "spin"
+ },
+ {
+ "default": 8,
+ "max": 32,
+ "min": 2,
+ "name": "SF Write Depth",
+ "type": "spin"
+ },
+ {
+ "default": 0,
+ "max": 32,
+ "min": 0,
+ "name": "SF Material Limit",
+ "type": "spin"
+ },
+ {
+ "default": 0,
+ "max": 999,
+ "min": 0,
+ "name": "SF Move Limit",
+ "type": "spin"
+ },
+ {
+ "name": "Clear SF",
+ "type": "button"
+ },
+ {
+ "default": false,
+ "name": "Resolve Score Drops",
+ "type": "check"
+ },
+ {
+ "default": 32,
+ "max": 100,
+ "min": 0,
+ "name": "King Safety Weight",
+ "type": "spin"
+ }
+ ],
+ "protocol": "uci",
+ "recheck": false
+ },
+ {
+ "analyze": true,
+ "command": "/usr/bin/fruit",
+ "comment": "",
+ "country": "fr",
+ "level": 5,
+ "md5": "d401d07d0b5c41cbf8be63574a9214ca",
+ "name": "fruit",
+ "options": [
+ {
+ "default": "book_small.bin",
+ "name": "BookFile",
+ "type": "text",
+ "value": "book_small.bin"
+ },
+ {
+ "default": 50,
+ "max": 500,
+ "min": 0,
+ "name": "Delta Margin",
+ "type": "spin",
+ "value": 50
+ },
+ {
+ "default": false,
+ "name": "Delta Pruning",
+ "type": "check",
+ "value": false
+ },
+ {
+ "default": 100,
+ "max": 500,
+ "min": 0,
+ "name": "Futility Margin",
+ "type": "spin",
+ "value": 100
+ },
+ {
+ "default": false,
+ "name": "Futility Pruning",
+ "type": "check",
+ "value": false
+ },
+ {
+ "default": 16,
+ "max": 1024,
+ "min": 4,
+ "name": "Hash",
+ "type": "spin",
+ "value": 16
+ },
+ {
+ "default": true,
+ "name": "History Pruning",
+ "type": "check",
+ "value": true
+ },
+ {
+ "default": 60,
+ "max": 100,
+ "min": 0,
+ "name": "History Threshold",
+ "type": "spin",
+ "value": 60
+ },
+ {
+ "default": 100,
+ "max": 400,
+ "min": 0,
+ "name": "King Safety",
+ "type": "spin",
+ "value": 100
+ },
+ {
+ "default": 100,
+ "max": 400,
+ "min": 0,
+ "name": "Material",
+ "type": "spin",
+ "value": 100
+ },
+ {
+ "choices": [
+ "Always",
+ "Fail High",
+ "Never"
+ ],
+ "default": "Fail High",
+ "name": "NullMove Pruning",
+ "type": "combo",
+ "value": "Fail High"
+ },
+ {
+ "default": 3,
+ "max": 3,
+ "min": 1,
+ "name": "NullMove Reduction",
+ "type": "spin",
+ "value": 3
+ },
+ {
+ "default": true,
+ "name": "OwnBook",
+ "type": "check",
+ "value": true
+ },
+ {
+ "default": 100,
+ "max": 400,
+ "min": 0,
+ "name": "Passed Pawns",
+ "type": "spin",
+ "value": 100
+ },
+ {
+ "default": 100,
+ "max": 400,
+ "min": 0,
+ "name": "Pawn Structure",
+ "type": "spin",
+ "value": 100
+ },
+ {
+ "default": 100,
+ "max": 400,
+ "min": 0,
+ "name": "Piece Activity",
+ "type": "spin",
+ "value": 100
+ },
+ {
+ "default": false,
+ "name": "Ponder",
+ "type": "check",
+ "value": false
+ },
+ {
+ "default": 1,
+ "max": 2,
+ "min": 0,
+ "name": "Quiescence Check Plies",
+ "type": "spin",
+ "value": 1
+ },
+ {
+ "default": 5,
+ "max": 6,
+ "min": 1,
+ "name": "Verification Reduction",
+ "type": "spin",
+ "value": 5
+ },
+ {
+ "choices": [
+ "Always",
+ "Endgame",
+ "Never"
+ ],
+ "default": "Endgame",
+ "name": "Verification Search",
+ "type": "combo",
+ "value": "Endgame"
+ }
+ ],
+ "protocol": "uci",
+ "recheck": false,
+ "workingDirectory": "/usr/bin"
+ },
+ {
+ "analyze": true,
+ "command": "/usr/bin/stockfish",
+ "comment": "",
+ "country": "no",
+ "level": 20,
+ "md5": "c72ee03eee276a3a8e142ed144b4c77b",
+ "name": "stockfish",
+ "options": [
+ {
+ "name": "Clear Hash",
+ "type": "button"
+ },
+ {
+ "default": "",
+ "name": "Debug Log File",
+ "type": "text",
+ "value": ""
+ },
+ {
+ "default": "nn-1c0000000000.nnue",
+ "name": "EvalFile",
+ "type": "text",
+ "value": "nn-1c0000000000.nnue"
+ },
+ {
+ "default": "nn-37f18f62d772.nnue",
+ "name": "EvalFileSmall",
+ "type": "text",
+ "value": "nn-37f18f62d772.nnue"
+ },
+ {
+ "default": 16,
+ "max": 33554432,
+ "min": 1,
+ "name": "Hash",
+ "type": "spin",
+ "value": 16
+ },
+ {
+ "default": 10,
+ "max": 5000,
+ "min": 0,
+ "name": "Move Overhead",
+ "type": "spin",
+ "value": 10
+ },
+ {
+ "default": 1,
+ "max": 256,
+ "min": 1,
+ "name": "MultiPV",
+ "type": "spin",
+ "value": 1
+ },
+ {
+ "default": 0,
+ "max": 10000,
+ "min": 0,
+ "name": "nodestime",
+ "type": "spin",
+ "value": 0
+ },
+ {
+ "default": "auto",
+ "name": "NumaPolicy",
+ "type": "text",
+ "value": "auto"
+ },
+ {
+ "default": false,
+ "name": "Ponder",
+ "type": "check",
+ "value": false
+ },
+ {
+ "default": 20,
+ "max": 20,
+ "min": 0,
+ "name": "Skill Level",
+ "type": "spin",
+ "value": 20
+ },
+ {
+ "default": true,
+ "name": "Syzygy50MoveRule",
+ "type": "check",
+ "value": true
+ },
+ {
+ "default": "",
+ "name": "SyzygyPath",
+ "type": "text",
+ "value": ""
+ },
+ {
+ "default": 1,
+ "max": 100,
+ "min": 1,
+ "name": "SyzygyProbeDepth",
+ "type": "spin",
+ "value": 1
+ },
+ {
+ "default": 7,
+ "max": 7,
+ "min": 0,
+ "name": "SyzygyProbeLimit",
+ "type": "spin",
+ "value": 7
+ },
+ {
+ "default": 1,
+ "max": 1024,
+ "min": 1,
+ "name": "Threads",
+ "type": "spin",
+ "value": 1
+ },
+ {
+ "default": false,
+ "name": "UCI_Chess960",
+ "type": "check",
+ "value": false
+ },
+ {
+ "default": 1320,
+ "max": 3190,
+ "min": 1320,
+ "name": "UCI_Elo",
+ "type": "spin",
+ "value": 1320
+ },
+ {
+ "default": false,
+ "name": "UCI_LimitStrength",
+ "type": "check",
+ "value": false
+ },
+ {
+ "default": false,
+ "name": "UCI_ShowWDL",
+ "type": "check",
+ "value": false
+ }
+ ],
+ "protocol": "uci",
+ "recheck": false
+ }
+]
\ No newline at end of file
diff --git a/dotfiles/system/.config/pychess/pydock.xml b/dotfiles/system/.config/pychess/pydock.xml
new file mode 100644
index 0000000..ef523db
--- /dev/null
+++ b/dotfiles/system/.config/pychess/pydock.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dotfiles/system/.config/qalculate/qalculate-gtk.cfg b/dotfiles/system/.config/qalculate/qalculate-gtk.cfg
new file mode 100644
index 0000000..7004d0e
--- /dev/null
+++ b/dotfiles/system/.config/qalculate/qalculate-gtk.cfg
@@ -0,0 +1,329 @@
+
+[General]
+version=5.8.2
+allow_multiple_instances=-1
+width=1167
+always_on_top=0
+enable_tooltips=1
+error_info_shown=1
+save_mode_on_exit=1
+save_definitions_on_exit=1
+save_history_separately=0
+auto_update_exchange_rates=-1
+clear_history_on_exit=0
+history_expression_type=2
+use_custom_history_font=0
+use_custom_expression_font=0
+replace_expression=0
+enable_completion=1
+enable_completion2=1
+completion_min=1
+completion_min2=1
+completion_delay=0
+use_custom_status_font=0
+vertical_button_padding=-1
+horizontal_button_padding=-1
+use_custom_keypad_font=0
+latest_button_currency=USD
+use_custom_result_font=0
+continuous_conversion=1
+set_missing_prefixes=0
+show_bases_keypad=1
+keep_function_dialog_open=0
+ignore_locale=0
+load_global_definitions=1
+local_currency_conversion=1
+use_binary_prefixes=0
+check_version=0
+show_keypad=1
+show_history=0
+history_height=0
+minimal_width=500
+show_stack=1
+show_convert=0
+persistent_keypad=0
+minimal_mode=0
+rpn_keys=1
+display_expression_status=1
+parsed_expression_in_resultview=0
+calculate_as_you_type_history_delay=2000
+use_unicode_signs=1
+lower_case_numbers=0
+duodecimal_symbols=0
+exp_display=3
+imaginary_j=0
+base_display=1
+twos_complement=1
+hexadecimal_twos_complement=0
+twos_complement_input=0
+hexadecimal_twos_complement_input=0
+spell_out_logical_operators=1
+caret_as_xor=0
+close_with_esc=-1
+digit_grouping=1
+copy_ascii=0
+copy_ascii_without_units=0
+decimal_comma=-1
+dot_as_separator=0
+comma_as_separator=1
+use_custom_application_font=0
+multiplication_sign=2
+division_sign=1
+expression_history=125×7×6
+expression_history=7600×4
+expression_history=7600X4
+expression_history=10619.57−3000
+expression_history=1991−1964
+expression_history=(5×75)+150
+expression_history=5×75
+expression_history=100−35
+expression_history=262690−165393.50
+expression_history=212770+24960
+expression_history=212770−24960
+expression_history=49920/2
+expression_history=262690−212770
+expression_history=68/24
+expression_history=168/197
+expression_history=197/168
+expression_history=262690−19100
+expression_history=38200/2
+expression_history=$38,200/2
+expression_history=30000/197000
+expression_history=197250+6360+6140+3840
+expression_history=4550+4050
+expression_history=4,500+4060
+expression_history=490×6
+expression_history=7688.16×4
+expression_history=3277.52+46.85
+expression_history=3310.94+109.04
+expression_history=3277.52+46.86
+expression_history=30 + 15 + 5 + 10 + 30 + 30
+expression_history=30 +5+15+30+20+20
+expression_history=30 + 5 + 15 + 30 + 20 + 5 + 5 + 20
+expression_history=1024×16
+expression_history=3278×12
+expression_history=1026×16
+expression_history=96×2
+history_time=1768330887
+history_expression=125×7×6
+history_parse=125 × 7 × 6
+history_result=5250
+history_time=1767984868
+history_expression=7600×4
+history_parse=7600 × 4
+history_result=30 400
+history_time=1767984861
+history_expression=7600X4
+history_parse=7600
+history_error=Trailing characters "X4" (not a valid variable/function/unit) in number "7600X4" were ignored.
+history_result=7600
+history_time=1767984665
+history_expression=10619.57−3000
+history_parse=10 619.57 − 3000
+history_result=7619.57
+history_time=1767807514
+history_expression=1991−1964
+history_parse=1991 − 1964
+history_result=27
+history_time=1766252840
+history_expression=(5×75)+150
+history_parse=(5 × 75) + 150
+history_result=525
+history_time=1766252831
+history_expression=5×75
+history_parse=5 × 75
+history_result=375
+history_time=1764874377
+history_expression=100−35
+history_parse=100 − 35
+history_result=65
+history_time=1762832920
+history_expression=262690−165393.50
+history_parse=262 690 − 165 393.5
+history_result=97 296.5
+history_time=1762825204
+history_expression=212770+24960
+history_parse=212 770 + 24 960
+history_result=237 730
+history_time=1762825199
+history_expression=212770−24960
+history_parse=212 770 − 24 960
+history_result=187 810
+history_time=1762825172
+history_expression=49920/2
+history_parse=49 920 ∕ 2
+history_result=24 960
+history_time=1762823711
+history_expression=262690−212770
+history_parse=262 690 − 212 770
+history_result=49 920
+history_time=1762731979
+history_expression=68/24
+history_parse=68 ∕ 24
+history_result_approximate=2.833 333 333
+history_time=1762176587
+history_expression=168/197
+history_parse=168 ∕ 197
+history_result_approximate=0.852 791 878 2
+history_time=1762176579
+history_expression=197/168
+history_parse=197 ∕ 168
+history_result_approximate=1.172 619 048
+history_time=1761510158
+history_expression=262690−19100
+history_parse=262 690 − 19 100
+history_result=243 590
+history_time=1761508703
+history_expression=38200/2
+history_parse=38 200 ∕ 2
+history_result=19 100
+history_time=1761508667
+history_expression=$38,200/2
+history_parse=(USD × 38 200) ∕ 2
+history_result=$19 100
+history_time=1761449979
+history_expression=30000/197000
+history_parse=30 000 ∕ 197 000
+history_result_approximate=0.152 284 264 0
+history_time=1761245928
+history_expression=197250+6360+6140+3840
+history_parse=197 250 + 6360 + 6140 + 3840
+history_result=213 590
+history_time=1761167849
+history_expression=4550+4050
+history_parse=4550 + 4050
+history_result=8600
+history_time=1761167803
+history_expression=4,500+4060
+history_parse=4500 + 4060
+history_result=8560
+history_time=1752350332
+history_expression=490×6
+history_parse=490 × 6
+history_result=2940
+history_time=1752334636
+history_expression=7688.16×4
+history_parse=7688.16 × 4
+history_result=30 752.64
+history_time=1751310907
+history_expression=3277.52+46.85
+history_parse=3277.52 + 46.85
+history_result=3324.37
+history_time=1751310861
+history_expression=3310.94+109.04
+history_parse=3310.94 + 109.04
+history_result=3419.98
+history_time=1751310628
+history_expression=3277.52+46.86
+history_parse=3277.52 + 46.86
+history_result=3324.38
+history_time=1750342970
+history_expression=30 + 15 + 5 + 10 + 30 + 30
+history_parse=30 + 15 + 5 + 10 + 30 + 30
+history_result=120
+history_time=1750342955
+history_expression=30 +5+15+30+20+20
+history_parse=30 + 5 + 15 + 30 + 20 + 20
+history_result=120
+history_time=1750342806
+history_expression=30 + 15 + 5 + 10 + 30 + 30
+history_parse=30 + 15 + 5 + 10 + 30 + 30
+history_result=120
+history_time=1750342726
+history_expression=30 + 5 + 15 + 30 + 20 + 5 + 5 + 20
+history_parse=30 + 5 + 15 + 30 + 20 + 5 + 5 + 20
+history_result=130
+history_time=1749150165
+history_expression=1024×16
+history_parse=1024 × 16
+history_result=16 384
+history_time=1748972784
+history_expression=3278×12
+history_parse=3278 × 12
+history_result=39 336
+history_time=1748621453
+history_expression=1026×16
+history_parse=1026 × 16
+history_result=16 416
+history_time=1747760701
+history_expression=96×2
+history_parse=96 × 2
+history_result=192
+recent_functions=
+recent_variables=
+recent_units=
+
+[Mode]
+min_deci=0
+use_min_deci=0
+max_deci=2
+use_max_deci=0
+precision=10
+interval_arithmetic=1
+interval_display=0
+min_exp=-1
+negative_exponents=0
+sort_minus_last=1
+number_fraction_format=0
+complex_number_form=0
+use_prefixes=1
+use_prefixes_for_all_units=0
+use_prefixes_for_currencies=0
+abbreviate_names=1
+all_prefixes_enabled=0
+denominator_prefix_enabled=1
+place_units_separately=1
+auto_post_conversion=3
+mixed_units_conversion=3
+number_base=10
+number_base_expression=10
+read_precision=0
+assume_denominators_nonzero=1
+warn_about_denominators_assumed_nonzero=1
+structuring=1
+angle_unit=1
+functions_enabled=1
+variables_enabled=1
+calculate_functions=1
+calculate_variables=1
+variable_units_enabled=1
+sync_units=1
+unknownvariables_enabled=0
+units_enabled=1
+allow_complex=1
+allow_infinite=1
+indicate_infinite_series=0
+show_ending_zeroes=1
+rounding_mode=0
+approximation=1
+interval_calculation=1
+concise_uncertainty_input=0
+calculate_as_you_type=0
+in_rpn_mode=0
+chain_mode=0
+limit_implicit_multiplication=0
+parsing_mode=0
+simplified_percentage=-1
+spacious=1
+excessive_parenthesis=0
+visible_keypad=0
+short_multiplication=1
+default_assumption_type=4
+default_assumption_sign=0
+
+[Plotting]
+plot_legend_placement=2
+plot_style=0
+plot_smoothing=0
+plot_display_grid=1
+plot_full_border=0
+plot_min=0
+plot_max=10
+plot_step=1
+plot_sampling_rate=1001
+plot_use_sampling_rate=1
+plot_variable=x
+plot_rows=0
+plot_type=0
+plot_color=1
+plot_linewidth=2
diff --git a/dotfiles/system/.config/qt5ct/qt5ct.conf b/dotfiles/system/.config/qt5ct/qt5ct.conf
new file mode 100644
index 0000000..56a88db
--- /dev/null
+++ b/dotfiles/system/.config/qt5ct/qt5ct.conf
@@ -0,0 +1,32 @@
+[Appearance]
+color_scheme_path=/usr/share/qt5ct/colors/darker.conf
+custom_palette=true
+icon_theme=Papirus-Dark
+standard_dialogs=default
+style=Adwaita-Dark
+
+[Fonts]
+fixed="Cantarell,11,-1,5,50,0,0,0,0,0"
+general="Cantarell,11,-1,5,50,0,0,0,0,0"
+
+[Interface]
+activate_item_on_single_click=1
+buttonbox_layout=0
+cursor_flash_time=1000
+dialog_buttons_have_icons=1
+double_click_interval=400
+gui_effects=@Invalid()
+keyboard_scheme=2
+menus_have_icons=true
+show_shortcuts_in_context_menus=true
+stylesheets=@Invalid()
+toolbutton_style=4
+underline_shortcut=1
+wheel_scroll_lines=3
+
+[SettingsWindow]
+geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\x11\0\0\x4g\0\0\x2\xf0\0\0\0\0\0\0\0\x11\0\0\x4g\0\0\x2\xf0\0\0\0\0\0\0\0\0\x4h\0\0\0\0\0\0\0\x11\0\0\x4g\0\0\x2\xf0)
+
+[Troubleshooting]
+force_raster_widgets=1
+ignored_applications=@Invalid()
diff --git a/dotfiles/system/.config/ranger/commands.py b/dotfiles/system/.config/ranger/commands.py
new file mode 100644
index 0000000..97b7909
--- /dev/null
+++ b/dotfiles/system/.config/ranger/commands.py
@@ -0,0 +1,62 @@
+# This is a sample commands.py. You can add your own commands here.
+#
+# Please refer to commands_full.py for all the default commands and a complete
+# documentation. Do NOT add them all here, or you may end up with defunct
+# commands when upgrading ranger.
+
+# A simple command for demonstration purposes follows.
+# -----------------------------------------------------------------------------
+
+from __future__ import (absolute_import, division, print_function)
+
+# You can import any python module as needed.
+import os
+
+# You always need to import ranger.api.commands here to get the Command class:
+from ranger.api.commands import Command
+
+
+# Any class that is a subclass of "Command" will be integrated into ranger as a
+# command. Try typing ":my_edit" in ranger!
+class my_edit(Command):
+ # The so-called doc-string of the class will be visible in the built-in
+ # help that is accessible by typing "?c" inside ranger.
+ """:my_edit
+
+ A sample command for demonstration purposes that opens a file in an editor.
+ """
+
+ # The execute method is called when you run this command in ranger.
+ def execute(self):
+ # self.arg(1) is the first (space-separated) argument to the function.
+ # This way you can write ":my_edit somefilename".
+ if self.arg(1):
+ # self.rest(1) contains self.arg(1) and everything that follows
+ target_filename = self.rest(1)
+ else:
+ # self.fm is a ranger.core.filemanager.FileManager object and gives
+ # you access to internals of ranger.
+ # self.fm.thisfile is a ranger.container.file.File object and is a
+ # reference to the currently selected file.
+ target_filename = self.fm.thisfile.path
+
+ # This is a generic function to print text in ranger.
+ self.fm.notify("Let's edit the file " + target_filename + "!")
+
+ # Using bad=True in fm.notify allows you to print error messages:
+ if not os.path.exists(target_filename):
+ self.fm.notify("The given file does not exist!", bad=True)
+ return
+
+ # This executes a function from ranger.core.acitons, a module with a
+ # variety of subroutines that can help you construct commands.
+ # Check out the source, or run "pydoc ranger.core.actions" for a list.
+ self.fm.edit_file(target_filename)
+
+ # The tab method is called when you press tab, and should return a list of
+ # suggestions that the user will tab through.
+ # tabnum is 1 for and -1 for by default
+ def tab(self, tabnum):
+ # This is a generic tab-completion function that iterates through the
+ # content of the current directory.
+ return self._tab_directory_content()
diff --git a/dotfiles/system/.config/ranger/commands_full.py b/dotfiles/system/.config/ranger/commands_full.py
new file mode 100644
index 0000000..d177203
--- /dev/null
+++ b/dotfiles/system/.config/ranger/commands_full.py
@@ -0,0 +1,1836 @@
+# -*- coding: utf-8 -*-
+# This file is part of ranger, the console file manager.
+# This configuration file is licensed under the same terms as ranger.
+# ===================================================================
+#
+# NOTE: If you copied this file to /etc/ranger/commands_full.py or
+# ~/.config/ranger/commands_full.py, then it will NOT be loaded by ranger,
+# and only serve as a reference.
+#
+# ===================================================================
+# This file contains ranger's commands.
+# It's all in python; lines beginning with # are comments.
+#
+# Note that additional commands are automatically generated from the methods
+# of the class ranger.core.actions.Actions.
+#
+# You can customize commands in the files /etc/ranger/commands.py (system-wide)
+# and ~/.config/ranger/commands.py (per user).
+# They have the same syntax as this file. In fact, you can just copy this
+# file to ~/.config/ranger/commands_full.py with
+# `ranger --copy-config=commands_full' and make your modifications, don't
+# forget to rename it to commands.py. You can also use
+# `ranger --copy-config=commands' to copy a short sample commands.py that
+# has everything you need to get started.
+# But make sure you update your configs when you update ranger.
+#
+# ===================================================================
+# Every class defined here which is a subclass of `Command' will be used as a
+# command in ranger. Several methods are defined to interface with ranger:
+# execute(): called when the command is executed.
+# cancel(): called when closing the console.
+# tab(tabnum): called when is pressed.
+# quick(): called after each keypress.
+#
+# tab() argument tabnum is 1 for and -1 for by default
+#
+# The return values for tab() can be either:
+# None: There is no tab completion
+# A string: Change the console to this string
+# A list/tuple/generator: cycle through every item in it
+#
+# The return value for quick() can be:
+# False: Nothing happens
+# True: Execute the command afterwards
+#
+# The return value for execute() and cancel() doesn't matter.
+#
+# ===================================================================
+# Commands have certain attributes and methods that facilitate parsing of
+# the arguments:
+#
+# self.line: The whole line that was written in the console.
+# self.args: A list of all (space-separated) arguments to the command.
+# self.quantifier: If this command was mapped to the key "X" and
+# the user pressed 6X, self.quantifier will be 6.
+# self.arg(n): The n-th argument, or an empty string if it doesn't exist.
+# self.rest(n): The n-th argument plus everything that followed. For example,
+# if the command was "search foo bar a b c", rest(2) will be "bar a b c"
+# self.start(n): Anything before the n-th argument. For example, if the
+# command was "search foo bar a b c", start(2) will be "search foo"
+#
+# ===================================================================
+# And this is a little reference for common ranger functions and objects:
+#
+# self.fm: A reference to the "fm" object which contains most information
+# about ranger.
+# self.fm.notify(string): Print the given string on the screen.
+# self.fm.notify(string, bad=True): Print the given string in RED.
+# self.fm.reload_cwd(): Reload the current working directory.
+# self.fm.thisdir: The current working directory. (A File object.)
+# self.fm.thisfile: The current file. (A File object too.)
+# self.fm.thistab.get_selection(): A list of all selected files.
+# self.fm.execute_console(string): Execute the string as a ranger command.
+# self.fm.open_console(string): Open the console with the given string
+# already typed in for you.
+# self.fm.move(direction): Moves the cursor in the given direction, which
+# can be something like down=3, up=5, right=1, left=1, to=6, ...
+#
+# File objects (for example self.fm.thisfile) have these useful attributes and
+# methods:
+#
+# tfile.path: The path to the file.
+# tfile.basename: The base name only.
+# tfile.load_content(): Force a loading of the directories content (which
+# obviously works with directories only)
+# tfile.is_directory: True/False depending on whether it's a directory.
+#
+# For advanced commands it is unavoidable to dive a bit into the source code
+# of ranger.
+# ===================================================================
+
+from __future__ import (absolute_import, division, print_function)
+
+from collections import deque
+import os
+import re
+
+from ranger.api.commands import Command
+
+
+class alias(Command):
+ """:alias
+
+ Copies the oldcommand as newcommand.
+ """
+
+ context = 'browser'
+ resolve_macros = False
+
+ def execute(self):
+ if not self.arg(1) or not self.arg(2):
+ self.fm.notify('Syntax: alias ', bad=True)
+ return
+
+ self.fm.commands.alias(self.arg(1), self.rest(2))
+
+
+class echo(Command):
+ """:echo
+
+ Display the text in the statusbar.
+ """
+
+ def execute(self):
+ self.fm.notify(self.rest(1))
+
+
+class cd(Command):
+ """:cd [-r]
+
+ The cd command changes the directory.
+ If the path is a file, selects that file.
+ The command 'cd -' is equivalent to typing ``.
+ Using the option "-r" will get you to the real path.
+ """
+
+ def execute(self):
+ if self.arg(1) == '-r':
+ self.shift()
+ destination = os.path.realpath(self.rest(1))
+ if os.path.isfile(destination):
+ self.fm.select_file(destination)
+ return
+ else:
+ destination = self.rest(1)
+
+ if not destination:
+ destination = '~'
+
+ if destination == '-':
+ self.fm.enter_bookmark('`')
+ else:
+ self.fm.cd(destination)
+
+ def _tab_args(self):
+ # dest must be rest because path could contain spaces
+ if self.arg(1) == '-r':
+ start = self.start(2)
+ dest = self.rest(2)
+ else:
+ start = self.start(1)
+ dest = self.rest(1)
+
+ if dest:
+ head, tail = os.path.split(os.path.expanduser(dest))
+ if head:
+ dest_exp = os.path.join(os.path.normpath(head), tail)
+ else:
+ dest_exp = tail
+ else:
+ dest_exp = ''
+ return (start, dest_exp, os.path.join(self.fm.thisdir.path, dest_exp),
+ dest.endswith(os.path.sep))
+
+ @staticmethod
+ def _tab_paths(dest, dest_abs, ends_with_sep):
+ if not dest:
+ try:
+ return next(os.walk(dest_abs))[1], dest_abs
+ except (OSError, StopIteration):
+ return [], ''
+
+ if ends_with_sep:
+ try:
+ return [os.path.join(dest, path) for path in next(os.walk(dest_abs))[1]], ''
+ except (OSError, StopIteration):
+ return [], ''
+
+ return None, None
+
+ def _tab_match(self, path_user, path_file):
+ if self.fm.settings.cd_tab_case == 'insensitive':
+ path_user = path_user.lower()
+ path_file = path_file.lower()
+ elif self.fm.settings.cd_tab_case == 'smart' and path_user.islower():
+ path_file = path_file.lower()
+ return path_file.startswith(path_user)
+
+ def _tab_normal(self, dest, dest_abs):
+ dest_dir = os.path.dirname(dest)
+ dest_base = os.path.basename(dest)
+
+ try:
+ dirnames = next(os.walk(os.path.dirname(dest_abs)))[1]
+ except (OSError, StopIteration):
+ return [], ''
+
+ return [os.path.join(dest_dir, d) for d in dirnames if self._tab_match(dest_base, d)], ''
+
+ def _tab_fuzzy_match(self, basepath, tokens):
+ """ Find directories matching tokens recursively """
+ if not tokens:
+ tokens = ['']
+ paths = [basepath]
+ while True:
+ token = tokens.pop()
+ matches = []
+ for path in paths:
+ try:
+ directories = next(os.walk(path))[1]
+ except (OSError, StopIteration):
+ continue
+ matches += [os.path.join(path, d) for d in directories
+ if self._tab_match(token, d)]
+ if not tokens or not matches:
+ return matches
+ paths = matches
+
+ return None
+
+ def _tab_fuzzy(self, dest, dest_abs):
+ tokens = []
+ basepath = dest_abs
+ while True:
+ basepath_old = basepath
+ basepath, token = os.path.split(basepath)
+ if basepath == basepath_old:
+ break
+ if os.path.isdir(basepath_old) and not token.startswith('.'):
+ basepath = basepath_old
+ break
+ tokens.append(token)
+
+ paths = self._tab_fuzzy_match(basepath, tokens)
+ if not os.path.isabs(dest):
+ paths_rel = basepath
+ paths = [os.path.relpath(path, paths_rel) for path in paths]
+ else:
+ paths_rel = ''
+ return paths, paths_rel
+
+ def tab(self, tabnum):
+ from os.path import sep
+
+ start, dest, dest_abs, ends_with_sep = self._tab_args()
+
+ paths, paths_rel = self._tab_paths(dest, dest_abs, ends_with_sep)
+ if paths is None:
+ if self.fm.settings.cd_tab_fuzzy:
+ paths, paths_rel = self._tab_fuzzy(dest, dest_abs)
+ else:
+ paths, paths_rel = self._tab_normal(dest, dest_abs)
+
+ paths.sort()
+
+ if self.fm.settings.cd_bookmarks:
+ paths[0:0] = [
+ os.path.relpath(v.path, paths_rel) if paths_rel else v.path
+ for v in self.fm.bookmarks.dct.values() for path in paths
+ if v.path.startswith(os.path.join(paths_rel, path) + sep)
+ ]
+
+ if not paths:
+ return None
+ if len(paths) == 1:
+ return start + paths[0] + sep
+ return [start + dirname for dirname in paths]
+
+
+class chain(Command):
+ """:chain ; ; ...
+
+ Calls multiple commands at once, separated by semicolons.
+ """
+
+ def execute(self):
+ if not self.rest(1).strip():
+ self.fm.notify('Syntax: chain ; ; ...', bad=True)
+ return
+ for command in [s.strip() for s in self.rest(1).split(";")]:
+ self.fm.execute_console(command)
+
+
+class shell(Command):
+ escape_macros_for_shell = True
+
+ def execute(self):
+ if self.arg(1) and self.arg(1)[0] == '-':
+ flags = self.arg(1)[1:]
+ command = self.rest(2)
+ else:
+ flags = ''
+ command = self.rest(1)
+
+ if command:
+ self.fm.execute_command(command, flags=flags)
+
+ def tab(self, tabnum):
+ from ranger.ext.get_executables import get_executables
+ if self.arg(1) and self.arg(1)[0] == '-':
+ command = self.rest(2)
+ else:
+ command = self.rest(1)
+ start = self.line[0:len(self.line) - len(command)]
+
+ try:
+ position_of_last_space = command.rindex(" ")
+ except ValueError:
+ return (start + program + ' ' for program
+ in get_executables() if program.startswith(command))
+ if position_of_last_space == len(command) - 1:
+ selection = self.fm.thistab.get_selection()
+ if len(selection) == 1:
+ return self.line + selection[0].shell_escaped_basename + ' '
+ return self.line + '%s '
+
+ before_word, start_of_word = self.line.rsplit(' ', 1)
+ return (before_word + ' ' + file.shell_escaped_basename
+ for file in self.fm.thisdir.files or []
+ if file.shell_escaped_basename.startswith(start_of_word))
+
+
+class open_with(Command):
+
+ def execute(self):
+ app, flags, mode = self._get_app_flags_mode(self.rest(1))
+ self.fm.execute_file(
+ files=[f for f in self.fm.thistab.get_selection()],
+ app=app,
+ flags=flags,
+ mode=mode)
+
+ def tab(self, tabnum):
+ return self._tab_through_executables()
+
+ def _get_app_flags_mode(self, string): # pylint: disable=too-many-branches,too-many-statements
+ """Extracts the application, flags and mode from a string.
+
+ examples:
+ "mplayer f 1" => ("mplayer", "f", 1)
+ "atool 4" => ("atool", "", 4)
+ "p" => ("", "p", 0)
+ "" => None
+ """
+
+ app = ''
+ flags = ''
+ mode = 0
+ split = string.split()
+
+ if len(split) == 1:
+ part = split[0]
+ if self._is_app(part):
+ app = part
+ elif self._is_flags(part):
+ flags = part
+ elif self._is_mode(part):
+ mode = part
+
+ elif len(split) == 2:
+ part0 = split[0]
+ part1 = split[1]
+
+ if self._is_app(part0):
+ app = part0
+ if self._is_flags(part1):
+ flags = part1
+ elif self._is_mode(part1):
+ mode = part1
+ elif self._is_flags(part0):
+ flags = part0
+ if self._is_mode(part1):
+ mode = part1
+ elif self._is_mode(part0):
+ mode = part0
+ if self._is_flags(part1):
+ flags = part1
+
+ elif len(split) >= 3:
+ part0 = split[0]
+ part1 = split[1]
+ part2 = split[2]
+
+ if self._is_app(part0):
+ app = part0
+ if self._is_flags(part1):
+ flags = part1
+ if self._is_mode(part2):
+ mode = part2
+ elif self._is_mode(part1):
+ mode = part1
+ if self._is_flags(part2):
+ flags = part2
+ elif self._is_flags(part0):
+ flags = part0
+ if self._is_mode(part1):
+ mode = part1
+ elif self._is_mode(part0):
+ mode = part0
+ if self._is_flags(part1):
+ flags = part1
+
+ return app, flags, int(mode)
+
+ def _is_app(self, arg):
+ return not self._is_flags(arg) and not arg.isdigit()
+
+ @staticmethod
+ def _is_flags(arg):
+ from ranger.core.runner import ALLOWED_FLAGS
+ return all(x in ALLOWED_FLAGS for x in arg)
+
+ @staticmethod
+ def _is_mode(arg):
+ return all(x in '0123456789' for x in arg)
+
+
+class set_(Command):
+ """:set