summaryrefslogtreecommitdiff
path: root/archsetup
blob: 58a8c761345e16fb54c41c5126c1b44f0015b04f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
#!/bin/sh
# ArchSetup - Craig Jennings <craigmartinjennings@gmail.com>
# 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

### Constants

username="cjennings"
password="welcome"  # will be changed on first login. :)

dwm_repo="https://git.cjennings.net/dwm.git"
dmenu_repo="https://git.cjennings.net/dmenu.git"
st_repo="https://git.cjennings.net/st.git"
slock_repo="https://git.cjennings.net/slock.git"
dotfiles_repo="https://git.cjennings.net/dotfiles"
dotemacs_repo="https://git.cjennings.net/dotemacs.git"
dotfiles_home="/home/$username/.dotfiles"

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"

### Intro
intro() {
    printf "\n\nArchSetup launched @ %s\n" "$(date +'%D %T')"| tee -a "$logfile"
    STARTTIME=$(date +%s)
    errors_encountered=0

    # 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 ! (pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1); then
        action="retrying $1" && display "task" "$action"
        if ! (pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1); then
            action="retrying $1 once more" && display "task" "$action"
            (pacman --noconfirm --needed -S "$1" >> "$logfile" 2>&1) ||
                error "error" "$action" "$?"
        fi
    fi
}

# Git Install
git_install() {
    prog_name="$(basename "$1" .git)"
    build_dir="$source_dir/$prog_name"
    action="building & installing $prog_name from source"
    display "task" "$action"

    if ! (sudo -u "$username" git clone --depth 1 "$1" "$build_dir" >> "$logfile" 2>&1); then
        error "error" "cloning source code for $prog_name" "$?"
        (cd "$build_dir" && sudo -u "$username" git pull --force origin master >> "$logfile" 2>&1) || \
            error "error" "pulling source code for $prog_name" "$?"
    fi

    (cd "$build_dir" && make install >> "$logfile" 2>&1) || \
        error "error" "building $prog_name from source code" "$?"
}

# AUR Install
aur_install() {
    action="installing $1 via the AUR" && display "task" "$action"
    if ! (sudo -u "$username" yay -S --noconfirm "$1" >> "$logfile" 2>&1); then
        action="retrying $1" && display "task" "$action"
        if ! (sudo -u "$username" yay -S --noconfirm "$1" >> "$logfile" 2>&1); then
            action="retrying $1 once more" && display "task" "$action"
            (sudo -u "$username" 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 | sudo -u "$username" pip install "$1" >> "$logfile" 2>&1)  || \
        error "error" "$action" "$?"
}

### Prerequisites
prerequisites() {
    # why these software packages are 'required'
    # 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
    # ntp               - must communicate with other servers in ordered 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'll need a shell interpreter for yay; this is mine

    display "title" "Prerequisites"

    display "subtitle" "Bootstrapping"

    action="ensuring current Arch Linux keyring" && display "task" "$action"
    (pacman -S --noconfirm archlinux-keyring) >> "$logfile" 2>&1  || \
        error "crash" "$action" "$?"

    display "task" "verifying Arch Linux keys"
    (pacman-key --populate archlinux >> "$logfile" 2>&1) || \
        error "crash" "verifying Arch Linux keys" "$?"

    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 ca-certificates coreutils curl git go ntp openssh \
                               python stow tar vi zsh; do
        pacman_install "$software"
    done

    display "subtitle" "Environment Configuration"

    # sync the time on this machine
    action="synchronizing system time" && display "task" "$action"
    (ntpdate 0.us.pool.ntp.org >> "$logfile" 2>&1) || error "error" "$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

    # enable pacman concurrent downloads and color
    action="enabling concurrent downloads" && display "task" "$action"
    sed -i "s/^#ParallelDownloads.*$/ParallelDownloads = 5/;s/^#Color$/Color/" /etc/pacman.conf

    action="Package Mirrors" && display "subtitle" "$action"
    pacman_install reflector

    action="configuring reflector" && display "task" "$action"
    (printf '
          --connection-timeout 3 \
          --download-timeout 3 \
          --protocol https \
          --age 12 \
          --latest 20 \
          --score 10 \
          --fastest 5 \
          --sort score \
          --save /etc/pacman.d/mirrorlist
    ' > /etc/xdg/reflector/reflector.conf >> "$logfile" 2>&1) || \
        error "error" "$action" "$?"

    action="updating repository mirrors" && display "task" "$action"
    (reflector --connection-timeout 3 \
               --download-timeout 3 \
               --protocol https \
               --age 12 \
               --latest 20 \
               --score 10 \
               --fastest 5 \
               --sort score \
               --save /etc/pacman.d/mirrorlist > /dev/null 2>&1)

    action="enabling the reflector timer" && display "task" "$action"
    (systemctl enable reflector.timer >> "$logfile" 2>&1) || \
        error "error" "$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 "crash" "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 "crash" "user '$username' already 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 "crash" "adding user '$username" "$?"

    display "task" "assigning the password"
    echo "$username:$password" | chpasswd  # any text is allowable! be careful!

    display "task" "configuring shell"
    # zsh cache required: $username will install via yay; zsh will run those commands
    mkdir -p "/home/$username/.cache/zsh/" | tee -a "$logfile"

    # give $username sudo nopasswd rights (required for aur installs)
    display "task" "granting permissions"
    (echo "%$username ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers) \
        || error "error" "$action" "$?"

    # mount as ramdisk to speed aur/git build/installs
    (sudo mount -t tmpfs -o size=4G archsetup $source_dir >> "$logfile" 2>&1) || \
        error "crash" "mounting the RAM disk for archsetup" "$?"

    (chown -R "$username":wheel "$(dirname "$source_dir")" >> "$logfile" 2>&1) || \
        error "crash" "changing ownership of $source_dir" "$?"
}

### User Customizations
user_customizations() {
    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" "$?"

    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 "error" "cloning source code for yay"
        (sudo -u "$username" -D "$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" && sudo -u "$username" makepkg --noconfirm -si >> "$logfile" 2>&1) || \
        error "crash" "$action" "$?"
}

### Essential Services
essential_services() {
    display "title" "Essential Services"

    # Networking

    display "subtitle" "Networking"
    pacman_install networkmanager

    # 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 "error" "$action" "$?"
    action="starting the openssh service" && display "task" "$action"
    systemctl start sshd >> "$logfile" 2>&1   || error "error" "$action" "$?"

    # Firewall

    # deny by default, then allow the following:
    # http/s                    : 80/tcp, 443/tcp
    # tor                       : 9040,9050,9051,9053,9119/tcp
    # email                     : IMAP, IMAPS
    # mDNS printer discovery    : 5353/udp
    # ssh                       : ssh
    # syncthing                 : 22000/tcp, 22000/udp, 21027/udp
    # torrents                  : transmission
    # calibre content server    : 8080/tcp

    display "subtitle" "Firewall"
    pacman_install ufw

    action="configuring ufw to deny by default" && display "task" "$action"
    ufw default deny incoming >> "$logfile" 2>&1 || error "error" "$action"

    # note on the protocols
    #   "80,443,8080/tcp"                                       # http and https traffic
    #   "9040,9050,9051,9053,9119/tcp"                  # tor network
    #   "55353/udp"                                                     # DNS
    #   "22000/tcp" "22000/udp" "21027/udp"     # syncthing

    for protocol in \
        "80,443,8080/tcp" \
            "9040,9050,9051,9053,9119/tcp" \
            "IMAP" "IMAPS" \
            "55353/udp" \
            "ssh" \
            "22000/tcp" "22000/udp" "21027/udp" \
            "transmission" \
        ; do
        action="adding ufw rule to allow $protocol" && display "task" "$action"
        (ufw allow $protocol  >> "$logfile" 2>&1) || error "error" "$action" "$?"
    done

    action="adding limits to protect from brute force attacks" && display "task" "$action"
    (ufw limit 22/tcp  >> "$logfile" 2>&1 && \
         ufw limit 443/tcp >> "$logfile" 2>&1) || \
        error "error" "action"

    action="enabling firewall service to launch on boot" && display "task" "$action"
    systemctl enable ufw.service >> "$logfile" 2>&1  || error "error" "$action" "$?"

    action="starting firewall service" && display "task" "$action"
    systemctl start ufw.service >> "$logfile" 2>&1 || error "error" "$action" "$?"

    # Service Discovery

    display "subtitle" "Network Service Discovery"
    pacman_install avahi

    action="configuring avahi" && display "task" "$action"
    systemctl disable systemd-resolved.service >> "$logfile" 2>&1 || error "error" "$action" "$?"
    systemctl enable avahi-daemon.service >> "$logfile" 2>&1 || error "error" "$action" "$?"

    # 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 "error" "$action" "$?"
    pacman_install at
    action "enabling the batch delayed command scheduler" && display "task" "$action"
    systemctl enable atd  >> "$logfile" 2>&1  || error "error" "$action" "$?"
}

### 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 xf86-video-intel \
                                xsel xorg-xbacklight xf86-input-libinput \
                                xorg-xdpyinfo xorg-xprop xorg-xwininfo \
                                xorg-xinput xorg-xkill ; do
        pacman_install $software
    done
}

### DWM Window Manager
dwm() {

    action="DWM Window Manager Dependencies" && display "subtitle" "$action"
    for software in coreutils fontconfig freetype2 glibc 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
}

### Desktop Environment
desktop_environment() {
    display "title" "Desktop Environment"

    # Core Fonts

    action="Core Fonts" && display "subtitle" "$action"
    pacman_install ttf-firacode-nerd
    pacman_install ttf-hack-nerd
    pacman_install noto-fonts-emoji
    aur_install ttf-all-the-icons
    aur_install ttf-ms-fonts
    aur_install ttf-ubraille

    # System Utilities

    action="System Utilities" && display "subtitle" "$action"
    pacman_install sshfs
    pacman_install dosfstools
    pacman_install exfat-utils
    pacman_install testdisk
    pacman_install ntfs-3g
    pacman_install udisks2
    pacman_install dmidecode
    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 {} \;

    #  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; do
        pacman_install $software
    done;
    # disable the pc speaker beep
    rmmod pcspkr >> "$logfile" 2>&1
    echo "blacklist pcspkr" >/etc/modprobe.d/nobeep.conf  >> "$logfile" 2>&1

    # 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

    # 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 "error" "$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 lf-git task-spooler speedtest-go gotop rar; do
        aur_install "$software"
    done;

    # Help And Documentation

    action="Help and Documentation" && display "subtitle" "$action"
    for software in man arch-wiki-docs arch-wiki-lite; do
        pacman_install $software
    done;

    aur_install cht.sh-git
    pacman_install tealdeer

    # Sync Services

    action="Sync Services" && display "subtitle" "$action"

    pacman_install syncthing
    systemctl enable syncthing@$username.service  >> "$logfile" 2>&1 || error "error" "$action" "$?"

    # Desktop Environment Utilities

    action="Desktop Environment Utilities" && display "subtitle" "$action"

    for software in brightnessctl xautolock network-manager-applet xclip bc \
                                  conky nitrogen qalculate-gtk; do
        pacman_install $software
    done;

    aur_install python-pulsectl
    aur_install caffeine-ng
    aur_install alarm-clock-applet
    aur_install colorpicker

    # Theme and Cursor

    action="UI Theme" && display "subtitle" "$action"

    for software in lxappearance gnome-themes-extra gtk-engine-murrine; do
        pacman_install $software
    done;

    for software in vimix-icon-theme vimix-cursors vimix-gtk-themes \
                                     qt5ct adwaita-color-schemes; do
        aur_install $software
    done;

    # Browsers

    action="Browsers" && display "subtitle" "$action"
    pacman_install firefox
    aur_install librewolf-bin
    aur_install tor-browser
    aur_install google-chrome-stable

    # Install Printing

    action="Print System" && display "subtitle" "$action"
    for software in cups cups-pdf foomatic-db-engine foomatic-db-ppds foomatic-db-nonfree-ppds \
                         gutenprint foomatic-db-gutenprint-ppds nss-mdns; do
        pacman_install "$software"
    done
    action="enabling printing service to launch at boot" && display "task" "$action"
    (systemctl enable cups.service  >> "$logfile" 2>&1) || error "error" "$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

    # 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      # (also for SICP)

    # Rust
    pacman_install rust        # Rust programming language

    # Python
    pacman_install pyright     # Python language server

    # 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 typescript  # Typescript programming language
    pacman_install npm         # Node-js package manager
    aur_install nvm            # Node-js version manager
    pacman_install jq          # JSON processor

    # 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

    action="Emacs Dependencies" && display "subtitle" "$action"
    # pacman_install emacs
    # ignoring; build from source instead

    # 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
    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"
    (sudo -u "$username" 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"
    # ensure headers exist first
    pacman_install linux-headers >> "$logfile" 2>&1 || error "error" "$action" "$?"
    pacman_install linux-lts-headers >> "$logfile" 2>&1 || error "error" "$action" "$?"

    pacman_install virtualbox >> "$logfile" 2>&1 || error "error" "$action" "$?"
    pacman_install virtualbox-guest-iso >> "$logfile" 2>&1 || error "error" "$action" "$?"
    pacman_install virtualbox-host-dkms >> "$logfile" 2>&1 || error "error" "$action" "$?"
    action="adding user to vboxusers group" && display "task" "$action"
    (gpasswd -a $username vboxusers >> "$logfile" 2>&1)  || error "error" "$action" "$?"
    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" "$?"
    pacman_install vagrant  >> "$logfile" 2>&1 || error "error" "$action" "$?"
    pacman_install ansible >> "$logfile" 2>&1 || error "error" "$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 calibre                           # ebook manager/viewer
    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 faac                              # open source mpeg 3 and aac encoder
    pacman_install faad2                             # processes an aac stream
    pacman_install figlet                            # words into ascii art
    pacman_install filezilla                         # ftp gui
    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 extra audio plugins
    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 ledger                            # CLI accounting software
    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 mediainfo                         # technical and tag information about media files
    pacman_install mosh                              # alt SSH terminal with roaming and responsiveness support
    pacman_install mpc                               # command line interface to mpd
    pacman_install mpd                               # the music player daemon
    pacman_install ncmpcpp                           # and mpd client to play music
    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 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 smartmontools                     # monitors hard drives
    pacman_install texlive-meta                      # latex
    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 w3m                               # text based browser
    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 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 picom-jonaburg-git                   # xorg compositor with enhancements
    aur_install tageditor                            # metadata editor for mkv, webm and related video files
    aur_install tidal-dl                             # tidal-dl:tidal as yt-dlp:youtube
    aur_install tremc                                # curses interface for transmission
    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

    # some nice fonts
    aur_install ttf-lato
    pacman_install ttf-crimson-pro
    pacman_install ttf-crimson-pro-variable
    pacman_install ttf-go-nerd
    pacman_install ttf-jetbrains-mono-nerd
    pacman_install ttf-meslo-nerd
    pacman_install ttf-sourcecodepro-nerd

}

### Boot-Related

silent_boot() {

    action="Silent Boot" && display "title" "$action"

    action="removing distro and date/time from initial screen" && display "task" "$action"
    (cat /dev/null >/etc/issue)  || error "error" "$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 "error" "$action" "$?"

    action="delegating fsck messages from udev to systemd" && display "task" "$action"
    sed -i "s/.*HOOKS=(base udev autodetect keyboard keymap modconf block filesystems fsck).*/HOOKS=(base systemd autodetect keyboard keymap modconf block filesystems fsck)/" /etc/mkinitcpio.conf  || error "error" "running sed on mkinitcpio.conf to hide fsck messages" "$?"
    mkinitcpio -P  >> "$logfile" 2>&1  || error "error" "running mkinitcpio -P to silence fsck messages" "$?"

    action="instructing systemd to check filesystems" && display "task" "$action"
    servicefile=/usr/lib/systemd/system/systemd-fsck-root.service
    [ -f $servicefile ] && echo "StandardOutput=null" >>$servicefile && \
        echo "StandardError=journal+console" >>$servicefile

    servicefile=/usr/lib/systemd/system/systemd-fsck@.service
    [ -f $servicefile ] && echo "StandardOutput=null" >>$servicefile && \
        echo "StandardError=journal+console" >>$servicefile

    action="removing hostname from login prompt" && display "task" "$action"
    sed -i "s/--noclear/--nohostname --noclear/g" /usr/lib/systemd/system/getty@.service  \
        || error "error" "$action" "$?"

    action="silencing the unneeded and chatty watchdog module" && display "task" "$action"
    echo "blacklist iTCO_wdt" >/etc/modprobe.d/nowatchdog.conf  || error "error" "$action" "$?"

    # SYSTEMD BOOT: eliminate timeout and silence boot text
    if [ -f /boot/loader/loader.conf ]; then
        action="eliminating timeout and silencing boot test on systemd boot" && display "task" "$action"
        echo "timeout=0" >/boot/loader/loader.conf

        # the file in this location was named based on the time of the install, so we must use find to identify it.
        kernelfile=$(find /boot/loader/entries/ -name "*.conf")
        # hush boot output via kernel parameters
        sed -i "/.*options root=PARTUUID.*/ s/$/quiet rd.systemd.show_status=auto rd.udev.log_level=2 nvme.noacpi=1 mem_sleep_default=deep nowatchdog/" "$kernelfile"
    fi

    # GRUB: reset timeouts and adjust log levels
    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=0/g" /etc/default/grub
        sed -i "s/.*GRUB_DEFAULT=.*/GRUB_DEFAULT=0/g" /etc/default/grub
        sed -i "s/.*GRUB_RECORDFAIL_TIMEOUT=.*/GRUB_RECORDFAIL_TIMEOUT=$GRUB_TIMEOUT/g" /etc/default/grub
        sed -i "s/.*GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"rw quiet loglevel=2 rd.systemd.show_status=auto rd.udev.log_level=2 nvme.noacpi=1 mem_sleep_default=deep nowatchdog\"/g" /etc/default/grub
        grub-mkconfig -o /boot/grub/grub.cfg  >> "$logfile" 2>&1  || error "error" "" "$?"
    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 "error" "$action" "$?"

    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" > "$archsetup_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 $archsetup_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                 # take start stats

prerequisites         # install software required to install software
create_user           # create user in wheel with :nopasswd sudo
user_customizations   # dotfiles
aur_installer         # install yay (comment out if using zfsarch install first)
essential_services    # ssh, firewall, printing, etc
xorg                  # display manager
dwm                   # window manager
desktop_environment   # commonly used applications
developer_workstation # development tools and utilities
supplemental_software # everything else
silent_boot           # make booting a bit less noisy

outro                 # take end stats; show summary

exit 0