Compare commits

...

3 commits

Author SHA1 Message Date
4f39c80e25
feat: more config 2025-12-02 23:00:51 -06:00
a88fde31cb
make config of davmail, neomutt, mbsync outside of nix 2025-12-02 16:44:38 -06:00
9e8182da6a
add email back 2025-12-02 15:41:10 -06:00
45 changed files with 1462 additions and 797 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*.sh]
indent_style = space
indent_size = 4
[bin/*]
indent_style = space
indent_size = 4

View file

@ -1,9 +1,17 @@
keys:
- &pickwick age13cfe8fhp4m978qlcur46vkkxepsl93ggwe53kmhue9xtpgr5zu5q4y6ln2
- &lemur age1pdk6dmyxqhdaja5d0nf8f9qjd43hmfahmkure5yrf8al9jyfmd8qfdxwl6
- &dango age15vscvpe79l287h8f3hssrj2r45xy0l3ns94zfue2fxlq43cqdsxq58vq3c
creation_rules:
- path_regex: secrets/wb\.txt$
key_groups:
- age:
- *pickwick
- *lemur
- *dango
- path_regex: home/email/secrets.yaml$
key_groups:
- age:
- *pickwick
- *lemur
- *dango

3
bin/b
View file

@ -10,8 +10,7 @@ title=${2:-}
global="$HOME/Personal/bookmarks.txt"
if [ -n "$url" ]
then
if [ -n "$url" ]; then
echo "$url $title" >>$global
else
local="$PWD/bookmarks.txt"

2
bin/cb
View file

@ -26,12 +26,10 @@ CYGWIN_paste() {
cat /dev/clipboard
}
MAC_copy() {
cat | pbcopy
}
MAC_paste() {
pbpaste
}

View file

@ -6,7 +6,5 @@ if [ $# == 1 ]; then
sleepsec=$1
fi
sleep $sleepsec
pbcopy </dev/null

31
bin/git-apply-patch Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Interactive git patch applier - finds git repos and applies patch from stdin
# Save stdin (the patch) to a temp file
patch_file=$(mktemp /tmp/patch.XXXXXX)
cat >"$patch_file"
# Find git repos and select with fzf
repo=$(find ~/projects ~/code ~/dotfiles ~ -maxdepth 3 -type d -name ".git" 2>/dev/null |
sed 's/\/.git$//' |
sort -u |
fzf --prompt="Select repo to apply patch: " --height=40% --reverse)
if [ -z "$repo" ]; then
echo "No repo selected, patch saved to: $patch_file"
exit 1
fi
cd "$repo" || exit 1
echo "Applying patch to: $repo"
git apply "$patch_file"
status=$?
if [ $status -eq 0 ]; then
echo "Patch applied successfully!"
rm "$patch_file"
else
echo "Failed to apply patch. Patch saved to: $patch_file"
fi
exit $status

336
bin/wb Executable file
View file

@ -0,0 +1,336 @@
#!/usr/bin/env bash
thisFile=$(realpath "$0")
DOTFILES="${DOTFILES:-$HOME/dotfiles}"
BOOKMARKS_FILE="${DOTFILES}/secrets/wb.txt"
# Decrypt bookmarks from sops
getBookmarks() {
if [[ -f "${BOOKMARKS_FILE}" ]]; then
# Unencrypted file (for development/testing)
cat "${BOOKMARKS_FILE}"
elif [[ -f "${BOOKMARKS_FILE%.txt}.enc" ]]; then
# Encrypted file
sops --decrypt --input-type binary "${BOOKMARKS_FILE%.txt}.enc" 2>/dev/null
else
echo "Error: No bookmarks file found" >&2
exit 1
fi
}
# Defaults
isCopy=0
editMode=0
q=""
noFuzzy=0
queryMode=0
if ! type fzf >/dev/null 2>&1; then
noFuzzy=1
fi
cleanup() {
rm -f /tmp/.wb
clear-pbcopy 60 &
}
trap cleanup EXIT
bks() {
getBookmarks | awk '!/^[[:space:]]*#/ && !/^[[:space:]]*$/ {print $1}'
}
execute() {
# --------------------------------------------------------------------
# If key == $q, then it's a match, then we do the following:
# a. Copy the "url" to the clipboard (with "pbcopy" in Mac or
# "clip" in Linux). How do we know it's a Mac, we just simply
# check if there's a ~/Music folder in your homedir. If
# there is, it's likely a Mac, else it's Linux.
# b. I have a "clear-pbcopy" script that basically clears the
# clipboard after 20 seconds (for security reason) because
# you don't want to accidentally copy-paste your personal
# links in your email or your chat applications. It is as simple as
# a oneline command:
# "sleep 20; echo "nothing-here" | pbcopy"
# c. If the first argument is "-c" then we mean we just want to
# copy the string to the clipboard, so we just continue
# d. Then we see if the "url" contains "http" or "www", if so
# we open it with ~/bin/browser. You must have your own
# "browser" script that opens your favorite web browser, e.g.
# in Mac, my "~/bin/browser" contains:
# open -a /Applications/Firefox.app $*
# However, if the "url" does not contain http/www,
# e.g. the key and url line is like this:
# mytodofile ~/Documents/mytodofile.txt
# then I open it "open" command line provided by Mac
# d. We are done (exiting)
# --------------------------------------------------------------------
echo ""
# ---------------------- a
echo " copying $url"
echo "$url" | cb
# ---------------------- b
if type clear-pbcopy >/dev/null 2>&1; then
clear-pbcopy 60 &
fi
# ---------------------- c
if [ $isCopy == 1 ]; then
echo " Copied to clipboard"
echo ""
exit
fi
# ---------------------- d
echo " open $url"
x-open $url
# ---------------------- d
echo ""
exit
}
fuzzy() {
# Use awk to parse bookmarks in a single pass (much faster than bash loop)
formatted=$(getBookmarks | awk '
# Skip decorative headers and section dividers
/^#[-=~# ]*$/ || /^# *--/ || /^##+/ { next }
# Accumulate description and extract tags from comments
/^#[^#]/ {
sub(/^# */, "")
desc = desc (desc ? " " : "") $0
# Extract @tags
n = split($0, words, " ")
for (i = 1; i <= n; i++) {
if (words[i] ~ /^@/) {
tags = tags (tags ? " " : "") words[i]
}
}
next
}
# Handle bookmark lines (non-comment, non-empty)
/^[^#[:space:]]/ {
key = $1
# Skip @-prefixed keys
if (key ~ /^@/) { desc = ""; tags = ""; next }
# Get URL (everything after first field)
url = $0
sub(/^[^ \t]+[ \t]+/, "", url)
# Clean description
gsub(/\n/, " ", desc)
if (desc == "") desc = "-"
if (tags == "") tags = "-"
print key "\t" url "\t" desc "\t" tags
desc = ""
tags = ""
}
')
selected_line=$(echo "$formatted" |
fzf -i --with-nth=1 \
--delimiter=$'\t' \
--query="$q" \
--prompt="🔍 Select bookmark: " \
--preview='bash -c "url=\$(echo {} | cut -f2); desc=\$(echo {} | cut -f3); tags=\$(echo {} | cut -f4); echo -e \"\$url\n\nDesc: \$desc\n\nTags: \$tags\""' \
--preview-window=down:12:wrap)
if [[ -n "$selected_line" ]]; then
IFS=$'\t' read -r key url _ <<<"$selected_line"
execute "$url"
else
echo " No selection made"
echo ""
fi
}
help() {
echo ""
echo "Usage: wb [-c] [-e] <key> [key2] [key3] ..."
echo ""
echo "Options:"
echo " -c Copy to clipboard without opening"
echo " -e Edit the bookmarks file"
echo " -nf No Fuzzy Match"
echo " -h Show help"
echo ""
echo "Examples:"
echo " wb cnn # Open the cnn bookmark"
echo " wb -c gmail # Copy Gmail URL without opening"
echo " wb 230 zoom # Open bookmark matching '230.*zoom'"
echo " wb ai sci zoom # Open bookmark matching 'ai.*sci.*zoom'"
echo ""
}
# --------------------------------------------------------------------
# [ 70-char wide ]
# 34567890123456789012345678901234567890123456789012345678901234567890
# 1 2 3 4 5 6 7
# --------------------------------------------------------------------
# --------------------------------------------------------------------
# 0. Setup $thisFile and
# if "wb" is called with no arguments, then
# just open this "wb" file with "e" --> emacs
# You can change "e" to "vi" or other text editor
# --------------------------------------------------------------------
# Parse arguments
args=()
while [ $# -gt 0 ]; do
case "$1" in
-c)
isCopy=1
;;
-e)
editMode=1
;;
-h | --help)
help
exit 0
;;
-nf)
noFuzzy=1
;;
-q)
queryMode=1
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
args+=("$1")
;;
esac
shift
done
# Build pattern from multiple arguments (e.g., "wb 230 si te" -> "230.*si.*te")
if [ ${#args[@]} -gt 0 ]; then
q="${args[0]}"
for ((i = 1; i < ${#args[@]}; i++)); do
q="$q.*${args[$i]}"
done
fi
if [[ $editMode == 1 ]]; then
encFile="${BOOKMARKS_FILE%.txt}.enc"
if [[ -f "$encFile" ]]; then
echo "Editing encrypted bookmarks file"
# Decrypt to the .txt file (matches .sops.yaml path_regex), edit, re-encrypt
trap "rm -f '${BOOKMARKS_FILE}'" EXIT
sops --decrypt --input-type binary --output-type binary "$encFile" >"${BOOKMARKS_FILE}"
${EDITOR:-vim} "${BOOKMARKS_FILE}"
sops --encrypt --input-type binary --output-type binary "${BOOKMARKS_FILE}" >"$encFile"
rm -f "${BOOKMARKS_FILE}"
echo "Saved and re-encrypted"
elif [[ -f "${BOOKMARKS_FILE}" ]]; then
echo "Editing ${BOOKMARKS_FILE}"
${EDITOR:-vim} "${BOOKMARKS_FILE}"
else
echo "Error: No bookmarks file found"
exit 1
fi
exit
fi
if [[ $queryMode == 1 ]]; then
if [[ -z "$q" ]]; then
bs=$(bks)
else
bs=$(bks | grep -i "$q")
fi
num=$(echo "$bs" | wc -l | tr -d ' ')
bs=$(echo "$bs" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
echo "$num bookmarks: ($bs)"
exit
fi
if [[ $q == "" ]]; then
if [[ $noFuzzy == 1 ]]; then
help
else
fuzzy
fi
exit
fi
## --------------------------------------------------------------------
## 1. Find all bookmarks matching the pattern using regex
## Store matches in /tmp/.wb (using awk for speed)
## --------------------------------------------------------------------
tempFile=/tmp/.wb
getBookmarks | awk -v pattern="$q" '
!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && $1 ~ pattern {print}
' >"$tempFile"
hitCount=$(wc -l <"$tempFile" | tr -d ' ')
## --------------------------------------------------------------------
## 2. Handle results based on hit count
## --------------------------------------------------------------------
# No hits found
if [ $hitCount -eq 0 ]; then
echo ""
if [[ $noFuzzy == 1 ]]; then
echo " '$q' not found"
echo " Available bookmarks matching pattern:"
bks | grep -iE "$q" | cat -n
else
fuzzy
fi
echo ""
exit
fi
# Multiple hits - check for exact match first
if [ $hitCount -gt 1 ]; then
exactHit=0
while read -r key url misc; do
if [[ "$key" == "$q" ]]; then
exactHit=1
echo "$key $url" >"$tempFile"
break
fi
done <"$tempFile"
if [ $exactHit -eq 1 ]; then
hitCount=1
fi
fi
# Still multiple hits - show options or use fuzzy
if [ $hitCount -gt 1 ]; then
if [[ $noFuzzy == 1 ]]; then
echo ""
echo " Multiple matches for '$q':"
sort "$tempFile" | while read -r key url misc; do
echo " $key"
done
echo ""
else
fuzzy
fi
exit
fi
# Single hit - execute
if [ $hitCount -eq 1 ]; then
read -r key url _ <"$tempFile"
execute "$url"
fi
## --------------------------------------------------------------------
## 5. Bookmarks are now stored in secrets/wb.txt (or secrets/wb.enc when encrypted)
## To encrypt: sops --encrypt --input-type binary --output-type binary secrets/wb.txt > secrets/wb.enc
## Then remove secrets/wb.txt
## --------------------------------------------------------------------

View file

@ -13,7 +13,7 @@ exec-on-workspace-change = ['/bin/bash', '-c',
'/run/current-system/sw/bin/sketchybar --trigger aerospace_workspace_changed FOCUSED_WORKSPACE=$AEROSPACE_FOCUSED_WORKSPACE PREV_WORKSPACE=$AEROSPACE_PREV_WORKSPACE && /etc/profiles/per-user/rayandrew/bin/aerospace-scratchpad hook pull-window $AEROSPACE_PREV_WORKSPACE $AEROSPACE_FOCUSED_WORKSPACE'
]
on-focus-changed = [
'move-mouse window-lazy-center',
# 'move-mouse window-lazy-center',
'exec-and-forget /bin/bash -c /run/current-system/sw/bin/sketchybar --trigger front_app_switched',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger update_windows'
]

View file

@ -0,0 +1,33 @@
# DavMail configuration
# See: http://davmail.sourceforge.net/serversetup.html
davmail.server=true
davmail.disableUpdateCheck=true
davmail.mode=O365Manual
davmail.url=https://outlook.office365.com/EWS/Exchange.asmx
davmail.keepDelay=30
# Ports
davmail.caldavPort=1080
davmail.imapPort=1143
davmail.ldapPort=1389
davmail.popPort=1110
davmail.smtpPort=1025
# Logging
davmail.logFilePath=/tmp/davmail.log
davmail.logFileSize=1MB
# OAuth token storage
davmail.oauth.tokenFilePath=/Users/rayandrew/.local/state/davmail-tokens
# Log levels
log4j.logger.davmail=WARN
log4j.logger.httpclient.wire=WARN
log4j.logger.org.apache.commons.httpclient=WARN
log4j.rootLogger=WARN
# log4j.logger.davmail=DEBUG
# log4j.logger.httpclient.wire=DEBUG
# log4j.logger.org.apache.commons.httpclient=DEBUG
# log4j.rootLogger=DEBUG

View file

@ -0,0 +1,2 @@
[global]
log_format = "\u001B[2mdirenv: %s\u001B[0m"

View file

@ -1,5 +1,11 @@
set fish_greeting
# Bootstrap Fisher (reads plugins from fish_plugins)
if not functions -q fisher
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source
fisher update
end
function sesh-sessions
set -l session (sesh list -t -c | fzf --height 40% --reverse --border-label ' sesh ' --border --prompt '⚡ ')
commandline -f repaint

2
config/fish/fish_plugins Normal file
View file

@ -0,0 +1,2 @@
jorgebucaran/fisher
IlanCosman/tide@v6

View file

@ -6,6 +6,8 @@ quit-after-last-window-closed = true
gtk-adwaita = false
# font-family = ${system-font}
font-family = Consolas
font-family = Symbols Nerd Font Mono
font-family = DejaVuSansM Nerd Font Mono
font-size = 14
app-notifications = no-clipboard-copy

47
config/home/.mailcap Normal file
View file

@ -0,0 +1,47 @@
# Mailcap - MIME type handlers for neomutt and other mail clients
# HTML - terminal inline view (for auto_view)
text/html; w3m -dump -T text/html %s; copiousoutput
# HTML - open in browser
text/html; open %s; nametemplate=%s.html
# Plain text
text/plain; TERM=xterm-256color less %s
text/*; TERM=xterm-256color less %s
# PDF
application/pdf; zathura %s
application/pdf; open %s
# Images - terminal (kitty protocol works in ghostty)
image/*; kitten icat --clear %s
image/*; open %s
# Video
video/*; iina %s
video/*; open %s
# Audio
audio/*; mpv --no-video %s
audio/*; open %s
# Microsoft Office - Word
application/msword; open %s
application/vnd.openxmlformats-officedocument.wordprocessingml.document; open %s
# Microsoft Office - Excel
application/vnd.ms-excel; open %s
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; open %s
# Microsoft Office - PowerPoint
application/vnd.ms-powerpoint; open %s
application/vnd.openxmlformats-officedocument.presentationml.presentation; open %s
# Archives - show contents
application/zip; unzip -l %s; copiousoutput
application/x-tar; tar -tvf %s; copiousoutput
application/gzip; tar -tzvf %s; copiousoutput
application/x-bzip2; tar -tjvf %s; copiousoutput
# Fallback - open with default macOS app
application/*; open %s

55
config/mbsync/mbsyncrc Normal file
View file

@ -0,0 +1,55 @@
# mbsync configuration
# Run: mbsync -a
# Personal Gmail account
IMAPAccount personal
Host imap.gmail.com
User raydreww@gmail.com
PassCmd "sops -d --extract '[\"personal\"]' ~/dotfiles/home/email/secrets.yaml"
TLSType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt
IMAPStore personal-remote
Account personal
MaildirStore personal-local
Path ~/mail/personal/
Inbox ~/mail/personal/Inbox
SubFolders Verbatim
Channel personal
Far :personal-remote:
Near :personal-local:
Create Both
Expunge Both
Patterns * !"[Airmail]/Done" !"[Airmail]/Snooze" !"[Airmail]/To Do" !"[Airmail]/Send Later" !"[Gmail]/All Mail" !"[Gmail]/Important" !"[Gmail]/Starred" !"[Gmail]/Bin"
Remove None
SyncState *
# UChicago account (via DavMail)
IMAPAccount uchicago
Host 127.0.0.1
Port 1143
User rayandrew@uchicago.edu
PassCmd "sops -d --extract '[\"uchicago\"]' ~/dotfiles/home/email/secrets.yaml"
TLSType None
AuthMechs LOGIN
Timeout 0
IMAPStore uchicago-remote
Account uchicago
MaildirStore uchicago-local
Path ~/mail/uchicago/
Inbox ~/mail/uchicago/Inbox
SubFolders Verbatim
Channel uchicago
Far :uchicago-remote:
Near :uchicago-local:
Create Both
Expunge Both
Patterns * !"[Airmail]/Done" !"[Airmail]/Snooze" !"[Airmail]/To Do" !"[Airmail]/Send Later"
Remove None
SyncState *

30
config/msmtp/config Normal file
View file

@ -0,0 +1,30 @@
# msmtp configuration
# Default settings for all accounts
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.local/state/msmtp.log
# Personal Gmail account
account personal
host smtp.gmail.com
from raydreww@gmail.com
user raydreww@gmail.com
passwordeval "sops -d --extract '[\"personal\"]' ~/dotfiles/home/email/secrets.yaml"
tls_starttls off
# UChicago account (via DavMail)
account uchicago
host 127.0.0.1
port 1025
from rayandrew@uchicago.edu
user rayandrew@uchicago.edu
passwordeval "sops -d --extract '[\"uchicago\"]' ~/dotfiles/home/email/secrets.yaml"
auth plain
tls off
tls_starttls off
# Default account
account default : uchicago

View file

@ -0,0 +1,38 @@
# Personal Gmail account configuration
unmailboxes *
set ssl_force_tls = yes
set certificate_file = /etc/ssl/certs/ca-certificates.crt
# GPG
set crypt_autosign = yes
set crypt_opportunistic_encrypt = no
set pgp_use_gpg_agent = yes
set mbox_type = Maildir
set sort = "threads"
# Account settings
set folder = '~/mail/personal'
set from = 'raydreww@gmail.com'
set realname = 'Ray Andrew'
set spoolfile = '+Inbox'
set postponed = '+Drafts'
set record = '+Sent'
set trash = '+Trash'
# PGP settings
set use_from = yes
set pgp_verify_sig = yes
set pgp_sign_as = 0x07AA5254804C009F
set pgp_timeout = 3600
# Mailboxes
named-mailboxes "p/inbox" =Inbox
named-mailboxes "p/drafts" =Drafts
named-mailboxes "p/sent" =Sent
named-mailboxes "p/important" =Important
named-mailboxes "p/trash" =Trash
named-mailboxes "p/archive" =Archive
# Signature
set signature = "~/.config/neomutt/signatures/personal"

View file

@ -0,0 +1,39 @@
# UChicago account configuration (via DavMail)
unmailboxes *
set ssl_force_tls = no
set certificate_file = /etc/ssl/certs/ca-certificates.crt
# GPG
set crypt_autosign = no
set crypt_opportunistic_encrypt = no
set pgp_use_gpg_agent = yes
set mbox_type = Maildir
set sort = "threads"
# Account settings
set folder = '~/mail/uchicago'
set from = 'rayandrew@uchicago.edu'
set realname = 'Ray Andrew'
set spoolfile = '+Inbox'
set postponed = '+Drafts'
set record = '+Sent'
set trash = '+Trash'
# PGP settings
set use_from = yes
set pgp_sign_as = 0xEEF04CFFE9DFE5FC
set pgp_verify_sig = yes
set pgp_timeout = 3600
# Mailboxes
named-mailboxes "u/inbox" =Inbox
named-mailboxes "u/drafts" =Drafts
named-mailboxes "u/sent" =Sent
named-mailboxes "u/important" =Important
named-mailboxes "u/trash" =Trash
named-mailboxes "u/archive" =Archive
named-mailboxes "u/teaching" =Teaching
# Signature
set signature = "~/.config/neomutt/signatures/uchicago"

93
config/neomutt/colors Normal file
View file

@ -0,0 +1,93 @@
# NeoMutt color scheme - Noctis Azureus
# Based on noctis_azureus_ghostty.lua palette
# Requires: set color_directcolor = yes
# Mono settings
mono bold bold
mono underline underline
mono indicator reverse
mono error bold
# General colors - using exact hex from palette
color normal '#becfda' default
color error '#e66533' default
color tilde '#051b29' default
color message '#49d6e9' default
color markers '#e66533' '#ffffff'
color attachment '#becfda' default
color search '#051b29' '#49e9a6'
color status '#e4b781' '#041520'
color indicator '#051b29' '#49d6e9'
color tree '#49d6e9' default
# Sidebar
color sidebar_indicator '#051b29' '#49d6e9'
color sidebar_highlight '#e4b781' '#0c3f5f'
color sidebar_divider '#475e6c' default
color sidebar_flagged '#e66533' default
color sidebar_new '#49e9a6' default
color sidebar_ordinary '#becfda' default
# Header colors
color header '#49ace9' default ".*"
color header '#df769b' default "^(From)"
color header '#49d6e9' default "^(Subject)"
color header '#becfda' default "^(CC|BCC)"
color hdrdefault '#49e9a6' default
# Quoted text (conversation depth)
color quoted '#49e9a6' default
color quoted1 '#49ace9' default
color quoted2 '#49d6e9' default
color quoted3 '#e4b781' default
color quoted4 '#e66533' default
color quoted5 '#e97749' default
color signature '#49e9a6' default
# Body patterns
color body '#e97749' default "[\-\.+_a-zA-Z0-9]+@[\-\.a-zA-Z0-9]+"
color body '#49ace9' default "(https?|ftp)://[\-\.,/%~_:?&=\#a-zA-Z0-9]+"
color body '#49e9a6' default "\`[^\`]*\`"
color body '#49ace9' default "^# \.*"
color body '#49d6e9' default "^## \.*"
color body '#49e9a6' default "^### \.*"
color body '#e4b781' default "^(\t| )*(-|\\*) \.*"
color body '#49d6e9' default "[;:][-o][)/(|]"
color body '#49d6e9' default "[;:][)(|]"
color body '#49d6e9' default "[ ][*][^*]*[*][ ]?"
color body '#49d6e9' default "[ ]?[*][^*]*[*][ ]"
color body '#e66533' default "(BAD signature)"
color body '#49d6e9' default "(Good signature)"
color body '#475e6c' default "^gpg: Good signature .*"
color body '#e4b781' default "^gpg: "
color body '#e4b781' '#e66533' "^gpg: BAD signature from.*"
mono body bold "^gpg: Good signature"
# Index colors
color index '#becfda' default '.*'
color index_author '#df769b' default '.*'
color index_number '#49ace9' default
color index_subject '#49d6e9' default '.*'
color index_date '#475e6c' default
color index_size '#475e6c' default
color index_flags '#49e9a6' default '.*'
# New mail - highlighted with bg_highlight
color index '#e4b781' '#0c3f5f' "~N"
color index_author '#df769b' '#0c3f5f' "~N"
color index_subject '#49d6e9' '#0c3f5f' "~N"
# Flagged mail
color index '#e66533' default "~F"
color index_author '#e66533' default "~F"
# Deleted mail
color index '#475e6c' default "~D"
color index_author '#475e6c' default "~D"
color index_subject '#475e6c' default "~D"
# Tagged mail
color index '#49e9a6' default "~T"
color index_author '#49e9a6' default "~T"
color progress '#051b29' '#49d6e9'

65
config/neomutt/keybinds Normal file
View file

@ -0,0 +1,65 @@
# NeoMutt keybindings
# Attachment
bind attach <return> view-mailcap
bind attach l view-mailcap
# Editor
bind editor <space> noop
bind editor <Tab> complete-query
bind editor ^T complete
# Pager
bind pager c imap-fetch-mail
bind pager j next-line
bind pager k previous-line
bind pager J next-entry
bind pager K previous-entry
bind pager l view-attachments
bind pager,attach h exit
bind pager \031 previous-line
bind pager \005 next-line
bind pager,browser gg top-page
bind pager,browser G bottom-page
# Index
bind index G last-entry
bind index g noop
bind index gg first-entry
bind index D delete-message
bind index U undelete-message
bind index L limit
bind index h noop
bind index l display-message
bind index R group-reply
bind index \031 previous-undeleted
bind index \005 next-undeleted
bind index <tab> sync-mailbox
bind index <space> collapse-thread
# Browser
bind browser h goto-parent
bind browser l select-entry
bind browser,pager,index n search-next
bind browser,pager,index N search-opposite
# Navigation (half page)
bind index,pager,browser d half-down
bind index,pager,browser u half-up
# Sidebar
bind index,pager \Cp sidebar-prev
bind index,pager \Cn sidebar-next
bind index,pager o sidebar-open
bind index,pager B sidebar-toggle-visible
# Misc
bind index,pager @ compose-to-sender
bind index,pager D purge-message
# Macros
macro index,pager a ":set confirmappend=no delete=yes\n<tag-prefix><save-message>=Archive\n<sync-mailbox>:set confirmappend=yes delete=ask-yes\n"
macro index,pager n "<tag-prefix><clear-flag>N<untag-pattern>.<enter>\n"
macro attach O "<enter-command>unset wait_key<enter><shell-escape>rm -f /tmp/mutt-attach<enter><save-entry><kill-line>/tmp/mutt-attach<enter>^A"
macro attach,pager A "|git apply<enter>" "Apply git patch"
macro attach,pager P "|git-apply-patch<enter>" "Apply git patch (interactive)"

66
config/neomutt/neomuttrc Normal file
View file

@ -0,0 +1,66 @@
# NeoMutt main configuration
# Cache
set header_cache = "~/.cache/neomutt/headers/"
set message_cachedir = "~/.cache/neomutt/messages/"
# Editor
set editor = "emacs -nw"
set edit_headers = yes
# General settings
set color_directcolor = yes
set implicit_autoview = yes
set crypt_use_gpgme = yes
alternative_order text/enriched text/plain text
set delete = yes
set abort_key = "<Esc>"
# Sidebar
set sidebar_visible
set sidebar_format = "%D%?F? [%F]?%* %?N?%N/?%S"
set mail_check_stats
# Status bar, date format
set status_chars = " *%A"
set status_format = "[ Folder: %D ] [%r%m messages%?n? (%n new)?%?d? (%d to delete)?%?t? (%t tagged)? ]%>─%?p?( %p postponed )?"
set date_format = "%d.%m.%Y %H:%M"
set sort = threads
set sort_aux = reverse-last-date-received
set uncollapse_jump
set sort_re
set index_format = "%4C %Z %{%b %d} %-15.15L %?E?(%E)&? %s"
set reply_regexp = "^(([Rr][Ee]?(\[[0-9]+\])?: *)?(\[[^]]+\] *)?)*"
set quote_regexp = "^( {0,4}[>|:#%]| {0,4}[a-z0-9]+[>|]+)+"
set send_charset = "utf-8:iso-8859-1:us-ascii"
set charset = "utf-8"
set arrow_cursor = "no"
# Pager View Options
set pager_index_lines = 10
set pager_context = 3
set pager_stop
set menu_scroll
set tilde
unset markers
# MTA (mail transfer agent)
set sendmail = 'msmtpq --read-envelope-from --read-recipients'
# Include keybindings
source ~/.config/neomutt/keybinds
source ~/.config/neomutt/colors
# Account switching macros
macro index,pager <f2> "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/uchicago<enter><change-folder>!<enter>"
macro index,pager <f3> "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/personal<enter><change-folder>!<enter>"
# Register accounts for folder hooks
named-mailboxes "p" "~/mail/personal/Inbox"
folder-hook ~/mail/personal/ "source ~/.config/neomutt/accounts/personal"
named-mailboxes "u" "~/mail/uchicago/Inbox"
folder-hook ~/mail/uchicago/ "source ~/.config/neomutt/accounts/uchicago"
# Source primary account (personal)
source ~/.config/neomutt/accounts/personal

View file

@ -0,0 +1 @@
-- Ray Andrew

View file

@ -0,0 +1 @@
-- Ray Andrew

View file

@ -150,6 +150,7 @@
age
ssh-to-age
nixfmt-rfc-style
shfmt
];
DIRENV_LOG_FORMAT = "";
};

View file

@ -2,219 +2,75 @@
pkgs,
lib,
config,
user,
home-dir,
dots,
...
}:
{
imports = [
./neomutt
./mailcap.nix
];
options.custom.email = with lib; {
enable = mkEnableOption "Enable email";
davmail = mkEnableOption "Enable DavMail";
mbsync = mkEnableOption "Enable Mbsync";
neomutt = mkEnableOption "Enable NeoMutt";
mailcap = mkEnableOption "Enable mailcap";
};
config = lib.mkIf config.custom.email.enable {
# DavMail service (Exchange gateway)
services.davmail = {
enable = config.custom.email.davmail;
settings = {
"davmail.mode" = "O365Manual";
"davmail.url" = "https://outlook.office365.com/EWS/Exchange.asmx";
# davmail.mode = "O365Modern";
"davmail.keepDelay" = 30;
# log4j.logger.davmail = "DEBUG";
};
configFile = "${dots}/config/davmail/davmail.properties";
};
programs = {
neomutt = {
macros = [
{
map = [
"index"
"pager"
];
key = "<f2>";
action = "<sync-mailbox><enter-command>source ~/.config/neomutt/uchicago<enter><change-folder>!<enter>";
}
{
map = [
"index"
"pager"
];
key = "<f3>";
action = "<sync-mailbox><enter-command>source ~/.config/neomutt/personal<enter><change-folder>!<enter>";
}
];
};
mbsync.enable = true;
msmtp = {
enable = true;
};
};
services = {
mbsync = {
# Mbsync service (mail sync)
services.mbsync = {
enable = config.custom.email.mbsync;
configFile = "${dots}/config/mbsync/mbsyncrc";
frequency = "*:0/1";
};
# Install mail-related packages
home.packages =
with pkgs;
[
isync # mbsync
msmtp
sops # for password decryption
age # for sops age backend
]
++ lib.optionals config.custom.email.neomutt [
# Wrapper script for neomutt with truecolor support
(writeShellScriptBin "neomutt" ''
export TERMINFO_DIRS="${ncurses}/share/terminfo''${TERMINFO_DIRS:+:$TERMINFO_DIRS}"
exec env TERM=xterm-direct ${neomutt}/bin/neomutt "$@"
'')
]
++ lib.optionals config.custom.email.mailcap [
mailcap
w3m # HTML rendering
zathura # PDF viewer
kitty # for kitten icat
];
# Symlink config files
xdg.configFile = {
"msmtp".source = config.lib.file.mkOutOfStoreSymlink "${dots}/config/msmtp";
"neomutt".source = config.lib.file.mkOutOfStoreSymlink "${dots}/config/neomutt";
"isyncrc".source = config.lib.file.mkOutOfStoreSymlink "${dots}/config/mbsync/mbsyncrc";
};
accounts.email =
let
cat = lib.getExe' pkgs.coreutils "cat";
in
rec {
maildirBasePath = "${home-dir}/mail";
accounts = lib.mkMerge ([
{
"personal" = {
userName = "raydreww@gmail.com";
address = "raydreww@gmail.com";
realName = "Ray Andrew";
primary = !config.custom.email.davmail;
signature = {
text = ''
-- Ray Andrew
'';
showSignature = "append";
};
passwordCommand = "${cat} ${config.sops.secrets."personal".path}";
gpg = {
key = "1913ECC8FD7076BC8330E11607AA5254804C009F";
signByDefault = true;
};
smtp = {
host = "smtp.gmail.com";
};
imap = {
host = "imap.gmail.com";
};
mbsync = {
enable = true;
create = "both";
expunge = "both";
patterns = [
"*"
"!\"[Airmail]/Done\""
"!\"[Airmail]/Snooze\""
"!\"[Airmail]/To Do\""
"!\"[Airmail]/Send Later\""
"!\"[Gmail]/All Mail\""
"!\"[Gmail]/Important\""
"!\"[Gmail]/Starred\""
"!\"[Gmail]/Bin\""
];
};
msmtp = {
enable = true;
};
neomutt = rec {
enable = true;
mailboxName = "p";
extraConfig = ''
set use_from = yes
set pgp_verify_sig = yes
set pgp_sign_as = 0x07AA5254804C009F
set pgp_timeout = 3600
named-mailboxes "${mailboxName}/inbox" =Inbox
named-mailboxes "${mailboxName}/drafts" =Drafts
named-mailboxes "${mailboxName}/sent" =Sent
named-mailboxes "${mailboxName}/important" =Important
named-mailboxes "${mailboxName}/trash" =Trash
named-mailboxes "${mailboxName}/archive" =Archive
'';
};
};
}
(lib.mkIf config.custom.email.davmail {
"uchicago" = {
userName = "rayandrew@uchicago.edu";
address = "rayandrew@uchicago.edu";
realName = "Ray Andrew";
primary = true;
signature = {
text = ''
-- Ray Andrew
'';
showSignature = "append";
};
passwordCommand = "${cat} ${config.sops.secrets."uchicago".path}";
gpg = {
key = "0BADFAD0FB93296C84956F9CEEF04CFFE9DFE5FC";
signByDefault = false;
};
smtp = {
host = "127.0.0.1";
port = 1025;
tls = {
enable = false;
certificatesFile = null;
};
};
imap = {
host = "127.0.0.1";
port = 1143;
tls.enable = false;
};
mbsync = {
enable = true;
create = "both";
expunge = "both";
patterns = [
"*"
"!\"[Airmail]/Done\""
"!\"[Airmail]/Snooze\""
"!\"[Airmail]/To Do\""
"!\"[Airmail]/Send Later\""
];
extraConfig.account = {
TLSType = "None";
AuthMechs = "LOGIN";
Timeout = 0;
};
};
msmtp = {
enable = true;
extraConfig = {
auth = "plain";
};
};
neomutt = rec {
enable = true;
mailboxName = "u";
extraConfig = ''
set use_from = yes
set pgp_sign_as = 0xEEF04CFFE9DFE5FC
set pgp_verify_sig = yes
set pgp_timeout = 3600
named-mailboxes "${mailboxName}/inbox" =Inbox
named-mailboxes "${mailboxName}/drafts" =Drafts
named-mailboxes "${mailboxName}/sent" =Sent
named-mailboxes "${mailboxName}/important" =Important
named-mailboxes "${mailboxName}/trash" =Trash
named-mailboxes "${mailboxName}/archive" =Archive
named-mailboxes "${mailboxName}/teaching" =Teaching
'';
};
};
})
]);
# mailcap symlink
home.file = lib.mkIf config.custom.email.mailcap {
".mailcap".source = config.lib.file.mkOutOfStoreSymlink "${dots}/config/home/.mailcap";
};
sops = {
age.keyFile = "${home-dir}/.config/sops/age/keys.txt";
age.generateKey = true;
defaultSopsFile = ./secrets.yaml;
secrets = {
"personal" = { };
"uchicago" = { };
};
};
# Create mail directories
home.activation.createMailDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
mkdir -p ~/mail/personal/Inbox
mkdir -p ~/mail/uchicago/Inbox
mkdir -p ~/.cache/neomutt/headers
mkdir -p ~/.cache/neomutt/messages
mkdir -p ~/.local/state
'';
};
}

View file

@ -1,48 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
w3m = lib.getExe pkgs.w3m;
zathura = lib.getExe config.programs.zathura.package;
# term = lib.getExe config.programs.kitty.package;
term = lib.getExe config.programs.wezterm.package;
in
{
options.custom.email = with lib; {
mailcap = mkEnableOption "Enable mailcap";
};
config = lib.mkIf config.custom.email.mailcap {
home.packages = with pkgs; [
mailcap
];
home.file.".mailcap" = {
text = ''
# HTML
text/html; ${w3m} -sixel -o tmp_dir=~/.cache/w3m -o auto_image=TRUE -o display_image=1 -T text/html %s; nametemplate=%s.html
# text/html; ${w3m} -o inline_img_protocol=4 -o tmp_dir=~/.cache/w3m -o auto_image=TRUE -o display_image=1 -T text/html %s; nametemplate=%s.html
# This second one is chosen by auto_view due to the copiousoutput tag
text/html; ${w3m} -I %{charset} -T text/html -cols 140 -o tmp_dir=~/.cache/w3m -o display_link_number=1 -dump; copiousoutput
text/plain; nvim %s
#PDFs
application/x-pdf; ${zathura} '%s'; test=test -n "$DISPLAY"
application/pdf; ${zathura} '%s'; test=test -n "$DISPLAY"
message/rfc822; nvim %s
#Images
# image/png; /usr/bin/feh %s
# image/jpeg; /usr/bin/feh %s
# image/*; (clear && ${term} +kitten icat %s); needsterminal
image/*; (clear && ${term} imgcat %s); needsterminal
'';
};
};
}

View file

@ -1,72 +0,0 @@
{ pkgs, ... }:
''
# Header colors:
color header blue default ".*"
color header brightmagenta default "^(From)"
color header brightcyan default "^(Subject)"
color header brightwhite default "^(CC|BCC)"
mono bold bold
mono underline underline
mono indicator reverse
mono error bold
color normal default default
color indicator brightyellow default # currently selected message. default makes bar clear, disabled arrow to save space.
color sidebar_highlight red default
color sidebar_divider brightblack black
color sidebar_flagged red black
color sidebar_new green black
color normal brightyellow default
color error red default
color tilde black default
color message cyan default
color markers red white
color attachment white default
color search brightmagenta default
color status brightyellow black
color hdrdefault brightgreen default
color quoted green default
color quoted1 blue default
color quoted2 cyan default
color quoted3 yellow default
color quoted4 red default
color quoted5 brightred default
color signature brightgreen default
color bold black default
color underline black default
color normal default default
color body brightred default "[\-\.+_a-zA-Z0-9]+@[\-\.a-zA-Z0-9]+" # Email addresses
color body brightblue default "(https?|ftp)://[\-\.,/%~_:?&=\#a-zA-Z0-9]+" # URL
color body green default "\`[^\`]*\`" # Green text between ` and `
color body brightblue default "^# \.*" # Headings as bold blue
color body brightcyan default "^## \.*" # Subheadings as bold cyan
color body brightgreen default "^### \.*" # Subsubheadings as bold green
color body yellow default "^(\t| )*(-|\\*) \.*" # List items as yellow
color body brightcyan default "[;:][-o][)/(|]" # emoticons
color body brightcyan default "[;:][)(|]" # emoticons
color body brightcyan default "[ ][*][^*]*[*][ ]?" # more emoticon?
color body brightcyan default "[ ]?[*][^*]*[*][ ]" # more emoticon?
color body red default "(BAD signature)"
color body cyan default "(Good signature)"
color body brightblack default "^gpg: Good signature .*"
color body brightyellow default "^gpg: "
color body brightyellow red "^gpg: BAD signature from.*"
mono body bold "^gpg: Good signature"
# mohttps://neomutt.org/code/config_vars.htmlno body bold "^gpg: BAD signature from.*"
color body red default "([a-z][a-z0-9+-]*://(((([a-z0-9_.!~*'();:&=+$,-]|%[0-9a-f][0-9a-f])*@)?((([a-z0-9]([a-z0-9-]*[a-z0-9])?)\\.)*([a-z]([a-z0-9-]*[a-z0-9])?)\\.?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]+)?)|([a-z0-9_.!~*'()$,;:@&=+-]|%[0-9a-f][0-9a-f])+)(/([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*(;([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*)*(/([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*(;([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*)*)*)?(\\?([a-z0-9_.!~*'();/?:@&=+$,-]|%[0-9a-f][0-9a-f])*)?(#([a-z0-9_.!~*'();/?:@&=+$,-]|%[0-9a-f][0-9a-f])*)?|(www|ftp)\\.(([a-z0-9]([a-z0-9-]*[a-z0-9])?)\\.)*([a-z]([a-z0-9-]*[a-z0-9])?)\\.?(:[0-9]+)?(/([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*(;([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*)*(/([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*(;([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*)*)*)?(\\?([-a-z0-9_.!~*'();/?:@&=+$,]|%[0-9a-f][0-9a-f])*)?(#([-a-z0-9_.!~*'();/?:@&=+$,]|%[0-9a-f][0-9a-f])*)?)[^].,:;!)? \t\r\n<>\"]"
# Default index colors:
color index yellow default '.*'
color index_author red default '.*'
color index_number blue default
color index_subject cyan default '.*'
# For new mail:
color index brightyellow black "~N"
color index_author brightred black "~N"
color index_subject brightcyan black "~N"
color progress black cyan
''

View file

@ -1,78 +0,0 @@
{
pkgs,
config,
lib,
...
}:
let
keybinds = import ./keybind.nix rec {
inherit config lib pkgs;
};
colors = import ./colors.nix rec {
inherit config lib pkgs;
};
in
{
options.custom.email = with lib; {
neomutt = mkEnableOption "Enable NeoMutt";
};
config = lib.mkIf (config.custom.email.enable && config.custom.email.neomutt) {
xsession.windowManager.i3.config =
let
i3config = config.xsession.windowManager.i3.config;
in
{
keybindings = lib.mkOptionDefault {
"${i3config.modifier}+m" =
"exec --no-startup-id ${i3config.terminal} -e ${config.programs.neomutt.package}/bin/neomutt";
};
};
programs.neomutt = {
enable = true;
vimKeys = false;
sort = "threads";
unmailboxes = true;
binds = keybinds.binds ++ [ ];
macros = keybinds.macros ++ [ ];
extraConfig = ''
set abort_key = "<Esc>"
# set editor = "nvim"
set editor = "emacs -nw"
set edit_headers = yes
set sidebar_visible
set sidebar_format = "%D%?F? [%F]?%* %?N?%N/?%S"
set mail_check_stats
# set new_mail_command="notify-send 'New Email' '%n new messages, %u unread.' &"
# status bar, date format, finding stuff etc.
set status_chars = " *%A"
# set status_format = "[ Folder: %f ] [%r%m messages%?n? (%n new)?%?d? (%d to delete)?%?t? (%t tagged)? ]%>─%?p?( %p postponed )?"
set status_format = "[ Folder: %D ] [%r%m messages%?n? (%n new)?%?d? (%d to delete)?%?t? (%t tagged)? ]%>%?p?( %p postponed )?"
set date_format = "%d.%m.%Y %H:%M"
set uncollapse_jump
set sort_re
set reply_regexp = "^(([Rr][Ee]?(\[[0-9]+\])?: *)?(\[[^]]+\] *)?)*"
set quote_regexp = "^( {0,4}[>|:#%]| {0,4}[a-z0-9]+[>|]+)+"
set send_charset = "utf-8:iso-8859-1:us-ascii"
set charset = "utf-8"
set arrow_cursor = "no" # Change `color indicator` depending
# Pager View Options
set pager_index_lines = 10 # Shows 10 lines of index when pager is active
set pager_context = 3
set pager_stop
set menu_scroll
set tilde
unset markers
${colors}
'';
};
};
}

View file

@ -1,316 +0,0 @@
{ pkgs, ... }:
{
binds = [
{
map = [ "attach" ];
key = "<return>";
action = "view-mailcap";
}
{
map = [ "attach" ];
key = "l";
action = "view-mailcap";
}
# {
# map = [ "attach" ];
# key = "O";
# action = "<enter-command>unset wait_key<enter><shell-escape>rm -f /tmp/mutt-attach<enter><save-entry><kill-line>/tmp/mutt-attach<enter>^A";
# }
{
map = [ "editor" ];
key = "<space>";
action = "noop";
}
{
map = [ "pager" ];
key = "c";
action = "imap-fetch-mail";
}
{
map = [ "index" ];
key = "G";
action = "last-entry";
}
{
map = [ "index" ];
key = "g";
action = "noop";
}
{
map = [ "index" ];
key = "gg";
action = "first-entry";
}
{
map = [
"pager"
"attach"
];
key = "h";
action = "exit";
}
{
map = [ "pager" ];
key = "j";
action = "next-line";
}
{
map = [ "pager" ];
key = "k";
action = "previous-line";
}
{
map = [ "pager" ];
key = "l";
action = "view-attachments";
}
{
map = [ "index" ];
key = "D";
action = "delete-message";
}
{
map = [ "index" ];
key = "U";
action = "undelete-message";
}
{
map = [ "index" ];
key = "L";
action = "limit";
}
{
map = [ "index" ];
key = "h";
action = "noop";
}
{
map = [
"browser"
"pager"
"index"
];
key = "n";
action = "search-next";
}
{
map = [
"browser"
"pager"
"index"
];
key = "N";
action = "search-opposite";
}
{
map = [ "index" ];
key = "l";
action = "display-message";
}
{
map = [ "browser" ];
key = "h";
action = "goto-parent";
}
{
map = [ "browser" ];
key = "l";
action = "select-entry";
}
{
map = [
"pager"
"browser"
];
key = "gg";
action = "top-page";
}
{
map = [
"pager"
"browser"
];
key = "G";
action = "bottom-page";
}
{
map = [
"index"
"pager"
"browser"
];
key = "d";
action = "half-down";
}
{
map = [
"index"
"pager"
"browser"
];
key = "u";
action = "half-up";
}
{
map = [ "index" ];
key = "R";
action = "group-reply";
}
{
map = [ "index" ];
key = "\\031";
action = "previous-undeleted";
}
{
map = [ "index" ];
key = "\\005";
action = "next-undeleted";
}
{
map = [ "pager" ];
key = "\\031";
action = "previous-line";
}
{
map = [ "pager" ];
key = "\\005";
action = "next-line";
}
{
map = [ "editor" ];
key = "<Tab>";
action = "complete-query";
}
{
map = [
"index"
"pager"
];
key = "\\Ck";
action = "sidebar-prev";
}
{
map = [
"index"
"pager"
];
key = "\\Cj";
action = "sidebar-next";
}
{
map = [
"index"
"pager"
];
key = "\\Co";
action = "sidebar-open";
}
{
map = [
"index"
"pager"
];
key = "\\Cp";
action = "sidebar-prev-new";
}
{
map = [
"index"
"pager"
];
key = "\\Cn";
action = "sidebar-next-new";
}
{
map = [
"index"
"pager"
];
key = "B";
action = "sidebar-toggle-visible";
}
{
map = [
"index"
"pager"
];
key = "@";
action = "compose-to-sender";
}
{
map = [
"index"
"pager"
];
key = "D";
action = "purge-message";
}
{
map = [ "index" ];
key = "<tab>";
action = "sync-mailbox";
}
{
map = [ "index" ];
key = "<space>";
action = "collapse-thread";
}
{
map = [ "editor" ];
key = "<Tab>";
action = "complete-query";
}
{
map = [ "editor" ];
key = "^T";
action = "complete";
}
# {
# map = [
# "index"
# "pager"
# ];
# key = "<f2>";
# action = "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/uchicago<enter><change-folder>!<enter>";
# }
# {
# map = [
# "index"
# "pager"
# ];
# key = "<f3>";
# action = "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/personal<enter><change-folder>!<enter>";
# }
# {
# map = [ "attach" ];
# key = "V";
# action = "<pipe-entry>iconv -c --to-code=UTF8 > ~/.cache/mutt-mail.html<enter><shell-escape>xdg-open ~/.cache/mutt-mail.html<enter>";
# }
];
macros = [
{
map = [
"index"
"pager"
];
key = "a";
action = ":set confirmappend=no delete=yes\\n<tag-prefix><save-message>=Archive\\n<sync-mailbox>:set confirmappend=yes delete=ask-yes\\n";
}
{
map = [
"index"
"pager"
];
key = "n";
action = "<tag-prefix><clear-flag>N<untag-pattern>.<enter>\\n";
}
{
map = [
"attach"
];
key = "O";
action = "<enter-command>unset wait_key<enter><shell-escape>rm -f /tmp/mutt-attach<enter><save-entry><kill-line>/tmp/mutt-attach<enter>^A";
}
];
}

View file

@ -1,31 +1,35 @@
personal: ENC[AES256_GCM,data:aaZAYmnoQfGIH6bBneKtTA==,iv:xiy1eCyBhFulfRXGz0WDFLaqPj3kXsMD4Xkk7D4s5XA=,tag:Nk0KsgICo505VEMwVUt3TA==,type:str]
uchicago: ENC[AES256_GCM,data:3ZkuIfvzOkKfQD0iyTk=,iv:aCfFrxDM5Ly/qLdLAQkK2tOxb89dFkCc9RhN5GVSGRw=,tag:7D54pkVX2F6IhM38JOUFQg==,type:str]
personal: ENC[AES256_GCM,data:MG1ryntM2YKyjb0YH/8GYg==,iv:SvwAc1dBl8wc1YWdnOuCNAb5RUNA2vLX87uzOGOaqec=,tag:ixVU3FWGpZqZx/1PD3/3KA==,type:str]
uchicago: ENC[AES256_GCM,data:LqZTprY/9Li8ab9i0dg=,iv:tLJHXwsoL7PnwonHlnXiaLahr0+fIizsoshE0GFbkn8=,tag:FT5brU6pW7xFxnzpBYU2jA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age13cfe8fhp4m978qlcur46vkkxepsl93ggwe53kmhue9xtpgr5zu5q4y6ln2
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMMTcwTEc5VTdEMlZSK1FI
c2VVdjJOc3B3V2RGS0pIVGQralJ4MGFwSkRvCm5mVTNxMHUzNythNHVyRG52bVYz
RVp2d1hKUnpucko1SG01SDdWNFZGV1EKLS0tIDB2OGF1UlUyN2xnRm1WR1RleUhN
S1hFWjhCMlNZVC83ZzR4NittcXlCQlUKQO6NHCMwWKwrfwwnwLK/sO4HO6ES+PyT
dh3tRhPlv7/viO+MtHqUfQ5cbW+OWoic4prfK/UxIz5VeY331kpq+g==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxUjF4Nm0yVWFwMUM1Y2hn
SVRhUmEweDNVd3Z2NVpEVW1yaDRYaDhLZ0NZCjE3WkVTWXUrKzE1VGN5ZUtSb2VZ
WnJFRkkwZmJKTm5OWlpMLzlCZHR6aE0KLS0tIFZ3UEFmcTJIYlcrUXhyU1hSMFRK
MjhvU0NQamQzcE1sWG5FSmpoajdNc0UKslJWrvq4BIeMoZ6ZSA6anlldGOpUuXrL
PV7pVpTihxWtzPbPV36oPRVoN3GzNZXUPJryExtUzdnufbhfu9LBTw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1pdk6dmyxqhdaja5d0nf8f9qjd43hmfahmkure5yrf8al9jyfmd8qfdxwl6
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0ajhrbkdqcmpRd2svYmNx
dHIzRDJTTTVGV3VON0NBZnpaQTdGM2w4aXpNClNqb2Y4Qzg4OWx2a3FNdG5MY1ZQ
R2dOSC9CdzBPM1I5b1lCVnptMGM3eUEKLS0tIGZRM1pSV3J0NlJvQUZBcTVNZGxy
SS9qQlJiRU1EelZySWpNdWRiTms3ZncKkwp1WT9LWxnJb+yjilikTHRm1fbs89TU
1Xzy+GNiZbtm2I6e5XaaD+9d3PFvqqu0OdqrMrTMmiNAys0WhwB8Iw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOZ01sMXhvMG5BMmxwa3h1
aXArSkxUNUkwOHAzQnlkYk4vVmEzckZkaFVzCnpYRmpqMW9QM29VTWFnc0NYTURj
REp0OVNPN1lRRUdPWlQrTmhYcEZwbm8KLS0tIFI2dFBzbmQ4Q01PU1BLUXI5eXIy
Y0ZMMmJTOVRrbXpOVkYxektxOFFkZUEKcjX/wDMvuZ+PzfydOGOf938mCVcFFD/h
nGdbXAoE+cD7/rmpaF0Bpm2WqWrkiAvE/csyaWV/HNzQ6JkXp/4jtQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-03-04T21:54:28Z"
mac: ENC[AES256_GCM,data:r+2SOcW7xLHee9kL8369yB6l/Z2XdnzGkeFygSrDgcZVfBfp/fT1xeMvu5tuu8aqsUeJ7lkFD2VKiBue95XSojdlM+5YyTerqdzLyMCRbkRivC3O2xXe90B4hVqm+twE9uu74mIznAQ2e0EO9E0MMlMNBo3EwsEcYA8gyVuB5mc=,iv:plNu3BircQ+kpPaXqlNvYlLAL5V5RX/yiETs+nY1pfw=,tag:7gVuOpzx5YsHtwodIRr4TQ==,type:str]
pgp: []
- recipient: age15vscvpe79l287h8f3hssrj2r45xy0l3ns94zfue2fxlq43cqdsxq58vq3c
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQdVV6Vy9pRmg1ZjR5c01w
L1JWWDF6U05RNnNGTjF1VEN6ZCt6SENod2xJCjA1QngrQnJXMi9BdFByT3FPSVVR
eUEyd29YQXphZ2VuTmorOWU4MjZ1TmMKLS0tIFNSZG5iV21MSHVBNkZoZW5pV09k
eS9JTWRDaVZVN2hLcVF1S2NTdk1TWGMKVnpl9T5ZycSlJmE8M8QY62kxDp5lgagF
D6hxceNiqvgGg+GwIRXoVYiVhmNAE1R9dVTyMlAfFa2pxGg7JrPj1A==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-12-02T05:42:16Z"
mac: ENC[AES256_GCM,data:95V5+/M85gUaeJD8UhkMZCcMbYEnEBIA3qFDSDd0ZoxXQkw+IXy3/C8rV9QPK/Bq/Hv3fPWbs+0CwH5GqhjfaoIlVgo3MBlk6Z1iSuw7MTy8vGOBFWpYBrObib8f9yYIZ74CrpvCHHoFtqP5cKevmHsCvgrJpbkw4gZIhZVkjLk=,iv:0/v8QzgZYc/VMei0qP1PlK4oc4b2ch5VoBEW3P3gXxU=,tag:rFTyhSixSzl1BOIhGDJo+g==,type:str]
unencrypted_suffix: _unencrypted
version: 3.9.4
version: 3.11.0

View file

@ -51,6 +51,7 @@
nerd-fonts.droid-sans-mono
nerd-fonts.space-mono
nerd-fonts.dejavu-sans-mono
nerd-fonts.symbols-only
];
}

View file

@ -60,6 +60,7 @@
pandoc
duckdb
hyperfine
fastfetch
(pkgs.python311.withPackages (ppkgs: [
ppkgs.numpy
]))
@ -69,10 +70,18 @@
})
imagemagick
spotify-player
eza
]
++ lib.optionals pkgs.stdenv.isDarwin [ coreutils ]
++ (lib.attrValues config.custom.shell.packages);
home.shellAliases = {
ls = "eza";
ll = "eza -la --icons --git";
la = "eza -a --icons";
lt = "eza --tree --level=2 --icons";
};
programs.bash = {
enable = true;
enableVteIntegration = true;
@ -103,6 +112,8 @@
enableFishIntegration = config.programs.fish.enable;
};
xdg.configFile."direnv/direnv.toml".source =
config.lib.file.mkOutOfStoreSymlink "${dots}/config/direnv/direnv.toml";
xdg.configFile."sesh".source = config.lib.file.mkOutOfStoreSymlink "${dots}/config/sesh";
xdg.configFile."yazi".source = config.lib.file.mkOutOfStoreSymlink "${dots}/config/yazi";
xdg.configFile."spotify-player".source =

View file

@ -1,5 +1,6 @@
{
pkgs,
home-dir,
...
}:
{
@ -80,6 +81,13 @@
];
hm.custom = {
environment = {
enable = true;
variables = {
EDITOR = "nvim";
SOPS_AGE_KEY_FILE = "${home-dir}/.config/sops/age/keys.txt";
};
};
emacs.enable = true;
neovim.enable = true;
latex.enable = true;
@ -91,5 +99,12 @@
hammerspoon.enable = true;
kitty.enable = true;
};
email = {
enable = true;
davmail = true;
neomutt = true;
mbsync = true;
mailcap = true;
};
};
}

View file

@ -40,6 +40,7 @@ let
./shared.nix
./${host}
./${host}/hardware.nix
../modules/nixos
../nixos
(
{ config, ... }:
@ -59,6 +60,7 @@ let
imports = [
inputs.nix-index-database.homeModules.nix-index
inputs.sops-nix.homeManagerModules.sops
../modules/home-manager
../home
]
++ lib.optional (builtins.pathExists ./${host}/home.nix) ./${host}/home.nix;
@ -114,6 +116,7 @@ let
modules = [
./shared.nix
./${host}
../modules/darwin
../darwin
(
{ config, ... }:
@ -134,6 +137,7 @@ let
inputs.nix-index-database.homeModules.nix-index
inputs.sops-nix.homeManagerModules.sops
inputs.mac-app-util.homeManagerModules.default
../modules/home-manager
../home
]
++ lib.optional (builtins.pathExists ./${host}/home.nix) ./${host}/home.nix;

View file

@ -1,13 +1,5 @@
{
inputs,
lib,
config,
pkgs,
system,
dots,
user,
hm,
host,
...
}:
{

View file

@ -0,0 +1,5 @@
{
imports = [
# Add darwin-specific custom modules here
];
}

View file

@ -0,0 +1,6 @@
{
imports = [
./overrides
./environment.nix
];
}

View file

@ -0,0 +1,50 @@
# Home Manager environment module
{
config,
lib,
...
}:
let
inherit (lib)
mkEnableOption
mkOption
mkIf
types
;
cfg = config.custom.environment;
# Convert attrset to shell export statements
toShellExports =
vars:
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: ''export ${name}="${value}"'') vars);
# Convert attrset to fish set statements
toFishSets =
vars:
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: ''set -gx ${name} "${value}"'') vars);
in
{
options.custom.environment = {
enable = mkEnableOption "custom environment configuration";
variables = mkOption {
type = types.attrsOf types.str;
default = { };
description = "Environment variables to set across all shells.";
example = {
EDITOR = "nvim";
SOPS_AGE_KEY_FILE = "$HOME/.config/sops/age/keys.txt";
};
};
};
config = mkIf cfg.enable {
home.sessionVariables = cfg.variables;
# Also add to shell configs for immediate availability
programs.bash.bashrcExtra = lib.mkAfter (toShellExports cfg.variables);
programs.zsh.initContent = lib.mkAfter (toShellExports cfg.variables);
programs.fish.shellInit = lib.mkAfter (toFishSets cfg.variables);
};
}

View file

@ -0,0 +1,181 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
getExe
mapAttrsRecursive
mkDefault
mkEnableOption
mkIf
mkMerge
mkOption
optionalAttrs
types
;
cfg = config.services.davmail;
isDarwin = pkgs.stdenv.isDarwin;
isLinux = pkgs.stdenv.isLinux;
javaProperties = pkgs.formats.javaProperties { };
generatedSettingsFile = javaProperties.generate "davmail.properties" cfg.settings;
# Use configFile if provided, otherwise use generated settings
settingsFile = if cfg.configFile != null then cfg.configFile else generatedSettingsFile;
in
{
disabledModules = [ "services/davmail.nix" ];
meta.maintainers = [ lib.maintainers.bmrips ];
options.services.davmail = {
enable = mkEnableOption "DavMail, an MS Exchange gateway.";
package = lib.mkPackageOption pkgs "davmail" { };
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a custom davmail.properties configuration file.
If set, this file will be used instead of generating one from settings.
'';
example = "~/.config/davmail/davmail.properties";
};
imitateOutlook = mkOption {
type = types.bool;
default = false;
description = "Whether DavMail pretends to be Outlook.";
example = true;
};
settings = mkOption {
type = javaProperties.type;
default = { };
description = ''
Davmail configuration. Refer to
<http://davmail.sourceforge.net/serversetup.html>
and <http://davmail.sourceforge.net/advanced.html>
for details on supported values.
'';
example = {
"davmail.url" = "https://outlook.office365.com/EWS/Exchange.asmx";
"davmail.allowRemote" = true;
"davmail.imapPort" = 55555;
"davmail.bindAddress" = "10.0.1.2";
"davmail.smtpSaveInSent" = true;
"davmail.folderSizeLimit" = 10;
"davmail.caldavAutoSchedule" = false;
"log4j.logger.rootLogger" = "DEBUG";
};
};
};
config = mkIf cfg.enable (mkMerge [
# Common configuration for all platforms
{
home.packages = [ cfg.package ];
}
# Default settings (only when configFile is not provided)
(mkIf (cfg.configFile == null) {
services.davmail.settings =
mapAttrsRecursive (_: mkDefault) {
"davmail.server" = true;
"davmail.disableUpdateCheck" = true;
"davmail.logFilePath" = "${config.xdg.stateHome}/davmail.log";
"davmail.logFileSize" = "1MB";
"davmail.mode" = "auto";
"davmail.url" = "https://outlook.office365.com/EWS/Exchange.asmx";
"davmail.caldavPort" = 1080;
"davmail.imapPort" = 1143;
"davmail.ldapPort" = 1389;
"davmail.popPort" = 1110;
"davmail.smtpPort" = 1025;
"davmail.oauth.tokenFilePath" = "${config.xdg.stateHome}/davmail-tokens";
"log4j.logger.davmail" = "WARN";
"log4j.logger.httpclient.wire" = "WARN";
"log4j.logger.org.apache.commons.httpclient" = "WARN";
"log4j.rootLogger" = "WARN";
}
// optionalAttrs cfg.imitateOutlook {
"davmail.oauth.clientId" = "d3590ed6-52b3-4102-aeff-aad2292ab01c";
"davmail.oauth.redirectUri" = "urn:ietf:wg:oauth:2.0:oob";
};
})
# Linux-specific: systemd user service
(mkIf isLinux {
systemd.user.services.davmail = {
Unit = {
Description = "DavMail POP/IMAP/SMTP Exchange Gateway";
After = [
"graphical-session.target"
"network.target"
];
};
Install.WantedBy = [ "graphical-session.target" ];
Service = {
Type = "exec";
ExecStart = "${getExe cfg.package} ${settingsFile}";
Restart = "on-failure";
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectSystem = "strict";
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
SystemCallErrorNumber = "EPERM";
UMask = "0077";
};
};
})
# Darwin-specific: launchd agent
(mkIf isDarwin {
launchd.agents.davmail = {
enable = true;
config = {
ProgramArguments = [
"${getExe cfg.package}"
"${settingsFile}"
];
KeepAlive = true;
RunAtLoad = true;
StandardErrorPath = "/tmp/davmail.err.log";
StandardOutPath = "/tmp/davmail.out.log";
};
};
})
]);
}

View file

@ -0,0 +1,6 @@
{
imports = [
./davmail.nix
./mbsync.nix
];
}

View file

@ -0,0 +1,161 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
concatStringsSep
mkIf
mkMerge
mkOption
optionalAttrs
types
;
cfg = config.services.mbsync;
isDarwin = pkgs.stdenv.isDarwin;
isLinux = pkgs.stdenv.isLinux;
mbsyncOptions = [
"--all"
]
++ lib.optional cfg.verbose "--verbose"
++ lib.optional (cfg.configFile != null) "--config ${cfg.configFile}";
mbsyncCommand = "${cfg.package}/bin/mbsync ${concatStringsSep " " mbsyncOptions}";
# Convert systemd calendar format to launchd interval (approximate)
# Format like "*:0/5" means every 5 minutes
# We'll parse simple cases, default to 5 minutes
parseFrequencyToSeconds =
freq:
let
# Try to extract minute interval from patterns like "*:0/5" or "*:*:0/30"
parts = builtins.match ".*\\*/([0-9]+).*" freq;
in
if parts != null then (lib.toInt (builtins.head parts)) * 60 else 300;
in
{
disabledModules = [ "services/mbsync.nix" ];
meta.maintainers = [ lib.maintainers.pjones ];
options.services.mbsync = {
enable = lib.mkEnableOption "mbsync";
package = lib.mkPackageOption pkgs "isync" { };
frequency = mkOption {
type = types.str;
default = "*:0/5";
description = ''
How often to run mbsync. On Linux, this value is passed to the systemd
timer configuration as the onCalendar option. See
{manpage}`systemd.time(7)` for more information about the format.
On Darwin, this is converted to an approximate interval in seconds.
'';
};
verbose = mkOption {
type = types.bool;
default = true;
description = ''
Whether mbsync should produce verbose output.
'';
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Optional configuration file to link to use instead of
the default file ({file}`~/.mbsyncrc`).
'';
};
preExec = mkOption {
type = types.nullOr types.str;
default = null;
example = "mkdir -p %h/mail";
description = ''
An optional command to run before mbsync executes. This is
useful for creating the directories mbsync is going to use.
'';
};
postExec = mkOption {
type = types.nullOr types.str;
default = null;
example = "\${pkgs.mu}/bin/mu index";
description = ''
An optional command to run after mbsync executes successfully.
This is useful for running mailbox indexing tools.
'';
};
};
config = mkIf cfg.enable (mkMerge [
# Linux-specific: systemd user service and timer
(mkIf isLinux {
systemd.user.services.mbsync = {
Unit = {
Description = "mbsync mailbox synchronization";
};
Service = {
Type = "oneshot";
ExecStart = mbsyncCommand;
}
// (optionalAttrs (cfg.postExec != null) {
ExecStartPost = cfg.postExec;
})
// (optionalAttrs (cfg.preExec != null) {
ExecStartPre = cfg.preExec;
});
};
systemd.user.timers.mbsync = {
Unit = {
Description = "mbsync mailbox synchronization";
};
Timer = {
OnCalendar = cfg.frequency;
Unit = "mbsync.service";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
})
# Darwin-specific: launchd agent
(mkIf isDarwin {
launchd.agents.mbsync = {
enable = true;
config =
let
# Build a script that handles pre/post exec
mbsyncScript = pkgs.writeShellScript "mbsync-wrapper" ''
set -e
${lib.optionalString (cfg.preExec != null) cfg.preExec}
${mbsyncCommand}
${lib.optionalString (cfg.postExec != null) cfg.postExec}
'';
in
{
ProgramArguments = [ "${mbsyncScript}" ];
StartInterval = parseFrequencyToSeconds cfg.frequency;
RunAtLoad = true;
StandardErrorPath = "/tmp/mbsync.err.log";
StandardOutPath = "/tmp/mbsync.out.log";
};
};
})
]);
}

View file

@ -0,0 +1,5 @@
{
imports = [
# Add nixos-specific custom modules here
];
}

22
secrets/wb.enc Normal file

File diff suppressed because one or more lines are too long