-
Notifications
You must be signed in to change notification settings - Fork 4
/
bittersweet
executable file
·1311 lines (1013 loc) · 38 KB
/
bittersweet
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
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env zsh
# dotfiles/bittersweet
# bittersweet
# Configures macOS just the way I like it
set -euo pipefail
# -e exit if any command returns non-zero status code
# -u prevent using undefined variables
# -o pipefail force pipelines to fail on first non-zero status code
IFS=$'\n\t'
# Set Internal Field Separator to newlines and tabs
# This makes bash consider newlines and tabs as separating words
# See: http://redsymbol.net/articles/unofficial-bash-strict-mode/
### Define Colours ###
/usr/bin/tput sgr0;
# reset colors
RESET=$(/usr/bin/tput sgr0);
readonly RESET
BOLD=$(/usr/bin/tput bold);
readonly BOLD
RED=$(/usr/bin/tput setaf 1);
readonly RED
### END Colours ###
function usage {
echo -e "\\nConfigures macOS just the way I like it 🤓\\n"
echo "Usage:"
echo " defaults - Write new system and application default settings"
echo " dotfiles - Symlink various config files into ${HOME}"
echo " scripts - Symlink scripts in bin into /usr/local/bin"
echo " sudotouch - Enable TouchID to sudo"
echo " secretive - Install Secretive 🔑"
echo " gpgtools - Install GPGTools"
echo " sublimetext - Install Sublime Text"
echo " tower - Install Tower"
echo " deckset - Install Deckset"
echo " maccy - Install Maccy"
echo " texpad - Install Texpad (Texifier) & BasicTeX"
echo " basictex - Install MacTeX BasicTeX Distribution"
echo " latex - Install LaTeX packages via tlmgr"
echo " blockblock - Install BlockBlock"
echo " oversight - Install OverSight 📸🎤"
echo " brew - Install Homebrew, Homebrew-file and export HOMEBREW_BREWFILE"
echo " brewfile - Install Homebrew packages from Brewfile"
echo " compinit - Fix permissions on zsh completion directory /usr/local/share"
echo " shell - Change shell to zsh as installed by Brew"
echo " mdmprofile - Open System Prefernces to install MDM Profile"
echo -e "\\n ${RED}hailmary${RESET} - ${BOLD}Run every function${RESET} in order listed above"
exit 0
}
### Utility Functions ###
# check_os_compatibility
# check_developer_tools
# download_application
# verify_sha
# verify_signature
# install_application
# objective_see_install
# get_sudo
# ctrl_c
# run_test
function check_os_compatibility {
# check_os_compatibility
# Check if the host is darwin (i.e macOS)
# Check if the host is ruuning macOS $supported_macos_version
local os
local current_macos_version
local -r supported_major_macos_version="14"
local -r supported_macos_name="Sonoma"
os="$(uname -s)"
if [[ "${os}" != "Darwin" ]]; then
echo "[❌] This script was written for macOS"
exit 1
fi
current_macos_version="$(sw_vers -productVersion | awk -F '.' '{print $1}')";
if [[ "${current_macos_version}" != "${supported_major_macos_version}" ]]; then
echo "[⚠️ ] This scipt was written for macOS ${supported_macos_name} (${supported_major_macos_version})"
sleep 3
fi
}
function check_developer_tools {
# check_developer_tools
# Checks if xcode command line tools are installed
if ! xcode-select -p >/dev/null 2>&1; then
echo "[❌] Command line developer tools required"
echo "[🍺] Install via: xcode-select --install"
exit 1
fi
}
function download_application {
# download_application
# download_application "download_path" "url"
# Download file using curl
# curl will follow redirects (--location)
local download_path=${1:?download_path not passed to download_application}
local url=${2:?url not passed to download_application}
local file_name
file_name=$(echo "${download_path}" | awk -F '/' '{print $(NF)}')
echo "[🍺] Downloading ${file_name}"
if curl --location --silent "${url}" --output "${download_path}"; then
# Download
echo "[✅] Successfully downloaded ${file_name}"
else
echo "[❌] Failed to download ${file_name}"
return 1
fi
}
function verify_sha {
# verify_sha
# Uses shasum to check hash of file is what
# we expect it to be
local file_name=${1:?file_name not passed to verify_sha}
local file_download_path=${2:?file_download_path not passed to verify_sha}
local file_sha_length=${3:?file_sha_length not passsed to verify_sha}
local file_sha=${4:?file_sha not passed to verify_sha}
echo "[🍺] Attempting to validate ${file_name} SHA${file_sha_length} hash"
echo "${file_sha} ${file_name}" > "${file_download_path}.sha${file_sha_length}"
(
# Execute in a subshell so the working directory is not permanantly changed
cd "${HOME}/Downloads" || exit
if shasum -a "${file_sha_length}" -c "${file_download_path}.sha${file_sha_length}" >/dev/null; then
# Attempt to validate the ZIPs SHA256 hash
echo "[✅] Successfully validated ${file_name} SHA${file_sha_length} hash"
else
echo "[❌] Failed to validate ${file_name} SHA${file_sha_length} hash"
exit 1
fi
)
rm "${file_download_path:?}.sha${file_sha_length:?}"
}
function verify_signature {
# verify_signature
# Check that .app and .pkg files are signed, if not hard exit
# Check that .app and .pkg files are notarised, if not print warning
# Paramters:
# $file_path REQUIRED
# Path to .app or .pkg file
# e.g. $HOME/Downloads/Maccy.app
# $application_name REQUIRED
# Name of Application being installed
# e.g Maccy
local file_path=${1:?file_path not passed to verify_signature}
local application_name=${2:?application_name not passed to verify_signature}
if [[ "${file_path}" =~ \.pkg$ ]]; then
local package_name
package_name="$(basename "${file_path}")"
echo "[🍺] Attempting to validated the signature on ${package_name}"
if /usr/sbin/pkgutil --check-signature "${file_path}" >/dev/null; then
echo "[✅] Successfully validated the signaturee on ${package_name}"
else
echo "[❌] Failed to validate the signature on ${package_name}"
exit 1
fi
if /usr/sbin/spctl --assess --type install "${file_path}"; then
echo "[✅] ${package_name} is notarised"
return 0
else
echo "[❌] ${package_name} is NOT notarised"
fi
fi
if [[ "${file_path}" =~ \.app$ ]]; then
echo "[🍺] Attempting to validated the signature on ${application_name}.app"
if /usr/bin/codesign --verify --deep --strict "${file_path}"; then
# Check the .app is correctly signed
echo "[✅] Successfully validated the signaturee on ${application_name}.app"
else
echo "[❌] Failed to validate the signature on ${application_name}.app"
exit 1
fi
if /usr/sbin/spctl --assess --type execute "${file_path}"; then
echo "[✅] ${application_name}.app is notarised"
return 0
else
echo "[❌] ${application_name}.app is NOT notarised"
fi
fi
}
function install_application {
# install_application
# Install Applications packaged as .dmg or .zip
# Does not work for application which require an installer (.pkg)
# Checks if .dmg or .zip then act accordingly
# Checks signature on .app before installing
local application_name=${1:?application_name not passed}
local downloaded_file_path=${2:?downloaded_file_path not passed}
local downloaded_file_name
local dmg_mount_path
downloaded_file_name="$(echo "${downloaded_file_path}" | awk -F '/' '{print $NF}')"
if file "${downloaded_file_path}" | grep --quiet 'Zip'; then
# IF file is a Zip
echo "[🍺] Attempting to unzip ${downloaded_file_name}"
if ditto -x -k "${downloaded_file_path}" "${HOME}/Downloads"; then
# Attempt to unzip
echo "[✅] Successfully unzipped ${downloaded_file_name}"
else
echo "[❌] Failed to unzip ${downloaded_file_name}"
exit 1
fi
verify_signature "${HOME}/Downloads/${application_name}.app" "${application_name}"
echo "[🍺] Attempting to copy ${application_name}.app into /Applications"
if cp -Ri "${HOME}/Downloads/${application_name}.app" "/Applications"; then
# Attempt to copy .app into /Applications
echo "[✅] Successfully installed ${application_name}"
else
echo "[❌] Failed to install ${application_name}"
exit 1
fi
## Cleanup
echo "[🍺] Deleting ${downloaded_file_path}"
# Delete the ZIP
rm "${downloaded_file_path:?}"
echo "[🍺] Deleting ${HOME}/Downloads/${application_name}.app"
# Delete the .app
rm -r "${HOME:?}/Downloads/${application_name:?}.app"
elif file "${downloaded_file_path}" | grep --quiet 'dmg'; then
# IF file is a dmg
echo "[🍺] Attempting to mount ${downloaded_file_path}"
if hdiutil attach -nobrowse -quiet "${downloaded_file_path}"; then
# Attempt to mount the DMG
echo "[✅] Successfully mounted ${downloaded_file_name}"
# shellcheck disable=SC2086
# Globbing is desired here, using it to avoid SC2010
dmg_mount_path="$(echo /Volumes/${application_name}*)"
else
echo "[❌] Failed to mount ${downloaded_file_path}"
exit 1
fi
verify_signature "${dmg_mount_path}/${application_name}.app" "${application_name}"
echo "[🍺] Attempting to copy ${application_name}.app into /Applications"
if cp -Ri "${dmg_mount_path}/${application_name}.app" "/Applications"; then
# Attempt to copy .app into /Applications
echo "[✅] Successfully installed ${application_name}"
else
echo "[❌] Failed to install ${application_name}"
exit 1
fi
## Cleanup
echo "[🍺] Unmounting ${dmg_mount_path}"
# Unmount the DMG
hdiutil detach -quiet "${dmg_mount_path}"
echo "[🍺] Deleting ${downloaded_file_path}"
# Delete the DMG
rm "${downloaded_file_path:?}"
fi
}
function objective_see_install {
# objective_see_install
# Installs tools from objective-see.com
# Currently used for BlockBlock & Oversight
local application_name=${1:?application_name not passed to objective_see_install}
local downloaded_file_path=${2:?downloaded_file_path not passed objective_see_install}
local downloaded_file_name
local installer_file_name
downloaded_file_name=$(echo "${downloaded_file_path}" | awk -F '/' '{print $(NF)}')
installer_file_name="${application_name} Installer"
echo "[🍺] Attempting to unzip ${downloaded_file_path}"
if unzip -qa "${downloaded_file_path}" -d "${HOME}/Downloads"; then
# Attempt to unzip
echo "[✅] Successfully unzipped ${downloaded_file_name}"
else
echo "[❌] Failed to unzip ${downloaded_file_name}"
exit 1
fi
verify_signature "${HOME}/Downloads/${installer_file_name}.app" "${application_name}"
get_sudo "to install ${application_name}"
if sudo "${HOME}/Downloads/${installer_file_name}.app/Contents/MacOS/${installer_file_name}" -install >/dev/null; then
echo "[✅] Successfully installed ${application_name}"
else
echo "[❌] Failed to install ${application_name}"
exit 1
fi
## Cleanup
echo "[🍺] Deleting ${downloaded_file_path}"
# Delete the zip
rm "${downloaded_file_path:?}"
echo "[🍺] Deleting ${HOME}/Downloads/${installer_file_name}.app"
# Delete installer application bundle
rm -rf "${HOME:?}/Downloads/${installer_file_name:?}.app"
}
function get_sudo {
# Ask user to input password to get sudo privilege
# Update user's cached credentials, adds five (5)
# minutes to sudo timeout (-v)
# Paramter:
# $purpose_string REQUIRED
# Descibe why sudo is needed
local purpose_string=${1:?$purpose_string not passed to get_sudo}
if sudo --prompt="[⚠️ ] Password required ${purpose_string}: " -v; then
# !! SUDO !!
return 0
else
return 1
fi
}
function ctrl_c {
# ctrl_c
# If user hits CTRL + C exit
echo -e "\\n[❌] ${USER} has chosen to quit!"
exit 1
}
function run_test {
./test
}
################################
################################
#### END Utility Functions #####
################################
################################
function write_defaults {
echo "[🍺] Writing system & application defaults"
defaults write com.apple.TextEdit RichText -bool false
# TextEdit: Use Plain Text Mode as Default
# Default: com.apple.TextEdit RichText -bool true
defaults write com.apple.TextEdit TabWidth -int 4
# TextEdit: Set tab width to 4 spaces
# Default: n/a
defaults write -g NSNavPanelExpandedStateForSaveMode -bool true
defaults write -g NSNavPanelExpandedStateForSaveMode2 -bool true
# Save Panel: Show expanded version
# Default: defaults write -g NSNavPanelExpandedStateForSaveMode -bool false && \
# defaults write -g NSNavPanelExpandedStateForSaveMode2 -bool false
defaults write com.apple.finder ShowStatusBar -bool true
# Finder: Show the status bar
# Default: defaults write com.apple.finder ShowStatusBar -bool false
defaults write com.apple.AppleMultitouchTrackpad TrackpadThreeFingerDrag -bool true
# Trackpad: Enable three finger drag
# Default: defaults write com.apple.AppleMultitouchTrackpad TrackpadThreeFingerDrag -bool false
defaults write com.apple.AppleMultitouchTrackpad Clicking -bool true
# Trackpad: Enable tap to click
# Default: defaults write com.apple.AppleMultitouchTrackpad Clicking -bool false
defaults write com.apple.menuextra.clock DateFormat -string "EEE d MMM HH:mm"
# Clock: Set format to Sun 26 Nov 16:00
defaults write com.apple.mail PreferPlainText -bool true
# Mail: Force emails to be opened in plain text
# Default: defaults write com.apple.mail PreferPlainText -bool false
defaults write com.apple.mail SendFormat -string Plain
# Mail: Force new emails to be composed as plain text
# Default: defaults write com.apple.mail SendFormat -string MIME
defaults write com.apple.mail minSizeKB 5000
# Mail: If attatchment is over 5MB ask to send via Mail Drop
# Default: com.apple.mail minSizeKB 20000
defaults write com.apple.ActivityMonitor IconType -int 5
# Activity Monitor: Set dock icon to "Show CPU Usage"
# Default: n/a
defaults write com.apple.finder FXRemoveOldTrashItems -bool true
# Finder: Empty trash after 30 days
# Default: defaults write com.apple.finder FXRemoveOldTrashItems -bool false
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true
# SMB: Don't write .DS_Store files on SMB shares
# Default: defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool false
}
function install_dotfiles {
# install_dotfiles
# Create directories in $HOME
# Soft links config files from repo into $HOME
# Config files for: zsh, git, ssh, gpg
local -r dir=$(pwd)
local -ar dot_dirs=(.ssh .gnupg .extra)
local dot_dir
local -ar dot_files=(.zshrc .functions .gitignore_global \
.gitconfig .aliases)
local dot_file
local -ar ssh_configs=(config .extra_ssh)
local ssh_config
local -ar gpg_files=(gpg-agent.conf gpg.conf)
local gpg_file
# Create required directories
for dot_dir in "${dot_dirs[@]}"; do
if mkdir -p "${HOME}/${dot_dir}"; then
echo "[✅] Successfully created ${HOME}/${dot_dir}"
else
echo "[❌] Failed to create ${HOME}/${dot_dir}"
fi
done
# Symlink general dotfiles into $HOME
for dot_file in "${dot_files[@]}"; do
if ln -sfn "${dir}/${dot_file}" "${HOME}/${dot_file}"; then
echo "[✅] Successfully linked ${dir}/${dot_file} to ${HOME}/${dot_file}"
else
echo "[❌] Failed to link ${dir}/${dot_file} to ${HOME}/${dot_file}"
fi
done
# Symlink ssh config in to $HOME/.ssh/
for ssh_config in "${ssh_configs[@]}"; do
if ln -sfn "${dir}/.ssh/${ssh_config}" "${HOME}/.ssh/${ssh_config}"; then
echo "[✅] Successfully linked ${ssh_config} to ${HOME}/.ssh/${ssh_config}"
else
echo "[❌] Failed to link ${ssh_config} to ${HOME}/.ssh/${ssh_config}"
fi
done
# Symlink GPG config files in to $HOME/.gnupg/
for gpg_file in "${gpg_files[@]}"; do
if ln -sfn "${dir}/.gnupg/${gpg_file}" "${HOME}/.gnupg/${gpg_file}"; then
echo "[✅] Successfully linked ${gpg_file} to ${HOME}/.gnupg/${gpg_file}"
else
echo "[❌] Failed to link ${gpg_file} to ${HOME}/.gnupg/${gpg_file}"
fi
done
}
function install_scripts {
# install_scripts
# Symlink dotfiles/bin/ scripts into /usr/local/bin
# Create /usr/local/bin
# Symlink files in /usr/local/bin
local -r dir=$(pwd)
local -ar bin_files=(dns pihole_stats sublime_backup)
local -ar macos_script_bin_files=(c0design)
# Create /usr/local/bin
if ! [[ -d "/usr/local/bin/" ]]; then
get_sudo "to create /usr/local/bin/"
if sudo mkdir -p "/usr/local/bin/"; then
echo "[✅] Successfully created /usr/local/bin/"
else
echo "[❌] Failed to create /usr/local/bin/"
fi
fi
get_sudo "to symlink scripts into /usr/local/bin/"
# Symlink files
for bin_file in "${bin_files[@]}"; do
if sudo ln -sfn "${dir}/bin/${bin_file}" "/usr/local/bin/${bin_file}"; then
echo "[✅] Successfully linked ${bin_file} to /usr/local/bin/${bin_file}"
else
echo "[❌] Failed to link ${bin_file} to /usr/local/bin/${bin_file}"
fi
done
# Symlink bittersweet into /usr/local/bin
if sudo ln -sfn "${dir}/bittersweet" "/usr/local/bin"; then
echo "[✅] Successfully linked bittersweet to /usr/local/bin/bittersweet"
else
echo "[❌] Failed to link bittersweet to /usr/local/bin/bittersweet"
fi
# Symlink some files in macos-scripts repo into /usr/local/bin
for macos_script in "${macos_script_bin_files[@]}"; do
if sudo ln -sfn "$HOME/Documents/Projects/macos-scripts/${macos_script}" "/usr/local/bin/${macos_script}"; then
echo "[✅] Successfully linked ${macos_script} to /usr/local/bin/${macos_script}"
else
echo "[❌] Failed to link ${macos_script} to /usr/local/bin/${macos_script}"
fi
done
}
function enable_touchid_sudo {
# enable_touchid_sudo
# As of macOS Sonoma (14.0) this setting can persist OS updates when set in
# /etc/pam.d/sudo_local. See: https://support.apple.com/en-us/HT213893
# Check if already enabled in /etc/pam.d/sudo and warn user
# Check if /etc/pam.d/sudo_local exists, if not create it
# Use vim to insert required text to enable TouchID for sudo into sudo_local
if grep -q 'pam_tid.so' "/etc/pam.d/sudo"; then
logger -p user.warning -s "Enabling TouchID for sudo in /etc/pam.d/sudo does not persist across updates.\
See https://support.apple.com/en-us/HT213893"
fi
if [[ -e "/etc/pam.d/sudo_local" ]]; then
if grep -q 'pam_tid.so' "/etc/pam.d/sudo_local"; then
logger -p user.info -s "TouchID for sudo already enabled"
return 0
fi
else
get_sudo "to create /etc/pam.d/sudo_local"
if sudo install -m "444" -g "wheel" -o "root" "/dev/null" "/etc/pam.d/sudo_local"; then
# Use install to create the file /etc/pam.d/sudo_local
# Copies from /dev/null which creates an empty file
# Set permissions to read only, group to wheel and owner to root
logger -p user.info -s "Created /etc/pam.d/sudo_local"
else
logger -p user.error -s "Failed to create /etc/pam.d/sudo_local"
return 1
fi
if sudo ex -s -c '1i|auth sufficient pam_tid.so' -c x! -c x! "/etc/pam.d/sudo_local"; then
# Invoke Vim in ex mode
# Select line 1, enter insert mode, insert that text write changes and exit
# Need to exit twice to get passed the read only file warning
logger -p user.info -s "TouchID sudo Enabled"
return 0
else
logger -p user.error -s "Failed to enable TouchID sudo"
return 1
fi
fi
}
function install_secretive {
# https://github.com/maxgoedjen/secretive/issues/117
if [[ -d "/Applications/Secretive.app" ]]; then
echo "[🍺] Secretive already installed"
return 0
fi
echo "Installing Secretive"
local zip_name
zip_name="Secretive.zip"
local latest_version
latest_version="$(curl -I https://github.com/maxgoedjen/secretive/releases/latest \
| grep 'location:' \
| awk -F '/' '{print $NF}' \
| sed 's/\r//')"
local zip_download_url
zip_download_url="https://github.com/maxgoedjen/secretive/releases/download/${latest_version}/${zip_name}"
local zip_download_path
zip_download_path="${HOME}/Downloads/${zip_name}"
download_application "${zip_download_path}" "${zip_download_url}"
install_application "Secretive" "${zip_download_path}"
}
function install_gpgtools {
if [[ -x "$(command -v gpg2)" ]]; then
echo "[🍺] $(gpg2 --version | head -n 1) already installed"
return 0
fi
echo "[🍺] Installing GPGTools (GPG Suite)"
# shellcheck disable=SC2155
local latest_version="$(curl -s "https://gpgtools.org/releases/gpgsuite/release-notes.html" \
| grep -m 1 'class="version"' \
| awk -F '"' '{print $(NF-1)}')"
# Get the latest version string
# Query the id field from the latest div class="version"
local -r dmg_name="GPG_Suite-${latest_version}.dmg"
local -r dmg_download_path="${HOME}/Downloads/${dmg_name}"
# shellcheck disable=SC2155
local dmg_sha256="$(curl -s "https://gpgtools.org" 2>/dev/null \
| grep -m 1 "SHA256" \
| perl -nle "print $& if m{(?<=class='tooltiptext'>).*(?=</span></span>)}")"
# Get the SHA256 hash of the latest DMG
local -r dmg_mount_point="/Volumes/GPG Suite/"
echo "[🍺] Downloading ${dmg_name}"
if curl --silent "https://releases.gpgtools.org/${dmg_name}" --output "${dmg_download_path}" ; then
# Download
echo "[✅] Successfully downloaded ${dmg_name}"
else
echo "[❌] Failed to download ${dmg_name}"
exit 1
fi
verify_sha "${dmg_name}" "${dmg_download_path}" "256" "${dmg_sha256}"
echo "[🍺] Attempting to mount ${dmg_download_path}"
if hdiutil attach -nobrowse -quiet "${dmg_download_path}"; then
# Attempt to mount the DMG
echo "[✅] Successfully mounted ${dmg_name}"
else
echo "[❌] Failed to mount ${dmg_name}"
exit 1
fi
verify_signature "${dmg_mount_point}/Install.pkg" "Install"
get_sudo "to install GPGTools"
if sudo installer -pkg "${dmg_mount_point}/Install.pkg" -target "/" >/dev/null; then
# !! SUDO !!
# Install
echo "[✅] Successfully installed GPGTools"
else
echo "[❌] Failed to install GPGTools"
exit 1
fi
# Cleanup
echo "[🍺] Unmounting ${dmg_mount_point}"
hdiutil detach -quiet "${dmg_mount_point}"
# Unmount the DMG
echo "[🍺] Deleting ${dmg_download_path}"
rm "${dmg_download_path:?}"
# Delete the DMG
}
function install_sublime_text {
if [[ -d "/Applications/Sublime Text.app" ]]; then
echo "[🍺] Sublime Text already installed"
return 0
fi
echo "[🍺] Installing Sublime Text"
local latest_build
latest_build="$(curl -s https://www.sublimetext.com/download \
| awk -F 'Build ' 'BEGIN {found=0} /Build/ && !found {sub("</p>", "", $2); print $2; found=1}')"
# Get the latest build number
# TIL https://stackoverflow.com/a/28879552
local zip_name="sublime_text_build_${latest_build}_mac.zip"
local zip_download_path="${HOME}/Downloads/${zip_name}"
download_application "${zip_download_path}" "https://download.sublimetext.com/sublime_text_build_${latest_build}_mac.zip"
install_application "Sublime Text" "${zip_download_path}"
## Install config files
echo "[🍺] Installing Sublime Text config files"
if mkdir -p "$HOME/Library/Application Support/Sublime Text 3/Packages/"; then
echo "[✅] Successfully created ~/Library/Application Support/Sublime Text 3/Packages/"
else
echo "[❌] Failed to create ~/Library/Application Support/Sublime Text 3/Packages/"
exit 1
fi
if cp -r "Sublime Text 3/Packages/User" "$HOME/Library/Application Support/Sublime Text 3/Packages"; then
echo "[✅] Successfully installed Sublime Text config files"
else
echo "[❌] Failed to install Sublime Text config files"
exit 1
fi
}
function install_tower {
if [[ -d "/Applications/Tower.app" ]]; then
echo "[🍺] Tower already installed"
return 0
fi
echo "[🍺] Installing Tower"
local url
local zip_name
local zip_download_path
url="$(curl -sI https://updates.fournova.com/tower3-mac/stable/releases/latest/download \
| grep 'Location' \
| awk '{print $2}' \
| tr -d '\r')"
zip_name="$(echo "${url}" | awk -F '/' '{print $7}')"
zip_download_path="${HOME}/Downloads/${zip_name}"
download_application "${zip_download_path}" "${url}"
install_application "Tower" "${zip_download_path}"
}
function install_deckset {
# install_deckset
# Install decket presentation software
# $url prebuilt, getting $version and $build from the http location header.
# Grabbing $url directly from curl output does not work.
# curl -sLI https://www.decksetapp.com/download | grep 'location: ' | awk 'END{print}'
# Produces: 'location: https://dl.decksetapp.com/Deckset+2.0.16+(2578).dmg\C-M'
# The "\C-M" string at the end is erroneous. It does not appear when run from
# the command line, only when run in the script with set -x.
# It's apprently an ascci carriage return.
# Failed to remove with: tr -d '\C-M', tr -d '\n', tr -d '\r'
if [[ -d "/Applications/Deckset.app" ]]; then
echo "[🍺] Deckset already installed"
return 0
fi
echo "[🍺] Installing Deckset"
local location_header
local version
local build
local url
local dmg_name
local dmg_download_path
location_header="$(curl -sLI https://www.decksetapp.com/download | grep 'dl.decksetapp')"
version="$(echo -e "${location_header}" | awk -F '[+|+]' '{print $2}')"
build="$(echo -e "${location_header}" | awk -F '[(|)]' '{print $2}')"
url="https://dl.decksetapp.com/Deckset+${version}+(${build}).dmg"
dmg_name=$(basename "${url}")
dmg_download_path="${HOME}/Downloads/${dmg_name}"
download_application "${dmg_download_path}" "${url}"
install_application "Deckset" "${dmg_download_path}"
}
function install_maccy {
# install_maccy
# Installs Maccy via usual helpers
if [[ -d "/Applications/Maccy.app" ]]; then
echo "[🍺] Maccy already installed"
return 0
fi
local release_version
local zip_download_url
local zip_download_path
local zip_file_name
zip_file_name="Maccy.app.zip"
release_version=$(curl -LIs https://github.com/p0deje/Maccy/releases/latest \
| awk '/^location: / {split($2, array, "/"); print array[length(array)]}' \
| tr -d '\r')
zip_download_url="https://github.com/p0deje/Maccy/releases/download/${release_version}/${zip_file_name}"
zip_download_path="${HOME}/Downloads/${zip_file_name}"
download_application "${zip_download_path}" "${zip_download_url}"
install_application "Maccy" "${zip_download_path}"
}
function install_texpad {
# install_texpad
# Installs Texpad via usual helpers
if [[ -d "/Applications/Texifier.app" ]]; then
echo "[🍺] Texifier already installed"
return 0
fi
local dmg_download_url
local dmg_name
local dmg_download_path
dmg_download_url="$(curl -s https://www.texifier.com/mac \
| awk -F'href="' '{split($2, a, "\""); if(a[1] ~ /download\.texifier\.com/) print a[1]}')"
dmg_name="$(basename "${dmg_download_url}")"
dmg_download_path="${HOME}/Downloads/${dmg_name}"
download_application "${dmg_download_path}" "${dmg_download_url}"
install_application "Texifier" "${dmg_download_path}"
}
function install_basictex {
# install_basictex
# Installs the MacTeX BasicTeX distribution via usual helpers
if [[ -x "$(command -v tlmgr)" ]]; then
echo "[🍺] MacTeX already installed"
return 0
fi
local pkg_download_url
local pkg_name
local pkg_download_path
pkg_download_url="https://mirror.ctan.org/systems/mac/mactex/BasicTeX.pkg"
pkg_name="$(basename "${pkg_download_url}")"
pkg_download_path="$HOME/Downloads/${pkg_name}"
download_application "${pkg_download_path}" "${pkg_download_url}"
echo "[🍺] Attempting to install ${pkg_name}"
get_sudo "to install BasicTeX"
if sudo installer -pkg "${pkg_download_path}" -target "/" >/dev/null; then
# !! SUDO !!
# Install
echo "[✅] Successfully installed BasicTeX"
else
echo "[❌] Failed to install BasicTeX"
exit 1
fi
# Cleanup
echo "[🍺] Deleting ${pkg_download_path}"
rm "${pkg_download_path:?}"
# Delete the pkg
}
function install_latex_packages {
# install_latex_packages
# Installs some LaTeX packages via tlmrg
# Updates tlmgr and all currently installed packages first
echo "[🍺] Updating tlmgr, all installed packages and installing packages..."
sleep 2
get_sudo "to update tlmgr, update installed packages and install packages"
# Update tlmgr
if sudo tlmgr update --self >/dev/null; then
echo "[✅] Successfully updated tlmgr"
else
echo "[❌] Failed to update tlmgr"
fi
# Update packages installed via tlmgr
if sudo tlmgr update --all >/dev/null; then
echo "[✅] Successfully updated LaTeX packages via tlmgr"
else
echo "[❌] Failed to update LaTeX packages via tlmgr"
fi
local -ar PACKAGES=(sectsty helvetic)
# sectsty: Required for the Custom sectioning
# helvetic: Installs the helvetica font (phv) for the custom sectioning
for PACKAGE in "${PACKAGES[@]}"; do
if sudo tlmgr install "${PACKAGE}"; then
echo "[✅] Successfully installed LaTeX package ${PACKAGE} via tlmgr"
else
echo "[❌] Failed to install LaTeX package ${PACKAGE} via tlmgr"
fi
done
}
function install_blockblock {
# install_blockblock
# Installs BlockBlock (BB) via usual helpers
# as well as objective_see_install.
# BB installer binary has a different name to the other Obj-s tools
if [[ -d "/Applications/BlockBlock Helper.app" ]]; then
echo "[🍺] BlockBlock already installed"
return 0
fi
echo "[🍺] Installing BlockBlock"
local url
local zip_name
local zip_download_path
local zip_sha1
url="$(curl -s https://objective-see.org/products/blockblock.html \
| grep -m 1 'downloads/BlockBlock' \
| awk -F '"' '{print $4}')"
zip_name="$(echo "${url}" | awk -F '/' '{print $NF}')"
zip_download_path="${HOME}/Downloads/${zip_name}"
zip_sha1="$(curl -s https://objective-see.org/products/blockblock.html \
| grep 'SHA-1' \
| awk -F '[> |<]' '{print $12}')"
download_application "${zip_download_path}" "${url}"
verify_sha "${zip_name}" "${zip_download_path}" "1" "${zip_sha1}"
objective_see_install "BlockBlock" "${zip_download_path}"
echo "[⚠️] Remember to enable Full Disk Access for BlockBlock"
# Enable headless mode
defaults write com.objectiveSee.BlockBlock headlessMode -bool true
}
function install_oversight {
# Install OverSight
if [[ -d "/Applications/OverSight.app" ]]; then
echo "[🍺] OverSight already installed"
return 0
fi