Dotfiles

This is my setup script for Linux Mint 22:

sudo apt update
sudo apt install emacs git homebank keepassxc

sudo locale-gen 'de_DE.UTF-8'

git clone https://github.com/fwinkelbauer/chunkyard ~/Projects/chunkyard
git clone https://github.com/fwinkelbauer/fwinkelbauer.github.io.git ~/Projects/website

emacs -Q --batch -l org --eval '(org-babel-tangle-file "~/Projects/website/content/notes/dotfiles.org")'

Tools

Git

[user]
  email = mail@florianwinkelbauer.com
  name = Florian Winkelbauer
  signingKey = ~/.ssh/git-commit.ed25519.pub

[gpg]
  format = ssh

[init]
  defaultBranch = main

[status]
  showUntrackedFiles = all

[commit]
  gpgSign = true

[tag]
  gpgSign = true

[fetch]
  prune = true

[push]
  followTags = true

[merge]
  ff = only
  conflictStyle = zdiff3

[log]
  date = iso

[url "git@github.com:fwinkelbauer/"]
  insteadOf = "https://github.com/fwinkelbauer/"

Chunkyard

A utility script to access my backup:

chunkyard "$@" --prompt 'Libsecret' --repository ~/.chunkyard

Create new backup:

paths=(
    ~/.local/share/fonts
    ~/.ssh
    ~/Documents
    ~/Music
    ~/Pictures
    ~/Videos
)

includes=(
    '!\~$'
    '!\.uuid$'
    '!authorized_keys$'
)

chunkyard store --prompt 'Libsecret' --repository ~/.chunkyard --path "${paths[@]}" --include "${includes[@]}" "$@"

Restore the latest backup:

chunkyard restore --prompt 'Libsecret' --repository ~/.chunkyard --directory ~/ "$@"

chmod 700 ~/.ssh
chmod 600 ~/.ssh/*

Copy local backup data from/to external devices:

local=~/.chunkyard

remotes=(
    /media/florian/Backup1/Florian/main.chunkyard
    /media/florian/Backup2/Florian/main.chunkyard
    /media/florian/DORA1/main.chunkyard
)

for remote in "${remotes[@]}"; do
    if ! [ -d "$remote" ]; then
        continue
    fi

    if ! [ -x "$(command -v chunkyard)" ]; then
        cp "$remote/chunkyard" ~/.local/bin/
        chmod +x ~/.local/bin/chunkyard
    fi

    echo "Synchronizing with: $remote"
    chunkyard copy --repository "$local" --destination "$remote" --prompt 'Libsecret' --last 20 "$@"
    chunkyard copy --repository "$remote" --destination "$local" --prompt 'Libsecret' --last 20 "$@"
done

Homebank

A script to turn my bank statements into csv files that I can import into Homebank:

input=$(find ~/Downloads -name 'meinElba*.csv')
output=~/Downloads/homebank.csv

awk '{ FS=";"; gsub("\"", ""); print $1 ";8;;;" $2 ";" $4 ";;" }' "$input" > "$output"
rm "$input"
read -rp "Press Enter to delete $output: "
rm "$output"

Emacs

Package Manager

(require 'package)
(require 'use-package-ensure)

(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)

(package-initialize)

(setq package-selected-packages '(embark-consult embark consult vertico orderless company magit color-theme-sanityinc-tomorrow)
      use-package-always-ensure t)

General

(setq global-auto-revert-non-file-buffers t
      gc-cons-threshold (* 128 1024 1024)
      use-short-answers t
      backup-inhibited t
      auto-save-default nil
      create-lockfiles nil
      visible-bell t
      inhibit-startup-message t
      initial-scratch-message nil
      column-number-mode t
      sentence-end-double-space nil
      require-final-newline t)

(setq-default fill-column 80
              indent-tabs-mode nil)

(add-to-list 'initial-frame-alist '(fullscreen . maximized))
(pixel-scroll-precision-mode)
(menu-bar-mode -1)
(tool-bar-mode -1)
(set-face-attribute 'default nil :family "JetBrains Mono" :foundry "JB" :slant 'normal :weight 'medium :height 120 :width 'normal)
(global-auto-revert-mode t)
(delete-selection-mode t)
(add-hook 'before-save-hook #'delete-trailing-whitespace)
(show-paren-mode t)

(setq-default mode-line-format
              '("%e" mode-line-front-space
                (:propertize
                 ("" mode-line-mule-info mode-line-client mode-line-modified
                  mode-line-remote)
                 display (min-width (5.0)))
                mode-line-frame-identification mode-line-buffer-identification
                "  %l:%c  " mode-line-modes
                mode-line-misc-info mode-line-end-spaces))

(use-package dired
  :ensure nil
  :hook
  ((dired-mode . dired-hide-details-mode))
  :config
  (setq dired-auto-revert-buffer t
        dired-dwim-target t
        dired-listing-switches "-alhv --time-style=+%Y-%m-%d --group-directories-first"))

(defun fw/split-window-vertically ()
  "Split the selected window into two vertical windows."
  (interactive)
  (split-window-vertically)
  (other-window 1))

(defun fw/split-window-horizontally ()
  "Split the selected window into two horizontal windows."
  (interactive)
  (split-window-horizontally)
  (other-window 1))

Org

(use-package org
  :ensure nil
  :hook ((org-mode . (lambda () (electric-indent-local-mode -1))))
  :config
  (setq org-directory "~/Documents/org/"
        org-default-notes-file (concat org-directory "inbox.org")
        org-agenda-files (list org-directory)
        org-edit-src-content-indentation 0)

  (setq org-capture-bookmark nil
        org-capture-templates '(("i" "Inbox" entry (file org-default-notes-file)
                                 "* %?" :empty-lines-before 1)))

  (defun fw/org-capture-inbox ()
    "Opens the `org-capture' inbox template."
    (interactive)
    (org-capture nil "i"))

  (setq org-agenda-custom-commands
        '(("." "Overview"
           ((agenda ""
                    ((org-agenda-overriding-header "Kalender\n")
                     (org-agenda-prefix-format "%-11c%?-12t")
                     (org-agenda-time-grid '((daily today require-timed)
                                             (800 1000 1200 1400 1600 1800 2000)
                                             " ....."
                                             "----------------"))
                     (org-agenda-time-leading-zero t)
                     (org-agenda-show-future-repeats nil)
                     (org-agenda-current-time-string "<<<<<<<<<<<<<<<<")
                     (org-agenda-scheduled-leaders '("" ""))
                     (org-agenda-skip-deadline-prewarning-if-scheduled t)))
            (todo "*"
                  ((org-agenda-overriding-header "\nSonstiges\n")
                   (org-agenda-block-separator nil)
                   (org-agenda-prefix-format "%-11c%?-12t")
                   (org-agenda-sorting-strategy '(todo-state-up))
                   (org-agenda-todo-ignore-deadlines 'all)
                   (org-agenda-todo-ignore-scheduled 'all)))))))

  (defun fw/org-overview ()
    "Show my inbox and custom org-agenda."
    (interactive)
    (delete-other-windows)
    (find-file org-default-notes-file)
    (org-agenda nil "."))

  (defun fw/org-icalendar-export ()
    "Export current buffer to an iCalendar file in ~/Downloads/"
    (let ((icalendar-export-sexp-enumeration-days 128))
      (rename-file (org-icalendar-export-to-ics)
                   "~/Downloads/"
                   t))))

The calendar should use my native language and know about my holidays:

(set-locale-environment "de_DE.UTF-8")

(setq calendar-week-start-day 1
      calendar-day-header-array ["So" "Mo" "Di" "Mi" "Do" "Fr" "Sa"]
      calendar-day-name-array ["Sonntag" "Montag" "Dienstag" "Mittwoch"
                               "Donnerstag" "Freitag" "Samstag"]
      calendar-month-name-array ["Jänner" "Februar" "März" "April"
                                 "Mai" "Juni" "Juli" "August"
                                 "September" "Oktober" "November" "Dezember"])

(setq parse-time-weekdays '(("so" . 0) ("mo" . 1) ("di" . 2) ("mi" . 3)
                            ("do" . 4) ("fr" . 5) ("sa" . 6)))

(setq calendar-holidays '((holiday-fixed 1 1 "Neujahr (frei)")
                          (holiday-fixed 1 6 "Heilige Drei Könige (frei)")
                          (holiday-fixed 2 14 "Valentinstag")
                          (holiday-easter-etc 1 "Ostermontag (frei)")
                          (holiday-easter-etc -46 "Aschermittwoch")
                          (holiday-easter-etc -2 "Karfreitag")
                          (holiday-fixed 5 1 "Österreichischer Staatsfeiertag (frei)")
                          (holiday-easter-etc 39 "Christi Himmelfahrt (frei)")
                          (holiday-easter-etc 50 "Pfingstmontag (frei)")
                          (holiday-easter-etc 60 "Fronleichnam (frei)")
                          (holiday-float 5 0 2 "Muttertag")
                          (holiday-float 6 0 2 "Vatertag")
                          (holiday-fixed 8 15 "Mariä Himmelfahrt (frei)")
                          (holiday-fixed 10 26 "Nationalfeiertag (frei)")
                          (holiday-fixed 11 1 "Allerheiligen (frei)")
                          (holiday-fixed 12 8 "Maria Empfängnis (frei)")
                          (holiday-fixed 12 24 "Heiliger Abend")
                          (holiday-fixed 12 25 "Erster Weihnachtstag (frei)")
                          (holiday-fixed 12 26 "Zweiter Weihnachtstag (frei)")))

Theme

(use-package color-theme-sanityinc-tomorrow
  :config
  (load-theme 'sanityinc-tomorrow-night t)
  (set-face-attribute 'org-agenda-structure nil :height 1.25)
  (set-face-attribute 'org-agenda-date-today nil :slant 'normal :underline t))

Magit

(use-package magit
  :config
  (setq magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1
        magit-save-repository-buffers 'dontask
        magit-repository-directories '(("~/Projects" . 1))))

Completion

(use-package vertico
  :config
  (vertico-mode)
  (keymap-set vertico-map "DEL" #'vertico-directory-delete-char))

(use-package orderless
  :config
  (setq completion-styles '(orderless basic)
        completion-category-overrides '((file (styles partial-completion)))))

(use-package embark
  :config
  (setq prefix-help-command #'embark-prefix-help-command)
  (keymap-global-set "<remap> <describe-bindings>" #'embark-bindings))

(use-package consult
  :config
  (defun fw/find-file ()
    "Find files in current project or directory."
    (interactive)
    (if (project-current)
        (project-find-file)
      (consult-find)))

  (defun fw/grep ()
    "Run grep in current project or directory."
    (interactive)
    (if (project-current)
        (consult-git-grep)
      (consult-grep))))

(use-package embark-consult)

(use-package company
  :config
  (setq company-dabbrev-downcase nil
        company-dabbrev-ignore-case nil)
  (global-company-mode t))

Keybindings

(bind-keys :prefix "<menu>"
           :prefix-map fw/main-map
           ("RET" . embark-act)
           ("f" . find-file)
           ("s" . consult-line)
           ("q" . query-replace)
           ("l" . consult-goto-line)
           ("k" . kill-this-buffer)
           ("b" . consult-buffer)
           ("h" . mark-whole-buffer)
           ("0" . delete-window)
           ("1" . delete-other-windows)
           ("2" . fw/split-window-vertically)
           ("3" . fw/split-window-horizontally)
           ("o" . other-window)
           ("." . highlight-symbol-at-point)
           ("r" . highlight-regexp)
           ("u" . unhighlight-regexp)
           ("SPC" . rectangle-mark-mode)
           ("t" . string-rectangle)
           ("d" . delete-rectangle)
           ("?" . count-words-region))

(bind-keys :prefix "<menu> g"
           :prefix-map fw/project-map
           ("f" . fw/find-file)
           ("s" . fw/grep)
           ("d" . magit-file-dispatch)
           ("g" . magit-status))

(bind-keys :prefix "<menu> c"
           :prefix-map fw/org-map
           ("c" . fw/org-overview)
           ("i" . fw/org-capture-inbox)
           ("l" . org-insert-link)
           ("t" . org-todo)
           ("s" . org-schedule)
           ("d" . org-deadline)
           ("." . org-time-stamp)
           (":" . org-time-stamp-inactive)
           ("m" . org-insert-structure-template)
           ("b" . org-babel-tangle))

(bind-keys :prefix "<menu> x"
           :prefix-map fw/x-map
           ("s" . save-buffer)
           ("c" . save-buffers-kill-terminal))

(bind-key* "C-z" 'undo)