Commit c71a52cb7a

c71a52cb7a80a102e58bd5465e994225ea98c44f

parent: 566a5ce4ec

Unsigned

cmc <hello@cleberg.net> · 2024-03-05 04:34 UTC

initial migration to test org-mode

Layout: unified · split

.gitignore +2 −2
@@ -1,3 +1,3 @@
1public/
2static/syntax*
3.DS_Store 1.DS_Store
2public/*
3sitemap.org
README.md deleted −28
@@ -1,28 +0,0 @@
1# cleberg.net
2
3[cleberg.net](https://cleberg.net) is my personal webpage.
4
5## Overview
6
7This website & blog uses Markdown and [Zola](https://www.getzola.org/).
8
9## Building
10
11Local testing:
12
13```sh
14zola serve
15```
16
17Building:
18
19```sh
20zola build
21```
22
23If you want to build locally and push remotely, you can include `zola build`
24in your CI\CD pipeline, or simply use `scp` after building.
25
26```sh
27scp -r public/* user@remote_host:/var/www/your_website/
28```
README.org added +83
@@ -0,0 +1,83 @@
1#+title: README
2#+description: Project README.
3
4* cleberg.net
5
6[[https://cleberg.net][cleberg.net]] is my personal webpage.
7
8This README is viewable on [[https://git.cleberg.net/cleberg.net.git/tree/README.org][cgit]] (raw), or on [[https://cleberg.net/README.html][my website]] (html).
9
10** Overview
11
12This website & blog uses [[https://orgmode.org/][Org-Mode]], published with =org-publish=.
13
14** Configuration
15
16In order to configure the project for publishing, add the following code to your =~/.emacs= file (for Doom, =~/.doom.d/config.el=).
17
18#+begin_src lisp
19;; org-publish
20(require 'ox-publish)
21
22(setq org-publish-project-alist
23 `(("blog"
24 :base-directory "~/Source/cleberg.net/"
25 :base-extension "org"
26 :recursive t
27 :publishing-directory "~/Source/cleberg.net/public/"
28 :publishing-function org-html-publish-to-html)
29 ;; HTML5
30 :html-doctype "html5"
31 :html-html5-fancy t
32 ;; Disable some Org's HTML defaults
33 :html-head-include-scripts nil
34 :html-head-include-default-style nil
35 ;; Generate sitemap
36 :auto-sitemap t
37 :sitemap-filename "sitemap.org"
38 ;; Customize head, preamble, & postamble
39 :html-head "<link rel='stylesheet' href='/static/styles.css' type='text/css'>"
40 :html-preamble ""
41 :html-postamble ""
42 ("static"
43 :base-directory "~/Source/cleberg.net/static/"
44 :base-extension "css\\|txt\\|jpg\\|gif\\|png"
45 :recursive t
46 :publishing-directory "~/Source/cleberg.net/public/"
47 :publishing-function org-publish-attachment)
48
49 ("cleberg.net" :components ("blog" "static"))));; org-publish
50(require 'ox-publish)
51#+end_src
52
53** Building
54
55Local testing can be done via [[https://www.gnu.org/software/emacs/][Emacs]].
56
57To get running:
58
59#+begin_src shell
60git clone https://git.cleberg.net/cleberg.net.git
61cd cleberg.net
62emacs -nw
63#+end_src
64
65Within Emacs, open any of the repository files. In Doom, I do this with =Spc f f= and selecting =README.org=. Once a file has been opened, you can publish the project with =C-c C-e P a=.
66
67If you need to re-publish unchanged files, I recommend using the following command:
68
69#+begin_src lisp
70M-: (org-publish "project name" t)
71#+end_src
72
73The files will be published to the =public= directory.
74
75** Tasks
76
77*** TODO Create RSS feed
78Possible Solution: [[https://writepermission.com/org-blogging-rss-feed.html][Org mode blogging: RSS feed]]
79Possible Solution: [[https://www.zoraster.org/blog/script-to-generate-rss-feed][Script to Generate RSS Feeds]]
80*** TODO Format all blog posts with =M q=
81*** TODO Create script to auto-generate the =/blog/= list and =/= most recent posts
82Possible Solution: [[https://taingram.org/blog/org-mode-blog.html#orgde61a58][Sitemap]]
83*** TODO Figure out how to get filetags to show up
blog/aes-encryption/index.org added +103
@@ -0,0 +1,103 @@
1#+title: AES Encryption
2#+description: Learn how the AES Encryption algorithm works.
3#+date: <2018-11-28 Wed>
4#+filetags: :security:
5
6* Basic AES
7If you're not familiar with encryption techniques, [[https://en.wikipedia.org/wiki/Advanced_Encryption_Standard][AES]] is the *Advanced
8Encryption Standard*. This specification was established by the National
9Institute of Standards and Technology, sub-selected from the Rijndael family of
10ciphers (128, 192, and 256 bits) in 2001. Furthering its popularity and status,
11the US government chose AES as their default encryption method for top-secret
12data, removing the previous standard which had been in place since 1977.
13
14AES has proven to be an extremely safe encryption method, with 7-round and
158-round attacks making no material improvements since the release of this
16encryption standard almost two decades ago.
17
18#+begin_quote
19Though many papers have been published on the cryptanalysis of AES, the fastest
20single-key attacks on round-reduced AES variants [20, 33] so far are only
21slightly more powerful than those proposed 10 years ago [23,24].
22
23- [[http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf][Bogdonav, et al.]]
24#+end_quote
25
26* How Secure is AES?
27In theory, AES-256 is non-crackable due to the massive number of combinations
28that can be produced. However, AES-128 is no longer recommended as a viable
29implementation to protect important data.
30
31A semi-short [[http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html][comic strip]] from Moserware quickly explains AES for the public to
32understand. Basically AES encrypts the data by obscuring the relationship
33between the data and the encrypted data. Additionally, this method spreads the
34message out. Lastly, the key produced by AES is the secret to decrypting it.
35Someone may know the method of AES, but without the key, they are powerless.
36
37To obscure and spread the data out, AES creates a substitution-permutation
38network. Wikipedia has a wonderful [[https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png][example of an SP network]] available. This
39network sends the data through a set of S boxes (using the unique key) to
40substitute the bits with another block of bits. Then, a P box will permutate, or
41rearrange, the bits. This is done over and over, with the key being derived from
42the last round. For AES, the key size specifies the number of transformation
43rounds: 10, 12, and 14 rounds for 128-bit, 192-bit, and 256-bit keys,
44respectively.
45
46* The Process
471. *KeyExpansion=: Using [[https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard][Rijndael's key schedule]], the keys are dynamically
48 generated.
492. *AddRoundKey*: Each byte of the data is combined with this key using bitwise
50 xor.
513. *SubBytes*: This is followed by the substitution of each byte of data.
524. *ShiftRows*: Then, the final three rows are shifted a certain number of
53 steps, dictated by the cipher.
545. *MixColumns*: After the rows have been shifted, the columns are mixed and
55 combined.
56
57This process does not necessarily stop after one full round. Steps 2 through 5
58will repeat for the number of rounds specified by the key. However, the final
59round excludes the MixColumns step. As you can see, this is a fairly complex
60process. One must have a solid understanding of general mathematic principles to
61fully understand how the sequence works (and to even attempt to find a
62weakness).
63
64According to research done by Bogdanov et al., it would take billions of years
65to brute force a 126-bit key with current hardware. Additionally, this brute
66force attack would require storing 2^{88} bits of data! However, there are a few
67different attacks that have been used to show vulnerabilities with the use of
68this technology. Side-channel attacks use inadvertent leaks of data from the
69hardware or software, which can allow attackers to obtain the key or run
70programs on a user's hardware.
71
72Please note that this is not something you should run out and try to implement
73in your =Hello, World!= app after only a few hours of research. While AES
74(basically all encryption methods) is extremely efficient in what it does, it
75takes a lot of time and patience to understand. If you're looking for something
76which currently implements AES, check out the [[https://www.bouncycastle.org/documentation.html][Legion of the Bouncy Castle]] for
77Java implementations of cryptographic algorithms.
78
79* Why Does Encryption Matter?
80There are limitless reasons to enable encryption at-rest or in-transit for
81various aspects of your digital life. You can research specific examples, such
82as [[https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/][Australia passes new law to thwart strong encryption]]. However, I will simply
83list a few basic reasons to always enable encryption, where feasible:
84
851. Privacy is a human right and is recognized as a national right in some
86 countries (e.g., [[https://www.law.cornell.edu/wex/fourth_amendment][US Fourth Amendment]]).
872. "Why not?" Encryption rarely affects performance or speed, so there's usually
88 not a reason to avoid it in the first place.
893. Your digital identity and activity (texts, emails, phone calls, online
90 accounts, etc.) are extremely valuable and can result in terrible
91 consequences, such as identity theft, if leaked to other parties. Encrypting
92 this data prevents such leaks from ruining lives.
934. Wiping or factory-resetting does not actually wipe all data from the storage
94 device. There are methods to read data from the physical disks/boards inside
95 devices.
965. Corporations, governments, and other nefarious groups/individuals are
97 actively looking for ways to collect personal information about anyone they
98 can. If someone's data is unencrypted, that person may become a target due to
99 the ease of data collection.
100
101​*Read More:*
102
103- [[http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf][Federal Information Processing Standards Publication 197]]
blog/agile-auditing/index.org added +137
@@ -0,0 +1,137 @@
1#+title: Agile Auditing: An Introduction
2#+date: <2023-08-18>
3#+description: A quick introduction to using the Agile methodology in an audit.
4#+filetags: :audit:
5
6* What is Agile Auditing?
7[[https://en.wikipedia.org/wiki/Agile_software_development][Agile]], the collaborative philosophy behind many software development methods,
8has been picking up steam as a beneficial tool to use in the external and
9internal auditing world.
10
11This blog post will walk through commonly used terms within Agile, Scrum, and
12Kanban in order to translate these terms and roles into audit-specific terms.
13
14Whether your team is in charge of a financial statement audit, an attestation
15(SOC 1, SOC 2, etc.), or a unique internal audit, the terms used throughout this
16post should still apply.
17
18* Agile
19To start, I'll take a look at Agile.
20
21#+begin_quote
22The Agile methodology is a project management approach that involves breaking
23the project into phases and emphasizes continuous collaboration and improvement.
24Teams follow a cycle of planning, executing, and evaluating.
25#+end_quote
26
27While this approach may seem familiar to what audit teams have historically
28done, an audit team must make distinct changes in their mentality and how they
29approach and manage a project.
30
31** Agile Values
32The Agile Manifesto, written in 2001 at a summit in Utah, contain a set of four
33main values that comprise the Agile approach:
34
351. Individuals and interactions over processes and tools.
362. Working software over comprehensive documentation.
373. Customer collaboration over contract negotiation.
384. Responding to change over following a plan.
39
40Beyond the four values, [[https://agilemanifesto.org/principles.html][twelve principles]] were also written as part of the
41summit.
42
43In order to relate these values to an audit or attestation engagement, we need
44to shift the focus from software development to the main goal of an engagement:
45completing sufficient audit testing to address to relevant risks over the
46processes and controls at hand.
47
48Audit Examples:
49
50- Engagement teams must value the team members, client contacts, and their
51 interactions over the historical processes and tools that have been used.
52- Engagement teams must value a final report that contains sufficient audit
53 documentation over excessive documentation or scope creep.
54- Engagement teams must collaborate with the audit clients as much as feasible
55 to ensure that both sides are constantly updated with current knowledge of the
56 engagement's status and any potential findings, rather than waiting for
57 pre-set meetings or the end of the engagement to communicate.
58- Engagement teams must be able to respond to change in an engagement's
59 schedule, scope, or environment to ensure that the project is completed in a
60 timely manner and that all relevant areas are tested.
61 - In terms of an audit department's portfolio, they must be able to respond to
62 changes in their company's or client's environment and be able to
63 dynamically change their audit plan accordingly.
64
65* Scrum
66The above section discusses the high-level details of the Agile philosophy and
67how an audit team can potentially mold that mindset into the audit world, but
68how does a team implement these ideas?
69
70There are many methods that use an Agile mindset, but I prefer [[https://en.wikipedia.org/wiki/Scrum_(software_development)][Scrum]]. Scrum is a
71framework based on Agile that enables a team to work through a project through a
72series of roles, ceremonies, artifacts, and values.
73
74Let's dive into each of these individually.
75
76** Scrum Team
77A scrum project is only as good as the team running the project. Standard scrum
78teams are separated into three distinct areas:
79
801. *Product Owner (Client Contact)*: The client contact is the audit equivalent
81 of the product owner in Scrum. They are responsible for partnering with the
82 engagement or audit team to ensure progress is being made, priorities are
83 established, and clear guidance is given when questions or findings arise
84 within each sprint.
852. *Scrum Master (Engagement Lead)*: The engagement or audit team lead is
86 responsible for coaching the team and the client contact on the scrum
87 process, tracking team progress against plan, scheduling necessary resources,
88 and helping remove obstacles.
893. *Scrum Developers (Engagement Members)*: The engagement or audit team is the
90 set of team members responsible for getting the work done. These team members
91 will work on each task, report progress, resolve obstacles, and collaborate
92 with other team members and the client contact to ensure goals are being met.
93
94** Scrum Ceremonies
95Scrum ceremonies are events that are performed on a regular basis.
96
971. *Sprint Planning*: The team works together to plan the upcoming sprint goal
98 and which user stories (tasks) will be added to the sprint to achieve that
99 goal.
1002. *Sprint*: The time period, typically at least one week and no more than one
101 month in length, where the team works on the stories and anything in the
102 backlog.
1033. *Daily Scrum*: A very short meeting held each day, typically 15 minutes, to
104 quickly emphasize alignment on the sprint goal and plan the next 24 hours.
105 Each team member may share what they did the day before, what they'll do
106 today, and any obstacles to their work.
1074. *Sprint Review*: At the end of each sprint, the team will gather and discuss
108 the progress, obstacles, and backlog from the previous sprint.
1095. *Sprint Retrospective*: More specific than the sprint review, the
110 retrospective is meant to discuss what worked and what did not work during
111 the sprint. This may be processes, tools, people, or even things related to
112 the Scrum ceremonies.
113
114One additional ceremony that may be applicable is organizing the backlog. This
115is typically the responsibility of the engagement leader and is meant to
116prioritize and clarify what needs to be done to complete items in the backlog.
117
118** Artifacts
119While artifacts are generally not customizable in the audit world (i.e., each
120control test must include some kind of working paper with evidence supporting
121the test results), I wanted to include some quick notes on associating scrum
122artifact terms with an audit.
123
1241. *Product Backlog*: This is the overall backlog of unfinished audit tasks from
125 all prior sprints.
1262. *Sprint Backlog*: This is the backlog of unfinished audit tasks from one
127 individual sprint.
1283. *Increment*: This is the output of each sprint - generally this is best
129 thought of as any documentation prepared during the sprint, such as risk
130 assessments, control working papers, deficiency analysis, etc.
131
132* Kanban
133Last but not least, Kanban is a methodology that relies on boards to categorize
134work into distinct, descriptive categories that allow an agile or scrum team to
135effectively plan the work of a sprint or project.
136
137See Atlassian's [[https://www.atlassian.com/agile/kanban][Kanban]] page for more information.
content/blog/2022-12-23-alpine-desktop.md → blog/alpine-desktop/index.org renamed +78 −89
@@ -1,11 +1,9 @@
1+++ 1#+title: Alpine Linux as a Desktop OS
2date = 2022-12-23 2#+date: 2022-12-23
3title = "Alpine Linux as a Desktop OS" 3#+description: Learn how to set up Alpine Linux with Sway to use as a desktop operating system.
4description = "Learn how to set up Alpine Linux with Sway to use as a desktop operating system." 4#+filetags: :linux:
5+++
6
7## Isn't Alpine Linux for Servers?
8 5
6* Isn't Alpine Linux for Servers?
9This is a question I see a lot when people are presented with an example 7This is a question I see a lot when people are presented with an example
10of Alpine Linux running as a desktop OS. 8of Alpine Linux running as a desktop OS.
11 9
@@ -15,76 +13,73 @@ functioning at a productive level for desktop users.
15This post is documentation of how I installed and modified Alpine Linux 13This post is documentation of how I installed and modified Alpine Linux
16to become my daily desktop OS. 14to become my daily desktop OS.
17 15
18## Installation 16* Installation
19
20Note that I cover the installation of Alpine Linux in my other post, so 17Note that I cover the installation of Alpine Linux in my other post, so
21I won't repeat it here: [Alpine Linux: My New Server 18I won't repeat it here: [[../alpine-linux/][Alpine Linux: My New
22OS](/blog/alpine-linux/). 19Server OS]].
23 20
24Basically, get a bootable USB or whatever you prefer with Alpine on it, 21Basically, get a bootable USB or whatever you prefer with Alpine on it,
25boot the ISO, and run the setup script. 22boot the ISO, and run the setup script.
26 23
27```sh 24#+begin_src sh
28setup-alpine 25setup-alpine
29``` 26#+end_src
30 27
31Once you have gone through all the options and installer finishes 28Once you have gone through all the options and installer finishes
32without errors, reboot. 29without errors, reboot.
33 30
34```sh 31#+begin_src sh
35reboot 32reboot
36``` 33#+end_src
37
38## Initial Setup
39 34
35* Initial Setup
40Once Alpine is installed and the machine has rebooted, login is as root 36Once Alpine is installed and the machine has rebooted, login is as root
41initially or `su` to root once you log in as your user. From 37initially or =su= to root once you log in as your user. From here, you
42here, you should start by updating and upgrading the system in case the 38should start by updating and upgrading the system in case the ISO was
43ISO was not fully up-to-date. 39not fully up-to-date.
44 40
45```sh 41#+begin_src sh
46# Update and upgrade system 42# Update and upgrade system
47apk -U update && apk -U upgrade 43apk -U update && apk -U upgrade
48 44
49# Add an editor so we can enable the community repository 45# Add an editor so we can enable the community repository
50apk add nano 46apk add nano
51``` 47#+end_src
52 48
53You need to uncomment the `community` repository for your 49You need to uncomment the =community= repository for your version of
54version of Alpine Linux. 50Alpine Linux.
55 51
56For v3.17, the `repositories` file should look like this: 52For v3.17, the =repositories= file should look like this:
57 53
58```sh 54#+begin_src sh
59nano /etc/apk/repositories 55nano /etc/apk/repositories
60``` 56#+end_src
61 57
62```conf 58#+begin_src conf
63#/media/sda/apks 59#/media/sda/apks
64http://mirrors.gigenet.com/alpinelinux/v3.17/main 60http://mirrors.gigenet.com/alpinelinux/v3.17/main
65http://mirrors.gigenet.com/alpinelinux/v3.17/community 61http://mirrors.gigenet.com/alpinelinux/v3.17/community
66#http://mirrors.gigenet.com/alpinelinux/edge/main 62#http://mirrors.gigenet.com/alpinelinux/edge/main
67#http://mirrors.gigenet.com/alpinelinux/edge/community 63#http://mirrors.gigenet.com/alpinelinux/edge/community
68#http://mirrors.gigenet.com/alpinelinux/edge/testing 64#http://mirrors.gigenet.com/alpinelinux/edge/testing
69``` 65#+end_src
70 66
71```sh 67#+begin_src sh
72# Add the rest of your packages 68# Add the rest of your packages
73apk add linux-firmware iwd doas git curl wget 69apk add linux-firmware iwd doas git curl wget
74 70
75# Add yourself to the wheel group so you can use the doas command 71# Add yourself to the wheel group so you can use the doas command
76adduser $USER wheel 72adduser $USER wheel
77``` 73#+end_src
78
79## Window Manager (Desktop)
80 74
81The [Sway installation guide](https://wiki.alpinelinux.org/wiki/Sway) 75* Window Manager (Desktop)
76The [[https://wiki.alpinelinux.org/wiki/Sway][Sway installation guide]]
82has everything you need to get Sway working on Alpine. 77has everything you need to get Sway working on Alpine.
83 78
84However, I'll include a brief list of the commands I ran and their 79However, I'll include a brief list of the commands I ran and their
85purpose for posterity here. 80purpose for posterity here.
86 81
87```sh 82#+begin_src sh
88# Add eudev and set it up 83# Add eudev and set it up
89apk add eudev 84apk add eudev
90setup-devd udev 85setup-devd udev
@@ -121,43 +116,41 @@ apk add \ # Install optional dependencies:
121 swaylock swaylockd \ # lockscreen tool 116 swaylock swaylockd \ # lockscreen tool
122 swaybg \ # wallpaper daemon 117 swaybg \ # wallpaper daemon
123 swayidle # idle management (DPMS) daemon 118 swayidle # idle management (DPMS) daemon
124``` 119#+end_src
125 120
126Once you have the packages installed and set-up, you need to export the 121Once you have the packages installed and set-up, you need to export the
127`XDG_RUNTIME_DIR` upon login. To do this, edit your 122=XDG_RUNTIME_DIR= upon login. To do this, edit your =.profile= file.
128`.profile` file.
129 123
130If you use another shell, such as `zsh`, you need to edit 124If you use another shell, such as =zsh=, you need to edit that shell's
131that shell's profile (e.g., `~/.zprofile`)! 125profile (e.g., =~/.zprofile=)!
132 126
133```sh 127#+begin_src sh
134nano ~/.profile 128nano ~/.profile
135``` 129#+end_src
136 130
137Within the file, paste this: 131Within the file, paste this:
138 132
139```sh 133#+begin_src sh
140if test -z "${XDG_RUNTIME_DIR}"; then 134if test -z "${XDG_RUNTIME_DIR}"; then
141 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir 135 export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir
142 if ! test -d "${XDG_RUNTIME_DIR}"; then 136 if ! test -d "${XDG_RUNTIME_DIR}"; then
143 mkdir "${XDG_RUNTIME_DIR}" 137 mkdir "${XDG_RUNTIME_DIR}"
144 chmod 0700 "${XDG_RUNTIME_DIR}" 138 chmod 0700 "${XDG_RUNTIME_DIR}"
145 fi 139 fi
146fi 140fi
147``` 141#+end_src
148 142
149Once that's complete, you can launch Sway manually. 143Once that's complete, you can launch Sway manually.
150 144
151```sh 145#+begin_src sh
152dbus-run-session -- sway 146dbus-run-session -- sway
153``` 147#+end_src
154
155## Personal Touches
156 148
149** Personal Touches
157I also added the following packages, per my personal preferences and 150I also added the following packages, per my personal preferences and
158situation. 151situation.
159 152
160```sh 153#+begin_src sh
161doas apk add brightnessctl \ # Brightness controller 154doas apk add brightnessctl \ # Brightness controller
162 zsh \ # Shell 155 zsh \ # Shell
163 firefox \ # Browser 156 firefox \ # Browser
@@ -167,93 +160,89 @@ doas apk add brightnessctl \ # Brightness controller
167 neomutt \ # CLI email client 160 neomutt \ # CLI email client
168 thunderbird \ # GUI email client 161 thunderbird \ # GUI email client
169 gnupg # GPG key manager 162 gnupg # GPG key manager
170``` 163#+end_src
171 164
172From here, I use my Syncthing storage to pull all the configuration 165From here, I use my Syncthing storage to pull all the configuration
173files I stored from prior desktops, such as [my 166files I stored from prior desktops, such as
174dotfiles](https://git.sr.ht/~cmc/dotfiles). 167[[https://git.sr.ht/~cmc/dotfiles][my dotfiles]].
175
176## Resolving Issues
177 168
178### WiFi Issues 169* Resolving Issues
170** WiFi Issues
171I initially tried to set up my Wi-Fi the standard way with =iwd=, but it
172didn't work.
179 173
180I initially tried to set up my Wi-Fi the standard way with 174Here is what I initially tried (I did all of this as =root=):
181`iwd`, but it didn't work.
182 175
183Here is what I initially tried (I did all of this as `root`): 176#+begin_src sh
184
185```sh
186apk add iwd 177apk add iwd
187rc-service iwd start 178rc-service iwd start
188iwctl station wlan0 connect <SSID> # This will prompt for the password 179iwctl station wlan0 connect <SSID> # This will prompt for the password
189rc-update add iwd boot && rc-update add dbus boot 180rc-update add iwd boot && rc-update add dbus boot
190``` 181#+end_src
191 182
192Then, I added the Wi-Fi entry to the bottom of the networking interface 183Then, I added the Wi-Fi entry to the bottom of the networking interface
193file: 184file:
194 185
195```sh 186#+begin_src sh
196nano /etc/network/interfaces 187nano /etc/network/interfaces
197``` 188#+end_src
198 189
199```conf 190#+begin_src conf
200auto wlan0 191auto wlan0
201iface wlan0 inet dhcp 192iface wlan0 inet dhcp
202``` 193#+end_src
203 194
204Finally, restart the networking service: 195Finally, restart the networking service:
205 196
206```sh 197#+begin_src sh
207rc-service networking restart 198rc-service networking restart
208``` 199#+end_src
209 200
210My Wi-Fi interface would receive an IP address from the router, but it 201My Wi-Fi interface would receive an IP address from the router, but it
211could not ping anything in the network. To solve the Wi-Fi issues, I 202could not ping anything in the network. To solve the Wi-Fi issues, I
212originally upgraded to Alpine's `edge` repositories, which 203originally upgraded to Alpine's =edge= repositories, which was
213was unnecessary. 204unnecessary.
214 205
215Really, the solution was to enable the 206Really, the solution was to enable the =NameResolvingService=resolvconf=
216`NameResolvingService=resolvconf` in 207in =/etc/iwd/main.conf=.
217`/etc/iwd/main.conf`.
218 208
219```sh 209#+begin_src sh
220doas nano /etc/iwd/main.conf 210doas nano /etc/iwd/main.conf
221``` 211#+end_src
222 212
223```conf 213#+begin_src conf
224[Network] 214[Network]
225 215
226NameResolvingService=resolvconf 216NameResolvingService=resolvconf
227``` 217#+end_src
228 218
229Once I finished this process, my Wi-Fi is working flawlessly. 219Once I finished this process, my Wi-Fi is working flawlessly.
230 220
231### Sound Issues 221** Sound Issues
232
233Same as with the Wi-Fi, I had no sound and could not control the 222Same as with the Wi-Fi, I had no sound and could not control the
234mute/unmute or volume buttons on my laptop. 223mute/unmute or volume buttons on my laptop.
235 224
236To resolve this, I installed 225To resolve this, I installed
237[pipewire](https://wiki.alpinelinux.org/wiki/PipeWire). 226[[https://wiki.alpinelinux.org/wiki/PipeWire][pipewire]].
238 227
239```sh 228#+begin_src sh
240# Add your user to the following groups 229# Add your user to the following groups
241addgroup $USER audio 230addgroup $USER audio
242addgroup $USER video 231addgroup $USER video
243 232
244# Install pipewire and other useful packages 233# Install pipewire and other useful packages
245apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa 234apk add pipewire wireplumber pipewire-pulse pipewire-jack pipewire-alsa
246``` 235#+end_src
247 236
248Finally, I needed to add `/usr/libexec/pipewire-launcher` to 237Finally, I needed to add =/usr/libexec/pipewire-launcher= to my
249my `.config/sway/config` file so that Pipewire would run 238=.config/sway/config= file so that Pipewire would run every time I
250every time I launched sway. 239launched sway.
251 240
252```sh 241#+begin_src sh
253nano ~/.config/sway/config 242nano ~/.config/sway/config
254``` 243#+end_src
255 244
256```conf 245#+begin_src conf
257# Run pipewire audio server 246# Run pipewire audio server
258exec /usr/libexec/pipewire-launcher 247exec /usr/libexec/pipewire-launcher
259 248
@@ -262,7 +251,7 @@ bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT
262bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5% 251bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -5%
263bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle 252bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle
264bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle 253bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle
265``` 254#+end_src
266 255
267Note that I do not use bluetooth or screen sharing, so I won't cover 256Note that I do not use bluetooth or screen sharing, so I won't cover
268those options in this post. 257those options in this post.
blog/alpine-linux/index.org added +269
@@ -0,0 +1,269 @@
1#+title: Alpine Linux: My New Server OS
2#+date: 2022-10-22
3#+description: A retrospective on installing and configuring Alpine Linux as my new server operating system.
4#+filetags: :linux:
5
6* Alpine Linux
7[[https://alpinelinux.org][Alpine Linux]] is a very small distro, built
8on musl libc and busybox. It uses ash as the default shell, OpenRC as
9the init system, and apk as the package manager. According to their
10website, an Alpine container "requires no more than 8 MB and a minimal
11installation to disk requires around 130 MB of storage." An actual bare
12metal machine is recommended to have 100 MB of RAM and 0-700 MB of
13storage space.
14
15Historically, I've used Ubuntu's minimal installation image as my server
16OS for the last five years. Ubuntu worked well and helped as my original
17server contained an nVidia GPU and no onboard graphics, so quite a few
18distros won't boot or install without a lot of tinkering.
19
20Alpine has given me a huge increase in performance across my Docker apps
21and Nginx websites. CPU load for the new server I'm using to test Alpine
22hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100 CPU @
233.70GHz.
24
25The only services I haven't moved over to Alpine are Plex Media Server
26and Syncthing, which may increase CPU load quite a bit depending on how
27many streams are running.
28
29** Installation
30In terms of installation, Alpine has an incredibly useful
31[[https://wiki.alpinelinux.org/wiki/Installation][wiki]] that will guide
32a user throughout the installation and post-installation processes, as
33well as various other articles and guides.
34
35To install Alpine, find an appropriate
36[[https://alpinelinux.org/downloads/][image to download]] and flash it
37to a USB using software such as Rufus or Etcher. I opted to use the
38Standard image for my x86_{64} architecture.
39
40Once the USB is ready, plug it into the machine and reboot. Note that
41you may have to use a key such as =Esc= or =F1-12= to access the boot
42menu. The Alpine Linux terminal will load quickly and for a login.
43
44To log in to the installation image, use the =root= account; there is no
45password. Once logged-in, execute the setup command:
46
47#+begin_src sh
48setup-alpine
49#+end_src
50
51The setup script will ask a series of questions to configure the system.
52Be sure to answer carefully or else you may have to re-configure the
53system after boot.
54
55- Keyboard Layout (Local keyboard language and usage mode, e.g., us and
56 variant of us-nodeadkeys.)
57- Hostname (The name for the computer.)
58- Network (For example, automatic IP address discovery with the "DHCP"
59 protocol.)
60- DNS Servers (Domain Name Servers to query. For privacy reasons, it is
61 NOT recommended to route every local request to servers like Google's
62 8.8.8.8 .)
63- Timezone
64- Proxy (Proxy server to use for accessing the web. Use "none" for
65 direct connections to the internet.)
66- Mirror (From where to download packages. Choose the organization you
67 trust giving your usage patterns to.)
68- SSH (Secure SHell remote access server. "Openssh" is part of the
69 default install image. Use "none" to disable remote login, e.g. on
70 laptops.)
71- NTP (Network Time Protocol client used for keeping the system clock in
72 sync with a time-server. Package "chrony" is part of the default
73 install image.)
74- Disk Mode (Select between diskless (disk="none"), "data" or "sys", as
75 described above.)
76
77Once the setup script is finished, be sure to reboot the machine and
78remove the USB device.
79
80#+begin_src sh
81reboot
82#+end_src
83
84** Post-Installation
85There are many things you can do once your Alpine Linux system is up and
86running, and it largely depends on what you'll use the machine for. I'm
87going to walk through my personal post-installation setup for my web
88server.
89
901. Upgrade the System
91
92 First, login as =root= in order to update and upgrade the system:
93
94 #+begin_src sh
95 apk -U upgrade
96 #+end_src
97
982. Adding a User
99
100 I needed to add a user so that I don't need to log in as root. Note
101 that if you're used to using the =sudo= command, you will now need to
102 use the =doas= command on Alpine Linux.
103
104 #+begin_src sh
105 apk add doas
106 adduser <username>
107 adduser <username> wheel
108 #+end_src
109
110 You can now log out and log back in using the newly-created user:
111
112 #+begin_src sh
113 exit
114 #+end_src
115
1163. Enable Community Packages
117
118 In order to install more common packages that aren't found in the
119 =main= repository, you will need to enable the =community=
120 repository:
121
122 #+begin_src sh
123 doas nano /etc/apk/repositories
124 #+end_src
125
126 Uncomment the community line for whichever version of Alpine you're
127 running:
128
129 #+begin_src sh
130 /media/usb/apks
131 http://dl-cdn.alpinelinux.org/alpine/v3.16/main
132 http://dl-cdn.alpinelinux.org/alpine/v3.16/community
133 #http://dl-cdn.alpinelinux.org/alpine/edge/main
134 #http://dl-cdn.alpinelinux.org/alpine/edge/community
135 #http://dl-cdn.alpinelinux.org/alpine/edge/testing
136 #+end_src
137
1384. Install Required Packages
139
140 Now that the community packages are available, you can install any
141 packages you need. In my case, I installed the web server packages I
142 need for my services:
143
144 #+begin_src sh
145 doas apk add nano nginx docker docker-compose ufw
146 #+end_src
147
1485. SSH
149
150 If you didn't install OpenSSH as part of the installation, you can do
151 so now:
152
153 #+begin_src sh
154 doas apk add openssh
155 #+end_src
156
157 Next, either create a new key or copy your SSH key to the server from
158 your current machines:
159
160 #+begin_src sh
161 # Create a new key
162 ssh-keygen
163 #+end_src
164
165 If you need to copy an existing SSH key from a current machine:
166
167 #+begin_src sh
168 # Copy key from existing machines
169 ssh-copy-id <username>@<ip_address>
170 #+end_src
171
1726. Firewall
173
174 Lastly, I installed =ufw= above as my firewall. To set up, default to
175 deny incoming and allow outgoing connections. Then selectively allow
176 other ports or apps as needed.
177
178 #+begin_src sh
179 doas ufw default deny incoming
180 doas ufw default allow outgoing
181 doas ufw allow SSH
182 doas ufw allow "WWW Full"
183 doas ufw allow 9418 # Git server port
184 #+end_src
185
1867. Change Hostname
187
188 If you don't like the hostname set during installation, you just need
189 to edit two files. First, edit the simple hostname file:
190
191 #+begin_src sh
192 doas nano /etc/hostname
193 #+end_src
194
195 #+begin_src sh
196 <hostname>
197 #+end_src
198
199 Next, edit the =hosts= file:
200
201 #+begin_src sh
202 doas nano /etc/hosts
203 #+end_src
204
205 #+begin_src sh
206 127.0.0.1 <hostname>.local <hostname> localhost.local localhost
207 ::1 <hostname> <hostname>.local
208 #+end_src
209
210* Nginx Web Server
211To set up my web server, I simply created the =www= user and created the
212necessary files.
213
214#+begin_src sh
215doas adduser -D -g 'www' www
216mkdir /www
217doas mkdir /www
218doas chown -R www:www /var/lib/nginx/
219doas chown -R www:www /www
220#+end_src
221
222If you're running a simple webroot, you can alter the main =nginx.conf=
223file. Otherwise, you can drop configuration files in the following
224directory. You don't need to enable or symlink the configuration file
225like you do in other systems.
226
227#+begin_src sh
228doas nano /etc/nginx/http.d/example_website.conf
229#+end_src
230
231Once the configuration is set and pointed at the =/www= directory to
232serve files, enable the Nginx service:
233
234#+begin_src sh
235# Note that 'default' must be included or Nginx will not start on boot
236doas rc-update add nginx default
237#+end_src
238
239* Docker Containers
240Docker works exactly the same as other systems. Either execute a
241=docker run= command or create a =docker-compose.yml= file and do
242=docker-compose up -d=.
243
244* Git Server
245I went in-depth on how to self-host a git server in another post:
246[[../git-server/][Self-Hosting a Personal Git Server]].
247
248However, there are a few differences with Alpine. First note that in
249order to change the =git= user's shell, you must do a few things a
250little different:
251
252#+begin_src sh
253doas apk add libuser
254doas touch /etc/login.defs
255doas mkdir /etc/default
256doas touch /etc/default/useradd
257doas lchsh git
258#+end_src
259
260* Thoughts on Alpine
261So far, I love Alpine Linux. I have no complaints about anything at this
262point, but I'm not completely finished with the migration yet. Once I'm
263able to upgrade my hardware to a rack-mounted server, I will migrate
264Plex and Syncthing over to Alpine as well - possibly putting Plex into a
265container or VM.
266
267The performance is stellar, the =apk= package manager is seamless, and
268system administration tasks are effortless. My only regret is that I
269didn't install Alpine sooner.
content/blog/2023-10-15-alpine-ssh-hardening.md → blog/alpine-ssh-hardening/index.org renamed +37 −40
@@ -1,74 +1,71 @@
1+++ 1#+title: SSH Hardening for Alpine Linux
2date = 2023-10-15 2#+date: 2023-10-15
3title = "SSH Hardening for Alpine Linux" 3#+description: A quick guide to harden SSH configuration on Alpine.
4description = "A quick guide to harden SSH configuration on Alpine." 4#+filetags: :linux:
5+++
6
7## Overview
8 5
6* Overview
9This guide follows the standard 7This guide follows the standard
10[ssh-audit](https://www.ssh-audit.com/hardening_guides.html) hardening 8[[https://www.ssh-audit.com/hardening_guides.html][ssh-audit]] hardening
11guide, tweaked for Alpine Linux. 9guide, tweaked for Alpine Linux.
12 10
13## Hardening Guide 11* Hardening Guide
14 12These steps must be performed as root. You can try to use =doas= or
15These steps must be performed as root. You can try to use 13=sudo=, but there may be issues.
16`doas` or `sudo`, but there may be issues.
17 14
181. Re-generate the RSA and ED25519 keys 151. Re-generate the RSA and ED25519 keys
19 16
20```sh 17#+begin_src sh
21rm /etc/ssh/ssh_host_* 18rm /etc/ssh/ssh_host_*
22ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" 19ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
23ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" 20ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
24``` 21#+end_src
25 22
262. Remove small Diffie-Hellman moduli 232. [@2] Remove small Diffie-Hellman moduli
27 24
28```sh 25#+begin_src sh
29awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe 26awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe
30mv /etc/ssh/moduli.safe /etc/ssh/moduli 27mv /etc/ssh/moduli.safe /etc/ssh/moduli
31``` 28#+end_src
32 29
333. Enable the RSA and ED25519 HostKey directives in the 303. [@3] Enable the RSA and ED25519 HostKey directives in the
34 /etc/ssh/sshd~config~ file 31 /etc/ssh/sshd_{config} file
35 32
36```sh 33#+begin_src sh
37sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config 34sed -i 's/^\#HostKey \/etc\/ssh\/ssh_host_\(rsa\|ed25519\)_key$/HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config
38``` 35#+end_src
39 36
404. Restrict supported key exchange, cipher, and MAC algorithms 374. [@4] Restrict supported key exchange, cipher, and MAC algorithms
41 38
42```sh 39#+begin_src sh
43echo -e "\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf 40echo -e "\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf
44``` 41#+end_src
45 42
465. Include the /etc/ssh/sshd~config~.d directory 435. [@5] Include the /etc/ssh/sshd_{config}.d directory
47 44
48```sh 45#+begin_src sh
49echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config 46echo -e "Include /etc/ssh/sshd_config.d/*.conf" > /etc/ssh/sshd_config
50``` 47#+end_src
51 48
526. Restart OpenSSH server 496. [@6] Restart OpenSSH server
53 50
54```sh 51#+begin_src sh
55rc-service sshd restart 52rc-service sshd restart
56``` 53#+end_src
57
58## Testing SSH
59 54
60You can test the results with the `ssh-audit` python script. 55* Testing SSH
56You can test the results with the =ssh-audit= python script.
61 57
62```sh 58#+begin_src sh
63pip3 install ssh-audit 59pip3 install ssh-audit
64ssh-audit localhost 60ssh-audit localhost
65``` 61#+end_src
66 62
67If everything succeeded, the results will show as all green. If anything 63If everything succeeded, the results will show as all green. If anything
68is yellow, orange, or red, you may need to tweak additional settings. 64is yellow, orange, or red, you may need to tweak additional settings.
69 65
70```txt 66#+begin_src txt
71#+caption: ssh audit 67,#+caption: ssh audit
72``` 68#+end_src
73 69
74![ssh-audit](https://img.cleberg.net/blog/20231015-ssh-hardening/ssh-audit.png "ssh-audit") 70#+caption: ssh-audit
71[[https://img.cleberg.net/blog/20231015-ssh-hardening/ssh-audit.png]]
blog/apache-redirect/index.org added +43
@@ -0,0 +1,43 @@
1#+title: Apache Redirect HTML Files to a Directory
2#+date: 2021-10-09
3#+description: A guide on redirecting HTML files to directory in Apache.
4#+filetags: :apache:
5
6* The Problem
7After recently switching static site generators (SSG), my blog URLs
8changed with no option to preserve the classic =.html= extension at the
9end of my blog post URLs.
10
11I really disliked using my old SSG ([[https://jekyllrb.com][Jekyll]])
12and prefer my new tool ([[https://www.getzola.org][Zola]]) much more, so
13I was determined to figure out a way to get the proper redirect set up
14so that people who find my posts online aren't constantly met by 404
15errors.
16
17* The Solution
18To solve this problem, I really needed to solve two pieces:
19
201. Redirect all blog post URL requests from =/blog/some-post.html= to
21 =/blog/some-post/=.
222. Ensure that no other =.html= files are redirected, such as
23 =index.html=.
24
25After /a lot/ of tweaking and testing, I believe I have finally found
26the solution. The solution is shown below.
27
28#+begin_src conf
29RewriteEngine On
30RewriteCond %{REQUEST_URI} !\index.html$ [NC]
31RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L]
32#+end_src
33
34This piece of code in the Apache =.conf= or =.htaccess= file will do the
35following:
36
371. Turn on the RewriteEngine so that we can modify URLs.
382. Ignore any =index.html= files from the rule we are about to specify.
393. Find any =.html= files within the website directory and redirect it
40 to exclude the file extension.
414. The final piece is adding the trailing slash (=/=) at the end of the
42 URL - you'll notice that I don't have an Apache rule for that since
43 Apache handles that automatically.
content/blog/2019-09-09-audit-analytics.md → blog/audit-analytics/index.org renamed +119 −121
@@ -1,17 +1,15 @@
1+++ 1#+title: Data Analysis in Auditing
2date = 2019-09-09 2#+date: 2019-09-09
3title = "Data Analysis in Auditing" 3#+description: Learn how to use data analysis in the world of auditing.
4description = "Learn how to use data analysis in the world of auditing." 4#+filetags: :audit:
5+++ 5
6 6* What Are Data Analytics?
7# What Are Data Analytics? 7A quick aside before I dive into this post: =data analytics= is a vague
8 8term that has become popular in recent years. Think of a =data analytic=
9A quick aside before I dive into this post: `data analytics` is a 9as the output of any data analysis you perform. For example, a pivot
10vague term that has become popular in recent years. Think of a `data 10table or a pie chart could be a data analytic.
11analytic` as the output of any data analysis you perform. For example, 11
12a pivot table or a pie chart could be a data analytic. 12[[https://en.wikipedia.org/wiki/Data_analysis][Data analysis]] is a
13
14[Data analysis](https://en.wikipedia.org/wiki/Data_analysis) is a
15process that utilizes statistics and other mathematical methods to 13process that utilizes statistics and other mathematical methods to
16discover useful information within datasets. This involves examining, 14discover useful information within datasets. This involves examining,
17cleaning, transforming, and modeling data so that you can use the data 15cleaning, transforming, and modeling data so that you can use the data
@@ -23,18 +21,16 @@ make is assuming that everything involved with this process is "data
23analytics". In fact, data analytics are only a small part of the 21analytics". In fact, data analytics are only a small part of the
24process. 22process.
25 23
26See **Figure 1** for a more accurate representation of where data 24See *Figure 1** for a more accurate representation of where data analysis
27analysis sits within the full process. This means that data analysis 25sits within the full process. This means that data analysis does not
28does not include querying or extracting data, selecting samples, or 26include querying or extracting data, selecting samples, or performing
29performing audit tests. These steps can be necessary for an audit (and 27audit tests. These steps can be necessary for an audit (and may even be
30may even be performed by the same associates), but they are not data 28performed by the same associates), but they are not data analytics.
31analytics.
32
33![The Intelligence
34Cycle](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/intelligence_cycle-min.png)
35 29
36# Current Use of Analytics in Auditing 30#+caption: The Intelligence Cycle
31[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/intelligence_cycle-min.png]]
37 32
33* Current Use of Analytics in Auditing
38While data analysis has been an integral part of most businesses and 34While data analysis has been an integral part of most businesses and
39departments for the better part of the last century, only recently have 35departments for the better part of the last century, only recently have
40internal audit functions been adopting this practice. The internal audit 36internal audit functions been adopting this practice. The internal audit
@@ -42,21 +38,23 @@ function works exclusively to provide assurance and consulting services
42to the business areas within the firm (except for internal auditing 38to the business areas within the firm (except for internal auditing
43firms who are hired by different companies to perform their roles). 39firms who are hired by different companies to perform their roles).
44 40
45> Internal Auditing helps an organization accomplish its objectives by 41#+begin_quote
46> bringing a systematic, disciplined approach to evaluate and improve 42Internal Auditing helps an organization accomplish its objectives by
47> the effectiveness of risk management, control and governance 43bringing a systematic, disciplined approach to evaluate and improve the
48> processes. 44effectiveness of risk management, control and governance processes.
49> 45
50> - The IIA's Definition of Internal Audit 46- The IIA's Definition of Internal Audit
47
48#+end_quote
51 49
52Part of the blame for the slow adoption of data analysis can be 50Part of the blame for the slow adoption of data analysis can be
53attributed to the fact that internal auditing is strongly based on 51attributed to the fact that internal auditing is strongly based on
54tradition and following the precedents set by previous auditors. 52tradition and following the precedents set by previous auditors.
55However, there can be no progress without auditors who are willing to 53However, there can be no progress without auditors who are willing to
56break the mold and test new audit techniques. In fact, as of 2018, [only 54break the mold and test new audit techniques. In fact, as of 2018,
5763% of internal audit departments currently utilize data 55[[https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics][only
58analytics](https://www.cpapracticeadvisor.com/accounting-audit/news/12404086/internal-audit-groups-are-lagging-in-data-analytics) 5663% of internal audit departments currently utilize data analytics]] in
59in North America. This number should be as close as possible to 100%. I 57North America. This number should be as close as possible to 100%. I
60have never been part of an audit that would not have benefited from data 58have never been part of an audit that would not have benefited from data
61analytics. 59analytics.
62 60
@@ -66,66 +64,70 @@ Chief Audit Executive that you're going to start implementing analytics
66in the next audit. You need a plan and a system to make the analysis 64in the next audit. You need a plan and a system to make the analysis
67process as effective as possible. 65process as effective as possible.
68 66
69# The DELTA Model 67* The DELTA Model
70
71One of the easiest ways to experiment with data analytics and gain an 68One of the easiest ways to experiment with data analytics and gain an
72understanding of the processes is to implement them within your own 69understanding of the processes is to implement them within your own
73department. But how do we do this if we've never worked with analysis 70department. But how do we do this if we've never worked with analysis
74before? One of the most common places to start is to research some data 71before? One of the most common places to start is to research some data
75analysis models currently available. For this post, we'll take a look 72analysis models currently available. For this post, we'll take a look at
76at the DELTA model. You can take a look at ****Figure 2**** for a quick 73the DELTA model. You can take a look at ****Figure 2***** for a quick
77overview of the model. 74overview of the model.
78 75
79The DELTA model sets a few guidelines for areas wanting to implement 76The DELTA model sets a few guidelines for areas wanting to implement
80data analytics so that the results can be as comprehensive as possible: 77data analytics so that the results can be as comprehensive as possible:
81 78
82- **Data**: Must be clean, accessible, and (usually) unique. 79- *Data*: Must be clean, accessible, and (usually) unique.
83- **Enterprise-Wide Focus**: Key data systems and analytical resources 80- *Enterprise-Wide Focus*: Key data systems and analytical resources
84 must be available for use (by the Internal Audit Function). 81 must be available for use (by the Internal Audit Function).
85- **Leaders**: Must promote a data analytics approach and show the 82- *Leaders*: Must promote a data analytics approach and show the value
86 value of analytical results. 83 of analytical results.
87- **Targets**: Must be set for key areas and risks that the analytics 84- *Targets*: Must be set for key areas and risks that the analytics can
88 can be compared against (KPIs). 85 be compared against (KPIs).
89- **Analysts**: There must be auditors willing and able to perform 86- *Analysts*: There must be auditors willing and able to perform data
90 data analytics or else the system cannot be sustained. 87 analytics or else the system cannot be sustained.
91 88
92![The Delta 89#+caption: The Delta Model
93Model](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/delta-min.png) 90[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/delta-min.png]]
94
95# Finding the Proper KPIs
96 91
92* Finding the Proper KPIs
97Once the Internal Audit Function has decided that they want to start 93Once the Internal Audit Function has decided that they want to start
98using data analytics internally and have ensured they're properly set 94using data analytics internally and have ensured they're properly set up
99up to do so, they need to figure out what they will be testing against. 95to do so, they need to figure out what they will be testing against. Key
100Key Performance Indicators (KPIs) are qualitative or quantitative 96Performance Indicators (KPIs) are qualitative or quantitative factors
101factors that can be evaluated and assessed to determine if the 97that can be evaluated and assessed to determine if the department is
102department is performing well, usually compared to historical or 98performing well, usually compared to historical or industry benchmarks.
103industry benchmarks. Once KPIs have been agreed upon and set, auditors 99Once KPIs have been agreed upon and set, auditors can use data analytics
104can use data analytics to assess and report on these KPIs. This allows 100to assess and report on these KPIs. This allows the person performing
105the person performing the analytics the freedom to express opinions on 101the analytics the freedom to express opinions on the results, whereas
106the results, whereas the results are ambiguous if no KPIs exist. 102the results are ambiguous if no KPIs exist.
107 103
108It should be noted that tracking KPIs in the department can help ensure 104It should be noted that tracking KPIs in the department can help ensure
109you have a rigorous Quality Assurance and Improvement Program (QAIP) in 105you have a rigorous Quality Assurance and Improvement Program (QAIP) in
110accordance with some applicable standards, such as IPPF Standard 1300. 106accordance with some applicable standards, such as IPPF Standard 1300.
111 107
112> The chief audit executive must develop and maintain a quality assurance 108#+begin_quote
113> and improvement program that covers all aspects of the internal audit 109The chief audit executive must develop and maintain a quality assurance
114> activity. 110and improvement program that covers all aspects of the internal audit
115> 111activity.
116> - IPPF Standard 1300 112
113- IPPF Standard 1300
114
115#+end_quote
117 116
118Additionally, IPPF Standard 2060 discusses reporting: 117Additionally, IPPF Standard 2060 discusses reporting:
119 118
120> The chief audit executive must report periodically to senior 119#+begin_quote
121> management and the board on the internal audit activity's purpose, 120The chief audit executive must report periodically to senior management
122> authority, responsibility, and performance relative to its plan and on 121and the board on the internal audit activity's purpose, authority,
123> its conformance with the Code of Ethics and the Standards. Reporting 122responsibility, and performance relative to its plan and on its
124> must also include significant risk and control issues, including fraud 123conformance with the Code of Ethics and the Standards. Reporting must
125> risks, governance issues, and other matters that require the attention 124also include significant risk and control issues, including fraud risks,
126> of senior management and/or the board. 125governance issues, and other matters that require the attention of
127> 126senior management and/or the board.
128> - IPPF Standard 2060 127
128- IPPF Standard 2060
129
130#+end_quote
129 131
130The hardest part of finding KPIs is to determine which KPIs are 132The hardest part of finding KPIs is to determine which KPIs are
131appropriate for your department. Since every department is different and 133appropriate for your department. Since every department is different and
@@ -133,37 +135,35 @@ has different goals, KPIs will vary drastically between companies. To
133give you an idea of where to look, here are some ideas I came up with 135give you an idea of where to look, here are some ideas I came up with
134when discussing the topic with a few colleagues. 136when discussing the topic with a few colleagues.
135 137
136- Efficiency/Budgeting: 138- Efficiency/Budgeting:
137 - Audit hours to staff utilization ratio (annual hours divided by 139 - Audit hours to staff utilization ratio (annual hours divided by
138 total annual work hours). 140 total annual work hours).
139 - Audit hours compared to the number of audits completed. 141 - Audit hours compared to the number of audits completed.
140 - Time between audit steps or to complete the whole audit. E.g., 142 - Time between audit steps or to complete the whole audit. E.g., time
141 time from fieldwork completion to audit report issuance. 143 from fieldwork completion to audit report issuance.
142- Reputation: 144- Reputation:
143 - The frequency that management has requested the services of the 145 - The frequency that management has requested the services of the IAF.
144 IAF. 146 - Management, audit committee, or external audit satisfaction survey
145 - Management, audit committee, or external audit satisfaction 147 results.
146 survey results. 148 - Education, experience, certifications, tenure, and training of the
147 - Education, experience, certifications, tenure, and training of 149 auditors on staff.
148 the auditors on staff. 150- Quality:
149- Quality: 151 - Number and frequency of audit findings. Assign monetary or numerical
150 - Number and frequency of audit findings. Assign monetary or 152 values, if possible.
151 numerical values, if possible. 153 - Percentage of recommendations issued and implemented.
152 - Percentage of recommendations issued and implemented. 154- Planning:
153- Planning: 155 - Percentage or number of key risks audited per year or per audit.
154 - Percentage or number of key risks audited per year or per audit. 156 - Proportion of audit universe audited per year.
155 - Proportion of audit universe audited per year. 157
156 158* Data Analysis Tools
157# Data Analysis Tools
158
159Finally, to be able to analyze and report on the data analysis, auditors 159Finally, to be able to analyze and report on the data analysis, auditors
160need to evaluate the tools at their disposal. There are many options 160need to evaluate the tools at their disposal. There are many options
161available, but a few of the most common ones can easily get the job 161available, but a few of the most common ones can easily get the job
162done. For example, almost every auditor already has access to Microsoft 162done. For example, almost every auditor already has access to Microsoft
163Excel. Excel is more powerful than most people give it credit for and 163Excel. Excel is more powerful than most people give it credit for and
164can accomplish a lot of basic statistics without much work. If you 164can accomplish a lot of basic statistics without much work. If you don't
165don't know a lot about statistics but still want to see some of the 165know a lot about statistics but still want to see some of the more basic
166more basic results, Excel is a great option. 166results, Excel is a great option.
167 167
168To perform more in-depth statistical analysis or to explore large 168To perform more in-depth statistical analysis or to explore large
169datasets that Excel cannot handle, auditors will need to explore other 169datasets that Excel cannot handle, auditors will need to explore other
@@ -182,8 +182,7 @@ increases, it usually means that the learning curve for that tool will
182also be higher. It will take auditors longer to learn how to utilize 182also be higher. It will take auditors longer to learn how to utilize
183Python, R, or ACL versus learning how to utilize Excel. 183Python, R, or ACL versus learning how to utilize Excel.
184 184
185# Visualization 185* Visualization
186
187Once an auditor has finally found the right data, KPIs, and tools, they 186Once an auditor has finally found the right data, KPIs, and tools, they
188must report these results so that actions can be taken. Performing 187must report these results so that actions can be taken. Performing
189in-depth data analysis is only useful if the results are understood by 188in-depth data analysis is only useful if the results are understood by
@@ -196,34 +195,33 @@ Microsoft PowerBI and Tableau Desktop. However, other tools exist such
196as JMP, Plotly, Qlikview, Alteryx, or D3. Some require commercial 195as JMP, Plotly, Qlikview, Alteryx, or D3. Some require commercial
197licenses while others are simply free to use. For corporate data, you 196licenses while others are simply free to use. For corporate data, you
198may want to make sure that the tool does not communicate any of the data 197may want to make sure that the tool does not communicate any of the data
199outside the company (such as cloud storage). I won't be going into 198outside the company (such as cloud storage). I won't be going into depth
200depth on any of these tools since visualization is largely a subjective 199on any of these tools since visualization is largely a subjective and
201and creative experience, but remember to constantly explore new options 200creative experience, but remember to constantly explore new options as
202as you repeat the process. 201you repeat the process.
203 202
204Lastly, let's take a look at an example of data visualization. This 203Lastly, let's take a look at an example of data visualization. This
205example comes from a [blog post written by Kushal 204example comes from a
206Chakrabarti](https://talent.works/2018/03/28/the-science-of-the-job-search-part-iii-61-of-entry-level-jobs-require-3-years-of-experience/) 205[[https://talent.works/2018/03/28/the-science-of-the-job-search-part-iii-61-of-entry-level-jobs-require-3-years-of-experience/][blog
207in 2018 about the percent of entry-level US jobs that require 206post written by Kushal Chakrabarti]] in 2018 about the percent of
208experience. **Figure 3** shows us an easy-to-digest picture of the data. 207entry-level US jobs that require experience. *Figure 3** shows us an
209We can quickly tell that only about 12.5% of entry-level jobs don't 208easy-to-digest picture of the data. We can quickly tell that only about
210require experience. 20912.5% of entry-level jobs don't require experience.
211 210
212This is the kind of result that easily describes the data for you. 211This is the kind of result that easily describes the data for you.
213However, make sure to include an explanation of what the results mean. 212However, make sure to include an explanation of what the results mean.
214Don't let the reader assume what the data means, especially if it 213Don't let the reader assume what the data means, especially if it
215relates to a complex subject. *Tell a story* about the data and why the 214relates to a complex subject. /Tell a story/ about the data and why the
216results matter. For example, **Figure 4** shows a part of the 215results matter. For example, *Figure 4** shows a part of the explanation
217explanation the author gives to illustrate his point. 216the author gives to illustrate his point.
218
219![Entry-Level
220Visualization](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example-min.png)
221 217
222![Visualization 218#+caption: Entry-Level Visualization
223Explanation](https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example_explanation-min.png) 219[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example-min.png]]
224 220
225# Wrap-Up 221#+caption: Visualization Explanation
222[[https://img.cleberg.net/blog/20190909-data-analysis-in-auditing/vis_example_explanation-min.png]]
226 223
224* Wrap-Up
227While this is not an all-encompassing program that you can just adopt 225While this is not an all-encompassing program that you can just adopt
228into your department, it should be enough to get anyone started on the 226into your department, it should be enough to get anyone started on the
229process of understanding and implementing data analytics. Always 227process of understanding and implementing data analytics. Always
blog/audit-dashboard/index.org added +171
@@ -0,0 +1,171 @@
1#+title: Building an Audit Status Dashboard
2#+date: 2024-01-26
3#+description: Learn how to utilize Alteryx Designer and Power BI Desktop to build a simple status tracking dashboard for an audit or other engagement.
4#+filetags: :audit:
5
6Alteryx and Power BI are powerful tools that can help turn your
7old-school audit trackers into interactive tools that provide useful
8insights and potential action plans.
9
10With these tools, we are going to build the following dashboard:
11
12[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png]]
13[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png]]
14
15* Requirements
16This project assumes the following:
17
18- You have access to Alteryx Designer and Power BI Desktop.
19 - If you only have Power BI Desktop, you may need to perform some
20 analysis in Power BI instead of Alteryx.
21- Your data is in a format that can be imported into Alteryx and/or
22 Power BI.
23- You have a basic understanding of data types and visualization.
24
25* Alteryx: Data Preparation & Analysis
26** Import Data
27With Alteryx, importing data is easy with the use of the =Input Data=
28tool. Simply drag this tool onto the canvas from the =In/Out= tab in the
29Ribbon to create it as a node.
30
31You can choose the File Format manually or simply connect to your
32file/database and let Alteryx determine the format for you. For this
33example, we will be importing an Excel file and changing the
34=Start Data Import on Line= variable to =2=.
35
36#+caption: Alteryx Excel Import
37[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_import.png]]
38
39** Transform Data
40Next, let's replace null data and remove whitespace to clean up our
41data. We can do this with the =Data Cleansing= tool in the =Preparation=
42tab in the Ribbon.
43
44Ensure that the following options are enabled:
45
46- Replace Nulls
47 - Replace with Blanks (String Fields)
48 - Replace with 0 (Numeric Fields)
49- Remove Unwanted Characters
50 - Leading and Trailing Whitespace
51
52#+caption: Data Cleansing
53[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_cleansing.png]]
54
55For our next step, we will transform the date fields from strings to
56datetime format. Add a =Datetime= tool for each field you want to
57transform - in the example below, I am using the tool twice for the
58"Started On" and "Submitted On" fields.
59
60#+caption: Data Transformation
61[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_transformation.png]]
62
63Now that the dates are in the correct format, let's perform a
64calculation based on those fields. Start by adding a =Filter= tool,
65naming a new Output Column, and pasting the formula below into it (the
66two fields used in this formula must match the output of the =Datetime=
67tools above):
68
69#+begin_src txt
70DateTimeDiff([SubmittedOn_Out],[StartedOn_Out], "days")
71#+end_src
72
73#+caption: Data Analysis
74[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_analysis.png]]
75
76** Export Data
77Finalize the process by exporting the transformed data set to a new
78file, for use in the following visualization step.
79
80#+caption: Data Export
81[[https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_export.png]]
82
83* Power BI: Data Visualization
84** Import Data
85To start, open the Power BI Desktop application. Upon first use, Power
86BI will ask if you want to open an existing dashboard or import new
87data.
88
89As we are creating our first dashboard, let's import our data. In my
90example below, I'm importing data from the "Tracker" sheet of the Excel
91file I'm using for this project.
92
93During this process, I also imported the export from the Alteryx
94workflow above. Therefore, we have two different files available for use
95in our dashboard.
96
97#+caption: Excel Tracker
98[[https://img.cleberg.net/blog/20240126-audit-dashboard/excel_tracker.png]]
99
100#+caption: Power BI Excel Import
101[[https://img.cleberg.net/blog/20240126-audit-dashboard/powerbi_import.png]]
102
103** Add Visuals
104To create the dashboard below, you will need to follow the list
105instructions below and format as needed:
106
107[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png]]
108[[https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png]]
109
110Instructions to create the visuals above:
111
112- =Text Box=: Explain the name and purpose of the dashboard. You can
113 also add images and logos at the top of the dashboard.
114- =Donut Chart=: Overall status of the project.
115 - =Legend=: Status
116 - =Values=: Count of Status
117- =Stacked Column Chart=: Task count by assignee.
118 - =X-axis=: Preparer
119 - =Y-axis=: Count of Control ID
120 - =Legend=: Status
121- =Treemap=: Top N client submitters by average days to submit.
122 - =Details=: Preparer
123 - =Values=: Sum of Avg_DaysToSubmit
124- =Line Chart=: Projected vs. actual hours over time.
125- =Clustered Bar Chart=: Projected vs. actual hours per person.
126- =Slicer & Table= - Upcoming due dates.
127 - =Slicer=:
128 - =Values=: Date Due
129 - =Table=:
130 - =Columns=: Count of Control ID, Date Due, Preparer, Status
131
132** Format the Dashboard
133You can choose a theme in the View tab of the Ribbon. You can even
134browse for custom JSON files that define themes, such as ones found
135online or custom ones created by your organization.
136
137For each visual, you can click the =Format= button in the
138=Visualizations= side pane and explore the options. You can custom
139options such as:
140
141- Visual
142 - Legend
143 - Colors
144 - Data labels
145 - Category labels
146- General
147 - Properties
148 - Title
149 - Effects
150 - Header icons
151 - Tooltips
152 - Alt text
153
154You can always look online for inspiration when trying to decide how
155best to organize and style your dashboard.
156
157* Sharing the Results
158Generally, you have a few different options for sharing your dashboards
159with others:
160
1611. Export the dashboard as a PDF in the file menu of Power BI. This will
162 export all tabs and visuals as they are set when the export button is
163 pressed. You will lose all interactivity with this option.
1642. Send the full Power BI file to those you wish to share the dashboard.
165 This will retain all settings and interactivity. However, you will
166 also need to send the source files if they need to refresh the
167 dashboard and you will need to re-send the files if you make updates.
1683. Store the dashboard in a synced location, such as a shared drive or
169 Microsoft Teams. Depending on how a user configures their local
170 Windows paths, the data source paths may not be compatible for all
171 users with such a setup.
blog/audit-review-template/index.org added +76
@@ -0,0 +1,76 @@
1#+title: Audit Testing Review Template
2#+date: 2023-06-20
3#+description: A handy reference template for audit review.
4#+filetags: :audit:
5
6* Overview
7This post is a /very/ brief overview on the basic process to review
8audit test results, focusing on work done as part of a financial
9statement audit (FSA) or service organization controls (SOC) report.
10
11While there are numerous different things to review and look for - all
12varying wildly depending on the report, client, and tester - this list
13serves as a solid base foundation for a reviewer.
14
15I have used this throughout my career as a starting point to my reviews,
16and it has worked wonders for creating a consistent and objective
17template to my reviews. The goal is to keep this base high-level enough
18to be used on a wide variety of engagements, while still ensuring that
19all key areas are covered.
20
21* Review Template
221. [ ] Check all documents for spelling and grammar.
232. [ ] Ensure all acronyms are fully explained upon first use.
243. [ ] For all people referenced, use their full names and job titles
25 upon first use.
264. [ ] All supporting documents must cross-reference to the lead sheet
27 and vice-versa.
285. [ ] Verify that the control has been adequately tested:
29 - [ ] *Test of Design*: Did the tester obtain information regarding
30 how the control should perform normally and abnormally (e.g.,
31 emergency scenarios)?
32 - [ ] *Test of Operating Effectiveness*: Did the tester inquire,
33 observe, inspect, or re-perform sufficient evidence to support
34 their conclusion over the control? Inquiry alone is not adequate!
356. [ ] For any information used in the control, whether by the control
36 operator or by the tester, did the tester appropriately document the
37 source (system or person), extraction method, parameters, and
38 completeness and accuracy (C&A)?
39 - [ ] For any reports, queries, etc. used in the extraction, did the
40 tester include a copy and notate C&A considerations?
417. [ ] Did the tester document the specific criteria that the control is
42 being tested against?
438. [ ] Did the tester notate in the supporting documents where each
44 criterion was satisfied?
459. [ ] If testing specific policies or procedures, are the documents
46 adequate?
47 - [ ] e.g., a test to validate that a review of policy XYZ occurs
48 periodically should also evaluate the sufficiency of the policy
49 itself, if meant to cover the risk that such a policy does not
50 exist and is not reviewed.
5110. [ ] Does the test cover the appropriate period under review?
52 - [ ] If the test is meant to cover only a portion of the audit
53 period, do other controls exist to mitigate the risks that exist
54 for the remainder of the period?
5511. [ ] For any computer-aided audit tools (CAATs) or other automation
56 techniques used in the test, is the use of such tools explained and
57 appropriately documented?
5812. [ ] If prior-period documentation exists, are there any missing
59 pieces of evidence that would further enhance the quality of the
60 test?
6113. [ ] Was any information discovered during the walkthrough or inquiry
62 phase that was not incorporated into the test?
6314. [ ] Are there new rules or expectations from your company's internal
64 guidance or your regulatory bodies that would affect the audit
65 approach for this control?
6615. [ ] Was an exception, finding, or deficiency identified as a result
67 of this test?
68 - [ ] Was the control deficient in design, operation, or both?
69 - [ ] What was the root cause of the finding?
70 - [ ] Does the finding indicate other findings or potential fraud?
71 - [ ] What's the severity and scope of the finding?
72 - [ ] Do other controls exist as a form of compensation against the
73 finding's severity, and do they mitigate the risk within the
74 control objective?
75 - [ ] Does the finding exist at the end of the period, or was it
76 resolved within the audit period?
content/blog/2021-08-25-audit-sampling.md → blog/audit-sampling/index.org renamed +74 −87
@@ -1,11 +1,9 @@
1+++ 1#+title: Audit Sampling with Python
2date = 2021-08-25 2#+date: 2021-08-25
3title = "Audit Sampling with Python" 3#+description: Learn how to sample populations with Python.
4description = "Learn how to sample populations with Python." 4#+filetags: :audit:
5+++
6
7## Introduction
8 5
6* Introduction
9For anyone who is familiar with internal auditing, external auditing, or 7For anyone who is familiar with internal auditing, external auditing, or
10consulting, you will understand how tedious audit testing can become 8consulting, you will understand how tedious audit testing can become
11when you are required to test large swaths of data. When we cannot 9when you are required to test large swaths of data. When we cannot
@@ -16,37 +14,35 @@ represent the population.
16 14
17However, sampling data within the world of audit still seems to confuse 15However, sampling data within the world of audit still seems to confuse
18quite a lot of people. While some audit-focused tools have introduced 16quite a lot of people. While some audit-focused tools have introduced
19sampling functionality (e.g. Wdesk), many audit departments and firms 17sampling functionality (e.g. Wdesk), many audit departments and firms
20cannot use software like this due to certain constraints, such as the 18cannot use software like this due to certain constraints, such as the
21team's budget or knowledge. Here is where this article comes in: we're 19team's budget or knowledge. Here is where this article comes in: we're
22going to use [Python](https://www.python.org), a free and open-source 20going to use [[https://www.python.org][Python]], a free and open-source
23programming language, to generate random samples from a dataset in order 21programming language, to generate random samples from a dataset in order
24to suffice numerous audit situations. 22to suffice numerous audit situations.
25 23
26## Audit Requirements for Sampling 24* Audit Requirements for Sampling
27
28Before we get into the details of how to sample with Python, I want to 25Before we get into the details of how to sample with Python, I want to
29make sure I discuss the different requirements that auditors may have of 26make sure I discuss the different requirements that auditors may have of
30samples used within their projects. 27samples used within their projects.
31 28
32### Randomness 29** Randomness
33 30First, let's discuss randomness. When testing out new technology to help
34First, let's discuss randomness. When testing out new technology to 31assist with audit sampling, you need to understand exactly how your
35help assist with audit sampling, you need to understand exactly how your
36samples are being generated. For example, if the underlying function is 32samples are being generated. For example, if the underlying function is
37just picking every 57th element from a list, that's not truly random; 33just picking every 57th element from a list, that's not truly random;
38it's a systematic form of sampling. Luckily, since Python is 34it's a systematic form of sampling. Luckily, since Python is
39open-source, we have access to its codebase. Through this blog post, I 35open-source, we have access to its codebase. Through this blog post, I
40will be using the [pandas](https://pandas.pydata.org) module in order to 36will be using the [[https://pandas.pydata.org][pandas]] module in order
41generate the random samples. More specifically, I will be using the 37to generate the random samples. More specifically, I will be using the
42[pandas.DataFrame.sample](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html) 38[[https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html][pandas.DataFrame.sample]]
43function provided by Pandas. 39function provided by Pandas.
44 40
45Now that you know what you're using, you can always check out the code 41Now that you know what you're using, you can always check out the code
46behind `pandas.DataFrame.sample`. This function does a lot of 42behind =pandas.DataFrame.sample=. This function does a lot of work, but
47work, but we really only care about the following snippets of code: 43we really only care about the following snippets of code:
48 44
49```python 45#+begin_src python
50# Process random_state argument 46# Process random_state argument
51rs = com.random_state(random_state) 47rs = com.random_state(random_state)
52 48
@@ -58,24 +54,22 @@ if ignore_index:
58result.index = ibase.default_index(len(result)) 54result.index = ibase.default_index(len(result))
59 55
60return result 56return result
61``` 57#+end_src
62 58
63The block of code above shows you that if you assign a 59The block of code above shows you that if you assign a =random_state=
64`random_state` argument when you run the function, that will 60argument when you run the function, that will be used as a seed number
65be used as a seed number in the random generation and will allow you to 61in the random generation and will allow you to reproduce a sample, given
66reproduce a sample, given that nothing else changes. This is critical to 62that nothing else changes. This is critical to the posterity of audit
67the posterity of audit work. After all, how can you say your audit 63work. After all, how can you say your audit process is adequately
68process is adequately documented if the next person can't run the code 64documented if the next person can't run the code and get the same
69and get the same sample? The final piece here on randomness is to look 65sample? The final piece here on randomness is to look at the
70at the 66[[https://docs.%20python.org/3/library/random.html#random.choice][choice]]
71[choice](https://docs.%20python.org/3/library/random.html#random.choice)
72function used above. This is the crux of the generation and can also be 67function used above. This is the crux of the generation and can also be
73examined for more detailed analysis on its reliability. As far as 68examined for more detailed analysis on its reliability. As far as
74auditing goes, we will trust that these functions are mathematically 69auditing goes, we will trust that these functions are mathematically
75random. 70random.
76 71
77### Sample Sizes 72** Sample Sizes
78
79As mentioned in the intro, sampling is only an effective method of 73As mentioned in the intro, sampling is only an effective method of
80auditing when it truly represents the entire population. While some 74auditing when it truly represents the entire population. While some
81audit departments or firms may consider certain judgmental sample sizes 75audit departments or firms may consider certain judgmental sample sizes
@@ -94,35 +88,32 @@ proprietary, table that will instruct auditors which sample sizes to
94choose. This allows for uniform testing and reduces overall workload. 88choose. This allows for uniform testing and reduces overall workload.
95See the table below for a common implementation of sample sizes: 89See the table below for a common implementation of sample sizes:
96 90
97 Control Frequency Sample Size - High Risk Sample Size - Low Risk 91| Control Frequency | Sample Size - High Risk | Sample Size - Low Risk |
98 ------------------- ------------------------- ------------------------ 92|-------------------+-------------------------+------------------------|
99 More Than Daily 40 25 93| More Than Daily | 40 | 25 |
100 Daily 40 25 94| Daily | 40 | 25 |
101 Weekly 12 5 95| Weekly | 12 | 5 |
102 Monthly 5 3 96| Monthly | 5 | 3 |
103 Quarterly 2 2 97| Quarterly | 2 | 2 |
104 Semi-Annually 1 1 98| Semi-Annually | 1 | 1 |
105 Annually 1 1 99| Annually | 1 | 1 |
106 Ad-hoc 1 1 100| Ad-hoc | 1 | 1 |
107 101
108## Sampling with Python & Pandas 102*** Sampling with Python & Pandas
109
110In this section, I am going to cover a few basic audit situations that 103In this section, I am going to cover a few basic audit situations that
111require sampling. While some situations may require more effort, the 104require sampling. While some situations may require more effort, the
112syntax, organization, and intellect used remain largely the same. If 105syntax, organization, and intellect used remain largely the same. If
113you've never used Python before, note that lines starting with a 106you've never used Python before, note that lines starting with a '=#='
114'`#`' symbol are called comments, and they will be skipped 107symbol are called comments, and they will be skipped by Python. I highly
115by Python. I highly recommend taking a quick tutorial online to 108recommend taking a quick tutorial online to understand the basics of
116understand the basics of Python if any of the code below is confusing to 109Python if any of the code below is confusing to you.
117you.
118
119### Simple Random Sample
120 110
111** Simple Random Sample
121First, let's look at a simple, random sample. The code block below will 112First, let's look at a simple, random sample. The code block below will
122import the `pandas` module, load a data file, sample the 113import the =pandas= module, load a data file, sample the data, and
123data, and export the sample to a file. 114export the sample to a file.
124 115
125```python 116#+begin_src python
126# Import the Pandas module 117# Import the Pandas module
127import pandas 118import pandas
128 119
@@ -140,14 +131,13 @@ sample = df.sample(n=25, random_state=0)
140 131
141# Save the sample to Excel 132# Save the sample to Excel
142sample.to_excel(file_output) 133sample.to_excel(file_output)
143``` 134#+end_src
144
145### Simple Random Sample: Using Multiple Input Files
146 135
136** Simple Random Sample: Using Multiple Input Files
147Now that we've created a simple sample, let's create a sample from 137Now that we've created a simple sample, let's create a sample from
148multiple files. 138multiple files.
149 139
150```python 140#+begin_src python
151# Import the Pandas module 141# Import the Pandas module
152import pandas 142import pandas
153 143
@@ -174,15 +164,14 @@ sample = pandas.concat([sample_01, sample_02, sample_03], ignore_index=True)
174 164
175# Save the sample to Excel 165# Save the sample to Excel
176sample.to_excel(file_output) 166sample.to_excel(file_output)
177``` 167#+end_src
178
179### Stratified Random Sample
180 168
169** Stratified Random Sample
181Well, what if you need to sample distinct parts of a single file? For 170Well, what if you need to sample distinct parts of a single file? For
182example, let's write some code to separate our data by "Region" and 171example, let's write some code to separate our data by "Region" and
183sample those regions independently. 172sample those regions independently.
184 173
185```python 174#+begin_src python
186# Import the Pandas module 175# Import the Pandas module
187import pandas 176import pandas
188 177
@@ -208,16 +197,15 @@ sample = pandas.concat([sample_east, sample_west], ignore_index=True)
208 197
209# Save the sample to Excel 198# Save the sample to Excel
210sample.to_excel(file_output) 199sample.to_excel(file_output)
211``` 200#+end_src
212
213### Stratified Systematic Sample
214 201
202** Stratified Systematic Sample
215This next example is quite useful if you need audit coverage over a 203This next example is quite useful if you need audit coverage over a
216certain time period. This code will generate samples for each month in 204certain time period. This code will generate samples for each month in
217the data and combine them all together at the end. Obviously, this code 205the data and combine them all together at the end. Obviously, this code
218can be modified to stratify by something other than months, if needed. 206can be modified to stratify by something other than months, if needed.
219 207
220```python 208#+begin_src python
221# Import the Pandas module 209# Import the Pandas module
222import pandas 210import pandas
223 211
@@ -257,21 +245,20 @@ def monthly_stratified_sample(df: pandas.DataFrame, date_column: str, num_select
257sample_size = 3 245sample_size = 3
258sample = monthly_stratified_sample(df, 'Date of Sale', sample_size) 246sample = monthly_stratified_sample(df, 'Date of Sale', sample_size)
259sample.to_excel(file_output) 247sample.to_excel(file_output)
260``` 248#+end_src
261 249
262## Documenting the Results 250*** Documenting the Results
263 251Once you've generated a proper sample, there are a few things left to do
264Once you've generated a proper sample, there are a few things left to 252in order to properly ensure your process is reproducible.
265do in order to properly ensure your process is reproducible. 253
266 2541. Document the sample. Make sure the resulting file is readable and
2671. Document the sample. Make sure the resulting file is readable and 255 includes the documentation listed in the next bullet.
268 includes the documentation listed in the next bullet. 2562. Include documentation around the data source, extraction techniques,
2692. Include documentation around the data source, extraction techniques, 257 any modifications made to the data, and be sure to include a copy of
270 any modifications made to the data, and be sure to include a copy of 258 the script itself.
271 the script itself. 2593. Whenever possible, perform a completeness and accuracy test to ensure
2723. Whenever possible, perform a completeness and accuracy test to 260 your sample is coming from a complete and accurate population. To
273 ensure your sample is coming from a complete and accurate 261 ensure completeness, compare the record count from the data source to
274 population. To ensure completeness, compare the record count from 262 the record count loaded into Python. To ensure accuracy, test a small
275 the data source to the record count loaded into Python. To ensure 263 sample against the source data (e.g., test 5 sales against the
276 accuracy, test a small sample against the source data (e.g., test 5 264 database to see if the details are accurate).
277 sales against the database to see if the details are accurate).
content/blog/2023-09-19-audit-sql-scripts.md → blog/audit-sql-scripts/index.org renamed +45 −55
@@ -1,24 +1,21 @@
1+++ 1#+title: Useful SQL Scripts for Auditing Logical Access
2date = 2023-09-19 2#+date: 2023-09-19
3title = "Useful SQL Scripts for Auditing Logical Access" 3#+description: A reference of SQL scripts for auditing logical access for common databases.
4description = "A reference of SQL scripts for auditing logical access for common databases." 4#+filetags: :audit:
5+++
6
7## Overview
8 5
6* Overview
9When you have to scope a database into your engagement, you may be 7When you have to scope a database into your engagement, you may be
10curious how to best extract the information from the database. While 8curious how to best extract the information from the database. While
11there are numerous different methods to extract this type of 9there are numerous different methods to extract this type of
12information, I'm going to show an example of how to gather all users 10information, I'm going to show an example of how to gather all users and
13and privileges from three main database types: Oracle, Microsoft SQL, 11privileges from three main database types: Oracle, Microsoft SQL, and
14and MySQL. 12MySQL.
15
16## Oracle
17 13
14* Oracle
18You can use the following SQL script to see all users and their 15You can use the following SQL script to see all users and their
19privileges in an Oracle database: 16privileges in an Oracle database:
20 17
21```sql 18#+begin_src sql
22SELECT 19SELECT
23 grantee AS "User", 20 grantee AS "User",
24 privilege AS "Privilege" 21 privilege AS "Privilege"
@@ -34,40 +31,37 @@ FROM
34 dba_tab_privs 31 dba_tab_privs
35WHERE 32WHERE
36 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs); 33 grantee IN (SELECT DISTINCT grantee FROM dba_tab_privs);
37``` 34#+end_src
38 35
39This script queries the `dba_sys_privs` and 36This script queries the =dba_sys_privs= and =dba_tab_privs= views to
40`dba_tab_privs` views to retrieve system and table-level 37retrieve system and table-level privileges respectively. It then
41privileges respectively. It then combines the results using 38combines the results using =UNION ALL= to show all users and their
42`UNION ALL` to show all users and their associated 39associated privileges. Please note that this method does not extract
43privileges. Please note that this method does not extract information 40information from the =dba_role_privs= table - use the method below for
44from the `dba_role_privs` table - use the method below for
45that data. 41that data.
46 42
47Please note that you might need appropriate privileges (e.g., DBA 43Please note that you might need appropriate privileges (e.g., DBA
48privileges) to access these views, and you should exercise caution when 44privileges) to access these views, and you should exercise caution when
49querying system tables in a production Oracle database. 45querying system tables in a production Oracle database.
50 46
51### Alternative Oracle Query 47** Alternative Oracle Query
52
53You can also extract each table's information separately and perform 48You can also extract each table's information separately and perform
54processing outside the database to explore and determine the information 49processing outside the database to explore and determine the information
55necessary for the audit: 50necessary for the audit:
56 51
57```sql 52#+begin_src sql
58SELECT * FROM sys.dba_role_privs; 53SELECT ** FROM sys.dba_role_privs;
59SELECT * FROM sys.dba_sys_privs; 54SELECT ** FROM sys.dba_sys_privs;
60SELECT * FROM sys.dba_tab_privs; 55SELECT ** FROM sys.dba_tab_privs;
61SELECT * FROM sys.dba_users; 56SELECT ** FROM sys.dba_users;
62``` 57#+end_src
63
64## Microsoft SQL
65 58
59* Microsoft SQL
66You can use the following SQL script to see all users and their 60You can use the following SQL script to see all users and their
67privileges in a Microsoft SQL Server database 61privileges in a Microsoft SQL Server database
68([source](https://stackoverflow.com/a/30040784)): 62([[https://stackoverflow.com/a/30040784][source]]):
69 63
70```sql 64#+begin_src sql
71/* 65/*
72Security Audit Report 66Security Audit Report
731) List all access provisioned to a sql user or windows user/group directly 671) List all access provisioned to a sql user or windows user/group directly
@@ -98,7 +92,7 @@ ObjectName : Name of the object that the user/role is assigned permissions
98 definitions. 92 definitions.
99ColumnName : Name of the column of the object that the user/role is assigned permissions on. This value 93ColumnName : Name of the column of the object that the user/role is assigned permissions on. This value
100 is only populated if the object is a table, view or a table value function. 94 is only populated if the object is a table, view or a table value function.
101*/ 95,*/
102 96
103--List all access provisioned to a sql user or windows user/group directly 97--List all access provisioned to a sql user or windows user/group directly
104SELECT 98SELECT
@@ -212,45 +206,41 @@ ORDER BY
212 perm.[permission_name], 206 perm.[permission_name],
213 perm.[state_desc], 207 perm.[state_desc],
214 obj.type_desc--perm.[class_desc] 208 obj.type_desc--perm.[class_desc]
215``` 209#+end_src
216
217## MySQL
218 210
211* MySQL
219You can use the following SQL script to see all users and their 212You can use the following SQL script to see all users and their
220privileges in a MySQL database: 213privileges in a MySQL database:
221 214
222```sh 215#+begin_src sh
223mysql -u root -p 216mysql -u root -p
224``` 217#+end_src
225 218
226Find all users and hosts with access to the database: 219Find all users and hosts with access to the database:
227 220
228```sql 221#+begin_src sql
229SELECT * FROM information_schema.user_privileges; 222SELECT ** FROM information_schema.user_privileges;
230``` 223#+end_src
231 224
232This script retrieves user information and their associated 225This script retrieves user information and their associated
233database-level privileges from the 226database-level privileges from the =information_schema.user_privileges=
234`information_schema.user_privileges` table in MySQL. It lists 227table in MySQL. It lists various privileges such as SELECT, INSERT,
235various privileges such as SELECT, INSERT, UPDATE, DELETE, CREATE, and 228UPDATE, DELETE, CREATE, and more for each user and database combination.
236more for each user and database combination.
237 229
238Please note that you may need appropriate privileges (e.g., 230Please note that you may need appropriate privileges (e.g., =SELECT=
239`SELECT` privileges on 231privileges on =information_schema.user_privileges=) to access this
240`information_schema.user_privileges`) to access this
241information in a MySQL database. Additionally, some privileges like 232information in a MySQL database. Additionally, some privileges like
242GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all 233GRANT OPTION, EXECUTE, EVENT, and TRIGGER may not be relevant for all
243users and databases. 234users and databases.
244 235
245### Alternative MySQL Query 236** Alternative MySQL Query
246
247You can also grab individual sets of data from MySQL if you prefer to 237You can also grab individual sets of data from MySQL if you prefer to
248join them after extraction. I have marked the queries below with 238join them after extraction. I have marked the queries below with
249`SELECT ...` and excluded most `WHERE` clauses for 239=SELECT ...= and excluded most =WHERE= clauses for brevity. You should
250brevity. You should determine the relevant privileges in-scope and query 240determine the relevant privileges in-scope and query for those
251for those privileges to reduce the length of time to query. 241privileges to reduce the length of time to query.
252 242
253```sql 243#+begin_src sql
254-- Global Permissions 244-- Global Permissions
255SELECT ... FROM mysql.user; 245SELECT ... FROM mysql.user;
256 246
@@ -269,4 +259,4 @@ WHERE db = @db_name;
269-- Password Configuration 259-- Password Configuration
270SHOW GLOBAL VARIABLES LIKE 'validate_password%'; 260SHOW GLOBAL VARIABLES LIKE 'validate_password%';
271SHOW VARIABLES LIKE 'validate_password%'; 261SHOW VARIABLES LIKE 'validate_password%';
272``` 262#+end_src
blog/backblaze-b2/index.org added +176
@@ -0,0 +1,176 @@
1#+title: Getting Started with Backblaze B2 Cloud Storage
2#+date: 2023-06-28
3#+description: An introduction to the free ttier of Backblaze B2 Cloud Storage.
4#+filetags: :sysadmin:
5
6* Overview
7Backblaze [[https://www.backblaze.com/b2/cloud-storage.html][B2 Cloud
8Storage]] is an inexpensive and reliable on-demand cloud storage and
9backup solution.
10
11The service starts at $5/TB/month ($0.005/GB/month) with a download rate
12of $0.01/GB/month.
13
14However, there are free tiers:
15
16- The first 10 GB of storage is free.
17- The first 1 GB of data downloaded each day is free.
18- Class A transactions are free.
19- The first 2500 Class B transactions each day are free.
20- The first 2500 Class C transactions each day are free.
21
22You can see which API calls fall into categories A, B, or C here:
23[[https://www.backblaze.com/b2/b2-transactions-price.html][Pricing
24Organized by API Calls]].
25
26For someone like me, who wants an offsite backup of their server's
27=/home/= directory and various other server configs that fall under 10
28GB total, Backblaze is a great solution from a financial perspective.
29
30* Create An Account
31To start with Backblaze, you'll need to
32[[https://www.backblaze.com/b2/sign-up.html][create a free account]] -
33no payment method is required to sign up.
34
35Once you have an account, you can test out the service with their web
36GUI, their mobile app, or their CLI tool. I'm going to use the CLI tool
37below to test a file upload and then sync an entire directory to my
38Backblaze bucket.
39
40* Create a Bucket
41Before you can start uploading, you need to create a bucket. If you're
42familiar with other object storage services, this will feel familiar. If
43not, it's pretty simple to create one.
44
45As their webpage says:
46
47#+begin_quote
48A bucket is a container that holds files that are uploaded into B2 Cloud
49Storage. The bucket name must be globally unique and must have a minimum
50of 6 characters. A limit of 100 buckets may be created per account. An
51unlimited number of files may be uploaded into a bucket.
52
53#+end_quote
54
55Once you click the =Create a Bucket= button on their webpage or mobile
56app, you need to provide the following:
57
58- Bucket Unique Name
59- Files in Bucket are: =Private= or =Public=
60- Default Encryption: =Disable= or =Enable=
61- Object Lock: =Disable= or =Enable=
62
63For my bucket, I created a private bucket with encryption enabled and
64object lock disabled.
65
66Once your bucket is created, you can test the upload/download feature on
67their web GUI or mobile app! At this point, you have a fully functional
68bucket and account.
69
70* Linux CLI Tool
71** Installation
72To install the =b2= CLI tool, you'll need to download it from the
73[[https://www.backblaze.com/docs/cloud-storage-command-line-tools][CLI
74Tools]] page. I recommend copying the URL from the link that says
75=Linux= and using wget to download it, as shown below.
76
77Once downloaded, make the file executable and move it to a location on
78your =$PATH=, so that you can execute that command from anywhere on the
79machine.
80
81#+begin_src sh
82wget <b2_cli_url>
83chmod +x b2_linux
84mv b2_linux /usr/bin/b2
85#+end_src
86
87** Log In
88The first step after installation is to log in. To do this, execute the
89following command and provide your =<applicationKeyId>= and
90=<applicationKey>=.
91
92If you don't want to provide these values in the command itself, you can
93simply execute the base command and it will request them in an
94interactive prompt.
95
96#+begin_src sh
97# if you want to provide the keys directly:
98b2 authorize-account [<applicationKeyId>] [<applicationKey>]
99
100# or, if you don't want your keys in your shell history:
101b2 authorize-account
102#+end_src
103
104** Upload a Test File
105In order to test the functionality of the CLI tool, I'll start by
106uploading a single test file to the bucket I created above. We can do
107this with the =upload_file= function.
108
109The command is issued as follows:
110
111#+begin_src sh
112b2 upload_file <bucket_name> <local_file> <remote_file>
113#+end_src
114
115In my situation, I executed the following command with my username.
116
117#+begin_src sh
118b2 upload_file my_unique_bucket /home/<user>/test.md test.md
119#+end_src
120
121To confirm that the file was uploaded successfully, list the files in
122your bucket:
123
124#+begin_src sh
125b2 ls <bucket_name>
126#+end_src
127
128#+begin_src txt
129test.md
130#+end_src
131
132** Sync a Directory
133If you have numerous files, you can use the =sync= function to perform
134functionality similar to =rsync=, where you can check what's in your
135bucket and sync anything that is new or modified.
136
137The command is issued as follows:
138
139#+begin_src sh
140b2 sync <source file location> <B2 bucket destination>
141#+end_src
142
143In my case, I can sync my user's entire home directory to my bucket
144without specifying any of the files directly:
145
146#+begin_src sh
147b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
148#+end_src
149
150* Caveats
151** Timing of Updates to the Web GUI
152When performing actions over a bucket, there is a slight delay in the
153web GUI when inspecting a bucket or its file. Note that simple actions
154such as uploading or deleting files may have a delay of a few minutes up
155to 24 hours. In my experience (<10 GB and ~20,000 files), any actions
156took only a few minutes to update across clients.
157
158** Symlinks
159Note that symlinks are resolved by b2, so if you have a link from
160=/home/<user>/nas-storage= that symlinks out to a =/mnt/nas-storage=
161folder that has 10TB of data, =b2= will resolve that link and start
162uploading all 10TB of data linked within the folder.
163
164If you're not sure if you have any symlinks, a symlink will look like
165this (note the =->= symbol):
166
167#+begin_src sh
168> ls -lha
169lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
170#+end_src
171
172You can recursively find symlink in a path with the following command:
173
174#+begin_src sh
175ls -lR /path/to/search | grep '^l'
176#+end_src
content/blog/2022-07-31-bash-it.md → blog/bash-it/index.org renamed +98 −107
@@ -1,117 +1,113 @@
1+++ 1#+title: Upgrade Bash with Bash-It & Ble.sh
2date = 2022-07-31 2#+date: 2022-07-31
3title = "Upgrade Bash with Bash-It & Ble.sh" 3#+description: Learn how to increase the power of bash with Bash-It and Ble.sh.
4description = "Learn how to increase the power of bash with Bash-It and Ble.sh." 4#+filetags: :sysadmin:
5+++
6
7## Bash
8 5
6* Bash
9For those who are not familiar, 7For those who are not familiar,
10[Bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) is a Unix shell 8[[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][Bash]] is a Unix
11that is used as the default login shell for most Linux distributions. 9shell that is used as the default login shell for most Linux
12This shell and command processor should be familiar if you've used 10distributions. This shell and command processor should be familiar if
13Linux (or older version of macOS) before. 11you've used Linux (or older version of macOS) before.
14 12
15However, bash is not the only option. There are numerous other shells 13However, bash is not the only option. There are numerous other shells
16that exist. Here are some popular examples: 14that exist. Here are some popular examples:
17 15
18- [zsh](https://en.wikipedia.org/wiki/Z_shell) 16- [[https://en.wikipedia.org/wiki/Z_shell][zsh]]
19- [fish](https://en.wikipedia.org/wiki/Fish_(Unix_shell)) 17- [[https://en.wikipedia.org/wiki/Fish_(Unix_shell)][fish]]
20- [oksh](https://github.com/ibara/oksh) 18- [[https://github.com/ibara/oksh][oksh]]
21- [mksh](https://wiki.gentoo.org/wiki/Mksh) 19- [[https://wiki.gentoo.org/wiki/Mksh][mksh]]
22- [dash](https://en.wikipedia.org/wiki/Debian_Almquist_shell) 20- [[https://en.wikipedia.org/wiki/Debian_Almquist_shell][dash]]
23 21
24While each shell has its differences, bash is POSIX compliant and the 22While each shell has its differences, bash is POSIX compliant and the
25default for many Linux users. Because of this, I am going to explore a 23default for many Linux users. Because of this, I am going to explore a
26program called `bash-it` below that helps bash users increase 24program called =bash-it= below that helps bash users increase the
27the utility of their shell without installing a completely new shell. 25utility of their shell without installing a completely new shell.
28
29### Installation
30 26
27** Installation
31First, if bash is not already installed on your system, you can 28First, if bash is not already installed on your system, you can
32[download bash from GNU](https://www.gnu.org/software/bash/) or use your 29[[https://www.gnu.org/software/bash/][download bash from GNU]] or use
33package manager to install it. 30your package manager to install it.
34 31
35For example, this is how you can install bash on Fedora Linux: 32For example, this is how you can install bash on Fedora Linux:
36 33
37```sh 34#+begin_src sh
38sudo dnf install bash 35sudo dnf install bash
39``` 36#+end_src
40 37
41If you are not using bash as your default shell, use the 38If you are not using bash as your default shell, use the =chsh= command
42`chsh` command to change your shell: 39to change your shell:
43 40
44```sh 41#+begin_src sh
45chsh 42chsh
46``` 43#+end_src
47 44
48You should see a prompt like the one below. If the brackets 45You should see a prompt like the one below. If the brackets (=[]=)
49(`[]`) contain `bash` already, you're done, and 46contain =bash= already, you're done, and you can simply continue by
50you can simply continue by hitting the Enter key. 47hitting the Enter key.
51 48
52If the brackets contain another shell path (e.g. =/usr/bin/zsh=), enter 49If the brackets contain another shell path (e.g. =/usr/bin/zsh=), enter
53the path to the bash program on your system (it's most likely located 50the path to the bash program on your system (it's most likely located at
54at `/usr/bin/bash`). 51=/usr/bin/bash=).
55 52
56```sh 53#+begin_src sh
57Changing shell for <user>. 54Changing shell for <user>.
58New shell [/usr/bin/bash]: 55New shell [/usr/bin/bash]:
59``` 56#+end_src
60 57
61You must log out or restart the machine in order for the login shell to 58You must log out or restart the machine in order for the login shell to
62be refreshed. You can do it now or wait until you're finished 59be refreshed. You can do it now or wait until you're finished
63customizing the shell. 60customizing the shell.
64 61
65```sh 62#+begin_src sh
66sudo reboot now 63sudo reboot now
67``` 64#+end_src
68
69## Bash-it
70 65
71As noted on the [Bash-it](https://github.com/Bash-it/bash-it) 66* Bash-it
67As noted on the [[https://github.com/Bash-it/bash-it][Bash-it]]
72repository: 68repository:
73 69
74> Bash-it is a collection of community Bash commands and scripts for 70#+begin_quote
75> Bash 3.2+. (And a shameless ripoff of oh-my-zsh 😃) 71Bash-it is a collection of community Bash commands and scripts for Bash
723.2+. (And a shameless ripoff of oh-my-zsh 😃)
73
74#+end_quote
76 75
77Bash-it makes it easy to install plugins, set up aliases for common 76Bash-it makes it easy to install plugins, set up aliases for common
78commands, and easily change the visual theme of your shell. 77commands, and easily change the visual theme of your shell.
79 78
80### Installation 79** Installation
81
82To install the framework, simply copy the repository files and use the 80To install the framework, simply copy the repository files and use the
83`install.sh` script provided. If you want, you can (and 81=install.sh= script provided. If you want, you can (and should!) inspect
84should!) inspect the contents of the installation script before you run 82the contents of the installation script before you run it.
85it.
86 83
87```sh 84#+begin_src sh
88git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it 85git clone --depth=1 https://github.com/Bash-it/bash-it.git ~/.bash_it
89~/.bash_it/install.sh 86~/.bash_it/install.sh
90``` 87#+end_src
91 88
92If you didn't restart your session after making bash the default, and 89If you didn't restart your session after making bash the default, and
93are currently working within another shell, be sure to enter a bash 90are currently working within another shell, be sure to enter a bash
94session before using `bash-it`: 91session before using =bash-it=:
95 92
96```sh 93#+begin_src sh
97bash 94bash
98``` 95#+end_src
99
100### Aliases
101 96
97** Aliases
102Bash-it contains a number of aliases for common commands to help improve 98Bash-it contains a number of aliases for common commands to help improve
103efficiency in the terminal. To list all available options, use the 99efficiency in the terminal. To list all available options, use the
104following command: 100following command:
105 101
106```sh 102#+begin_src sh
107bash-it show aliases 103bash-it show aliases
108``` 104#+end_src
109 105
110This will provide you a list that looks like the following text block. 106This will provide you a list that looks like the following text block.
111Within this screen, you will be able to see all available options and 107Within this screen, you will be able to see all available options and
112which ones are currently enabled. 108which ones are currently enabled.
113 109
114```txt 110#+begin_src txt
115Alias Enabled? Description 111Alias Enabled? Description
116ag [ ] the silver searcher (ag) aliases 112ag [ ] the silver searcher (ag) aliases
117ansible [ ] ansible abbreviations 113ansible [ ] ansible abbreviations
@@ -124,33 +120,32 @@ clipboard [ ] xclip shortcuts
124composer [ ] common composer abbreviations 120composer [ ] common composer abbreviations
125curl [x] Curl aliases for convenience. 121curl [x] Curl aliases for convenience.
126... 122...
127``` 123#+end_src
128 124
129To enable an alias, do: 125To enable an alias, do:
130 126
131```sh 127#+begin_src sh
132bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all 128bash-it enable alias <alias name> [alias name]... -or- $ bash-it enable alias all
133``` 129#+end_src
134 130
135To disable an alias, do: 131To disable an alias, do:
136 132
137```sh 133#+begin_src sh
138bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all 134bash-it disable alias <alias name> [alias name]... -or- $ bash-it disable alias all
139``` 135#+end_src
140
141### Plugins
142 136
137** Plugins
143Similar to aliases, plugins are available with bash-it. You can find a 138Similar to aliases, plugins are available with bash-it. You can find a
144complete list of plugins in the same way as aliases. Simply execute the 139complete list of plugins in the same way as aliases. Simply execute the
145following: 140following:
146 141
147```sh 142#+begin_src sh
148bash-it show plugins 143bash-it show plugins
149``` 144#+end_src
150 145
151You will see the following output showing enabled and disabled plugins: 146You will see the following output showing enabled and disabled plugins:
152 147
153```txt 148#+begin_src txt
154Plugin Enabled? Description 149Plugin Enabled? Description
155alias-completion [ ] 150alias-completion [ ]
156autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details 151autojump [ ] Autojump configuration, see https://github.com/wting/autojump for more details
@@ -161,82 +156,78 @@ battery [x] display info about your battery charge level
161blesh [ ] load ble.sh, the Bash line editor! 156blesh [ ] load ble.sh, the Bash line editor!
162boot2docker [ ] Helpers to get Docker setup correctly for boot2docker 157boot2docker [ ] Helpers to get Docker setup correctly for boot2docker
163browser [ ] render commandline output in your browser 158browser [ ] render commandline output in your browser
164``` 159#+end_src
165 160
166To enable a plugin, do: 161To enable a plugin, do:
167 162
168```sh 163#+begin_src sh
169bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all 164bash-it enable plugin <plugin name> [plugin name]... -or- $ bash-it enable plugin all
170``` 165#+end_src
171 166
172To disable a plugin, do: 167To disable a plugin, do:
173 168
174```sh 169#+begin_src sh
175bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all 170bash-it disable plugin <plugin name> [plugin name]... -or- $ bash-it disable plugin all
176``` 171#+end_src
177
178### Themes
179 172
173** Themes
180There are quite a few pre-defined 174There are quite a few pre-defined
181[themes](https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes) 175[[https://bash-it.readthedocs.io/en/latest/themes-list/#list-of-themes][themes]]
182available with bash-it. 176available with bash-it.
183 177
184To list all themes: 178To list all themes:
185 179
186```sh 180#+begin_src sh
187ls ~/.bash_it/themes/ 181ls ~/.bash_it/themes/
188``` 182#+end_src
189 183
190To use a new theme, you'll need to edit `.bashrc` and alter 184To use a new theme, you'll need to edit =.bashrc= and alter the
191the `BASH_IT_THEME` variable to your desired theme. For 185=BASH_IT_THEME= variable to your desired theme. For example, I am using
192example, I am using the `zork` theme. 186the =zork= theme.
193 187
194```sh 188#+begin_src sh
195nano ~/.bashrc 189nano ~/.bashrc
196``` 190#+end_src
197 191
198```sh 192#+begin_src sh
199export BASH_IT_THEME='zork' 193export BASH_IT_THEME='zork'
200``` 194#+end_src
201 195
202Once you save your changes, you just need to exit your terminal and 196Once you save your changes, you just need to exit your terminal and
203create a new one in order to see your changes to the 197create a new one in order to see your changes to the =.bashrc= file. You
204`.bashrc` file. You can also `source` the file to 198can also =source= the file to see changes, but I recommend starting a
205see changes, but I recommend starting a completely new shell instead. 199completely new shell instead.
206 200
207## ble.sh 201*** ble.sh
208 202One big feature I was missing in Bash that both =zsh= and =fish= have is
209One big feature I was missing in Bash that both `zsh` and 203an autosuggestion feature. To explain: as you type, an autosuggestion
210`fish` have is an autosuggestion feature. To explain: as you 204feature in the shell will offer suggestions in a lighter font color
211type, an autosuggestion feature in the shell will offer suggestions in a 205beyond the characters already typed. Once you see the command you want,
212lighter font color beyond the characters already typed. Once you see the 206you can click the right arrow and have the shell auto-complete that line
213command you want, you can click the right arrow and have the shell 207for you.
214auto-complete that line for you. 208
215 209Luckily, the [[https://github.com/akinomyoga/ble.sh][Bash Line Editor]]
216Luckily, the [Bash Line Editor](https://github.com/akinomyoga/ble.sh)
217(ble.sh) exists! This program provides a wonderful autosuggestions 210(ble.sh) exists! This program provides a wonderful autosuggestions
218feature perfectly, among other features that I haven't tested yet. 211feature perfectly, among other features that I haven't tested yet.
219 212
220In order to install ble.sh, execute the following: 213In order to install ble.sh, execute the following:
221 214
222```sh 215#+begin_src sh
223git clone --recursive https://github.com/akinomyoga/ble.sh.git 216git clone --recursive https://github.com/akinomyoga/ble.sh.git
224make -C ble.sh install PREFIX=~/.local 217make -C ble.sh install PREFIX=~/.local
225echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc 218echo 'source ~/.local/share/blesh/ble.sh' >> ~/.bashrc
226``` 219#+end_src
227 220
228Again, exit the terminal and open a new one in order to see the 221Again, exit the terminal and open a new one in order to see the
229newly-configured shell. 222newly-configured shell.
230 223
231## Restart the Session 224* Restart the Session
232
233Finally, as mentioned above, you'll need to restart the session to 225Finally, as mentioned above, you'll need to restart the session to
234ensure that your user is using bash by default. 226ensure that your user is using bash by default.
235 227
236You will also need to exit and re-open a shell (e.g., terminal or 228You will also need to exit and re-open a shell (e.g., terminal or
237terminal tab) any time you make changes to the `.bashrc` 229terminal tab) any time you make changes to the =.bashrc= file.
238file.
239 230
240```sh 231#+begin_src sh
241sudo reboot now 232sudo reboot now
242``` 233#+end_src
content/blog/2023-05-22-burnout.md → blog/burnout/index.org renamed +18 −21
@@ -1,19 +1,17 @@
1+++ 1#+title: RE: Burnout
2date = 2023-05-22 2#+date: 2023-05-22
3title = "RE: Burnout" 3#+description: A response to Drew DeVault's burnout post.
4description = "A response to Drew DeVault's burnout post." 4#+filetags: :personal:
5+++ 5
6 6* RE: Burnout
7## RE: Burnout 7I recently read
8 8[[https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html][Drew
9I recently read [Drew DeVault's post on 9DeVault's post on burnout]] around the same time I was pulling out of a
10burnout](https://drewdevault.com/2023/05/01/2023-05-01-Burnout.html) 10burnout rut myself earlier this month. Finally, seeing the light at the
11around the same time I was pulling out of a burnout rut myself earlier 11end of my burnout tunnel made me want to write my first post back on
12this month. Finally, seeing the light at the end of my burnout tunnel 12this topic.
13made me want to write my first post back on this topic. 13
14 14* Busy Seasons on Busy Seasons
15## Busy Seasons on Busy Seasons
16
17My career deals with busy seasons, generally driven by client demand. 15My career deals with busy seasons, generally driven by client demand.
18This last year, I dealt with a harsh busy season from Aug to Oct 2022 to 16This last year, I dealt with a harsh busy season from Aug to Oct 2022 to
19issue a few SOC reports for the period ending 2022-09-30. Immediately 17issue a few SOC reports for the period ending 2022-09-30. Immediately
@@ -28,12 +26,11 @@ I have a brief break and need to focus on the 2023-09-30 SOC reports
28again. While auditing and consulting always involve a busy season, this 26again. While auditing and consulting always involve a busy season, this
29is the first time I've had one last 9+ months without a break. 27is the first time I've had one last 9+ months without a break.
30 28
31While it's been tough, I have a handful of breaks pre-planned 29While it's been tough, I have a handful of breaks pre-planned throughout
32throughout this next cycle and should be able to moderate the level of 30this next cycle and should be able to moderate the level of commitment
33commitment required for each client. 31required for each client.
34
35## Refocusing
36 32
33* Refocusing
37Outside of work, I finally have time to work on hobbies such as this 34Outside of work, I finally have time to work on hobbies such as this
38website, programming, athletics, games, etc. 35website, programming, athletics, games, etc.
39 36
content/blog/2020-07-26-business-analysis.md → blog/business-analysis/index.org renamed +57 −66
@@ -1,31 +1,28 @@
1+++ 1#+title: Algorithmically Analyzing Local Businesses
2date = 2020-07-26 2#+date: 2020-07-26
3title = "Algorithmically Analyzing Local Businesses" 3#+description: Exploring and visualizing data with Python.
4description = "Exploring and visualizing data with Python." 4#+filetags: :data:
5+++
6
7# Background Information
8 5
6* Background Information
9This project aims to help investors learn more about a random city in 7This project aims to help investors learn more about a random city in
10order to determine optimal locations for business investments. The data 8order to determine optimal locations for business investments. The data
11used in this project was obtained using Foursquare's developer API. 9used in this project was obtained using Foursquare's developer API.
12 10
13Fields include: 11Fields include:
14 12
15- Venue Name 13- Venue Name
16- Venue Category 14- Venue Category
17- Venue Latitude 15- Venue Latitude
18- Venue Longitude 16- Venue Longitude
19 17
20There are 232 records found using the center of Lincoln as the area of 18There are 232 records found using the center of Lincoln as the area of
21interest with a radius of 10,000. 19interest with a radius of 10,000.
22 20
23# Import the Data 21* Import the Data
24
25The first step is the simplest: import the applicable libraries. We will 22The first step is the simplest: import the applicable libraries. We will
26be using the libraries below for this project. 23be using the libraries below for this project.
27 24
28```python 25#+begin_src python
29# Import the Python libraries we will be using 26# Import the Python libraries we will be using
30import pandas as pd 27import pandas as pd
31import requests 28import requests
@@ -34,24 +31,24 @@ import math
34import json 31import json
35from pandas.io.json import json_normalize 32from pandas.io.json import json_normalize
36from sklearn.cluster import KMeans 33from sklearn.cluster import KMeans
37``` 34#+end_src
38 35
39To begin our analysis, we need to import the data for this project. The 36To begin our analysis, we need to import the data for this project. The
40data we are using in this project comes directly from the Foursquare 37data we are using in this project comes directly from the Foursquare
41API. The first step is to get the latitude and longitude of the city 38API. The first step is to get the latitude and longitude of the city
42being studied (Lincoln, NE) and setting up the folium map. 39being studied (Lincoln, NE) and setting up the folium map.
43 40
44```python 41#+begin_src python
45# Define the latitude and longitude, then map the results 42# Define the latitude and longitude, then map the results
46latitude = 40.806862 43latitude = 40.806862
47longitude = -96.681679 44longitude = -96.681679
48map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12) 45map_LNK = folium.Map(location=[latitude, longitude], zoom_start=12)
49 46
50map_LNK 47map_LNK
51``` 48#+end_src
52 49
53![Blank 50#+caption: Blank Map
54Map](https://img.cleberg.net/blog/20200726-ibm-data-science/01_blank_map-min.png) 51[[https://img.cleberg.net/blog/20200726-ibm-data-science/01_blank_map-min.png]]
55 52
56Now that we have defined our city and created the map, we need to go get 53Now that we have defined our city and created the map, we need to go get
57the business data. The Foursquare API will limit the results to 100 per 54the business data. The Foursquare API will limit the results to 100 per
@@ -59,7 +56,7 @@ API call, so we use our first API call below to determine the total
59results that Foursquare has found. Since the total results are 232, we 56results that Foursquare has found. Since the total results are 232, we
60perform the API fetching process three times (100 + 100 + 32 = 232). 57perform the API fetching process three times (100 + 100 + 32 = 232).
61 58
62```python 59#+begin_src python
63# Foursquare API credentials 60# Foursquare API credentials
64CLIENT_ID = 'your-client-id' 61CLIENT_ID = 'your-client-id'
65CLIENT_SECRET = 'your-client-secret' 62CLIENT_SECRET = 'your-client-secret'
@@ -117,19 +114,18 @@ url3 = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret
117 114
118# Fetch the final results (201 - 232) 115# Fetch the final results (201 - 232)
119results3 = requests.get(url3).json() 116results3 = requests.get(url3).json()
120``` 117#+end_src
121
122# Clean the Data
123 118
119* Clean the Data
124Now that we have our data in three separate dataframes, we need to 120Now that we have our data in three separate dataframes, we need to
125combine them into a single dataframe and make sure to reset the index so 121combine them into a single dataframe and make sure to reset the index so
126that we have a unique ID for each business. The `get~categorytype~` 122that we have a unique ID for each business. The =get~categorytype~=
127function below will pull the categories and name from each business's 123function below will pull the categories and name from each business's
128entry in the Foursquare data automatically. Once all the data has been 124entry in the Foursquare data automatically. Once all the data has been
129labeled and combined, the results are stored in the 125labeled and combined, the results are stored in the =nearby_venues=
130`nearby_venues` dataframe. 126dataframe.
131 127
132```python 128#+begin_src python
133# This function will extract the category of the venue from the API dictionary 129# This function will extract the category of the venue from the API dictionary
134def get_category_type(row): 130def get_category_type(row):
135 try: 131 try:
@@ -192,18 +188,17 @@ nearby_venues3.columns = [col.split(".")[-1] for col in nearby_venues3.columns]
192nearby_venues = nearby_venues.append(nearby_venues3) 188nearby_venues = nearby_venues.append(nearby_venues3)
193nearby_venues = nearby_venues.reset_index(drop=True) 189nearby_venues = nearby_venues.reset_index(drop=True)
194nearby_venues 190nearby_venues
195``` 191#+end_src
196
197![Clean
198Data](https://img.cleberg.net/blog/20200726-ibm-data-science/02_clean_data-min.png)
199 192
200# Visualize the Data 193#+caption: Clean Data
194[[https://img.cleberg.net/blog/20200726-ibm-data-science/02_clean_data-min.png]]
201 195
196* Visualize the Data
202We now have a complete, clean data set. The next step is to visualize 197We now have a complete, clean data set. The next step is to visualize
203this data onto the map we created earlier. We will be using folium's 198this data onto the map we created earlier. We will be using folium's
204`CircleMarker()` function to do this. 199=CircleMarker()= function to do this.
205 200
206```python 201#+begin_src python
207# add markers to map 202# add markers to map
208for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']): 203for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng'], nearby_venues['name'], nearby_venues['categories']):
209 label = '{} ({})'.format(name, categories) 204 label = '{} ({})'.format(name, categories)
@@ -219,25 +214,23 @@ for lat, lng, name, categories in zip(nearby_venues['lat'], nearby_venues['lng']
219 ).add_to(map_LNK) 214 ).add_to(map_LNK)
220 215
221map_LNK 216map_LNK
222``` 217#+end_src
223 218
224\![Initial data 219![[https://img.cleberg.net/blog/20200726-ibm-data-science/03_data_map-min.png][Initial
225map](<https://img.cleberg.net/blog/20200726-ibm-data-science/03_data_map-min.png> 220data map]]
226"Initial data map")
227 221
228# Clustering: *k-means* 222* Clustering: /k-means/
229 223To cluster the data, we will be using the /k-means/ algorithm. This
230To cluster the data, we will be using the *k-means* algorithm. This
231algorithm is iterative and will automatically make sure that data points 224algorithm is iterative and will automatically make sure that data points
232in each cluster are as close as possible to each other, while being as 225in each cluster are as close as possible to each other, while being as
233far as possible away from other clusters. 226far as possible away from other clusters.
234 227
235However, we first have to figure out how many clusters to use (defined 228However, we first have to figure out how many clusters to use (defined
236as the variable *'k'*). To do so, we will use the next two functions 229as the variable /'k'/). To do so, we will use the next two functions to
237to calculate the sum of squares within clusters and then return the 230calculate the sum of squares within clusters and then return the optimal
238optimal number of clusters. 231number of clusters.
239 232
240```python 233#+begin_src python
241# This function will return the sum of squares found in the data 234# This function will return the sum of squares found in the data
242def calculate_wcss(data): 235def calculate_wcss(data):
243 wcss = [] 236 wcss = []
@@ -271,13 +264,13 @@ def optimal_number_of_clusters(wcss):
271 264
272# calculating the optimal number of clusters 265# calculating the optimal number of clusters
273n = optimal_number_of_clusters(sum_of_squares) 266n = optimal_number_of_clusters(sum_of_squares)
274``` 267#+end_src
275 268
276Now that we have found that our optimal number of clusters is six, we 269Now that we have found that our optimal number of clusters is six, we
277need to perform k-means clustering. When this clustering occurs, each 270need to perform k-means clustering. When this clustering occurs, each
278business is assigned a cluster number from 0 to 5 in the dataframe. 271business is assigned a cluster number from 0 to 5 in the dataframe.
279 272
280```python 273#+begin_src python
281# set number of clusters equal to the optimal number 274# set number of clusters equal to the optimal number
282kclusters = n 275kclusters = n
283 276
@@ -286,13 +279,13 @@ kmeans = KMeans(n_clusters=kclusters, random_state=0).fit(cluster_df)
286 279
287# add clustering labels to dataframe 280# add clustering labels to dataframe
288nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_) 281nearby_venues.insert(0, 'Cluster Labels', kmeans.labels_)
289``` 282#+end_src
290 283
291Success! We now have a dataframe with clean business data, along with a 284Success! We now have a dataframe with clean business data, along with a
292cluster number for each business. Now let's map the data using six 285cluster number for each business. Now let's map the data using six
293different colors. 286different colors.
294 287
295```python 288#+begin_src python
296# create map with clusters 289# create map with clusters
297map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12) 290map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12)
298colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717'] 291colors = ['#0F9D58', '#DB4437', '#4285F4', '#800080', '#ce12c0', '#171717']
@@ -311,13 +304,12 @@ for lat, lng, name, categories, cluster in zip(nearby_venues['lat'], nearby_venu
311 fill_opacity=0.7).add_to(map_clusters) 304 fill_opacity=0.7).add_to(map_clusters)
312 305
313map_clusters 306map_clusters
314``` 307#+end_src
315
316![Clustered
317Map](https://img.cleberg.net/blog/20200726-ibm-data-science/04_clusters-min.png)
318 308
319# Investigate Clusters 309#+caption: Clustered Map
310[[https://img.cleberg.net/blog/20200726-ibm-data-science/04_clusters-min.png]]
320 311
312* Investigate Clusters
321Now that we have figured out our clusters, let's do a little more 313Now that we have figured out our clusters, let's do a little more
322analysis to provide more insight into the clusters. With the information 314analysis to provide more insight into the clusters. With the information
323below, we can see which clusters are more popular for businesses and 315below, we can see which clusters are more popular for businesses and
@@ -325,23 +317,23 @@ which are less popular. The results below show us that clusters 0
325through 3 are popular, while clusters 4 and 5 are not very popular at 317through 3 are popular, while clusters 4 and 5 are not very popular at
326all. 318all.
327 319
328```python 320#+begin_src python
329# Show how many venues are in each cluster 321# Show how many venues are in each cluster
330color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black'] 322color_names = ['Dark Green', 'Red', 'Blue', 'Purple', 'Pink', 'Black']
331for x in range(0,6): 323for x in range(0,6):
332 print("Color of Cluster", x, ":", color_names[x]) 324 print("Color of Cluster", x, ":", color_names[x])
333 print("Venues found in Cluster", x, ":", nearby_venues.loc[nearby_venues['Cluster Labels'] == x, nearby_venues.columns[:]].shape[0]) 325 print("Venues found in Cluster", x, ":", nearby_venues.loc[nearby_venues['Cluster Labels'] == x, nearby_venues.columns[:]].shape[0])
334 print("---") 326 print("---")
335``` 327#+end_src
336 328
337![Venues per 329#+caption: Venues per Cluster
338Cluster](https://img.cleberg.net/blog/20200726-ibm-data-science/05_venues_per_cluster-min.png) 330[[https://img.cleberg.net/blog/20200726-ibm-data-science/05_venues_per_cluster-min.png]]
339 331
340Our last piece of analysis is to summarize the categories of businesses 332Our last piece of analysis is to summarize the categories of businesses
341within each cluster. With these results, we can clearly see that 333within each cluster. With these results, we can clearly see that
342restaurants, coffee shops, and grocery stores are the most popular. 334restaurants, coffee shops, and grocery stores are the most popular.
343 335
344```python 336#+begin_src python
345# Calculate how many venues there are in each category 337# Calculate how many venues there are in each category
346# Sort from largest to smallest 338# Sort from largest to smallest
347temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng']) 339temp_df = nearby_venues.drop(columns=['name', 'lat', 'lng'])
@@ -361,16 +353,15 @@ with pd.option_context('display.max_rows', None, 'display.max_columns', None):
361 print("\n\n", "Cluster 3:", "\n", cluster3_grouped.loc[cluster3_grouped['Cluster Labels'] > 1]) 353 print("\n\n", "Cluster 3:", "\n", cluster3_grouped.loc[cluster3_grouped['Cluster Labels'] > 1])
362 print("\n\n", "Cluster 4:", "\n", cluster4_grouped.loc[cluster4_grouped['Cluster Labels'] > 1]) 354 print("\n\n", "Cluster 4:", "\n", cluster4_grouped.loc[cluster4_grouped['Cluster Labels'] > 1])
363 print("\n\n", "Cluster 5:", "\n", cluster5_grouped.loc[cluster5_grouped['Cluster Labels'] > 1]) 355 print("\n\n", "Cluster 5:", "\n", cluster5_grouped.loc[cluster5_grouped['Cluster Labels'] > 1])
364``` 356#+end_src
365
366![Venues per Cluster, pt.
3671](https://img.cleberg.net/blog/20200726-ibm-data-science/06_categories_per_cluster_pt1-min.png)
368 357
369![Venues per Cluster, pt. 358#+caption: Venues per Cluster, pt. 1
3702](https://img.cleberg.net/blog/20200726-ibm-data-science/07_categories_per_cluster_pt2-min.png) 359[[https://img.cleberg.net/blog/20200726-ibm-data-science/06_categories_per_cluster_pt1-min.png]]
371 360
372# Discussion 361#+caption: Venues per Cluster, pt. 2
362[[https://img.cleberg.net/blog/20200726-ibm-data-science/07_categories_per_cluster_pt2-min.png]]
373 363
364* Discussion
374In this project, we gathered location data for Lincoln, Nebraska, USA 365In this project, we gathered location data for Lincoln, Nebraska, USA
375and clustered the data using the k-means algorithm in order to identify 366and clustered the data using the k-means algorithm in order to identify
376the unique clusters of businesses in Lincoln. Through these actions, we 367the unique clusters of businesses in Lincoln. Through these actions, we
content/blog/2023-06-23-byobu.md → blog/byobu/index.org renamed +17 −20
@@ -1,36 +1,33 @@
1+++ 1#+title: Byobu
2date = 2023-06-23 2#+date: 2023-06-23
3title = "Byobu" 3#+description: Learning about the Byobu application for terminals.
4description = "Learning about the Byobu application for terminals." 4#+filetags: :linux:
5+++
6 5
7## Byobu 6* Byobu
8 7[[https://www.byobu.org][byobu]] is a command-line tool that allows you
9[byobu](https://www.byobu.org) is a command-line tool that allows you to 8to use numerous screens within a single terminal emulator instance. More
10use numerous screens within a single terminal emulator instance. More 9specifically, it's a text based window manager, using either =screen= or
11specifically, it's a text based window manager, using either 10=tmux=.
12`screen` or `tmux`.
13 11
14This post is mostly just a self-reference as I explore byobu, so I may 12This post is mostly just a self-reference as I explore byobu, so I may
15come back later and update this post with more content. 13come back later and update this post with more content.
16 14
17### Screenshot 15** Screenshot
18
19Take a look below at my current multi-window set-up in byobu while I 16Take a look below at my current multi-window set-up in byobu while I
20write this blog post: 17write this blog post:
21 18
22![byobu](https://img.cleberg.net/blog/20230623-byobu/byobu.png) 19#+caption: byobu
23 20[[https://img.cleberg.net/blog/20230623-byobu/byobu.png]]
24## Keybindings
25 21
22*** Keybindings
26You can open the help menu with either of the following commands; they 23You can open the help menu with either of the following commands; they
27will both open the same manpage: 24will both open the same manpage:
28 25
29```sh 26#+begin_src sh
30byobu --help 27byobu --help
31# or 28# or
32man byobu 29man byobu
33``` 30#+end_src
34 31
35While the manpage contains a ton of information about the functionality 32While the manpage contains a ton of information about the functionality
36of byobu (such as status notifications, sessions, and windows), the 33of byobu (such as status notifications, sessions, and windows), the
@@ -38,7 +35,7 @@ first location to explore should be the keybindings section.
38 35
39The keybindings are configured as follows: 36The keybindings are configured as follows:
40 37
41```txt 38#+begin_src txt
42byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or 39byobu keybindings can be user defined in /usr/share/byobu/keybindings/ (or
43within .screenrc if byobu-export was used). The common key bindings are: 40within .screenrc if byobu-export was used). The common key bindings are:
44 41
@@ -66,4 +63,4 @@ Ctrl-a R - Reload profile
66Ctrl-a ! - Toggle key bindings on and off 63Ctrl-a ! - Toggle key bindings on and off
67Ctrl-a k - Kill the current window 64Ctrl-a k - Kill the current window
68Ctrl-a ~ - Save the current window's scrollback buffer 65Ctrl-a ~ - Save the current window's scrollback buffer
69``` 66#+end_src
content/blog/2021-05-30-changing-git-authors.md → blog/changing-git-authors/index.org renamed +16 −18
@@ -1,11 +1,9 @@
1+++ 1#+title: Changing Git Authors
2date = 2021-05-30 2#+date: 2021-05-30
3title = "Changing Git Authors" 3#+description: A guide to change Git author names and emails in old commits.
4description = "A guide to change Git author names and emails in old commits." 4#+filetags: :dev:
5+++
6
7## Changing Git Author/Email Based on Previously Committed Email
8 5
6* Changing Git Author/Email Based on Previously Committed Email
9Here's the dilemma: You've been committing changes to your git 7Here's the dilemma: You've been committing changes to your git
10repository with an incorrect name or email (or multiple repositories), 8repository with an incorrect name or email (or multiple repositories),
11and now you want to fix it. Luckily, there's a semi-reliable way to fix 9and now you want to fix it. Luckily, there's a semi-reliable way to fix
@@ -15,18 +13,18 @@ of commits, so use this method only if you're okay accepting that risk.
15 13
16Okay, let's create the bash script: 14Okay, let's create the bash script:
17 15
18```sh 16#+begin_src sh
19nano change_git_authors.sh 17nano change_git_authors.sh
20``` 18#+end_src
21 19
22The following information can be pasted directly into your bash script. 20The following information can be pasted directly into your bash script.
23The only changes you need to make are to the following variables: 21The only changes you need to make are to the following variables:
24 22
25- `OLD_EMAIL` 23- =OLD_EMAIL=
26- `CORRECT_NAME` 24- =CORRECT_NAME=
27- `CORRECT_EMAIL` 25- =CORRECT_EMAIL=
28 26
29```sh 27#+begin_src sh
30#!/bin/sh 28#!/bin/sh
31 29
32# List all sub-directories in the current directory 30# List all sub-directories in the current directory
@@ -59,16 +57,16 @@ do
59 57
60 cd .. 58 cd ..
61done 59done
62``` 60#+end_src
63 61
64Finally, save the bash script and make it executable. 62Finally, save the bash script and make it executable.
65 63
66```sh 64#+begin_src sh
67chmod a+x change_git_authors.sh 65chmod a+x change_git_authors.sh
68``` 66#+end_src
69 67
70Now you can run the script and should see the process begin. 68Now you can run the script and should see the process begin.
71 69
72```sh 70#+begin_src sh
73./change_git_authors.sh 71./change_git_authors.sh
74``` 72#+end_src
content/blog/2021-12-04-cisa.md → blog/cisa/index.org renamed +75 −84
@@ -1,21 +1,18 @@
1+++ 1#+title: I Passed the CISA!
2date = 2021-12-04 2#+date: 2021-12-04
3title = "I Passed the CISA!" 3#+description: A recap of the CISA certification exam and my results.
4description = "A recap of the CISA certification exam and my results." 4#+filetags: :audit:
5+++
6
7## What is the CISA?
8 5
6* What is the CISA?
9For those of you lucky enough not to be knee-deep in the world of IT/IS 7For those of you lucky enough not to be knee-deep in the world of IT/IS
10Auditing, [CISA](https://www.isaca.org/credentialing/cisa) stands for 8Auditing, [[https://www.isaca.org/credentialing/cisa][CISA]] stands for
11Certified Information Systems Auditor. This certification and exam are 9Certified Information Systems Auditor. This certification and exam are
12part of ISACA's suite of certifications. As I often explain it to 10part of ISACA's suite of certifications. As I often explain it to people
13people like my family, it basically means you're employed to use your 11like my family, it basically means you're employed to use your knowledge
14knowledge of information systems, regulations, common threats, risks, 12of information systems, regulations, common threats, risks, etc. in
15etc. in order to assess an organization's current control of their 13order to assess an organization's current control of their risk. If a
16risk. If a risk isn't controlled (and the company doesn't want to 14risk isn't controlled (and the company doesn't want to accept the risk),
17accept the risk), an IS auditor will suggest implementing a control to 15an IS auditor will suggest implementing a control to address that risk.
18address that risk.
19 16
20Now, the CISA certification itself is, in my opinion, the main 17Now, the CISA certification itself is, in my opinion, the main
21certification for this career. While certifications such as the CPA or 18certification for this career. While certifications such as the CPA or
@@ -27,13 +24,13 @@ However, to be honest, I am a skeptic of most certifications. I
27understand the value they hold in terms of how much you need to commit 24understand the value they hold in terms of how much you need to commit
28to studying or learning on the job, as well as the market value for 25to studying or learning on the job, as well as the market value for
29certifications such as the CISA. But I also have known some very 26certifications such as the CISA. But I also have known some very
30~~incompetent~~ *less than stellar* auditors who have CPAs, CISAs, CIAs, 27+incompetent+ /less than stellar/ auditors who have CPAs, CISAs, CIAs,
31etc. 28etc.
32 29
33The same goes for most industries: if a person is good at studying, they 30The same goes for most industries: if a person is good at studying, they
34can earn the certification. However, that knowledge means nothing unless 31can earn the certification. However, that knowledge means nothing unless
35you're actually able to use it in real life and perform as expected of 32you're actually able to use it in real life and perform as expected of a
36a certification holder. The challenge comes when people are hired or 33certification holder. The challenge comes when people are hired or
37connected strictly because of their certifications or resume; you need 34connected strictly because of their certifications or resume; you need
38to see a person work before you can assume them having a CISA means 35to see a person work before you can assume them having a CISA means
39they're better than someone without the CISA. 36they're better than someone without the CISA.
@@ -42,50 +39,47 @@ Okay, rant over. Certifications are generally accepted as a measuring
42stick of commitment and quality of an employee, so I am accepting it 39stick of commitment and quality of an employee, so I am accepting it
43too. 40too.
44 41
45## Exam Content 42* Exam Content
46
47The CISA is broken down into five sections, each weighted with a 43The CISA is broken down into five sections, each weighted with a
48percentage of test questions that may appear. 44percentage of test questions that may appear.
49 45
50![CISA exam 46#+caption: CISA exam sections
51sections](https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-exam-sections.png) 47[[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-exam-sections.png]]
52 48
53Since the exam contains 150 questions, here's how those sections break 49Since the exam contains 150 questions, here's how those sections break
54down: 50down:
55 51
56 Exam Section Percentage of Exam Questions 52| Exam Section | Percentage of Exam | Questions |
57 ----------------- -------------------- ----------- 53|---------------+--------------------+-----------|
58 1 21% 32 54| 1 | 21% | 32 |
59 2 17% 26 55| 2 | 17% | 26 |
60 3 12% 18 56| 3 | 12% | 18 |
61 4 23% 34 57| 4 | 23% | 34 |
62 5 27% 40 58| 5 | 27% | 40 |
63 **Grand Total** **100%** **150** 59| *Grand Total* | *100%* | *150* |
64
65## My Studying Habits
66 60
61* My Studying Habits
67This part is a little hard for me to break down into specific detail due 62This part is a little hard for me to break down into specific detail due
68to the craziness of the last year. While I officially purchased my 63to the craziness of the last year. While I officially purchased my
69studying materials in December 2020 and opened them to "start 64studying materials in December 2020 and opened them to "start studying"
70studying" in January 2021, I really wasn't able to study much due to 65in January 2021, I really wasn't able to study much due to the demands
71the demands of my job and personal life. 66of my job and personal life.
72 67
73Let me approach this from a few different viewpoints. 68Let me approach this from a few different viewpoints.
74 69
75### Study Materials 70** Study Materials
76
77Let's start by discussing the study materials I purchased. I'll be 71Let's start by discussing the study materials I purchased. I'll be
78referring to #1 as the CRM and #2 as the QAE. 72referring to #1 as the CRM and #2 as the QAE.
79 73
801. [CISA Review Manual, 27th Edition \| 741. [[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK][CISA
81 Print](https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCbEAK) 75 Review Manual, 27th Edition | Print]]
822. [[<https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK>][CISA 762. [[[[https://store.isaca.org/s/store#/store/browse/detail/a2S4w000004KoCcEAK]]][CISA
83 Review Questions, Answers & Explanations Manual, 12th Edition \| 77 Review Questions, Answers & Explanations Manual, 12th Edition |
84 Print]] 78 Print]]
85 79
86The CRM is an excellent source of information and could honestly be used 80The CRM is an excellent source of information and could honestly be used
87as a reference for most IS auditors as a learning reference during their 81as a reference for most IS auditors as a learning reference during their
88daily audit responsibilities. However, it is **full** of information and 82daily audit responsibilities. However, it is *full** of information and
89can be overloading if you're not good at filtering out useless 83can be overloading if you're not good at filtering out useless
90information while studying. 84information while studying.
91 85
@@ -95,10 +89,10 @@ complaint about the QAE is that each question is immediately followed
95with the correct answer and explanations below it, which means I had to 89with the correct answer and explanations below it, which means I had to
96use something to constantly cover the answers while I was studying. 90use something to constantly cover the answers while I was studying.
97 91
98I didn't use the online database version of the QAE, but I've heard 92I didn't use the online database version of the QAE, but I've heard that
99that it's easier to use than the printed book. However, it is more 93it's easier to use than the printed book. However, it is more expensive
100expensive (\$299 database vs \$129 book) which might be important if 94($299 database vs $129 book) which might be important if you're paying
101you're paying for materials yourself. 95for materials yourself.
102 96
103In terms of question difficulty, I felt that the QAE was a good 97In terms of question difficulty, I felt that the QAE was a good
104representation of the actual exam. I've seen a lot of people online say 98representation of the actual exam. I've seen a lot of people online say
@@ -111,8 +105,7 @@ topics you struggle with), and use the QAE to continue practicing
111exam-like questions, you should be fine. I didn't use any online 105exam-like questions, you should be fine. I didn't use any online
112courses, videos, etc. - the ISACA materials are more than enough. 106courses, videos, etc. - the ISACA materials are more than enough.
113 107
114### Studying Process 108** Studying Process
115
116While I was able to briefly read through sections 1 and 2 in early 2021, 109While I was able to briefly read through sections 1 and 2 in early 2021,
117I had to stop and take a break from February/March to September. I 110I had to stop and take a break from February/March to September. I
118switched jobs in September, which allowed me a lot more free time to 111switched jobs in September, which allowed me a lot more free time to
@@ -124,60 +117,59 @@ practice exam from the QAE manual and scored 70% (105/150).
124 117
125Here's a breakdown of my initial practice exam: 118Here's a breakdown of my initial practice exam:
126 119
127 Exam Section Incorrect Correct Grand Total Percent 120| Exam Section | Incorrect | Correct | Grand Total | Percent |
128 ----------------- ----------- --------- ------------- --------- 121|---------------+-----------+---------+-------------+---------|
129 1 8 25 33 76% 122| 1 | 8 | 25 | 33 | 76% |
130 2 5 20 25 80% 123| 2 | 5 | 20 | 25 | 80% |
131 3 6 12 18 67% 124| 3 | 6 | 12 | 18 | 67% |
132 4 10 23 33 70% 125| 4 | 10 | 23 | 33 | 70% |
133 5 16 25 41 61% 126| 5 | 16 | 25 | 41 | 61% |
134 **Grand Total** **45** **105** **150** **70%** 127| *Grand Total** | *45** | *105** | *150** | *70%** |
135 128
136As I expected, my toughest sections were related to project management, 129As I expected, my toughest sections were related to project management,
137development, implementation, and security. 130development, implementation, and security.
138 131
139This just leaves October and November. For these months, I tried to 132This just leaves October and November. For these months, I tried to
140practice every few days, doing 10 questions for each section, until the 133practice every few days, doing 10 questions for each section, until the
141exam. This came out to 13 practice sessions, \~140 questions per 134exam. This came out to 13 practice sessions, ~140 questions per section,
142section, and \~700 questions total. 135and ~700 questions total.
143 136
144While some practice sessions were worse and some were better, the final 137While some practice sessions were worse and some were better, the final
145results were similar to my practice exam results. As you can see below, 138results were similar to my practice exam results. As you can see below,
146my averages were slightly worse than my practice exam. However, I got in 139my averages were slightly worse than my practice exam. However, I got in
147over 700 questions of practice and, most importantly, \*I read through 140over 700 questions of practice and, most importantly, *I read through
148the explanations every time I answered incorrectly and learned from my 141the explanations every time I answered incorrectly and learned from my
149mistakes\*. 142mistakes*.
150
151 Exam Section Incorrect Correct Grand Total Percent
152 ----------------- ----------- --------- ------------- ---------
153 1 33 108 141 77%
154 2 33 109 142 77%
155 3 55 89 144 62%
156 4 52 88 140 63%
157 5 55 85 140 61%
158 **Grand Total** **228** **479** **707** **68%**
159 143
160![CISA practice question 144| Exam Section | Incorrect | Correct | Grand Total | Percent |
161results](https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-practice-questions-results.png) 145|---------------+-----------+---------+-------------+---------|
146| 1 | 33 | 108 | 141 | 77% |
147| 2 | 33 | 109 | 142 | 77% |
148| 3 | 55 | 89 | 144 | 62% |
149| 4 | 52 | 88 | 140 | 63% |
150| 5 | 55 | 85 | 140 | 61% |
151| *Grand Total** | *228** | *479** | *707** | *68%** |
162 152
163## Results 153#+caption: CISA practice question results
154[[https://img.cleberg.net/blog/20211204-i-passed-the-cisa/cisa-practice-questions-results.png]]
164 155
156* Results
165Now, how do the practice scores reflect my actual results? After all, 157Now, how do the practice scores reflect my actual results? After all,
166it's hard to tell how good a practice regimen is unless you see how it 158it's hard to tell how good a practice regimen is unless you see how it
167turns out. 159turns out.
168 160
169 Exam Section Section Name Score 161| Exam Section | Section Name | Score |
170 -------------- ------------------------------------------------------------------ --------- 162|--------------+------------------------------------------------------------------+-------|
171 1 Information Systems Auditing Process 678 163| 1 | Information Systems Auditing Process | 678 |
172 2 Governance and Management of IT 590 164| 2 | Governance and Management of IT | 590 |
173 3 Information Systems Acquisition, Development, and Implementation 721 165| 3 | Information Systems Acquisition, Development, and Implementation | 721 |
174 4 Information Systems Operations and Business Resilience 643 166| 4 | Information Systems Operations and Business Resilience | 643 |
175 5 Protection of Information Assets 511 167| 5 | Protection of Information Assets | 511 |
176 **TOTAL** **616** 168| *TOTAL* | | *616* |
177 169
178Now, in order to pass the CISA, you need at least 450 on a sliding scale 170Now, in order to pass the CISA, you need at least 450 on a sliding scale
179of 200-800. Personally, I really have no clue what an average CISA score 171of 200-800. Personally, I really have no clue what an average CISA score
180is. After a *very* brief look online, I can see that the high end is 172is. After a /very/ brief look online, I can see that the high end is
181usually in the low 700s. In addition, only about 50-60% of people pass 173usually in the low 700s. In addition, only about 50-60% of people pass
182the exam. 174the exam.
183 175
@@ -196,8 +188,7 @@ CISA topics at all, the most important part of studying is doing
196practice questions. You really need to understand how to read the 188practice questions. You really need to understand how to read the
197questions critically and pick the best answer. 189questions critically and pick the best answer.
198 190
199## Looking Forward 191* Looking Forward
200
201I am extremely happy that I was finally able to pass the CISA. Looking 192I am extremely happy that I was finally able to pass the CISA. Looking
202to the future, I'm not sure what's next in terms of professional 193to the future, I'm not sure what's next in terms of professional
203learning. My current company offers internal learning courses, so I will 194learning. My current company offers internal learning courses, so I will
content/blog/2021-03-19-clone-github-repos.md → blog/clone-github-repos/index.org renamed +42 −49
@@ -1,11 +1,9 @@
1+++ 1#+title: How to Clone All Repositories from a GitHub or Sourcehut Account
2date = 2021-03-19 2#+date: 2021-03-19
3title = "How to Clone All Repositories from a GitHub or Sourcehut Account" 3#+description: Learn how to clone all GitHub or Sourcehut repositories.
4description = "Learn how to clone all GitHub or Sourcehut repositories." 4#+filetags: :dev:
5+++
6
7## Cloning from GitHub
8 5
6* Cloning from GitHub
9If you're like me and use a lot of different devices (and sometimes 7If you're like me and use a lot of different devices (and sometimes
10decide to just wipe your device and start with a new OS), you probably 8decide to just wipe your device and start with a new OS), you probably
11know the pain of cloning all your old code repositories down to your 9know the pain of cloning all your old code repositories down to your
@@ -13,47 +11,45 @@ local file system.
13 11
14If you're using GitHub, you can easily clone all of your code back down 12If you're using GitHub, you can easily clone all of your code back down
15in just seconds. First, create a bash script. I do so by opening a new 13in just seconds. First, create a bash script. I do so by opening a new
16file in `nano`, but you can use `gedit`, 14file in =nano=, but you can use =gedit=, =vim=, or something else:
17`vim`, or something else:
18 15
19```sh 16#+begin_src sh
20nano clone_github_repos.sh 17nano clone_github_repos.sh
21``` 18#+end_src
22 19
23Next, paste in the following information. Note that you can replace the 20Next, paste in the following information. Note that you can replace the
24word `users` in the first line with `orgs` and 21word =users= in the first line with =orgs= and type an organization's
25type an organization's name instead of a user's name. 22name instead of a user's name.
26 23
27```sh 24#+begin_src sh
28CNTX=users; NAME=YOUR-USERNAME; PAGE=1 25CNTX=users; NAME=YOUR-USERNAME; PAGE=1
29curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" | 26curl "https://api.github.com/$CNTX/$NAME/repos?page=$PAGE&per_page=100" |
30 grep -e 'git_url*' | 27 grep -e 'git_url*' |
31 cut -d \" -f 4 | 28 cut -d \" -f 4 |
32 xargs -L1 git clone 29 xargs -L1 git clone
33``` 30#+end_src
34 31
35Finally, save the bash script and make it executable. 32Finally, save the bash script and make it executable.
36 33
37```sh 34#+begin_src sh
38chmod a+x clone_github_repos.sh 35chmod a+x clone_github_repos.sh
39``` 36#+end_src
40 37
41Now you can run the script and should see the cloning process begin. 38Now you can run the script and should see the cloning process begin.
42 39
43```sh 40#+begin_src sh
44./clone_github_repos.sh 41./clone_github_repos.sh
45``` 42#+end_src
46
47## Cloning from Sourcehut
48 43
44* Cloning from Sourcehut
49I haven't fully figured out how to directly incorporate Sourcehut's 45I haven't fully figured out how to directly incorporate Sourcehut's
50GraphQL API into a bash script yet, so this one will take two steps. 46GraphQL API into a bash script yet, so this one will take two steps.
51 47
52First, log-in to Sourcehut and go to their [GraphQL playground for 48First, log-in to Sourcehut and go to their
53Git](https://git.sr.ht/graphql). Next, paste the following query into 49[[https://git.sr.ht/graphql][GraphQL playground for Git]]. Next, paste
54the left box: 50the following query into the left box:
55 51
56```sh 52#+begin_src sh
57query { 53query {
58 me { 54 me {
59 canonicalName 55 canonicalName
@@ -65,7 +61,7 @@ query {
65 } 61 }
66 } 62 }
67} 63}
68``` 64#+end_src
69 65
70The output on the right side will give you an object of all your 66The output on the right side will give you an object of all your
71repositories. Just grab that text and remove all the characters such as 67repositories. Just grab that text and remove all the characters such as
@@ -74,19 +70,18 @@ space-separated values for the next step.
74 70
75Now let's create the bash script: 71Now let's create the bash script:
76 72
77```sh 73#+begin_src sh
78nano clone_sourcehut_repos.sh 74nano clone_sourcehut_repos.sh
79``` 75#+end_src
80 76
81Next, paste the following bash script in with the list of repositories 77Next, paste the following bash script in with the list of repositories
82you obtained above and replace `your-username` with your 78you obtained above and replace =your-username= with your username.
83username.
84 79
85Note that this uses the SSH-based Git cloning method 80Note that this uses the SSH-based Git cloning method
86(e.g. =git@git...=), so you'll need to ensure you have set up Sourcehut 81(e.g. =git@git...=), so you'll need to ensure you have set up Sourcehut
87with your SSH key. 82with your SSH key.
88 83
89```sh 84#+begin_src sh
90repos=(repo1 repo2 repo3) 85repos=(repo1 repo2 repo3)
91 86
92# List all sub-directories in the current directory 87# List all sub-directories in the current directory
@@ -95,37 +90,35 @@ do
95 # Clone 90 # Clone
96 git clone git@git.sr.ht:~your-username/$repo 91 git clone git@git.sr.ht:~your-username/$repo
97done 92done
98``` 93#+end_src
99 94
100Finally, save the bash script and make it executable. 95Finally, save the bash script and make it executable.
101 96
102```sh 97#+begin_src sh
103chmod a+x clone_sourcehut_repos.sh 98chmod a+x clone_sourcehut_repos.sh
104``` 99#+end_src
105 100
106Now you can run the script and should see the cloning process begin. 101Now you can run the script and should see the cloning process begin.
107 102
108```sh 103#+begin_src sh
109./clone_sourcehut_repos.sh 104./clone_sourcehut_repos.sh
110``` 105#+end_src
111
112## Moving Repositories to a New Host
113 106
107* Moving Repositories to a New Host
114Now that you have all of your code repositories cloned to your local 108Now that you have all of your code repositories cloned to your local
115computer, you may want to change the remote host (e.g., moving from 109computer, you may want to change the remote host (e.g., moving from
116GitHub to GitLab). To do this, let's create another bash script: 110GitHub to GitLab). To do this, let's create another bash script:
117 111
118```sh 112#+begin_src sh
119nano change_remote_urls.sh 113nano change_remote_urls.sh
120``` 114#+end_src
121 115
122Past the following information and be sure to change the URL information 116Past the following information and be sure to change the URL information
123to whichever host you are moving to. For this example, I am looping 117to whichever host you are moving to. For this example, I am looping
124through all of my cloned GitHub directories and changing them to 118through all of my cloned GitHub directories and changing them to
125Sourcehut (e.g. `<YOUR_NEW_REMOTE_URL>` -\> 119Sourcehut (e.g. =<YOUR_NEW_REMOTE_URL>= -> =git@git.sr.ht:~myusername=).
126`git@git.sr.ht:~myusername`).
127 120
128```sh 121#+begin_src sh
129# List all sub-directories in the current directory 122# List all sub-directories in the current directory
130for dir in */ 123for dir in */
131do 124do
@@ -140,16 +133,16 @@ do
140 # Go back to main directory 133 # Go back to main directory
141 cd .. 134 cd ..
142done 135done
143``` 136#+end_src
144 137
145Finally, save the bash script and make it executable. 138Finally, save the bash script and make it executable.
146 139
147```sh 140#+begin_src sh
148chmod a+x change_remote_urls.sh 141chmod a+x change_remote_urls.sh
149``` 142#+end_src
150 143
151Now you can run the script and should see the cloning process begin. 144Now you can run the script and should see the cloning process begin.
152 145
153```sh 146#+begin_src sh
154./change_remote_urls.sh 147./change_remote_urls.sh
155``` 148#+end_src
content/blog/2022-03-23-cloudflare-dns-api.md → blog/cloudflare-dns-api/index.org renamed +44 −49
@@ -1,41 +1,39 @@
1+++ 1#+title: Dynamic DNS with Cloudflare API
2date = 2022-03-23 2#+date: 2022-03-23
3title = "Dynamic DNS with Cloudflare API" 3#+description: Learn how to dynamically update DNS records for changing IPs with Cloudflare.
4description = "Learn how to dynamically update DNS records for changing IPs with Cloudflare." 4#+filetags: :sysadmin:
5+++ 5
6 6* DDNS: Dynamic DNS
7## DDNS: Dynamic DNS 7If you're hosting a service from a location with dynamic DNS (where your
8 8IP may change at any time), you must have a solution to update the DNS
9If you're hosting a service from a location with dynamic DNS (where 9so that you can access your service even when the IP of the server
10your IP may change at any time), you must have a solution to update the
11DNS so that you can access your service even when the IP of the server
12changes. 10changes.
13 11
14The process below uses the [Cloudflare API](https://api.cloudflare.com/) 12The process below uses the [[https://api.cloudflare.com/][Cloudflare
15to update DNS `A` and `AAAA` records with the 13API]] to update DNS =A= and =AAAA= records with the server's current IP.
16server's current IP. If you use another DNS provider, you will have to 14If you use another DNS provider, you will have to find a way to update
17find a way to update your DNS (or find a way to get a static IP). 15your DNS (or find a way to get a static IP).
18 16
19First, install `jq` since we will use it in the next script: 17First, install =jq= since we will use it in the next script:
20 18
21```sh 19#+begin_src sh
22sudo apt install jq 20sudo apt install jq
23``` 21#+end_src
24 22
25Next, create a location for your DDNS update scripts and open the first 23Next, create a location for your DDNS update scripts and open the first
26script: 24script:
27 25
28```sh 26#+begin_src sh
29mkdir ~/ddns 27mkdir ~/ddns
30nano ~/ddns/update.sh 28nano ~/ddns/update.sh
31``` 29#+end_src
32 30
33The following `update.sh` script will take all of your 31The following =update.sh= script will take all of your domains and
34domains and subdomains and check Cloudflare to see if the current 32subdomains and check Cloudflare to see if the current =A= and =AAAA=
35`A` and `AAAA` records match your server's IP 33records match your server's IP address. If not, it will update the
36address. If not, it will update the records. 34records.
37 35
38```sh 36#+begin_src sh
39# file: update.sh 37# file: update.sh
40#!/bin/bash 38#!/bin/bash
41 39
@@ -57,22 +55,20 @@ do
57 echo -e "\nUpdating $subdomain..." 55 echo -e "\nUpdating $subdomain..."
58 zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh 56 zone_name=$domain dns_record=$subdomain /home/<your-username>/ddns/ddns.sh
59done 57done
60``` 58#+end_src
61 59
62Next, open up the `ddns.sh` script. Paste the following into 60Next, open up the =ddns.sh= script. Paste the following into the script
63the script and update the `api_token` and `email` 61and update the =api_token= and =email= variables.
64variables.
65 62
66```sh 63#+begin_src sh
67nano ~/ddns/ddns.sh 64nano ~/ddns/ddns.sh
68``` 65#+end_src
69 66
70:warning: **Note**: If you want your DNS records to be proxied through 67*Note*: If you want your DNS records to be proxied through
71Cloudflare, find and update the following snippet: 68Cloudflare, find and update the following snippet: ="proxied":false}"=
72`"proxied":false}"` to say `true` instead of 69to say =true= instead of =false=.
73`false`.
74 70
75```sh 71#+begin_src sh
76# file: ddns.sh 72# file: ddns.sh
77#!/bin/bash 73#!/bin/bash
78# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a 74# based on https://gist.github.com/Tras2/cba88201b17d765ec065ccbedfb16d9a
@@ -165,31 +161,30 @@ then
165else 161else
166 echo "There is a problem with either the email or the password" 162 echo "There is a problem with either the email or the password"
167fi 163fi
168``` 164#+end_src
169 165
170Once the script is saved and closed, make the scripts executable: 166Once the script is saved and closed, make the scripts executable:
171 167
172```sh 168#+begin_src sh
173chmod +x ~/ddns/ddns.sh 169chmod +x ~/ddns/ddns.sh
174chmod +x ~/ddns/update.sh 170chmod +x ~/ddns/update.sh
175``` 171#+end_src
176 172
177You can test the script by running it manually: 173You can test the script by running it manually:
178 174
179```sh 175#+begin_src sh
180./update.sh 176./update.sh
181``` 177#+end_src
182 178
183To make sure the scripts run automatically, add it to the 179To make sure the scripts run automatically, add it to the =cron= file so
184`cron` file so that it will run on a schedule. To do this, 180that it will run on a schedule. To do this, open the cron file:
185open the cron file:
186 181
187```sh 182#+begin_src sh
188crontab -e 183crontab -e
189``` 184#+end_src
190 185
191In the cron file, paste the following at the bottom of the editor: 186In the cron file, paste the following at the bottom of the editor:
192 187
193```sh 188#+begin_src sh
194*/5 * * * * bash /home/<your_username>/ddns/update.sh 189,*/5 ** ** ** ** bash /home/<your_username>/ddns/update.sh
195``` 190#+end_src
content/blog/2018-11-28-cpp-compiler.md → blog/cpp-compiler/index.org renamed +39 −49
@@ -1,12 +1,10 @@
1+++ 1#+title: The C++ Compiler
2date = 2018-11-28 2#+date: 2018-11-28
3title = "The C++ Compiler" 3#+description: Learn basics about the C++ compilation process.
4description = "Learn basics about the C++ compilation process." 4#+filetags: :dev:
5+++
6 5
7# A Brief Introduction 6* A Brief Introduction
8 7[[https://en.wikipedia.org/wiki/C%2B%2B][C++]] is a general-purpose
9[C++](https://en.wikipedia.org/wiki/C%2B%2B) is a general-purpose
10programming language with object-oriented, generic, and functional 8programming language with object-oriented, generic, and functional
11features in addition to facilities for low-level memory manipulation. 9features in addition to facilities for low-level memory manipulation.
12 10
@@ -15,19 +13,17 @@ can be executed. There are many steps and intricacies to the compilation
15process, and this post was a personal exercise to learn and remember as 13process, and this post was a personal exercise to learn and remember as
16much information as I can. 14much information as I can.
17 15
18```cpp 16#+begin_src cpp
19#include <iostream> 17#include <iostream>
20 18
21int main() 19int main()
22{ 20{
23 std::cout << "Hello, world!\n"; 21 std::cout << "Hello, world!\n";
24} 22}
25``` 23#+end_src
26
27# Compilation Process
28
29## An Overview
30 24
25** Compilation Process
26*** An Overview
31Compiling C++ projects is a frustrating task most days. Seemingly 27Compiling C++ projects is a frustrating task most days. Seemingly
32nonexistent errors keeping your program from successfully compiling can 28nonexistent errors keeping your program from successfully compiling can
33be annoying (especially since you know you wrote it perfectly the first 29be annoying (especially since you know you wrote it perfectly the first
@@ -35,50 +31,46 @@ time, right?).
35 31
36I'm learning more and more about C++ these days and decided to write 32I'm learning more and more about C++ these days and decided to write
37this concept down so that I can cement it even further in my own head. 33this concept down so that I can cement it even further in my own head.
38However, C++ is not the only compiled language. Check out [the Wikipedia 34However, C++ is not the only compiled language. Check out
39entry for compiled 35[[https://en.wikipedia.org/wiki/Compiled_language][the Wikipedia entry
40languages](https://en.wikipedia.org/wiki/Compiled_language) for more 36for compiled languages]] for more examples of compiled languages.
41examples of compiled languages.
42 37
43I'll start with a wonderful, graphical way to conceptualize the C++ 38I'll start with a wonderful, graphical way to conceptualize the C++
44compiler. View [The C++ Compilation 39compiler. View
45Process](https://web.archive.org/web/20190419035048/http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html) 40[[https://web.archive.org/web/20190419035048/http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html][The
46by Kurt MacMahon, an NIU professor, to see the graphic and an 41C++ Compilation Process]] by Kurt MacMahon, an NIU professor, to see the
47explanation. The goal of the compilation process is to take the C++ code 42graphic and an explanation. The goal of the compilation process is to
48and produce a shared library, dynamic library, or an executable file. 43take the C++ code and produce a shared library, dynamic library, or an
49 44executable file.
50## Compilation Phases 45
51 46** Compilation Phases
52Let's break down the compilation process. There are four major steps to 47Let's break down the compilation process. There are four major steps to
53compiling C++ code. 48compiling C++ code.
54 49
55### Step 1 50*** Step 1
56
57The first step is to expand the source code file to meet all 51The first step is to expand the source code file to meet all
58dependencies. The C++ preprocessor includes the code from all the header 52dependencies. The C++ preprocessor includes the code from all the header
59files, such as `#include <iostream>`. Now, what does that 53files, such as =#include <iostream>=. Now, what does that mean? The
60mean? The previous example includes the `iostream` header. 54previous example includes the =iostream= header. This tells the computer
61This tells the computer that you want to use the `iostream` 55that you want to use the =iostream= standard library, which contains
62standard library, which contains classes and functions written in the 56classes and functions written in the core language. This specific header
63core language. This specific header allows you to manipulate 57allows you to manipulate input/output streams. After all this, you'll
64input/output streams. After all this, you'll end up which a temporary 58end up which a temporary file that contains the expanded source code.
65file that contains the expanded source code.
66
67In the example of the C++ code above, the `iostream` class
68would be included in the expanded code.
69 59
70### Step 2 60In the example of the C++ code above, the =iostream= class would be
61included in the expanded code.
71 62
63*** Step 2
72After the code is expanded, the compiler comes into play. The compiler 64After the code is expanded, the compiler comes into play. The compiler
73takes the C++ code and converts this code into the assembly language, 65takes the C++ code and converts this code into the assembly language,
74understood by the platform. You can see this in action if you head over 66understood by the platform. You can see this in action if you head over
75to the [GodBolt Compiler Explorer](https://godbolt.org), which shows C++ 67to the [[https://godbolt.org][GodBolt Compiler Explorer]], which shows
76being converted into assembly dynamically. 68C++ being converted into assembly dynamically.
77 69
78For example, the `Hello, world!` code snippet above compiles 70For example, the =Hello, world!= code snippet above compiles into the
79into the following assembly code: 71following assembly code:
80 72
81```asm 73#+begin_src asm
82.LC0: 74.LC0:
83 .string "Hello, world!\n" 75 .string "Hello, world!\n"
84main: 76main:
@@ -118,10 +110,9 @@ _GLOBAL__sub_I_main:
118 call __static_initialization_and_destruction_0(int, int) 110 call __static_initialization_and_destruction_0(int, int)
119 pop rbp 111 pop rbp
120 ret 112 ret
121``` 113#+end_src
122
123### Step 3
124 114
115*** Step 3
125Third, the assembly code generated by the compiler is assembled into the 116Third, the assembly code generated by the compiler is assembled into the
126object code for the platform. Essentially, this is when the compiler 117object code for the platform. Essentially, this is when the compiler
127takes the assembly code and assembles it into machine code in a binary 118takes the assembly code and assembles it into machine code in a binary
@@ -130,8 +121,7 @@ compilers will allow you to stop compilation at this step. This would be
130useful for compiling each source code file separately. This saves time 121useful for compiling each source code file separately. This saves time
131later if a single file changes; only that file needs to be recompiled. 122later if a single file changes; only that file needs to be recompiled.
132 123
133### Step 4 124*** Step 4
134
135Finally, the object code file generated by the assembler is linked 125Finally, the object code file generated by the assembler is linked
136together with the object code files for any library functions used to 126together with the object code files for any library functions used to
137produce a shared library, dynamic library, or an executable file. It 127produce a shared library, dynamic library, or an executable file. It
content/blog/2020-02-09-cryptography-basics.md → blog/cryptography-basics/index.org renamed +70 −77
@@ -1,30 +1,29 @@
1+++ 1#+title: Cryptography Basics
2date = 2020-02-09 2#+date: 2020-02-09
3title = "Cryptography Basics" 3#+description: Learn about the basics of cryptography.
4description = "Learn about the basics of cryptography." 4#+filetags: :security:
5+++ 5
6 6* Similar Article Available
7# Similar Article Available 7If you haven't already, feel free to read my post on
8 8[[../aes-encryption/][AES Encryption]].
9If you haven't already, feel free to read my post on [AES 9
10Encryption](/blog/aes-encryption/). 10* What is Cryptography?
11 11In layman's terms, cryptography is a process that can change data from a
12# What is Cryptography? 12readable format into an unreadable format (and vice-versa) through a
13
14In layman's terms, cryptography is a process that can change data from
15a readable format into an unreadable format (and vice-versa) through a
16series of processes and secrets. More technically, this is the Internet 13series of processes and secrets. More technically, this is the Internet
17Security Glossary's definition: 14Security Glossary's definition:
18 15
19> [Cryptography is] the mathematical science that deals with 16#+begin_quote
20> transforming data to render its meaning unintelligible (i.e., to hide 17[Cryptography is] the mathematical science that deals with transforming
21> its semantic content), prevent its undetected alteration, or prevent 18data to render its meaning unintelligible (i.e., to hide its semantic
22> its unauthorized use. If the transformation is reversible, 19content), prevent its undetected alteration, or prevent its unauthorized
23> cryptography also deals with restoring encrypted data to an 20use. If the transformation is reversible, cryptography also deals with
24> intelligible form. 21restoring encrypted data to an intelligible form.
25> 22
26> - [Internet Security Glossary 23- [[https://tools.ietf.org/html/rfc2828][Internet Security Glossary
27> (2000)](https://tools.ietf.org/html/rfc2828) 24 (2000)]]
25
26#+end_quote
28 27
29Cryptography cannot offer protection against the loss of data; it simply 28Cryptography cannot offer protection against the loss of data; it simply
30offers encryption methods to protect data at-rest and data in-traffic. 29offers encryption methods to protect data at-rest and data in-traffic.
@@ -34,25 +33,23 @@ decrypted back to plaintext). The encryption process is completed
34through the use of a mathematical function that utilizes one or more 33through the use of a mathematical function that utilizes one or more
35values called keys to encrypt or decrypt the data. 34values called keys to encrypt or decrypt the data.
36 35
37# Key Elements of Cryptographic Systems 36* Key Elements of Cryptographic Systems
38
39To create or evaluate a cryptographic system, you need to know the 37To create or evaluate a cryptographic system, you need to know the
40essential pieces to the system: 38essential pieces to the system:
41 39
42- **Encryption Algorithm (Primitive):** A mathematical process that 40- *Encryption Algorithm (Primitive):** A mathematical process that
43 encrypts and decrypts data. 41 encrypts and decrypts data.
44- **Encryption Key:** A string of bits used within the encryption 42- *Encryption Key:** A string of bits used within the encryption
45 algorithm as the secret that allows successful encryption or 43 algorithm as the secret that allows successful encryption or
46 decryption of data. 44 decryption of data.
47- **Key Length (Size):** The maximum number of bits within the 45- *Key Length (Size):** The maximum number of bits within the encryption
48 encryption key. It's important to remember that key size is 46 key. It's important to remember that key size is regulated in many
49 regulated in many countries. 47 countries.
50- **Message Digest:** A smaller, fixed-size bit string version of the 48- *Message Digest:** A smaller, fixed-size bit string version of the
51 original message. This is practically infeasible to reverse, which 49 original message. This is practically infeasible to reverse, which is
52 is why it's commonly used to verify integrity. 50 why it's commonly used to verify integrity.
53 51
54# Symmetric Systems (Secret Key Cryptography) 52* Symmetric Systems (Secret Key Cryptography)
55
56Symmetric cryptography utilizes a secret, bidirectional key to perform 53Symmetric cryptography utilizes a secret, bidirectional key to perform
57both encryption and decryption of the data. The most common 54both encryption and decryption of the data. The most common
58implementation of symmetric cryptography is the Advanced Encryption 55implementation of symmetric cryptography is the Advanced Encryption
@@ -76,14 +73,13 @@ key is private, it can be difficult to safely distribute keys to
76communication partners. Additionally, the key cannot be used to sign 73communication partners. Additionally, the key cannot be used to sign
77messages since it's necessary to keep the key private. 74messages since it's necessary to keep the key private.
78 75
79# Asymmetric Systems (Public Key Cryptography) 76* Asymmetric Systems (Public Key Cryptography)
80
81Asymmetric cryptography utilizes two keys within the system: a secret 77Asymmetric cryptography utilizes two keys within the system: a secret
82key that is privately-held and a public key that can be distributed 78key that is privately-held and a public key that can be distributed
83freely. The interesting aspect of asymmetric cryptography is that either 79freely. The interesting aspect of asymmetric cryptography is that either
84key can be used to encrypt the data, there's no rule that dictates 80key can be used to encrypt the data, there's no rule that dictates which
85which key must be used for encryption. Once one key is used to encrypt 81key must be used for encryption. Once one key is used to encrypt the
86the data, only the other key can be used to decrypt the data. This means 82data, only the other key can be used to decrypt the data. This means
87that if the private key encrypts the data, only the public key can 83that if the private key encrypts the data, only the public key can
88decrypt the data. 84decrypt the data.
89 85
@@ -103,17 +99,16 @@ with digital signatures and public keys. To combat this, applications
103often use a public key infrastructure (PKI) to independently 99often use a public key infrastructure (PKI) to independently
104authenticate the validity of signatures and keys. 100authenticate the validity of signatures and keys.
105 101
106Due to the large key size and [inefficient mathematical 102Due to the large key size and
107functions](https://crypto.stackexchange.com/a/591) of asymmetric 103[[https://crypto.stackexchange.com/a/591][inefficient mathematical
108encryption, elliptical curve cryptography (ECC) is often used to 104functions]] of asymmetric encryption, elliptical curve cryptography
109increase security while using fewer resources. 105(ECC) is often used to increase security while using fewer resources.
110
111# Applications of Cryptographic Systems
112 106
107* Applications of Cryptographic Systems
113There are quite a few implementations of cryptographic systems around 108There are quite a few implementations of cryptographic systems around
114the world. Here are a few popular examples: 109the world. Here are a few popular examples:
115 110
116**Transport Layer Security (TLS):** One of the most famous cryptographic 111*Transport Layer Security (TLS):** One of the most famous cryptographic
117solutions created is TLS, a session-layered or connection-layered 112solutions created is TLS, a session-layered or connection-layered
118internet protocol that allows for secure communications between browsers 113internet protocol that allows for secure communications between browsers
119and servers. Using handshakes, peer negotiation, and authentication 114and servers. Using handshakes, peer negotiation, and authentication
@@ -123,12 +118,12 @@ was found in the SSL protocol in 2014. Instead of SSL, TLS can be used
123with HTTP to form HTTPS and is the preferred method for modern web 118with HTTP to form HTTPS and is the preferred method for modern web
124development due to its increased security. 119development due to its increased security.
125 120
126**Secure Hypertext Transfer Protocol (HTTPS):** An application layer 121*Secure Hypertext Transfer Protocol (HTTPS):** An application layer
127protocol that allows for secure transport of data between servers and 122protocol that allows for secure transport of data between servers and
128web clients. One of the unique parts of HTTPS is that it uses a secured 123web clients. One of the unique parts of HTTPS is that it uses a secured
129port number instead of the default web port address. 124port number instead of the default web port address.
130 125
131**Virtual Private Network (VPN):** VPNs are made to securely extend a 126*Virtual Private Network (VPN):** VPNs are made to securely extend a
132private network across public networks by utilizing an encrypted layered 127private network across public networks by utilizing an encrypted layered
133tunneling protocol paired with an authentication method, such as 128tunneling protocol paired with an authentication method, such as
134usernames and passwords. This technology originally allowed remote 129usernames and passwords. This technology originally allowed remote
@@ -136,7 +131,7 @@ employees to access their company's data but have evolved into one of
136the top choices for anyone who wishes to mask their sensitive personal 131the top choices for anyone who wishes to mask their sensitive personal
137data. 132data.
138 133
139**Internet Protocol Security (IPSec):** This protocol suite facilitates 134*Internet Protocol Security (IPSec):** This protocol suite facilitates
140communication between two or more hosts or subnets by authenticating and 135communication between two or more hosts or subnets by authenticating and
141encrypting the data packets. IPSec is used in a lot of VPNs to establish 136encrypting the data packets. IPSec is used in a lot of VPNs to establish
142the VPN connection through the transport and tunnel mode encryption 137the VPN connection through the transport and tunnel mode encryption
@@ -144,35 +139,33 @@ methods. IPSec encrypts just the data portion of packets in the
144transport methods, but it encrypts both the data and headers in the 139transport methods, but it encrypts both the data and headers in the
145tunnel method (introducing an additional header for authentication). 140tunnel method (introducing an additional header for authentication).
146 141
147**Secure Shell (SSH):** SSH is another network protocol used to protect 142*Secure Shell (SSH):** SSH is another network protocol used to protect
148network services by authenticating users through a secure channel. This 143network services by authenticating users through a secure channel. This
149protocol is often used for command-line (shell) functions such as remote 144protocol is often used for command-line (shell) functions such as remote
150shell commands, logins, and file transfers. 145shell commands, logins, and file transfers.
151 146
152**Kerberos:** Developed by MIT, Kerberos is a computer-network 147*Kerberos:** Developed by MIT, Kerberos is a computer-network
153authentication protocol that works on the basis of tickets to allow 148authentication protocol that works on the basis of tickets to allow
154nodes communicating over a non-secure network to prove their identity to 149nodes communicating over a non-secure network to prove their identity to
155one another securely. This is most commonly used in business 150one another securely. This is most commonly used in business
156environments when used as the authentication and encryption method for 151environments when used as the authentication and encryption method for
157Windows Active Directory (AD). 152Windows Active Directory (AD).
158 153
159# Cybersecurity Controls 154* Cybersecurity Controls
160 155If you're someone who needs solutions on how to control risks associated
161If you're someone who needs solutions on how to control risks 156with utilizing a crytograhpic system, start with a few basic controls:
162associated with utilizing a crytograhpic system, start with a few basic 157
163controls: 158- *Policies:** A policy on the use of cryptographic controls for
164 159 protection of information is implemented and is in accordance with
165- **Policies:** A policy on the use of cryptographic controls for 160 organizational objectives.
166 protection of information is implemented and is in accordance with 161- *Key management:** A policy on the use, protection and lifetime of
167 organizational objectives. 162 cryptographic keys is implemented through the entire application
168- **Key management:** A policy on the use, protection and lifetime of 163 lifecycle.
169 cryptographic keys is implemented through the entire application 164- *Key size:** The organization has researched the optimal key size for
170 lifecycle. 165 their purposes, considering national laws, required processing power,
171- **Key size:** The organization has researched the optimal key size 166 and longevity of the solution.
172 for their purposes, considering national laws, required processing 167- *Algorithm selection:** Implemented algorithms are sufficiently
173 power, and longevity of the solution. 168 appropriate for the business of the organization, robust, and align
174- **Algorithm selection:** Implemented algorithms are sufficiently 169 with recommended guidelines.
175 appropriate for the business of the organization, robust, and align 170- *Protocol configuration:** Protocols have been reviewed and configured
176 with recommended guidelines. 171 suitable to the purpose of the business.
177- **Protocol configuration:** Protocols have been reviewed and
178 configured suitable to the purpose of the business.
blog/curseradio/index.org added +95
@@ -0,0 +1,95 @@
1#+title: CurseRadio: Listening to the Radio on the Command Line
2#+date: 2022-07-25
3#+description: Use Curse Radio to listen to radio on the command line.
4#+filetags: :linux:
5
6* Overview
7While exploring some interesting Linux applications, I stumbled across
8[[https://github.com/chronitis/curseradio][curseradio]], a command-line
9radio player based on Python.
10
11This application is fantastic and incredibly easy to install, so I
12wanted to dedicate a post today to this app. Let's look at the features
13within the app and then walk through the installation process I took to
14get =curseradio= working.
15
16* Features
17#+caption: curseradio
18[[https://img.cleberg.net/blog/20220725-curseradio/curseradio.png]]
19
20The radio player itself is quite minimal. As you can see in the
21screenshot above, it contains a simple plaintext list of all available
22categories, which can be broken down further and further. In addition,
23radio shows are available for listening, alongside regular radio
24stations.
25
26For example, the =Sports= > =Pro Basketball= > =Shows= category contains
27a number of specific shows related to Professional Basketball.
28
29Aside from being able to play any of the listed stations/shows, you can
30make a channel your favorite by pressing =f=. It will now show up at the
31top of the radio player in the =Favourites= category.
32
33** Commands/Shortcuts
34| Key(s) | Command |
35|------------+---------------------------------|
36| ↑, ↓ | navigate |
37| PgUp, PgDn | navigate quickly |
38| Home, End | to top/bottom |
39| Enter | open/close folders, play stream |
40| k | stop playing stream |
41| q | quit |
42| f | toggle favourite |
43
44* Installation
45** Dependencies
46Before installing =curseradio=, a handful of system and Python packages
47are required. To get started, install =python3=, =pip3=, and =mpv= on
48your system. In this example, I'm using Fedora Linux, which uses the
49=dnf= package manager. You may need to adjust this if you're using a
50different system.
51
52#+begin_src sh
53sudo dnf install python3 pip3 mpv
54#+end_src
55
56Next, use =pip3= to install =requests=, =xdg=, and =lxml=:
57
58#+begin_src sh
59pip3 install requests xdg lxml
60#+end_src
61
62** Repository Source Installation
63Once all the dependencies are installed, we can clone the source code
64and enter that directory:
65
66#+begin_src sh
67git clone https://github.com/chronitis/curseradio && cd curseradio
68#+end_src
69
70Once you're within the =curseradio= directory, you can install the
71application with the provided =setup.py= script.
72
73#+begin_src sh
74sudo python3 setup.py install
75#+end_src
76
77In my case, I ran into a few errors and needed to create the folders
78that curseradio wanted to use for its installation. If you don't get any
79errors, you can skip this and run the app.
80
81#+begin_src sh
82sudo mkdir /usr/local/lib/python3.10/
83sudo mkdir /usr/local/lib/python3.10/site-packages/
84#+end_src
85
86#+begin_src sh
87sudo chown -R $USER:$USER /usr/local/lib/python3.10/
88#+end_src
89
90* Run the Application
91Once fully installed without errors, you can run the application!
92
93#+begin_src sh
94python3 /usr/local/bin/curseradio
95#+end_src
blog/customizing-ubuntu/index.org added +195
@@ -0,0 +1,195 @@
1#+title: Beginner's Guide: Customizing Ubuntu
2#+date: 2020-05-19
3#+description: A beginner's guide to customizing the Ubuntu operating system.
4#+filetags: :linux:
5
6* More Information
7For inspiration on designing your *nix computer, check out the
8[[https://libredd.it/r/unixporn][r/unixporn]] subreddit!
9
10* Customizing Ubuntu
11New to Linux and want to add a personal touch to your machine? One of
12the best perks of Linux is that it is *extremely** customizable. You can
13change the styles of the windows, shell (status bars/docks), icons,
14fonts, terminals, and more.
15
16In this post, I'm going to go through customization on Ubuntu 20.04
17(GNOME) since most new users tend to choose Ubuntu-based distros. If
18you've found a way to install Arch with i3-gaps, I'm assuming you know
19how to find more advanced tutorials out there on customizations.
20
21** Required Tools
22#+caption: Gnome Tweaks
23[[https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-tweaks-min.png]]
24
25Ubuntu 20.04 ships with the default desktop environment
26[[https://www.gnome.org/][Gnome]], which includes the handy
27=gnome-tweaks= tool to quickly change designs. To install this, just
28open your terminal and enter the following command:
29
30#+begin_src sh
31sudo apt install gnome-tweaks
32#+end_src
33
34After you've finished installing the tool, simply launch the Tweaks
35application, and you'll be able to access the various customization
36options available by default on Ubuntu. You might even like some of the
37pre-installed options.
38
39** GNOME Application Themes
40To change the themes applied to applications in GNOME, you will need to
41change the Applications dropdown in the Appearance section of Tweaks. To
42add more themes, you will have to find your preferred theme online and
43follow the steps below to have it show up in the Tweaks tool. While you
44may find themes anywhere, one of the most popular sites for GNOME themes
45is [[https://www.gnome-look.org/][gnome-look.org]]. This website
46contains themes for applications, shells, icons, and cursors.
47
48Steps to import themes into Tweaks:
49
501. Download the theme.
512. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you
52 will need to extract the contents. This is easiest when opening the
53 file explorer, right-clicking the compressed file, and choosing
54 "Extract here."
553. Move the theme folder to =/usr/share/themes/=. You can do so with the
56 following command: =sudo mv theme-folder/ /usr/share/themes/=.
57 - Icons and cursors will be moved to the =/usr/share/icons/= folder.
58 - Fonts will be moved to the =/usr/share/fonts/= folder
59 Alternatively, you can move them to the
60 =/usr/share/fonts/opentype/= or =/usr/share/fonts/opentype/=
61 folders, if you have a specific font type.
624. Close tweaks if it is open. Re-open Tweaks and your new theme will be
63 available in the Applications dropdown in the Appearance section of
64 Tweaks.
65
66If the theme is not showing up after you've moved it into the themes
67folder, you may have uncompressed the folder into a sub-folder. You can
68check this by entering the theme folder and listing the contents:
69
70#+begin_src sh
71cd /usr/share/themes/Mojave-Dark && ls -la
72#+end_src
73
74This is an example of what the contents of your theme folder should look
75like. If you just see another folder there, you should move that folder
76up into the =/usr/share/themes/= folder.
77
78#+begin_src sh
79cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
80#+end_src
81
82** GNOME Shell Themes
83To change the appearance of the title bar, default dock, app menu, and
84other parts of the GNOME shell, you'll need to install the
85[[https://extensions.gnome.org/extension/19/user-themes/][user themes]]
86extension on [[https://extensions.gnome.org/][Gnome Extensions]]. To be
87able to install extensions, you will first need to install the browser
88extension that the website instructs you to. See this screenshot for the
89blue box with a link to the extension.
90
91#+caption: Gnome Extensions
92[[https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-extensions-min.png]]
93
94After the browser extension is installed, you will need to install the
95native host connector:
96
97#+begin_src sh
98sudo apt install chrome-gnome-shell
99#+end_src
100
101Finally, you can go the
102[[https://extensions.gnome.org/extension/19/user-themes/][user themes]]
103extension page and click the installation button. This will enable the
104Shell option in Tweaks. Now you can move shell themes to the
105=/usr/share/themes= directory, using the same steps mentioned in the
106previous section, and enable the new theme in Tweaks.
107
108** Icons & Cursors
109Icons and cursors are installed exactly the same way, so I'm grouping
110these together in this post. Both of these items will need to follow the
111same process as installing themes, except you will want to move your
112font folders to the =/usr/share/icons/= directory instead.
113
114** Fonts
115Fonts are one of the overlooked parts of customization, but a good font
116can make the whole screen look different. For example, I have installed
117the [[https://github.com/IBM/plex/releases][IBM Plex]] fonts on my
118system. This follows the same process as installing themes, except you
119will want to move your font folders to the =/usr/share/fonts/= directory
120instead.
121
122** Terminal
123If you spend a lot of time typing commands, you know how important the
124style and functionality of the terminal is. After spending a lot of time
125using the default GNOME terminal with
126[[https://en.wikipedia.org/wiki/Bash_(Unix_shell)][unix shell]], I
127decided to try some different options. I ended up choosing
128[[https://terminator-gtk3.readthedocs.io/en/latest/][Terminator]] with
129[[https://en.wikipedia.org/wiki/Z_shell][zsh]].
130
131Terminator is great if you need to open multiple terminals at one time
132by simply right-clicking and splitting the screen into as many terminals
133as you want. While this project hasn't been updated in a while,
134[[https://github.com/gnome-terminator/terminator/issues/1][it is coming
135under new development]]. However, this terminal is great and I haven't
136experienced any errors yet.
137
138For the shell choice, I decided to choose zsh after trying it out on a
139fresh Manjaro installation. Zsh is great if you like to change the
140themes of your terminal, include icons, or add plugins.
141
142The desktop uses the
143[[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]]
144to suggest past commands as you type. In addition, it suggests
145corrections if you misspell a command. Lastly, it uses the =af-magic=
146theme, which adds dashed lines between commands, moving the user@host
147tag to the right side of the terminal, and changes the colors. There are
148plenty of plugins and themes to choose from. Just figure out what you
149like and add it to your =~/.zshrc= file!
150
151*** Steps to Replicate My Terminal
152To install zsh on Ubuntu, enter the following command into a terminal:
153
154#+begin_src sh
155sudo apt install zsh
156#+end_src
157
158Then, enter the next command to activate zsh:
159
160#+begin_src sh
161sudo chsh -s $(which zsh) $(whoami)
162#+end_src
163
164To install Terminator on Ubuntu:
165
166#+begin_src sh
167sudo apt install terminator
168#+end_src
169
170To install Oh My Zsh on Ubuntu:
171
172#+begin_src sh
173sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
174#+end_src
175
176To install zsh-autosuggestions via Oh My Zsh:
177
178#+begin_src sh
179git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
180#+end_src
181
182Then, add the following plugin wording to your =~/.zshrc= file (the
183default config usually has the =git= plugin activated, so just add any
184other plugins to the parentheses separated by a space):
185
186#+begin_src sh
187nano ~/.zshrc
188#+end_src
189
190#+begin_src sh
191plugins=(git zsh-autosuggestions)
192#+end_src
193
194Finally, you need to log out of your computer and log back in so your
195user shell can refresh.
content/blog/2022-06-22-daily-poetry.md → blog/daily-poetry/index.org renamed +69 −80
@@ -1,17 +1,14 @@
1+++ 1#+title: Daily Plaintext Poetry via Email
2date = 2022-06-22 2#+date: 2022-06-22
3title = "Daily Plaintext Poetry via Email" 3#+description: A small project to automatically deliver poetry to your inbox daily.
4description = "A small project to automatically deliver poetry to your inbox daily." 4#+filetags: :selfhosting:
5+++
6
7## Source Code
8 5
6* Source Code
9I don't want to bury the lede here, so if you'd like to see the full 7I don't want to bury the lede here, so if you'd like to see the full
10source code I use to email myself plaintext poems daily, visit the 8source code I use to email myself plaintext poems daily, visit the
11repository: [daily-poem](https://git.sr.ht/~cmc/daily-poem/). 9repository: [[https://git.cleberg.net/daily-poem.git][daily-poem.git]].
12
13## My Daily Dose of Poetry
14 10
11* My Daily Dose of Poetry
15Most of my programming projects are small, random projects that are made 12Most of my programming projects are small, random projects that are made
16strictly to fix some small problem I have or enhance my quality of life. 13strictly to fix some small problem I have or enhance my quality of life.
17 14
@@ -25,47 +22,43 @@ each morning, as I know I would simply give up and stop reading daily.
25Thus, I found a way to deliver poetry to myself in plain-text format, on 22Thus, I found a way to deliver poetry to myself in plain-text format, on
26a daily basis, and scheduled to deliver automatically. 23a daily basis, and scheduled to deliver automatically.
27 24
28## Prerequisites 25* Prerequisites
29
30This solution uses Python and email, so the following process requires 26This solution uses Python and email, so the following process requires
31the following to be installed: 27the following to be installed:
32 28
331. An SMTP server, which can be as easy as installing 291. An SMTP server, which can be as easy as installing =mailutils= if
34 `mailutils` if you're on a Debian-based distro. 30 you're on a Debian-based distro.
352. Python (& pip!) 312. Python (& pip!)
363. The following Python packages: `email`, 323. The following Python packages: =email=, =smtplib=, =json=, and
37 `smtplib`, `json`, and `requests` 33 =requests=
38
39## Breaking Down the Logic
40 34
35* Breaking Down the Logic
41I want to break down the logic for this program, as it's quite simple 36I want to break down the logic for this program, as it's quite simple
42and informational. 37and informational.
43 38
44### Required Packages 39** Required Packages
45
46This program starts with a simple import of the required packages, so I 40This program starts with a simple import of the required packages, so I
47wanted to explain why each package is used: 41wanted to explain why each package is used:
48 42
49```python 43#+begin_src python
50from email.mime.text import MIMEText # Required for translating MIMEText 44from email.mime.text import MIMEText # Required for translating MIMEText
51import smtplib # Required to process the SMTP mail delivery 45import smtplib # Required to process the SMTP mail delivery
52import json # Required to parse the poetry API results 46import json # Required to parse the poetry API results
53import requests # Required to send out a request to the API 47import requests # Required to send out a request to the API
54``` 48#+end_src
55
56### Sending the API Request
57 49
50** Sending the API Request
58Next, we need to actually send the API request. In my case, I'm calling 51Next, we need to actually send the API request. In my case, I'm calling
59a random poem from the entire API. If you want, you can call specific 52a random poem from the entire API. If you want, you can call specific
60poems or authors from this API. 53poems or authors from this API.
61 54
62```python 55#+begin_src python
63json_data = requests.get('https://poetrydb.org/random').json() 56json_data = requests.get('https://poetrydb.org/random').json()
64``` 57#+end_src
65 58
66This gives us the following result in JSON: 59This gives us the following result in JSON:
67 60
68```json 61#+begin_src json
69[ 62[
70 { 63 {
71 "title": "Sonnet XXII: With Fools and Children", 64 "title": "Sonnet XXII: With Fools and Children",
@@ -91,40 +84,40 @@ This gives us the following result in JSON:
91 "linecount": "15" 84 "linecount": "15"
92 } 85 }
93] 86]
94``` 87#+end_src
95 88
96### Parsing the API Results 89** Parsing the API Results
97 90In order to parse this into a readable format, we need to use the =json=
98In order to parse this into a readable format, we need to use the 91package and extract the fields we want. In the example below, I am
99`json` package and extract the fields we want. In the example 92grabbing every field presented by the API.
100below, I am grabbing every field presented by the API.
101 93
102For the actual poem content, we need to loop over each line in the 94For the actual poem content, we need to loop over each line in the
103`lines` variable since each line is a separate string by 95=lines= variable since each line is a separate string by default.
104default. 96
105 97#+begin_quote
106> You *could* also extract the title or author and make another call out 98You /could/ also extract the title or author and make another call out
107> to the API to avoid having to build the plaintext poem with a loop, 99to the API to avoid having to build the plaintext poem with a loop, but
108> but it just doesn't make sense to me to send multiple requests when 100it just doesn't make sense to me to send multiple requests when we can
109> we can create a simple loop on our local machine to work with the data 101create a simple loop on our local machine to work with the data we
110> we already have. 102already have.
111> 103
112> For 104For
113> [example](https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text), 105[[https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text][example]],
114> look at the raw data response of this link to see the poem's lines 106look at the raw data response of this link to see the poem's lines
115> returned in plaintext. 107returned in plaintext.
116 108
117```python 109#+end_quote
110
111#+begin_src python
118title = json_data[0]['title'] 112title = json_data[0]['title']
119author = json_data[0]['author'] 113author = json_data[0]['author']
120line_count = json_data[0]['linecount'] 114line_count = json_data[0]['linecount']
121lines = '' 115lines = ''
122for line in json_data[0]['lines']: 116for line in json_data[0]['lines']:
123 lines = lines + line + "\n" 117 lines = lines + line + "\n"
124``` 118#+end_src
125
126### Composing the Email
127 119
120** Composing the Email
128Now that I have all the data I need, I just need to compose it into a 121Now that I have all the data I need, I just need to compose it into a
129message and prepare the message metadata. 122message and prepare the message metadata.
130 123
@@ -133,43 +126,41 @@ by the author, then a blank line, and finally the full poem. This code
133snippet combines that data and packages it into a MIMEText container, 126snippet combines that data and packages it into a MIMEText container,
134ready to be emailed. 127ready to be emailed.
135 128
136```python 129#+begin_src python
137msg_body = title + "\n" + author + "\n\n" + lines 130msg_body = title + "\n" + author + "\n\n" + lines
138msg = MIMEText(msg_body) 131msg = MIMEText(msg_body)
139``` 132#+end_src
140 133
141Before we send the email, we need to prepare the metadata (subject, 134Before we send the email, we need to prepare the metadata (subject,
142from, to, etc.): 135from, to, etc.):
143 136
144```python 137#+begin_src python
145sender_email = 'example@server.local' 138sender_email = 'example@server.local'
146recipient_emails = ['user@example.com'] 139recipient_emails = ['user@example.com']
147msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)' 140msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)'
148msg['From'] = sender_email 141msg['From'] = sender_email
149msg['To'] = recipient_email 142msg['To'] = recipient_email
150``` 143#+end_src
151
152### Sending the Email
153 144
145** Sending the Email
154Now that I have everything ready to be emailed, the last step is to 146Now that I have everything ready to be emailed, the last step is to
155simply connect to an SMTP server and send the email out to the 147simply connect to an SMTP server and send the email out to the
156recipients. In my case, I installed `mailutils` on Ubuntu and 148recipients. In my case, I installed =mailutils= on Ubuntu and let my
157let my SMTP server be `localhost`. 149SMTP server be =localhost=.
158 150
159```python 151#+begin_src python
160smtp_server = 'localhost' 152smtp_server = 'localhost'
161s = smtplib.SMTP(smtp_server) 153s = smtplib.SMTP(smtp_server)
162s.sendmail(sender_email, recipient_emails, msg.as_string()) 154s.sendmail(sender_email, recipient_emails, msg.as_string())
163s.quit() 155s.quit()
164``` 156#+end_src
165
166## The Result!
167 157
168Instead of including a screenshot, I've copied the contents of the 158* The Result!
169email that was delivered to my inbox below since I set this process up 159Instead of including a screenshot, I've copied the contents of the email
170in plaintext format. 160that was delivered to my inbox below since I set this process up in
161plaintext format.
171 162
172```txt 163#+begin_src txt
173Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC) 164Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC)
174From: REDACTED 165From: REDACTED
175To: REDACTED 166To: REDACTED
@@ -195,25 +186,23 @@ You that behold us, laugh us not to scorn;
195Give Nature thanks you are not such as we. 186Give Nature thanks you are not such as we.
196Yet fools and children sometimes tell in play 187Yet fools and children sometimes tell in play
197Some, wise in show, more fools indeed than they. 188Some, wise in show, more fools indeed than they.
198``` 189#+end_src
199
200## Scheduling the Daily Email
201 190
202Last, but not least, is scheduling this Python script with 191* Scheduling the Daily Email
203`crontab`. To schedule a script to run daily, you can add it 192Last, but not least, is scheduling this Python script with =crontab=. To
204to the `crontab` file. To do this, open `crontab` 193schedule a script to run daily, you can add it to the =crontab= file. To
205in editing mode: 194do this, open =crontab= in editing mode:
206 195
207```sh 196#+begin_src sh
208crontab -e 197crontab -e
209``` 198#+end_src
210 199
211In the file, simply paste the following snippet at the bottom of the 200In the file, simply paste the following snippet at the bottom of the
212file and ensure that the file path is correctly pointing to wherever you 201file and ensure that the file path is correctly pointing to wherever you
213saved your Python script: 202saved your Python script:
214 203
215```config 204#+begin_src config
2160 8 * * * python3 /home/<your_user>/dailypoem/main.py 2050 8 ** ** ** python3 /home/<your_user>/dailypoem/main.py
217``` 206#+end_src
218 207
219We have now set up the script and scheduled it to run daily at 08:00! 208We have now set up the script and scheduled it to run daily at 08:00!
content/blog/2022-02-16-debian-and-nginx.md → blog/debian-and-nginx/index.org renamed +65 −73
@@ -1,13 +1,11 @@
1+++ 1#+title: Migrating to a New Web Server Setup with Debian, Nginx, and Agate
2date = 2022-02-16 2#+date: 2022-02-16
3title = "Migrating to a New Web Server Setup with Debian, Nginx, and Agate" 3#+description: A retrospective on my recent server migration.
4description = "A retrospective on my recent server migration." 4#+filetags: :sysadmin:
5+++
6 5
7## Server OS: Debian 6* Server OS: Debian
8 7#+caption: Debian + neofetch
9![Debian + 8[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/neofetch.png]]
10neofetch](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/neofetch.png)
11 9
12I've used various Linux distributions throughout the years, but I've 10I've used various Linux distributions throughout the years, but I've
13never used anything except Ubuntu for my servers. Why? I really have no 11never used anything except Ubuntu for my servers. Why? I really have no
@@ -16,62 +14,61 @@ idea, mostly just comfort around the commands and software availability.
16However, I have always wanted to try Debian as a server OS after testing 14However, I have always wanted to try Debian as a server OS after testing
17it out in a VM a few years ago (side-note: I'd love to try Alpine too, 15it out in a VM a few years ago (side-note: I'd love to try Alpine too,
18but I always struggle with compatibility). So, I decided to launch a new 16but I always struggle with compatibility). So, I decided to launch a new
19VPS and use [Debian](https://www.debian.org) 11 as the OS. Spoiler 17VPS and use [[https://www.debian.org][Debian]] 11 as the OS. Spoiler
20alert: it feels identical to Ubuntu for my purposes. 18alert: it feels identical to Ubuntu for my purposes.
21 19
22I did the normal things when first launching the VPS, such as adding a 20I did the normal things when first launching the VPS, such as adding a
23new user, locking down SSH, etc. If you want to see that level of 21new user, locking down SSH, etc. If you want to see that level of
24detail, read my other post about [How to Set Up a VPS Web 22detail, read my other post about
25Server](https://cleberg.net/blog/how-to-set-up-a-vps-web-server/). 23[[https://cleberg.net/blog/how-to-set-up-a-vps-web-server/][How to Set
24Up a VPS Web Server]].
26 25
27All of this has been similar, apart from small things such as the 26All of this has been similar, apart from small things such as the
28location of users' home folders. No complaints at all from me - Debian 27location of users' home folders. No complaints at all from me - Debian
29seems great. 28seems great.
30 29
31## Web Server: Nginx 30* Web Server: Nginx
32 31#+caption: Nginx status
33![Nginx 32[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/nginx.png]]
34status](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/nginx.png)
35 33
36Once I had the baseline server configuration set-up for Debian, I moved 34Once I had the baseline server configuration set-up for Debian, I moved
37on to trying out [Nginx](https://nginx.org) as my web server software. 35on to trying out [[https://nginx.org][Nginx]] as my web server software.
38This required me to install the `nginx` and `ufw` 36This required me to install the =nginx= and =ufw= packages, as well as
39packages, as well as setting up the initial UFW config: 37setting up the initial UFW config:
40 38
41```sh 39#+begin_src sh
42sudo apt install nginx ufw 40sudo apt install nginx ufw
43sudo ufw allow 'Nginx Full' 41sudo ufw allow 'Nginx Full'
44sudo ufw allow SSH 42sudo ufw allow SSH
45sudo ufw enable 43sudo ufw enable
46sudo ufw status 44sudo ufw status
47sudo systemctl status nginx 45sudo systemctl status nginx
48``` 46#+end_src
49 47
50Once I had the firewall set, I moved on to creating the directories and 48Once I had the firewall set, I moved on to creating the directories and
51files for my website. This is very easy and is basically the same as 49files for my website. This is very easy and is basically the same as
52setting up an Apache server, so no struggles here. 50setting up an Apache server, so no struggles here.
53 51
54```sh 52#+begin_src sh
55sudo mkdir -p /var/www/your_domain/html 53sudo mkdir -p /var/www/your_domain/html
56sudo chown -R $USER:$USER /var/www/your_domain/html 54sudo chown -R $USER:$USER /var/www/your_domain/html
57sudo chmod -R 755 /var/www/your_domain 55sudo chmod -R 755 /var/www/your_domain
58nano /var/www/your_domain/html/index.html 56nano /var/www/your_domain/html/index.html
59``` 57#+end_src
60 58
61The next part, creating the Nginx configuration files, is quite a bit 59The next part, creating the Nginx configuration files, is quite a bit
62different from Apache. First, you need to create the files in the 60different from Apache. First, you need to create the files in the
63`sites-available` folder and symlink it the 61=sites-available= folder and symlink it the =sites-enabled= folder.
64`sites-enabled` folder.
65 62
66Creating the config file for your domain: 63Creating the config file for your domain:
67 64
68```sh 65#+begin_src sh
69sudo nano /etc/nginx/sites-available/your_domain 66sudo nano /etc/nginx/sites-available/your_domain
70``` 67#+end_src
71 68
72Default content for an Nginx config file: 69Default content for an Nginx config file:
73 70
74```sh 71#+begin_src sh
75server { 72server {
76 listen 80; 73 listen 80;
77 listen [::]:80; 74 listen [::]:80;
@@ -85,45 +82,43 @@ server {
85 try_files $uri $uri/ =404; 82 try_files $uri $uri/ =404;
86 } 83 }
87} 84}
88``` 85#+end_src
89 86
90Finally, symlink it together: 87Finally, symlink it together:
91 88
92```sh 89#+begin_src sh
93sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/ 90sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
94``` 91#+end_src
95 92
96This will make your site available to the public (as long as you have 93This will make your site available to the public (as long as you have
97`your_domain` DNS records pointed at the server's IP 94=your_domain= DNS records pointed at the server's IP address)!
98address)!
99 95
100Next, I used [certbot](https://certbot.eff.org/) to issue an HTTPS 96Next, I used [[https://certbot.eff.org/][certbot]] to issue an HTTPS
101certificate for my domains using the following commands: 97certificate for my domains using the following commands:
102 98
103```sh 99#+begin_src sh
104sudo apt install snapd; sudo snap install core; sudo snap refresh core 100sudo apt install snapd; sudo snap install core; sudo snap refresh core
105sudo snap install --classic certbot 101sudo snap install --classic certbot
106sudo ln -s /snap/bin/certbot /usr/bin/certbot 102sudo ln -s /snap/bin/certbot /usr/bin/certbot
107sudo certbot --nginx 103sudo certbot --nginx
108``` 104#+end_src
109 105
110Now that certbot ran successfully and updated my Nginx config files to 106Now that certbot ran successfully and updated my Nginx config files to
111include a `443` server block of code, I went back in and 107include a =443= server block of code, I went back in and edited the
112edited the config file to include security HTTP headers. This part is 108config file to include security HTTP headers. This part is optional, but
113optional, but is recommended for security purposes; you can even test a 109is recommended for security purposes; you can even test a website's HTTP
114website's HTTP header security at [Security 110header security at [[https://securityheaders.com/][Security Headers]].
115Headers](https://securityheaders.com/).
116 111
117The configuration below shows a set-up where you only want your website 112The configuration below shows a set-up where you only want your website
118to serve content from its own domain, except for images and scripts, 113to serve content from its own domain, except for images and scripts,
119which may come from `nullitics.com`. All other content would 114which may come from =nullitics.com=. All other content would be blocked
120be blocked from loading in a browser. 115from loading in a browser.
121 116
122```sh 117#+begin_src sh
123sudo nano /etc/nginx/sites-available/your_domain 118sudo nano /etc/nginx/sites-available/your_domain
124``` 119#+end_src
125 120
126```sh 121#+begin_src sh
127server { 122server {
128 ... 123 ...
129 add_header Content-Security-Policy "default-src 'none'; img-src 'self' https://nullitics.com; script-src 'self' https://nullitics.com; style-src 'self'; font-src 'self'"; 124 add_header Content-Security-Policy "default-src 'none'; img-src 'self' https://nullitics.com; script-src 'self' https://nullitics.com; style-src 'self'; font-src 'self'";
@@ -134,47 +129,44 @@ server {
134 add_header Referrer-Policy "no-referrer"; 129 add_header Referrer-Policy "no-referrer";
135 ... 130 ...
136} 131}
137``` 132#+end_src
138 133
139```sh 134#+begin_src sh
140sudo systemctl restart nginx 135sudo systemctl restart nginx
141``` 136#+end_src
142
143### Nginx vs. Apache
144 137
138** Nginx vs. Apache
145As I stated at the beginning, my historical hesitation with trying Nginx 139As I stated at the beginning, my historical hesitation with trying Nginx
146was that the differences in configuration formats scared me away from 140was that the differences in configuration formats scared me away from
147leaving Apache. However, I prefer Nginx to Apache for a few reasons: 141leaving Apache. However, I prefer Nginx to Apache for a few reasons:
148 142
1491. Nginx uses only one config file (`your_domain`) 1431. Nginx uses only one config file (=your_domain=) vs. Apache's two-file
150 vs. Apache's two-file approach for HTTP vs. HTTPS 144 approach for HTTP vs. HTTPS (=your_domain.conf= and
151 (`your_domain.conf` and 145 =your_domain-le-ssl.conf=).
152 `your_domain-le-ssl.conf`). 1462. Symlinking new configurations files and reloading Nginx are way
1532. Symlinking new configurations files and reloading Nginx are way 147 easier than Apache's process of having to enable headers with
154 easier than Apache's process of having to enable headers with 148 =a2enmod mod_headers=, enable PHP with =a2enmod php= (plus any other
155 `a2enmod mod_headers`, enable PHP with 149 mods you need), and then enabling sites with =a2ensite=, and THEN
156 `a2enmod php` (plus any other mods you need), and then 150 reloading Apache.
157 enabling sites with `a2ensite`, and THEN reloading 1513. The contents of the Nginx config files seem more organized and
158 Apache. 152 logical with the curly-bracket approach. This is a minor reason, but
1593. The contents of the Nginx config files seem more organized and 153 everything just felt cleaner while I was installing my sites and that
160 logical with the curly-bracket approach. This is a minor reason, but 154 had a big quality of life impact on the installation for me.
161 everything just felt cleaner while I was installing my sites and
162 that had a big quality of life impact on the installation for me.
163 155
164They're both great software packages, but Nginx just seems more 156They're both great software packages, but Nginx just seems more
165organized and easier to use these days. I will certainly be exploring 157organized and easier to use these days. I will certainly be exploring
166the Nginx docs to see what other fun things I can do with all of this. 158the Nginx docs to see what other fun things I can do with all of this.
167 159
168## Gemini Server: Agate 160* Gemini Server: Agate
169 161#+caption: Agate status
170![Agate 162[[https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/agate.png]]
171status](https://img.cleberg.net/blog/20220216-migrating-to-debian-and-nginx/agate.png)
172 163
173Finally, I set up the Agate software on this server again to host my 164Finally, I set up the Agate software on this server again to host my
174Gemini server content, using Rust as I have before. You can read my 165Gemini server content, using Rust as I have before. You can read my
175other post for more information on installing Agate: [Hosting a Gemini 166other post for more information on installing Agate:
176Server](https://cleberg.net/blog/hosting-a-gemini-server/). 167[[https://cleberg.net/blog/hosting-a-gemini-server/][Hosting a Gemini
168Server]].
177 169
178All in all, Debian + Nginx is very slick and I prefer it over my old 170All in all, Debian + Nginx is very slick and I prefer it over my old
179combination of Ubuntu + Apache (although it's really just Nginx \> 171combination of Ubuntu + Apache (although it's really just Nginx > Apache
180Apache for me, since Debian seems mostly the same as Ubuntu is so far). 172for me, since Debian seems mostly the same as Ubuntu is so far).
content/blog/2021-07-15-delete-gitlab-repos.md → blog/delete-gitlab-repos/index.org renamed +28 −33
@@ -1,11 +1,9 @@
1+++ 1#+title: How to Delete All GitLab Repositories
2date = 2021-07-15 2#+date: 2021-07-15
3title = "How to Delete All GitLab Repositories" 3#+description: Learn how to delete all GitLab repositories in your account.
4description = "Learn how to delete all GitLab repositories in your account." 4#+filetags: :dev:
5+++
6
7## Background
8 5
6* Background
9Have you ever used GitLab to host your source code, moved to a different 7Have you ever used GitLab to host your source code, moved to a different
10host, and wanted to delete everything from your GitLab account? Well, 8host, and wanted to delete everything from your GitLab account? Well,
11this post covers any scenario where you would want to delete all 9this post covers any scenario where you would want to delete all
@@ -16,37 +14,34 @@ manually delete them whenever I switch host. GitHub has a few different
16tools online to delete all repositories for you, but I have not found 14tools online to delete all repositories for you, but I have not found
17anything similar for GitLab, so I needed an alternative solution. 15anything similar for GitLab, so I needed an alternative solution.
18 16
19## Use a Python Script 17* Use a Python Script
20 18** Requirements
21### Requirements
22
23Before we look at the script, make sure you know your GitLab username. 19Before we look at the script, make sure you know your GitLab username.
24Next, [create an authorization 20Next, [[https://gitlab.com/-/profile/personal_access_tokens][create an
25token](https://gitlab.com/-/profile/personal_access_tokens) so that the 21authorization token]] so that the Python script can delete your
26Python script can delete your repositories. Don't lose this token or 22repositories. Don't lose this token or else you'll need to create a new
27else you'll need to create a new one. 23one.
28
29### Create the Script
30 24
25** Create the Script
31To run a Python script, you must first create it. Open a terminal and 26To run a Python script, you must first create it. Open a terminal and
32enter the following commands in whichever directory you prefer to store 27enter the following commands in whichever directory you prefer to store
33the script. You can do the same things in a file manager if you prefer. 28the script. You can do the same things in a file manager if you prefer.
34 29
35```sh 30#+begin_src sh
36mkdir delete-gitlab 31mkdir delete-gitlab
37``` 32#+end_src
38 33
39```sh 34#+begin_src sh
40cd delete-gitlab 35cd delete-gitlab
41``` 36#+end_src
42 37
43```sh 38#+begin_src sh
44nano main.py 39nano main.py
45``` 40#+end_src
46 41
47Enter the following code into your `main.py` script. 42Enter the following code into your =main.py= script.
48 43
49```python 44#+begin_src python
50import request 45import request
51import json 46import json
52 47
@@ -100,16 +95,16 @@ def main():
100 95
101if __name__ == "__main__": 96if __name__ == "__main__":
102 main() 97 main()
103``` 98#+end_src
104 99
105Now that you have the proper information, replace `{user-id}` 100Now that you have the proper information, replace ={user-id}= with your
106with your GitLab username and `{auth-token}` with the 101GitLab username and ={auth-token}= with the authorization token you
107authorization token you created earlier. 102created earlier.
108 103
109Finally, simply run the script and watch the output. You can also use 104Finally, simply run the script and watch the output. You can also use
110PyCharm Community Edition to edit and run the Python script if you 105PyCharm Community Edition to edit and run the Python script if you don't
111don't want to work in a terminal. 106want to work in a terminal.
112 107
113```sh 108#+begin_src sh
114python3 main.py 109python3 main.py
115``` 110#+end_src
blog/digital-minimalism/index.org added +100
@@ -0,0 +1,100 @@
1#+title: Digital Minimalism
2#+date: 2023-10-04
3#+description: My personal retrospective on digital minimalism.
4#+filetags: :personal:
5
6I've written [[/wiki/#digital-garden][a note about minimalism]] before,
7but I wanted to dedicate some time to reflect on digital minimalism and
8how I've been able to minimize the impact of digital devices in my life.
9
10#+begin_quote
11These changes crept up on us and happened fast, before we had a chance
12to step back and ask what we really wanted out of the rapid advances of
13the past decade. We added new technologies to the periphery of our
14experience for minor reasons, then woke one morning to discover that
15they had colonized the core of our daily life. We didn't, in other
16words, sign up for the digital world in which we're currently
17entrenched; we seem to have stumbled backward into it.
18
19/(Digital Minimalism, 2019)/
20
21#+end_quote
22
23* The Principles of Digital Minimalism
24As noted in Cal Newport's book, /Digital Minimalism/, there are three
25main principles to digital minimalism that I tend to agree with:
26
271. Clutter is costly.
28 - Digital minimalists recognize that cluttering their time and
29 attention with too many devices, apps, and services creates an
30 overall negative cost that can swamp the small benefits that each
31 individual item provides in isolation.
322. Optimization is important.
33 - Digital minimalists believe that deciding a particular technology
34 supports something they value is only the first step. To truly
35 extract its full potential benefit, it's necessary to think
36 carefully about how they'll use the technology.
373. Intentionality is satisfying.
38 - Digital minimalists derive significant satisfaction from their
39 general commitment to being more intentional about how they engage
40 with new technologies. This source of satisfaction is independent
41 of the specific decisions they make and is one of the biggest
42 reasons that minimalism tends to be immensely meaningful to its
43 practitioners.
44
45* Taking Action
46In order to put the logic into practice, I've created a few new habits
47and continued performing old habits that are working well:
48
49** Using Devices With Intention
50- I already rarely use "social media", mostly limited to forums such as
51 Hacker News and Tildes, so I've just tweaked my behavior to stop
52 looking for content in those places when I'm bored.
53- Use devices with intention. Each time I pick up a digital device,
54 there should be an intention to use the device to improve my current
55 situation. No more endless scrolling or searching for something to
56 interest me.
57
58** Prevent Distractions
59- Disable (most) notifications on all devices. I spent 15-30 minutes
60 going through the notifications on my phone, watch, and computer to
61 ensure that only a select few apps have the ability to interrupt me:
62 Calendar, Messages, Phone, Reminders, & Signal.
63- Disable badges for any apps except the ones mentioned in the bullet
64 above.
65- Set-up focus profiles across devices so that I can enable different
66 modes, such as Personal when I only want to see notifications from
67 people I care about or Do Not Disturb, where absolutely nothing can
68 interrupt me.
69- Clean up my home screens. This one was quite easy as I already
70 maintain a minimalist set-up, but I went extreme by limiting my phone
71 to just eight apps on the home screen and four in the dock. If I need
72 another app, I'll have to search or use the app library.
73- Remove the work profile from my phone. This was a tough decision as
74 having my work profile on my device definitely makes my life easier at
75 times, but it also has quite a negative effect when I'm "always
76 online" and can see the notifications and team activity 24/7. I
77 believe creating a distinct barrier between my work and personal
78 devices will be beneficial in the end.
79
80** Creating Alternative Activities
81This is the most difficult piece, as most of my hobbies and interests
82lie in the digital world. However, I'm making a concerted effort to put
83devices down unless necessary and force myself to perform other
84activities in the physical world instead.
85
86I've started with a few basics that are always readily available to me:
87
88- Do a chore, such as organizing or cleaning.
89- Read a book, study a piece of art, etc.
90- Exercise or get outdoors.
91- Participate in a hobby, such as photography, birding, disc golf, etc.
92- Let yourself be bored and wander into creativity.
93
94* Making Progress
95I'll be taking notes as I continue down this journey and hope to see
96positive trends. I've always been a minimalist in the physical world and
97it feels refreshing to filter out the clutter that has come to dominate
98my digital life over the years.
99
100I'm excited to see where this journey leads.
content/blog/2022-06-01-ditching-cloudflare.md → blog/ditching-cloudflare/index.org renamed +36 −41
@@ -1,11 +1,9 @@
1+++ 1#+title: Ditching Cloudflare for Njalla
2date = 2022-06-01 2#+date: 2022-06-01
3title = "Ditching Cloudflare for Njalla" 3#+description: A retrospective on my decision to leave Cloudflare and move to Njalla for domain registration and DNS.
4description = "A retrospective on my decision to leave Cloudflare and move to Njalla for domain registration and DNS." 4#+filetags: :sysadmin:
5+++
6
7## Registrar
8 5
6* Registrar
9After spending a year or so using Cloudflare for DNS only - no proxying 7After spending a year or so using Cloudflare for DNS only - no proxying
10or applications - I spent the last few months using Cloudflare Tunnels 8or applications - I spent the last few months using Cloudflare Tunnels
11and Cloudflare Access to protect my self-hosted websites and 9and Cloudflare Access to protect my self-hosted websites and
@@ -15,22 +13,24 @@ However, I have never liked using Cloudflare due to their increasingly
15large share of control over web traffic, as well as their business model 13large share of control over web traffic, as well as their business model
16of being a MITM for all of your traffic. 14of being a MITM for all of your traffic.
17 15
18So, as of today, I have switched over to [Njalla](https://njal.la) as my 16So, as of today, I have switched over to [[https://njal.la][Njalla]] as
19registrar and DNS manager. I was able to easily transfer my domains over 17my registrar and DNS manager. I was able to easily transfer my domains
20rapidly, with only one domain taking more than 15-30 minutes to 18over rapidly, with only one domain taking more than 15-30 minutes to
21propagate. 19propagate.
22 20
23+I do still have two domains sitting at Cloudflare for the moment while 21+I do still have two domains sitting at Cloudflare for the moment while
24I decide if they're worth the higher rates (one domain is 30€ and the 22I decide if they're worth the higher rates (one domain is 30€ and the
25other is 45€).+ 23other is 45€).+
26 24
27> **Update (2022.06.03)**: I ended up transferring my final two domains 25#+begin_quote
28> over to Njalla, clearing my Cloudflare account of personal data, and 26*Update (2022.06.03)*: I ended up transferring my final two domains over
29> deleting the Cloudflare account entirely. *I actually feel relieved to 27to Njalla, clearing my Cloudflare account of personal data, and deleting
30> have moved on to a provider I trust.* 28the Cloudflare account entirely. /I actually feel relieved to have moved
29on to a provider I trust./
31 30
32## DNS 31#+end_quote
33 32
33* DNS
34As noted above, I'm using Njalla exclusively for DNS configurations on 34As noted above, I'm using Njalla exclusively for DNS configurations on
35my domains. 35my domains.
36 36
@@ -42,48 +42,43 @@ back in one-by-one.
42 42
43This would be much simpler if I were able to edit the plain-text format 43This would be much simpler if I were able to edit the plain-text format
44of the DNS configuration. I was able to do that at a past registrar 44of the DNS configuration. I was able to do that at a past registrar
45(perhaps it was [Gandi.net](https://gandi.net/)?) and it made life a lot 45(perhaps it was [[https://gandi.net/][Gandi.net]]?) and it made life a
46easier. 46lot easier.
47
48### Dynamic DNS Updates
49 47
50I have built an easy Python script to run (or set-up in 48** Dynamic DNS Updates
51`cron` to run automatically) that will check my server's 49I have built an easy Python script to run (or set-up in =cron= to run
52IPv4 and IPv6, compare it to Njalla, and update the DNS records if they 50automatically) that will check my server's IPv4 and IPv6, compare it to
53don't match. You can see the full script and process in my other post: 51Njalla, and update the DNS records if they don't match. You can see the
54[Updating Dynamic DNS with Njalla API](/blog/njalla-dns-api/). 52full script and process in my other post:
53[[../njalla-dns-api/][Updating Dynamic DNS with Njalla API]].
55 54
56I haven't used this other method, but I do know that you can create 55I haven't used this other method, but I do know that you can create
57`Dynamic` DNS records with Njalla that [work for updating 56=Dynamic= DNS records with Njalla that
58dynamic subdomains](https://njal.la/docs/ddns/). 57[[https://njal.la/docs/ddns/][work for updating dynamic subdomains]].
59
60### Njalla's DNS Tool
61 58
62One neat upside to Njalla is that they have a [DNS lookup 59** Njalla's DNS Tool
63tool](https://check.njal.la/dns/) that provides a lot of great 60One neat upside to Njalla is that they have a
64information for those of you (AKA: me) who hate using the 61[[https://check.njal.la/dns/][DNS lookup tool]] that provides a lot of
65`dig` command. 62great information for those of you (AKA: me) who hate using the =dig=
63command.
66 64
67This was very useful for monitoring a couple of my transferred domains 65This was very useful for monitoring a couple of my transferred domains
68to see when the changes in nameservers, records, and DNSSEC went into 66to see when the changes in nameservers, records, and DNSSEC went into
69effect. 67effect.
70 68
71## Tunnel 69* Tunnel
72
73Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on 70Cloudflare Tunnel is a service that acts as a reverse-proxy (hosted on
74Cloudflare's servers) and allowed me to mask the private IP address of 71Cloudflare's servers) and allowed me to mask the private IP address of
75the server hosting my various websites and apps. 72the server hosting my various websites and apps.
76 73
77However, as I was moving away from Cloudflare, I was not able to find a 74However, as I was moving away from Cloudflare, I was not able to find a
78suitable replacement that was both inexpensive and simple. So, I simply 75suitable replacement that was both inexpensive and simple. So, I simply
79went back to hosting [my own reverse proxy with 76went back to hosting [[/blog/set-up-nginx-reverse-proxy/][my own reverse
80Nginx](/blog/set-up-nginx-reverse-proxy/). With the recent 77proxy with Nginx]]. With the recent additions of Unifi hardware in my
81additions of Unifi hardware in my server/network rack, I am much more 78server/network rack, I am much more protected against spam and malicious
82protected against spam and malicious attacks at the network edge than I 79attacks at the network edge than I was before I switched to Cloudflare.
83was before I switched to Cloudflare.
84
85## Access
86 80
81* Access
87Cloudflare Access, another app I used in combination with Cloudflare 82Cloudflare Access, another app I used in combination with Cloudflare
88Tunnel, provided an authentication screen that required you to enter 83Tunnel, provided an authentication screen that required you to enter
89valid credentials before Cloudflare would forward you to the actual 84valid credentials before Cloudflare would forward you to the actual
blog/dont-say-hello/index.org added +26
@@ -0,0 +1,26 @@
1#+title: Don't Say Hello
2#+date: 2024-01-08
3#+description: A short post describing my displeasure with cliffhanger conversations.
4#+filetags: :personal:
5
6I recently came back from a winter break and have started working
7again... only to immediately run into the dilemma of people sending me
8cliffhanger messages again.
9
10* No Hello
11A year or two ago, I discovered [[https://nohello.net/en/][no hello]]
12and have thought about it often since then. I've even sent it to a few
13people since then (who wouldn't take offense to it).
14
15I work in a fast-paced environment where efficiency is extremely
16valuable. Therefore, I have always held a deep displeasure for
17conversations where people start with "Hello" and then.
18
19I searched back through my work messages and found that I received ~50
20messages from ~10 people last year from people that contained "hi",
21"hey", or "hello" and did not contain any indication of the purpose of
22the conversation. I also noticed that a few of the users were
23responsible for the large majority of the cliffhangers.
24
25There's no real point to this post, just a desparate request for people
26to please stop doing this.
blog/exiftool/index.org added +60
@@ -0,0 +1,60 @@
1#+title: Stripping Image Metadata with exiftool
2#+date: 2022-02-17
3#+description: A simple guide to remove exif data with exiftool.
4#+filetags: :privacy:
5
6** Why Strip Metadata?
7Okay, so you want to strip metadata from your photos. Perhaps you take
8pictures of very rare birds, and the location metadata is a gold mine
9for poachers, or perhaps you're just privacy-oriented like me and prefer
10to strip metadata from publicly-available images.
11
12There are various components of image metadata that you may want to
13delete before releasing a photo to the public. Here's an incomplete list
14of things I could easily see just by inspecting a photo on my laptop:
15
16- Location (Latitude & Longitude)
17- Dimensions
18- Device Make & Model
19- Color Space
20- Color Profile
21- Focal Length
22- Alpha Channel
23- Red Eye
24- Metering Mode
25- F Number
26
27Regardless of your reasoning, I'm going to explain how I used the
28=exiftool= package in Linux to automatically strip metadata from all
29images in a directory (+ subdirectories).
30
31** Installing =exiftool=
32First things first: we need to install the tool. I'm running Debian 11
33on my server (Ubuntu will work the same), so the command is as simple
34as:
35
36#+begin_src sh
37sudo apt install exiftool
38#+end_src
39
40There are different tools that can accomplish the same thing across
41distributions, but I really only care to test out this one package.
42
43** Recursively Strip Data
44I actually use this tool extensively to strip any photos uploaded to the
45website that serves all the images for my blog (=img.cleberg.net=).
46
47The following command is incredibly useful and can be modified to
48include any image extensions that =exiftool= supports:
49
50#+begin_src sh
51exiftool -r -all= -ext jpg -ext png /path/to/directory/
52#+end_src
53
54See below for the results of my most recent usage of =exiftool= after I
55uploaded the image for this blog post. You can see that the command will
56let you know how many directories were scanned, how many images were
57updated, and how many images were unchanged.
58
59#+caption: exiftool results
60[[https://img.cleberg.net/blog/20220217-stripping-metadata-with-exiftool/exiftool.png]]
blog/exploring-hare/index.org added +169
@@ -0,0 +1,169 @@
1#+title: Exploring the Hare Programming Language
2#+date: 2023-02-02
3#+description: A retrospective on my first time using the Hare Programming Language.
4#+filetags: :dev:
5
6* A Quick Note
7By no means am I a professional developer, so this post will be rather
8short. I won't be going into depth on the specification or anything that
9technical.
10
11Instead, I will simply be talking about how I (a relatively basic
12hobbyist programmer) have been playing with Hare and what intrigues me
13about the language.
14
15* Hare
16The [[https://harelang.org][Hare]] programming language is a
17straightforward language that should look familiar if you've ever
18programmed with C, Rust, or other languages that aim to build software
19at the system-level.
20
21The Hare homepage states the following:
22
23#+begin_quote
24Hare is a systems programming language designed to be simple, stable,
25and robust. Hare uses a static type system, manual memory management,
26and minimal runtime. It is well-suited to writing operating systems,
27system tools, compilers, networking software, and other low-level, high
28performance tasks.
29
30#+end_quote
31
32I have found this all to be true while playing with it for the first
33time today. In the next few sections, I'm going to walk through my
34installation and first program.
35
36** Installation
37I'm currently running Alpine Linux on my Thinkpad, so the installation
38was quite easy as there is a package for Hare in the =apk= repositories.
39
40#+begin_src sh
41doas apk add hare hare-doc
42#+end_src
43
44However, I was able to install Hare from scratch on Fedora Linux a short
45while ago, which was also very easy to do. If you need further
46instructions and Hare doesn't have a package on your system, take a look
47at the [[https://harelang.org/installation/][Hare Installation]] page.
48
49** Creating a Test Project
50In order to play with the language, I created
51[[https://git.sr.ht/~cmc/hare-projects][hare-test]] and will be putting
52any of my Hare-related adventures in here.
53
54#+begin_quote
55*Update:** I also created a simple Hare program for creating a file from
56user input:
57[[https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha][files.ha]]
58
59#+end_quote
60
61Luckily, Hare doesn't require any complex set-up tools or build
62environment. Once you have Hare installed, you simply need to create a
63file ending with =.ha= and you can run a Hare program.
64
65I created a file called =rgb.ha= in order to test out the random number
66generation and passing parameters between functions.
67
68#+begin_src sh
69nano rgb.ha
70#+end_src
71
72Within this file, I was able to easily import a few of the
73[[https://harelang.org/tutorials/stdlib/][standard library modules]]:
74=fmt=, =math::random=, and =datetime=.
75
76With these modules, I created two functions:
77
781. =main=: This function calls the =generate_rgb= function and then
79 prints out the returned values.
802. =generate_rgb=: This function uses the current Unix epoch time to
81 generate a pseudo-random value and uses this value to create three
82 more random values between 0 and 255. These three numbers represent a
83 color in RGB format.
84
85#+begin_quote
86*Note*: Some syntax coloring may look odd, as Zola currently doesn't
87have a syntax highlighting theme for Hare. Instead, I'm using the C
88theme, which may not be exactly accurate when coloring the code below.
89
90#+end_quote
91
92#+begin_src C
93use datetime;
94use fmt;
95use math::random;
96
97export fn main() void = {
98 const rgb = generate_rgb();
99 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
100};
101
102fn generate_rgb() []u64 = {
103 // Use the current Unix epoch time as the seed value
104 let datetime = datetime::epochunix(&datetime::now());
105
106 // Generate initial pseudo-random value
107 // You must cast the datetime from int to u64
108 let x = random::init(datetime: u64);
109
110 // Generate RGB values between (0, 255) using pseudo-random init value
111 let r = random::u64n(&x, 255);
112 let g = random::u64n(&x, 255);
113 let b = random::u64n(&x, 255);
114
115 // Structure data as array and return
116 let rgb_array: [3]u64 = [r, g, b];
117 return rgb_array;
118};
119#+end_src
120
121** Running a Program
122Once you have a Hare file written and ready to run, you simply need to
123run it:
124
125#+begin_src sh
126hare run file.ha
127#+end_src
128
129You can also compile the program into an executable:
130
131#+begin_src sh
132hare build -o example file.ha
133./example
134#+end_src
135
136** Initial Thoughts
1371. Documentation Improvements Would Help
138
139 While I was able to piece everything together eventually, the biggest
140 downfall right now in Hare's documentation. For such a new project,
141 the documentation is in a great spot. However, bare specifications
142 don't help as much as a brief examples section would.
143
144 For example, it took me a while to figure out what the =u64n=
145 function was looking for. I could tell that it took two parameters
146 and the second was my max value (255), but couldn't figure out what
147 the first value should be. Eventually, I inspected the =random.ha=
148 file in the
149 [[https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha][Hare
150 source code]] and found the test suite that helped me discover that
151 it needed an =init()= value in the form of =&var=.
152
1532. More Basic Modules
154
155 This is another point that comes from Hare being new and awaiting
156 more contributions, but there are some basic functions that I would
157 personally enjoy seeing in Hare, such as one to convert decimal
158 (base 10) values to hexadecimal (base 16).
159
160 If I'm feeling comfortable with my math, I may work on the list of
161 functions I want and see if any can make it into the Hare source
162 code.
163
1643. Overall Thoughts
165
166 Overall, I actually really enjoy Hare. It's not as tedious to get a
167 project up and running as Rust, but it's also simpler and more
168 user-friendly than learning C. I am going to continue playing with it
169 and see if I can make anything of particular value.
content/blog/2021-01-04-fediverse.md → blog/fediverse/index.org renamed +40 −54
@@ -1,29 +1,27 @@
1+++ 1#+title: A Simple Guide to the Fediverse
2date = 2021-01-04 2#+date: 2021-01-04
3title = "A Simple Guide to the Fediverse" 3#+description: Learn about the basics of the Fediverse.
4description = "Learn about the basics of the Fediverse." 4#+filetags: :social:
5+++
6
7## What is the Fediverse?
8 5
6* What is the Fediverse?
9The fediverse is a federated universe of servers commonly used for 7The fediverse is a federated universe of servers commonly used for
10sharing content, like social media. So, instead of having to rely on a 8sharing content, like social media. So, instead of having to rely on a
11single organization to run the server (e.g. Facebook), the fediverse is 9single organization to run the server (e.g. Facebook), the fediverse is
12a giant collection of servers across the world, owned by many people and 10a giant collection of servers across the world, owned by many people and
13organizations. 11organizations.
14 12
15Take a look at this depiction of a federated network. Each server in 13Take a look at this depiction of a federated network. Each server in
16this photo is owned and run by different administrators/owners. 14this photo is owned and run by different administrators/owners.
17Federated networks are best explained as email servers: you have an 15Federated networks are best explained as email servers: you have an
18email account that exists on a server (e.g. Outlook), your friend has an 16email account that exists on a server (e.g. Outlook), your friend has an
19account on a different server (e.g. GMail), and another friend has an 17account on a different server (e.g. GMail), and another friend has an
20account on a third server (e.g. ProtonMail). All three of you can talk 18account on a third server (e.g. ProtonMail). All three of you can talk
21and communicate back and forth without having to be on the same server. 19and communicate back and forth without having to be on the same server.
22However, responsible email admins are there to set rules and control the 20However, responsible email admins are there to set rules and control the
23traffic going in/out of the server. 21traffic going in/out of the server.
24 22
25![Federated services 23#+caption: Federated services diagram
26diagram](https://img.cleberg.net/blog/20210104-a-simple-guide-to-the-fediverse/federated-example.svg) 24[[https://img.cleberg.net/blog/20210104-a-simple-guide-to-the-fediverse/federated-example.svg]]
27 25
28The main objective of this architecture is to decentralize the control 26The main objective of this architecture is to decentralize the control
29within the internet connections. For example, if you run your own 27within the internet connections. For example, if you run your own
@@ -36,13 +34,12 @@ This strategy is great for making sure control of the social web isn't
36controlled by a single organization, but it also has some downsides. If 34controlled by a single organization, but it also has some downsides. If
37I create a Mastodon instance and get a ton of users to sign up, I can 35I create a Mastodon instance and get a ton of users to sign up, I can
38shut the server down at any time. That means you're at risk of losing 36shut the server down at any time. That means you're at risk of losing
39the content you've created unless you back it up, or the server backs 37the content you've created unless you back it up, or the server backs it
40it up for you. Also, depending on the software used (e.g. Mastodon, 38up for you. Also, depending on the software used (e.g. Mastodon,
41Pixelfed, etc.), censorship may still be an issue if the server admins 39Pixelfed, etc.), censorship may still be an issue if the server admins
42decide they want to censor their users. Now, censorship isn't always a 40decide they want to censor their users. Now, censorship isn't always a
43bad thing and can even benefit the community as a whole, but you'll 41bad thing and can even benefit the community as a whole, but you'll want
44want to determine which servers align with your idea of proper 42to determine which servers align with your idea of proper censorship.
45censorship.
46 43
47However, these are risks that we take when we sign up for any online 44However, these are risks that we take when we sign up for any online
48platform. Whatever your reason is for trying out federated social 45platform. Whatever your reason is for trying out federated social
@@ -52,55 +49,44 @@ with the increased difficulty understanding and signing up for these
52platforms. Perhaps increased regulation and litigation against current 49platforms. Perhaps increased regulation and litigation against current
53social media sites will push more users into the fediverse. 50social media sites will push more users into the fediverse.
54 51
55## Federated Alternatives to Popular Sites 52* Federated Alternatives to Popular Sites
56
57The list below is a small guide that will show you federated 53The list below is a small guide that will show you federated
58alternatives to current popular websites. There are many more out there, 54alternatives to current popular websites. There are many more out there,
59so go and explore: you might just find the perfect home. 55so go and explore: you might just find the perfect home.
60 56
61### Reddit 57** Reddit
62 58- [[https://lemmy.ml/instances][Lemmy]]
63- [Lemmy](https://lemmy.ml/instances)
64
65### Twitter/Facebook/Tumblr
66
67- [Mastodon](https://joinmastodon.org)
68- [Diaspora](https://diasporafoundation.org)
69- [Friendica](https://friendi.ca)
70- [GNU Social](https://gnusocial.network)
71- [Pleroma](https://pleroma.social)
72
73### Instagram
74
75- [Pixelfed](https://pixelfed.org)
76
77### Slack/Discord
78
79- [Matrix](https://element.io)
80
81### Youtube/Vimeo
82
83- [Peertube](https://joinpeertube.org)
84 59
85### Spotify/Soundcloud 60** Twitter/Facebook/Tumblr
61- [[https://joinmastodon.org][Mastodon]]
62- [[https://diasporafoundation.org][Diaspora]]
63- [[https://friendi.ca][Friendica]]
64- [[https://gnusocial.network][GNU Social]]
65- [[https://pleroma.social][Pleroma]]
86 66
87- [Funkwhale](https://funkwhale.audio) 67** Instagram
68- [[https://pixelfed.org][Pixelfed]]
88 69
89### Podcasting 70** Slack/Discord
71- [[https://element.io][Matrix]]
90 72
91- [Pubcast](https://pubcast.pub) 73** Youtube/Vimeo
74- [[https://joinpeertube.org][Peertube]]
92 75
93### Medium/Blogger 76** Spotify/Soundcloud
77- [[https://funkwhale.audio][Funkwhale]]
94 78
95- [WriteFreely](https://writefreely.org) 79** Podcasting
80- [[https://pubcast.pub][Pubcast]]
96 81
97## Get Started 82** Medium/Blogger
83- [[https://writefreely.org][WriteFreely]]
98 84
85* Get Started
99The best way to get started is to simply sign up and learn as you go. If 86The best way to get started is to simply sign up and learn as you go. If
100you're comfortable signing up through a Mastodon, Pleroma, or Friendica 87you're comfortable signing up through a Mastodon, Pleroma, or Friendica
101server, here is [a list of themed 88server, here is [[https://fediverse.party/en/portal/servers][a list of
102servers](https://fediverse.party/en/portal/servers) to choose from. If 89themed servers]] to choose from. If you're looking for something else,
103you're looking for something else, try a web search for a federated 90try a web search for a federated alternative to your favorite sites.
104alternative to your favorite sites.
105 91
106Find a server that focuses on your passions and start there! 92Find a server that focuses on your passions and start there!
blog/fedora-i3/index.org added +152
@@ -0,0 +1,152 @@
1#+title: Rebooting My Love Affair with Linux
2#+date: 2022-06-24
3#+description: A retrospective on moving from macOS to Linux.
4#+filetags: :linux:
5
6* Leaving macOS
7As I noted [[../foss-macos-apps][in a recent post]], I have been
8planning on migrating from macOS back to a Linux-based OS. I am happy to
9say that I have finally completed my migration and am now stuck in the
10wonderful world of Linux again.
11
12My decision to leave macOS really came down to just a few important
13things:
14
15- Apple Security (Gatekeeper) restricting me from running any software I
16 want. Even if you disable Gatekeeper and allow software to bypass the
17 rest of the device installation security, you still have to repeat
18 that process every time the allowed software is updated.
19- macOS sends out nearly constant connections, pings, telemetry, etc. to
20 a myriad of mysterious Apple services. I'm not even going to dive into
21 how many macOS apps have constant telemetry on, as well.
22- Lastly, I just /really/ missed the customization and freedom that
23 comes with Linux. Being able to switch to entirely new kernel, OS, or
24 desktop within minutes is a freedom I took for granted when I switched
25 to macOS.
26
27Now that I've covered macOS, I'm going to move on to more exciting
28topics: my personal choice of OS, DE, and various customizations I'm
29using.
30
31* Fedora
32After trying a ton of distros (I think I booted and tested around 20-25
33distros), I finally landed on [[https://getfedora.org/][Fedora Linux]].
34I have quite a bit of experience with Fedora and enjoy the =dnf= package
35manager. Fedora allows me to keep up-to-date with recent software (I'm
36looking at you, Debian), but still provides a level of stability you
37don't find in every distro.
38
39In a very close second place was Arch Linux, as well as its spin-off:
40Garuda Linux (Garuda w/ sway is /beautiful/). Arch is great for
41compatibility and the massive community it has, but I have just never
42had the time to properly sit down and learn the methodology behind their
43packaging systems.
44
45Basically, everything else I tested was unacceptable in at least one way
46or another. Void (=glibc=) was great, but doesn't support all the
47software I need. Slackware worked well as a tui, but I wasn't skilled
48enough to get a tiling window manager (WM) working on it.
49
50** i3
51One of the reasons I settled on Fedora is that it comes with an official
52i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of
53the biggest things I wanted to do as soon as I adopted Linux again.
54
55I will probably set up a dotfile repository soon, so that I don't lose
56any of my configurations, but nothing big has been configured thus far.
57
58The two main things I have updated in i3wm are natural scrolling and
59binding my brightness keys to the =brightnessctl= program.
60
611. Natural Scrolling
62
63 You can enable natural scrolling by opening the following file:
64
65 #+begin_src sh
66 sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
67 #+end_src
68
69 Within the =40-libinput.conf= file, find the following input sections
70 and enable the natural scrolling option.
71
72 This is the =pointer= section:
73
74 #+begin_src conf
75 Section "InputClass"
76 Identifier "libinput pointer catchall"
77 MatchIsPointer "on"
78 MatchDevicePath "/dev/input/event*"
79 Driver "libinput"
80 Option "NaturalScrolling" "True"
81 EndSection
82 #+end_src
83
84 This is the =touchpad= section:
85
86 #+begin_src conf
87 Section "InputClass"
88 Identifier "libinput touchpad catchall"
89 MatchIsTouchpad "on"
90 MatchDevicePath "/dev/input/event*"
91 Driver "libinput"
92 Option "NaturalScrolling" "True"
93 EndSection
94 #+end_src
95
962. Enabling Brightness Keys
97
98 Likewise, enabling brightness key functionality is as simple as
99 binding the keys to the =brightnessctl= program.
100
101 To do this, open up your i3 config file. Mine is located here:
102
103 #+begin_src sh
104 nano /home/<my-user>/.config/i3/config
105 #+end_src
106
107 #+begin_src conf
108 # Use brightnessctl to adjust brightness.
109 bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%-
110 bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+
111 #+end_src
112
1133. =polybar=
114
115 Instead of using the default =i3status= bar, I have opted to use
116 =polybar= instead (as you can also see in the screenshot above).
117
118 My config for this menu bar is basically just the default settings
119 with modified colors and an added battery block to quickly show me
120 the machine's battery info.
121
1224. =alacritty=
123
124 Not much to say on this part yet, as I haven't configured it much,
125 but I installed =alacritty= as my default terminal, and I am using
126 =zsh= and the shell.
127
128* Software Choices
129Again, I'm not going to say much that I haven't said yet in other blog
130posts, so I'll just do a quick rundown of the apps I installed
131immediately after I set up the environment.
132
133Flatpak Apps:
134
135- Cryptomator
136- pCloud
137- Signal
138
139Fedora Packages:
140
141- gomuks
142- neomutt
143- neofetch
144- Firefox
145 - uBlock Origin
146 - Bitwarden
147 - Stylus
148 - Privacy Redirect
149
150Other:
151
152- exiftool
content/blog/2023-01-08-fedora-remove-login-manager.md → blog/fedora-login-manager/index.org renamed +17 −21
@@ -1,11 +1,9 @@
1+++ 1#+title: How to Remove the Login Manager from Fedora i3
2date = 2023-01-08 2#+date: 2023-01-08
3title = "How to Remove the Login Manager from Fedora i3" 3#+description: Learn how to completely remove the login manager from Fedora i3.
4description = "Learn how to completely remove the login manager from Fedora i3." 4#+filetags: :linux:
5+++
6
7## Fedora i3's Login Manager
8 5
6* Fedora i3's Login Manager
9Since I use the i3 spin of Fedora Workstation, I don't like to have a 7Since I use the i3 spin of Fedora Workstation, I don't like to have a
10login manager installed by default. As of the current version of Fedora 8login manager installed by default. As of the current version of Fedora
11i3, the default login manager is LightDM. 9i3, the default login manager is LightDM.
@@ -14,31 +12,29 @@ If this is no longer the case, you can search for currently-installed
14packages with the following command and see if you can identify a 12packages with the following command and see if you can identify a
15different login manager. 13different login manager.
16 14
17```sh 15#+begin_src sh
18sudo dnf list installed 16sudo dnf list installed
19``` 17#+end_src
20
21## Removing the Login Manager
22 18
19* Removing the Login Manager
23In order to remove the login manager, simply uninstall the package. 20In order to remove the login manager, simply uninstall the package.
24 21
25```sh 22#+begin_src sh
26sudo dnf remove lightdm 23sudo dnf remove lightdm
27``` 24#+end_src
28
29## Launching i3 Manually
30 25
26* Launching i3 Manually
31In order to launch i3 manually, you need to set up your X session 27In order to launch i3 manually, you need to set up your X session
32properly. To start, create or edit the `~/.xinitrc` file to 28properly. To start, create or edit the =~/.xinitrc= file to include the
33include the following at the bottom. 29following at the bottom.
34 30
35```config 31#+begin_src config
36exec i3 32exec i3
37``` 33#+end_src
38 34
39Now, whenever you log in to the TTY, you can launch your desktop with 35Now, whenever you log in to the TTY, you can launch your desktop with
40the following command. 36the following command.
41 37
42```sh 38#+begin_src sh
43startx 39startx
44``` 40#+end_src
blog/financial-database/index.org added +256
@@ -0,0 +1,256 @@
1#+title: Maintaining a Personal Financial Database
2#+date: 2022-03-03
3#+description: An example project showing to build and maintain a simple financial database.
4#+filetags: :personal:
5
6* Personal Financial Tracking
7For the last 6-ish years, I've tracked my finances in a spreadsheet.
8This is common practice in the business world, but any good dev will
9cringe at the thought of storing long-term data in a spreadsheet. A
10spreadsheet is not for long-term storage or as a source of data to pull
11data/reports.
12
13As I wanted to expand the functionality of my financial data (e.g.,
14adding more reports), I decided to migrate the data into a database. To
15run reports, I would query the database and use a language like Python
16or Javascript to process the data, perform calculations, and visualize
17the data.
18
19* SQLite
20When choosing the type of database I wanted to use for this project, I
21was split between three options:
22
231. MySQL: The database I have the most experience with and have used for
24 years.
252. PostgreSQL: A database I'm new to, but want to learn.
263. SQLite: A database that I've used for a couple projects and have
27 moderate experience.
28
29I ended up choosing SQLite since it can be maintained within a single
30=.sqlite= file, which allows me more flexibility for storage and backup.
31I keep this file in my cloud storage and pull it up whenever needed.
32
33** GUI Editing
34Since I didn't want to try and import 1000--1500 records into my new
35database via the command line, I opted to use
36[[https://sqlitebrowser.org/][DB Browser for SQLite (DB4S)]] as a GUI
37tool. This application is excellent, and I don't see myself going back
38to the CLI when working in this database.
39
40DB4S allows you to copy a range of cells from a spreadsheet and paste it
41straight into the SQL table. I used this process for all 36 accounts,
421290 account statements, and 126 pay statements. Overall, I'm guessing
43this took anywhere between 4--8 hours. In comparison, it probably took
44me 2-3 days to initially create the spreadsheet.
45
46#+caption: DB4S
47[[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/db4s.png]]
48
49** Schema
50The schema for this database is actually extremely simple and involves
51only three tables (for now):
52
531. Accounts
542. Statements
553. Payroll
56
57*Accounts*
58
59The Accounts table contains summary information about an account, such
60as a car loan or a credit card. By viewing this table, you can find
61high-level data, such as interest rate, credit line, or owner.
62
63#+begin_src sql
64CREATE TABLE "Accounts" (
65 "AccountID" INTEGER NOT NULL UNIQUE,
66 "AccountType" TEXT,
67 "AccountName" TEXT,
68 "InterestRate" NUMERIC,
69 "CreditLine" NUMERIC,
70 "State" TEXT,
71 "Owner" TEXT,
72 "Co-Owner" TEXT,
73 PRIMARY KEY("AccountID" AUTOINCREMENT)
74)
75#+end_src
76
77*Statements*
78
79The Statements table uses the same unique identifier as the Accounts
80table, meaning you can join the tables to find a monthly statement for
81any of the accounts listed in the Accounts table. Each statement has an
82account ID, statement date, and total balance.
83
84#+begin_src sql
85CREATE TABLE "Statements" (
86 "StatementID" INTEGER NOT NULL UNIQUE,
87 "AccountID" INTEGER,
88 "StatementDate" INTEGER,
89 "Balance" NUMERIC,
90 PRIMARY KEY("StatementID" AUTOINCREMENT),
91 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID")
92)
93#+end_src
94
95*Payroll*
96
97The Payroll table is a separate entity, unrelated to the Accounts or
98Statements tables. This table contains all information you would find on
99a pay statement from an employer. As you change employers or obtain new
100perks/benefits, just add new columns to adapt to the new data.
101
102#+begin_src sql
103CREATE TABLE "Payroll" (
104 "PaycheckID" INTEGER NOT NULL UNIQUE,
105 "PayDate" TEXT,
106 "Payee" TEXT,
107 "Employer" TEXT,
108 "JobTitle" TEXT,
109 "IncomeRegular" NUMERIC,
110 "IncomePTO" NUMERIC,
111 "IncomeHoliday" NUMERIC,
112 "IncomeBonus" NUMERIC,
113 "IncomePTOPayout" NUMERIC,
114 "IncomeReimbursements" NUMERIC,
115 "FringeHSA" NUMERIC,
116 "FringeStudentLoan" NUMERIC,
117 "Fringe401k" NUMERIC,
118 "PreTaxMedical" NUMERIC,
119 "PreTaxDental" NUMERIC,
120 "PreTaxVision" NUMERIC,
121 "PreTaxLifeInsurance" NUMERIC,
122 "PreTax401k" NUMERIC,
123 "PreTaxParking" NUMERIC,
124 "PreTaxStudentLoan" NUMERIC,
125 "PreTaxOther" NUMERIC,
126 "TaxFederal" NUMERIC,
127 "TaxSocial" NUMERIC,
128 "TaxMedicare" NUMERIC,
129 "TaxState" NUMERIC,
130 PRIMARY KEY("PaycheckID" AUTOINCREMENT)
131)
132#+end_src
133
134** Python Reporting
135Once I created the database tables and imported all my data, the only
136step left was to create a process to report and visualize on various
137aspects of the data.
138
139In order to explore and create the reports I'm interested in, I utilized
140a two-part process involving Jupyter Notebooks and Python scripts.
141
1421. Step 1: Jupyter Notebooks
143
144 When I need to explore data, try different things, and re-run my code
145 cell-by-cell, I use Jupyter Notebooks. For example, I explored the
146 =Accounts= table until I found the following useful information:
147
148 #+begin_src python
149 import sqlite3
150 import pandas as pd
151 import matplotlib
152
153 # Set up database filename and connect
154 db = "finances.sqlite"
155 connection = sqlite3.connect(db)
156 df = pd.read_sql_query("SELECT ** FROM Accounts", connection)
157
158 # Set global matplotlib variables
159 %matplotlib inline
160 matplotlib.rcParams['text.color'] = 'white'
161 matplotlib.rcParams['axes.labelcolor'] = 'white'
162 matplotlib.rcParams['xtick.color'] = 'white'
163 matplotlib.rcParams['ytick.color'] = 'white'
164 matplotlib.rcParams['legend.labelcolor'] = 'black'
165
166 # Display graph
167 df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%')
168 #+end_src
169
1702. Step 2: Python Scripts
171
172 Once I explored enough through the notebooks and had a list of
173 reports I wanted, I moved on to create a Python project with the
174 following structure:
175
176 #+begin_src txt
177 finance/
178 ├── notebooks/
179 │ │ ├── account_summary.ipynb
180 │ │ ├── account_details.ipynb
181 │ │ └── payroll.ipynb
182 ├── public/
183 │ │ ├── image-01.png
184 │ │ └── image-0X.png
185 ├── src/
186 │ └── finance.sqlite
187 ├── venv/
188 ├── _init.py
189 ├── database.py
190 ├── process.py
191 ├── requirements.txt
192 └── README.md
193 #+end_src
194
195 This structure allows me to:
196
197 1. Compile all required python packages into =requirements.txt= for
198 easy installation if I move to a new machine.
199 2. Activate a virtual environment in =venv/= so I don't need to
200 maintain a system-wide Python environment just for this project.
201 3. Keep my =notebooks/= folder to continuously explore the data as I
202 see fit.
203 4. Maintain a local copy of the database in =src/= for easy access.
204 5. Export reports, images, HTML files, etc. to =public/=.
205
206 Now, onto the differences between the code in a Jupyter Notebook and
207 the actual Python files. To create the report in the Notebook snippet
208 above, I created the following function inside =process.py=:
209
210 #+begin_src python
211 # Create summary pie chart
212 def summary_data(accounts: pandas.DataFrame) -> None:
213 accounts_01 = accounts[accounts["Owner"] == "Person01"]
214 accounts_02 = accounts[accounts["Owner"] == "Person02"]
215 for x in range(1, 4):
216 if x == 1:
217 df = accounts
218 account_string = "All Accounts"
219 elif x == 2:
220 df = accounts_01
221 account_string = "Person01's Accounts"
222 elif x == 3:
223 df = accounts_02
224 account_string = "Person02's Accounts"
225 print(f"Generating pie chart summary image for {account_string}...")
226 summary_chart = (
227 df.groupby(["AccountType"])
228 .sum()
229 .plot.pie(
230 title=f"Credit Line by Type for {account_string}",
231 y="CreditLine",
232 autopct="%1.1f%%",
233 )
234 )
235 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200)
236 #+end_src
237
238 The result? A high-quality pie chart that is read directly by the
239 =public/index.html= template I use.
240
241 #+caption: Summary Pie Chart
242 [[https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/summary_chart.png]]
243
244 Other charts generated by this project include:
245
246 - Charts of account balances over time.
247 - Line chart of effective tax rate (taxes divided by taxable income).
248 - Salary projections and error limits using past income and inflation
249 rates.
250 - Multi-line chart of gross income, taxable income, and net income.
251
252 The best thing about this project? I can improve it at any given
253 time, shaping it into whatever helps me the most for that time. I
254 imagine that I will be introducing an asset tracking table soon to
255 track the depreciating value of cars, houses, etc. Who knows what's
256 next?
content/blog/2022-07-30-flac-to-opus.md → blog/flac-to-opus/index.org renamed +56 −64
@@ -1,70 +1,65 @@
1+++ 1#+title: Recursive Command-Line FLAC to Opus Conversion
2date = 2022-07-30 2#+date: 2022-07-30
3title = "Recursive Command-Line FLAC to Opus Conversion" 3#+description: Learn how to convert all FLAC files to Opus, including recursive files in subdirectories.
4description = "Learn how to convert all FLAC files to Opus, including recursive files in subdirectories." 4#+filetags: :linux:
5+++
6
7## Converting FLAC to OPUS
8 5
6* Converting FLAC to OPUS
9I am currently rebuilding my music library from scratch so that I can 7I am currently rebuilding my music library from scratch so that I can
10effectively archive all the music I own in the [FLAC file 8effectively archive all the music I own in the
11format](https://en.wikipedia.org/wiki/FLAC), a lossless audio codec. 9[[https://en.wikipedia.org/wiki/FLAC][FLAC file format]], a lossless
10audio codec.
12 11
13However, streaming FLAC files outside the home can be difficult due to 12However, streaming FLAC files outside the home can be difficult due to
14the size of the files, especially if you're using a weak connection. 13the size of the files, especially if you're using a weak connection.
15 14
16So, in order to archive the music in a lossless format and still be able 15So, in order to archive the music in a lossless format and still be able
17to stream it easily, I opted to create a copy of my FLAC files in the 16to stream it easily, I opted to create a copy of my FLAC files in the
18[Opus audio codec](https://en.wikipedia.org/wiki/Opus_(audio_format)). 17[[https://en.wikipedia.org/wiki/Opus_(audio_format)][Opus audio codec]].
19This allows me to archive a quality, lossless version of the music and 18This allows me to archive a quality, lossless version of the music and
20then point my streaming service to the smaller, stream-ready version. 19then point my streaming service to the smaller, stream-ready version.
21 20
22### Dependencies 21** Dependencies
23 22The process I follow utilizes the =opus-tools= package in Ubuntu. Before
24The process I follow utilizes the `opus-tools` package in 23proceeding, install the package:
25Ubuntu. Before proceeding, install the package:
26 24
27```sh 25#+begin_src sh
28sudo apt install opus-tools 26sudo apt install opus-tools
29``` 27#+end_src
30
31If you want to use a different conversion method, such as
32`ffmpeg` or `avconv`, simply install that package
33instead.
34 28
35### Conversion Process 29If you want to use a different conversion method, such as =ffmpeg= or
30=avconv=, simply install that package instead.
36 31
32** Conversion Process
37The script I'm using is stored in my home directory, but feel free to 33The script I'm using is stored in my home directory, but feel free to
38create it wherever you want. It does not need to be in the same 34create it wherever you want. It does not need to be in the same
39directory as your music files. 35directory as your music files.
40 36
41```sh 37#+begin_src sh
42cd ~ && nano transform.sh 38cd ~ && nano transform.sh
43``` 39#+end_src
44 40
45Once you have your new bash script opened in an editor, go ahead and 41Once you have your new bash script opened in an editor, go ahead and
46paste the following logic into the script. 42paste the following logic into the script.
47 43
48You **MUST** edit the following variables in order for it to work: 44You *MUST* edit the following variables in order for it to work:
49 45
50- `source`: The source directory where your FLAC files are 46- =source=: The source directory where your FLAC files are stored.
51 stored. 47- =dest=: The destination directory where you want the resulting Opus
52- `dest`: The destination directory where you want the 48 files to be stored.
53 resulting Opus files to be stored.
54 49
55You **MAY** want to edit the following variables to suit your needs: 50You *MAY* want to edit the following variables to suit your needs:
56 51
57- `filename`: If you are converting to a file format other 52- =filename=: If you are converting to a file format other than Opus,
58 than Opus, you'll need to edit this so that your resulting files 53 you'll need to edit this so that your resulting files have the correct
59 have the correct filename extension. 54 filename extension.
60- `reldir`: This variable can be edited to strip out more 55- =reldir=: This variable can be edited to strip out more leading
61 leading directories in the file path. As you'll see later, I ignore 56 directories in the file path. As you'll see later, I ignore this for
62 this for now and simply clean it up afterward. 57 now and simply clean it up afterward.
63- `opusenc`: This is the actual conversion process. You may 58- =opusenc=: This is the actual conversion process. You may want to edit
64 want to edit the bitrate to suit your needs. I set mine at 128 but 59 the bitrate to suit your needs. I set mine at 128 but some prefer 160
65 some prefer 160 or higher. 60 or higher.
66 61
67```sh 62#+begin_src sh
68#!/bin/bash 63#!/bin/bash
69## - The IFS takes care of spaces in file and dirnames 64## - The IFS takes care of spaces in file and dirnames
70## - your folders may vary 65## - your folders may vary
@@ -126,52 +121,49 @@ opusenc --vbr --bitrate 128 --date "$DATE" \
126## just for testing 121## just for testing
127# sleep 1 122# sleep 1
128done 123done
129``` 124#+end_src
130 125
131Once you're done, simply save the file and exit your editor. Don't 126Once you're done, simply save the file and exit your editor. Don't
132forget to enable execution of the script: 127forget to enable execution of the script:
133 128
134```sh 129#+begin_src sh
135chmod +x transform.sh 130chmod +x transform.sh
136``` 131#+end_src
137 132
138Finally, you may now run the script: 133Finally, you may now run the script:
139 134
140```sh 135#+begin_src sh
141./transform.sh 136./transform.sh
142``` 137#+end_src
143 138
144If you used `opusenc`, you'll see the conversions happen 139If you used =opusenc=, you'll see the conversions happen within the
145within the terminal as it progresses. You will also see variables 140terminal as it progresses. You will also see variables printed if you
146printed if you uncommented any of the bash script's comments. 141uncommented any of the bash script's comments.
147 142
148### Cleanup 143** Cleanup
149 144As I noted above, I didn't customize my =reldir= variable in the script,
150As I noted above, I didn't customize my `reldir` variable in 145which caused my output directory to be =/mnt/music/library/archives=
151the script, which caused my output directory to be 146instead of =/mnt/music/library=. So, I moved the output up one level and
152`/mnt/music/library/archives` instead of
153`/mnt/music/library`. So, I moved the output up one level and
154deleted the accidental directory. 147deleted the accidental directory.
155 148
156```sh 149#+begin_src sh
157cd /mnt/music/library 150cd /mnt/music/library
158mv archives/* . 151mv archives/** .
159rm -rf archives 152rm -rf archives
160``` 153#+end_src
161
162### Check the Resulting Size
163 154
164If you want to see what kind of file size savings you've gained, you 155** Check the Resulting Size
165can always use the `du` command to check: 156If you want to see what kind of file size savings you've gained, you can
157always use the =du= command to check:
166 158
167```sh 159#+begin_src sh
168cd /mnt/music 160cd /mnt/music
169du -h --max-depth=1 . 161du -h --max-depth=1 .
170``` 162#+end_src
171 163
172In my case, my small library went from 78GB to 6.3GB! 164In my case, my small library went from 78GB to 6.3GB!
173 165
174```txt 166#+begin_src txt
17578G ./archives 16778G ./archives
1766.3G ./library 1686.3G ./library
177``` 169#+end_src
content/blog/2023-01-21-running-flatpak-apps-easily.md → blog/flatpak-symlinks/index.org renamed +21 −25
@@ -1,50 +1,46 @@
1+++ 1#+title: Running Flatpak Apps with Symlinks
2date = 2023-01-21 2#+date: 2023-01-21
3title = "Running Flatpak Apps with Symlinks" 3#+description: Learn how to run Flatpak apps through menu launchers with symlinks.
4description = "Learn how to run Flatpak apps through menu launchers with symlinks." 4#+filetags: :linux:
5+++
6
7## Running Flatpak Apps Should Be Faster
8 5
6* Running Flatpak Apps Should Be Faster
9If you're like me and use Flatpak for those pesky apps that cannot run 7If you're like me and use Flatpak for those pesky apps that cannot run
10on your system for one reason or another, you likely get annoyed with 8on your system for one reason or another, you likely get annoyed with
11opening a terminal and manually running the Flatpak app with the lengthy 9opening a terminal and manually running the Flatpak app with the lengthy
12`flatpak run ...` command. 10=flatpak run ...= command.
13 11
14In the past, I manually created aliases in my `.zshrc` file 12In the past, I manually created aliases in my =.zshrc= file for certain
15for certain apps. For example, an alias would look like the example 13apps. For example, an alias would look like the example below.
16below.
17 14
18This would allow me to run the command fast within the terminal, but it 15This would allow me to run the command fast within the terminal, but it
19wouldn't allow me to run it in an application launcher. 16wouldn't allow me to run it in an application launcher.
20 17
21```sh 18#+begin_src sh
22# ~/.zshrc 19# ~/.zshrc
23alias librewolf = "flatpak run io.gitlab.librewolf-community" 20alias librewolf = "flatpak run io.gitlab.librewolf-community"
24``` 21#+end_src
25 22
26However, I now use a much faster and better method that integrates with 23However, I now use a much faster and better method that integrates with
27the tiling WMs I use and their application launchers - 24the tiling WMs I use and their application launchers - =dmenu= and
28`dmenu` and `bemenu`. 25=bemenu=.
29
30## Creating Symlinks for Flatpak Apps
31 26
27* Creating Symlinks for Flatpak Apps
32Let's use the example of Librewolf below. I can install the application 28Let's use the example of Librewolf below. I can install the application
33like so: 29like so:
34 30
35```sh 31#+begin_src sh
36flatpak install flathub io.gitlab.librewolf-community 32flatpak install flathub io.gitlab.librewolf-community
37``` 33#+end_src
38 34
39Once installed, I can create a symlink to link the flatpak app to my new 35Once installed, I can create a symlink to link the flatpak app to my new
40symlink in a location commonly included in your PATH. In this case, I 36symlink in a location commonly included in your PATH. In this case, I
41chose `/usr/bin`. You may need to choose a different location 37chose =/usr/bin=. You may need to choose a different location if
42if `/usr/bin` isn't in your PATH. 38=/usr/bin= isn't in your PATH.
43 39
44```sh 40#+begin_src sh
45ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf 41ln -s /var/lib/flatpak/exports/bin/io.gitlab.librewolf-community /usr/bin/librewolf
46``` 42#+end_src
47 43
48Once complete, you should be able to launch the app using the command 44Once complete, you should be able to launch the app using the command
49name you chose above in the symlink (`librewolf`) from a 45name you chose above in the symlink (=librewolf=) from a terminal or
50terminal or from your application launcher! 46from your application launcher!
content/blog/2021-03-28-gemini-capsule.md → blog/gemini-capsule/index.org renamed +63 −71
@@ -1,30 +1,28 @@
1+++ 1#+title: Launching a Gemini Capsule
2date = 2021-03-28 2#+date: 2021-03-28
3title = "Launching a Gemini Capsule" 3#+description: A guide to self-hosting a Gemini capsule on your own server.
4description = "A guide to self-hosting a Gemini capsule on your own server." 4#+filetags: :dev:
5+++
6 5
7## What is Gemini? 6* What is Gemini?
8 7[[https://gemini.circumlunar.space/][Gemini]] is an internet protocol
9[Gemini](https://gemini.circumlunar.space/) is an internet protocol
10introduced in June 2019 as an alternative to HTTP(S) or Gopher. In 8introduced in June 2019 as an alternative to HTTP(S) or Gopher. In
11layman's terms, it's an alternative way to browse sites (called 9layman's terms, it's an alternative way to browse sites (called
12capsules) that requires a special browser. Since Gemini is not 10capsules) that requires a special browser. Since Gemini is not
13standardized as an internet standard, normal web browsers won't be able 11standardized as an internet standard, normal web browsers won't be able
14to load a Gemini capsule. Instead, you'll need to use [a 12to load a Gemini capsule. Instead, you'll need to use
15Gemini-specific 13[[https://gemini.%20circumlunar.space/clients.html][a Gemini-specific
16browser](https://gemini.%20circumlunar.space/clients.html). 14browser]].
17 15
18The content found within a Gemini page is called 16The content found within a Gemini page is called
19[Gemtext](https://gemini.circumlunar.space/docs/cheatsheet.gmi) and is 17[[https://gemini.circumlunar.space/docs/cheatsheet.gmi][Gemtext]] and is
20*extremely* basic (on purpose). Gemini only processes the text, no media 18/extremely/ basic (on purpose). Gemini only processes the text, no media
21content like images. However, you're able to style 3 levels of 19content like images. However, you're able to style 3 levels of headings,
22headings, regular text, links (which will display on their own line), 20regular text, links (which will display on their own line), quotes, and
23quotes, and an unordered list. 21an unordered list.
24 22
25Here's a complete listing of valid Gemtext: 23Here's a complete listing of valid Gemtext:
26 24
27```txt 25#+begin_src txt
28# Heading 1 26# Heading 1
29## Heading 2 27## Heading 2
30### Heading 3 28### Heading 3
@@ -37,14 +35,13 @@ Regular text! Lorem ipsum dolor sit amet.
37> "If life were predictable it would cease to be life, and be without flavor." - Eleanor Roosevelt 35> "If life were predictable it would cease to be life, and be without flavor." - Eleanor Roosevelt
38 36
39My List: 37My List:
40* Item 38,** Item
41* Item 39,** Item
42 40
43```Anything between three backticks will be rendered as code.``` 41```Anything between three backticks will be rendered as code.```
44``` 42#+end_src
45
46## Free Option
47 43
44*** Free Option
48There are probably numerous websites that allow you to create your 45There are probably numerous websites that allow you to create your
49personal Gemini capsule, but I'm going to focus on the two sites that I 46personal Gemini capsule, but I'm going to focus on the two sites that I
50have personally tested. The first option below, Midnight Pub, allows you 47have personally tested. The first option below, Midnight Pub, allows you
@@ -54,18 +51,17 @@ second option below, Sourcehut, allows you to use a Git repository and
54automatic build process to deploy your personal Gemini capsule every 51automatic build process to deploy your personal Gemini capsule every
55time you push a commit. 52time you push a commit.
56 53
57### Midnight Pub - Beginner Friendly 54** Midnight Pub - Beginner Friendly
58 55[[https://midnight.pub/][Midnight Pub]] is a small, virtual community
59[Midnight Pub](https://midnight.pub/) is a small, virtual community
60meant to reflect the atmosphere of wandering into a small alley pub. The 56meant to reflect the atmosphere of wandering into a small alley pub. The
61site is built in Gemtext and has a server-side process to convert 57site is built in Gemtext and has a server-side process to convert
62Gemtext to HTML if someone loads the site in an HTTP(S) browser. 58Gemtext to HTML if someone loads the site in an HTTP(S) browser.
63 59
64To create an account, you'll need to email the owner of the website to 60To create an account, you'll need to email the owner of the website to
65obtain a key. You can find their email on the Midnight Pub homepage. 61obtain a key. You can find their email on the Midnight Pub homepage.
66Once registered, head to [your account](https://midnight.pub/account) 62Once registered, head to [[https://midnight.pub/account][your account]]
67and select [manage site](https://midnight.pub/site). This is the screen 63and select [[https://midnight.pub/site][manage site]]. This is the
68where you can upload or create any files to be displayed on the 64screen where you can upload or create any files to be displayed on the
69internet. 65internet.
70 66
71For example, I've created both an HTML file and a Gemini file. Remember 67For example, I've created both an HTML file and a Gemini file. Remember
@@ -73,22 +69,19 @@ that Gemini is automatically converted to HTML on the Pub, so you don't
73need an HTML version. For example, I created an HTML version to add in 69need an HTML version. For example, I created an HTML version to add in
74some extra styling. 70some extra styling.
75 71
76All you need to do is create a page like `index.gmi` and use 72All you need to do is create a page like =index.gmi= and use your Gemini
77your Gemini browser to head over to your-username.midnight.pub to see 73browser to head over to your-username.midnight.pub to see the result.
78the result.
79 74
80That's all there is to it! Easy enough, right? Let's check out a more 75That's all there is to it! Easy enough, right? Let's check out a more
81advanced version in the next section. 76advanced version in the next section.
82 77
83## Paid Option 78* Paid Option
84
85As of 2021, Sourcehut has decided to require users to have a paid 79As of 2021, Sourcehut has decided to require users to have a paid
86account in order to utilize their automated build system. For now, paid 80account in order to utilize their automated build system. For now, paid
87accounts can be as low as \$2/month. 81accounts can be as low as $2/month.
88
89### Sourcehut
90 82
91[Sourcehut](https://sourcehut.org/) is a collection of software 83** Sourcehut
84[[https://sourcehut.org/][Sourcehut]] is a collection of software
92development tools, but mostly surrounds their hosted Git repository 85development tools, but mostly surrounds their hosted Git repository
93service. Simply put, it's a minimal and more private alternative to 86service. Simply put, it's a minimal and more private alternative to
94services like GitHub. 87services like GitHub.
@@ -100,43 +93,43 @@ instead.
100 93
101The first thing you'll need to do is create an SSH key pair, if you 94The first thing you'll need to do is create an SSH key pair, if you
102don't already have one on your system. Once created, grab the contents 95don't already have one on your system. Once created, grab the contents
103of `id_rsa.pub` and add it to your Sourcehut account 96of =id_rsa.pub= and add it to your Sourcehut account settings - this
104settings - this will allow you to push and pull code changes without 97will allow you to push and pull code changes without using a
105using a username/password. 98username/password.
106 99
107```sh 100#+begin_src sh
108ssh keygen 101ssh keygen
109``` 102#+end_src
110 103
111Next up, let's create a repository with the proper name so that the 104Next up, let's create a repository with the proper name so that the
112Sourcehut build system will know we want them to host a website for us. 105Sourcehut build system will know we want them to host a website for us.
113Use the following format exactly: 106Use the following format exactly:
114 107
115```sh 108#+begin_src sh
116mkdir your-username.srht.site && cd your-username.srht.site 109mkdir your-username.srht.site && cd your-username.srht.site
117``` 110#+end_src
118 111
119Now that we've created the repo, let's initialize Git and add the 112Now that we've created the repo, let's initialize Git and add the proper
120proper remote URL. 113remote URL.
121 114
122```sh 115#+begin_src sh
123git init 116git init
124``` 117#+end_src
125 118
126```sh 119#+begin_src sh
127git remote add origin git@git.sr.ht:~your-username/your-username.srht.site 120git remote add origin git@git.sr.ht:~your-username/your-username.srht.site
128``` 121#+end_src
129 122
130Now that our repository is set up and configured, we will need to create 123Now that our repository is set up and configured, we will need to create
131at least two files: 124at least two files:
132 125
133- `index.gmi` 126- =index.gmi=
134- `.build.yml` 127- =.build.yml=
135 128
136For your `.build.yml` file, use the following content and be 129For your =.build.yml= file, use the following content and be sure to
137sure to update the `site` line with your username! 130update the =site= line with your username!
138 131
139```yaml 132#+begin_src yaml
140image: alpine/latest 133image: alpine/latest
141oauth: pages.sr.ht/PAGES:RW 134oauth: pages.sr.ht/PAGES:RW
142environment: 135environment:
@@ -147,39 +140,38 @@ tasks:
147 tar -cvz . > ../site.tar.gz 140 tar -cvz . > ../site.tar.gz
148 - upload: | 141 - upload: |
149 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI 142 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
150``` 143#+end_src
151 144
152For the `index.gmi` file, put whatever you want in there and 145For the =index.gmi= file, put whatever you want in there and save it.
153save it. You could even just copy and paste the Gemtext cheatsheet. 146You could even just copy and paste the Gemtext cheatsheet.
154 147
155If you want to serve both HTML and Gemini files from this repository, 148If you want to serve both HTML and Gemini files from this repository,
156just add a second command to the `upload` section: 149just add a second command to the =upload= section:
157 150
158```yaml 151#+begin_src yaml
159- upload: | 152- upload: |
160 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI 153 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz -Fprotocol=GEMINI
161 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz 154 acurl -f https://pages.sr.ht/publish/$site -Fcontent=@site.tar.gz
162``` 155#+end_src
163 156
164Lastly, commit your changes and push them to the remote repo. 157Lastly, commit your changes and push them to the remote repo.
165 158
166```sh 159#+begin_src sh
167git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD 160git add .; git commit -m "initial commit"; git push --set-upstream origin HEAD
168``` 161#+end_src
169 162
170If you've successfully created the files with the proper format, 163If you've successfully created the files with the proper format, you'll
171you'll see the terminal print a message that lets you know where the 164see the terminal print a message that lets you know where the automatic
172automatic build is taking place. For example, here's what the terminal 165build is taking place. For example, here's what the terminal tells me:
173tells me:
174 166
175```sh 167#+begin_src sh
176remote: Build started: 168remote: Build started:
177remote: https://builds.sr.ht/~user/job/689803 [.build.yml] 169remote: https://builds.sr.ht/~user/job/689803 [.build.yml]
178``` 170#+end_src
179 171
180Now that you've properly built your Sourcehut page, you can browse to 172Now that you've properly built your Sourcehut page, you can browse to
181your-username.srht.site in a Gemini browser and view the final results. 173your-username.srht.site in a Gemini browser and view the final results.
182Take a look at the image below for my Sourcehut Gemini capsule. 174Take a look at the image below for my Sourcehut Gemini capsule.
183 175
184![Gemini page on the amfora 176#+caption: Gemini page on the amfora browser
185browser](https://img.cleberg.net/blog/20210328-launching-a-gemini-capsule/amfora.png) 177[[https://img.cleberg.net/blog/20210328-launching-a-gemini-capsule/amfora.png]]
content/blog/2021-04-17-gemini-server.md → blog/gemini-server/index.org renamed +63 −77
@@ -1,164 +1,150 @@
1+++ 1#+title: Hosting a Gemini Server
2date = 2021-04-17 2#+date: 2021-04-17
3title = "Hosting a Gemini Server" 3#+description: A guide to self-hosting a Gemini web server on your own server.
4description = "A guide to self-hosting a Gemini web server on your own server." 4#+filetags: :sysadmin:
5+++
6
7## Similar Article Available
8 5
6* Similar Article Available
9To read more about Gemini and ways to test out this new protocol without 7To read more about Gemini and ways to test out this new protocol without
10your own server, see my previous post [Launching a Gemini 8your own server, see my previous post
11Capsule](/blog/launching-a-gemini-capsule/). 9[[../launching-a-gemini-capsule/][Launching a Gemini Capsule]].
12
13## Preparation
14 10
11* Preparation
15This guide assumes you have access to a server accessible to the world 12This guide assumes you have access to a server accessible to the world
16through a public IP address and that you own a domain name used for this 13through a public IP address and that you own a domain name used for this
17Gemini capsule. 14Gemini capsule.
18 15
19## Getting Started with Agate 16* Getting Started with Agate
20 17We are going to use [[https://github.com/mbrubeck/agate][Agate]] for
21We are going to use [Agate](https://github.com/mbrubeck/agate) for this 18this tutorial. This is a basic Gemini server written in Rust. It takes
22tutorial. This is a basic Gemini server written in Rust. It takes very 19very little time and maintenance to get it running.
23little time and maintenance to get it running.
24
25## Install Dependencies
26 20
21* Install Dependencies
27First, you will need to install the Rust package for your system. On 22First, you will need to install the Rust package for your system. On
28Ubuntu, use the following commands (remember to use `sudo` if 23Ubuntu, use the following commands (remember to use =sudo= if you are
29you are not the root user). The Rust installation will give you options 24not the root user). The Rust installation will give you options to
30to customize the installation; I used the default installation options. 25customize the installation; I used the default installation options.
31 26
32```sh 27#+begin_src sh
33sudo apt update && sudo apt upgrade -y 28sudo apt update && sudo apt upgrade -y
34curl https://sh.rustup.rs -sSf | sh 29curl https://sh.rustup.rs -sSf | sh
35``` 30#+end_src
36 31
37Remember to configure your shell with the new configuration: 32Remember to configure your shell with the new configuration:
38 33
39```sh 34#+begin_src sh
40source $HOME/.cargo/env 35source $HOME/.cargo/env
41``` 36#+end_src
42 37
43Before we install agate, make sure you have the `gcc` package 38Before we install agate, make sure you have the =gcc= package installed:
44installed:
45 39
46```sh 40#+begin_src sh
47sudo apt install gcc 41sudo apt install gcc
48``` 42#+end_src
49 43
50Next, you'll need to install the agate executable with Rust's Cargo 44Next, you'll need to install the agate executable with Rust's Cargo
51package maintainer: 45package maintainer:
52 46
53```sh 47#+begin_src sh
54cargo install agate 48cargo install agate
55``` 49#+end_src
56
57## Create Symlinks
58 50
51* Create Symlinks
59Once Cargo has finished installing all the required packages, symlink 52Once Cargo has finished installing all the required packages, symlink
60the executable to your \$PATH. 53the executable to your $PATH.
61 54
62```sh 55#+begin_src sh
63sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate 56sudo ln -s $HOME/.cargo/bin/agate /usr/local/bin/agate
64``` 57#+end_src
65
66## Using Agate's Built-In Installation Tool
67 58
59* Using Agate's Built-In Installation Tool
68If you're running Ubuntu or Debian, use the Debian installation script 60If you're running Ubuntu or Debian, use the Debian installation script
69found in Agate's GitHub repository, under the `tools/debian` 61found in Agate's GitHub repository, under the =tools/debian= folder.
70folder.
71 62
72```sh 63#+begin_src sh
73git clone https://github.com/mbrubeck/agate 64git clone https://github.com/mbrubeck/agate
74cd agate/tools/debian 65cd agate/tools/debian
75sudo ./install.sh 66sudo ./install.sh
76``` 67#+end_src
77
78## Configure the Gemini Service
79 68
69* Configure the Gemini Service
80We have a little more to do, but since this script tries to immediately 70We have a little more to do, but since this script tries to immediately
81run the service, it will likely fail with an exit code. Let's add our 71run the service, it will likely fail with an exit code. Let's add our
82finishing touches. Edit the following file and replace the hostname with 72finishing touches. Edit the following file and replace the hostname with
83your desired URL. You can also change the directory where content will 73your desired URL. You can also change the directory where content will
84be served. 74be served.
85 75
86```sh 76#+begin_src sh
87sudo nano /etc/systemd/system/gemini.service 77sudo nano /etc/systemd/system/gemini.service
88``` 78#+end_src
89 79
90```sh 80#+begin_src sh
91# Edit these lines to whatever you want - see the next code block for my personal configuration. 81# Edit these lines to whatever you want - see the next code block for my personal configuration.
92WorkingDirectory=/srv/gemini 82WorkingDirectory=/srv/gemini
93ExecStart=agate --hostname $(uname -n) --lang en 83ExecStart=agate --hostname $(uname -n) --lang en
94``` 84#+end_src
95 85
96This is my personal config: 86This is my personal config:
97 87
98```sh 88#+begin_src sh
99WorkingDirectory=/var/gemini/ 89WorkingDirectory=/var/gemini/
100ExecStart=agate --hostname gemini.example.com --lang en 90ExecStart=agate --hostname gemini.example.com --lang en
101``` 91#+end_src
102 92
103Since we've altered the systemd configuration files, we have to reload 93Since we've altered the systemd configuration files, we have to reload
104the daemon. Let's do that, restart our service, and check its status. 94the daemon. Let's do that, restart our service, and check its status.
105 95
106```sh 96#+begin_src sh
107sudo systemctl daemon-reload 97sudo systemctl daemon-reload
108sudo systemctl restart gemini.service 98sudo systemctl restart gemini.service
109sudo systemctl status gemini.service 99sudo systemctl status gemini.service
110``` 100#+end_src
111
112## Fixing Systemd Errors
113 101
102* Fixing Systemd Errors
114If you're still getting errors, the installation process may not have 103If you're still getting errors, the installation process may not have
115properly enabled the gemini service. Fix it with the following commands. 104properly enabled the gemini service. Fix it with the following commands.
116 105
117```sh 106#+begin_src sh
118sudo systemctl enable gemini.service 107sudo systemctl enable gemini.service
119sudo systemctl restart gemini.service 108sudo systemctl restart gemini.service
120sudo systemctl status gemini.service 109sudo systemctl status gemini.service
121``` 110#+end_src
122
123## Firewall Rules
124 111
112* Firewall Rules
125Great! Our server is now functional and running. The first consideration 113Great! Our server is now functional and running. The first consideration
126now is that you need to be able to access port 1965 on the server. If 114now is that you need to be able to access port 1965 on the server. If
127you have a firewall enabled, you'll need to open that port up. 115you have a firewall enabled, you'll need to open that port up.
128 116
129```sh 117#+begin_src sh
130sudo ufw allow 1965 118sudo ufw allow 1965
131sudo ufw reload 119sudo ufw reload
132``` 120#+end_src
133
134## Creating Content
135 121
122* Creating Content
136Let's create the Gemini capsule. Note that wherever you set the 123Let's create the Gemini capsule. Note that wherever you set the
137WorkingDirectory variable to earlier, Agate will expect you to put your 124WorkingDirectory variable to earlier, Agate will expect you to put your
138Gemini capsule contents in a sub-folder called "content." So, I place 125Gemini capsule contents in a sub-folder called "content." So, I place my
139my files in "/var/gmi/content." I'm going to create that folder now 126files in "/var/gmi/content." I'm going to create that folder now and put
140and put a file in there. 127a file in there.
141 128
142```sh 129#+begin_src sh
143sudo mkdir /var/gemini/content 130sudo mkdir /var/gemini/content
144sudo nano /var/gemini/content/index.gmi 131sudo nano /var/gemini/content/index.gmi
145``` 132#+end_src
146 133
147You can put whatever you want in the "index.gmi" file, just make sure 134You can put whatever you want in the "index.gmi" file, just make sure
148it's valid Gemtext. 135it's valid Gemtext.
149 136
150## The Results 137* The Results
151
152Here are some screenshots of the Gemini page I just created in the 138Here are some screenshots of the Gemini page I just created in the
153[Lagrange](https://gmi.skyjake.fi/lagrange/) browser and the 139[[https://gmi.skyjake.fi/lagrange/][Lagrange]] browser and the
154[amfora](https://github.com/makeworld-the-better-one/amfora) browser. 140[[https://github.com/makeworld-the-better-one/amfora][amfora]] browser.
155 141
156![GUI Gemini 142#+caption: GUI Gemini browser
157browser](https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/lagrange.png) 143[[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/lagrange.png]]
158 144
159*Lagrange* 145/Lagrange/
160 146
161![CLI Gemini 147#+caption: CLI Gemini browser
162browser](https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/amfora.png) 148[[https://img.cleberg.net/blog/20210417-hosting-a-gemini-server/amfora.png]]
163 149
164*Amfora* 150/Amfora/
content/blog/2022-07-01-git-server.md → blog/git-server/index.org renamed +234 −271
@@ -1,11 +1,9 @@
1+++ 1#+title: Self-Hosting a Personal Git Server
2date = 2022-07-01 2#+date: 2022-07-01
3title = "Self-Hosting a Personal Git Server" 3#+description: A guide to self-hosting a Git server on your own server.
4description = "A guide to self-hosting a Git server on your own server." 4#+filetags: :selfhosting:
5+++
6
7## My Approach to Self-Hosting Git
8 5
6* My Approach to Self-Hosting Git
9I have often tried to self-host my Git repositories, but have always 7I have often tried to self-host my Git repositories, but have always
10fallen short when I tried to find a suitable web interface to show on 8fallen short when I tried to find a suitable web interface to show on
11the front-end. 9the front-end.
@@ -17,240 +15,223 @@ access them from anywhere.
17Before I dive into the details, I want to state a high-level summary of 15Before I dive into the details, I want to state a high-level summary of
18my self-hosted Git approach: 16my self-hosted Git approach:
19 17
20- This method uses the `ssh://` (read & write) and 18- This method uses the =ssh://= (read & write) and =git://= (read-only)
21 `git://` (read-only) protocols for push and pull access. 19 protocols for push and pull access.
22 - For the `git://` protocol, I create a 20 - For the =git://= protocol, I create a =git-daemon-export-ok= file in
23 `git-daemon-export-ok` file in any repository that I 21 any repository that I want to be cloneable by anyone.
24 want to be cloneable by anyone. 22 - The web interface I am using (=cgit=) allows simple HTTP cloning by
25 - The web interface I am using (`cgit`) allows simple 23 default. I do not disable this setting as I want beginners to be
26 HTTP cloning by default. I do not disable this setting as I want 24 able to clone one of my repositories even if they don't know the
27 beginners to be able to clone one of my repositories even if 25 proper method.
28 they don't know the proper method. 26- I am not enabling Smart HTTPS for any repositories. Updates to
29- I am not enabling Smart HTTPS for any repositories. Updates to 27 repositories must be pushed via SSH.
30 repositories must be pushed via SSH. 28- Beyond the actual repository management, I am using =cgit= for the
31- Beyond the actual repository management, I am using 29 front-end web interface.
32 `cgit` for the front-end web interface. 30 - If you use the =scan-path=<path>= configuration in the =cgitrc=
33 - If you use the `scan-path=<path>` configuration in 31 configuration file to automatically find repositories, you can't
34 the `cgitrc` configuration file to automatically find 32 exclude a repository from =cgit= if it's stored within the path that
35 repositories, you can't exclude a repository from 33 =cgit= reads. To host private repositories, you'd need to set up
36 `cgit` if it's stored within the path that 34 another directory that =cgit= can't read.
37 `cgit` reads. To host private repositories, you'd 35
38 need to set up another directory that `cgit` can't 36* Assumptions
39 read.
40
41## Assumptions
42
43For the purposes of this walkthrough, I am assuming you have a URL 37For the purposes of this walkthrough, I am assuming you have a URL
44(`git.example.com`) or IP address 38(=git.example.com=) or IP address (=207.84.26.991=) addressed to the
45(`207.84.26.991`) addressed to the server that you will be 39server that you will be using to host your git repositories.
46using to host your git repositories.
47
48## Adding a Git User
49 40
41* Adding a Git User
50In order to use the SSH method associated with git, we will need to add 42In order to use the SSH method associated with git, we will need to add
51a user named `git`. If you have used the SSH method for other 43a user named =git=. If you have used the SSH method for other git
52git hosting sites, you are probably used to the following syntax: 44hosting sites, you are probably used to the following syntax:
53 45
54```sh 46#+begin_src sh
55git clone [user@]server:project.git 47git clone [user@]server:project.git
56``` 48#+end_src
57 49
58The syntax above is an `scp`-like syntax for using SSH on the 50The syntax above is an =scp=-like syntax for using SSH on the =git= user
59`git` user on the server to access your repository. 51on the server to access your repository.
60 52
61Let's delete any remnants of an old `git` user, if any, and 53Let's delete any remnants of an old =git= user, if any, and create the
62create the new user account: 54new user account:
63 55
64```sh 56#+begin_src sh
65sudo deluser --remove-home git 57sudo deluser --remove-home git
66sudo adduser git 58sudo adduser git
67``` 59#+end_src
68 60
69### Import Your SSH Keys to the Git User 61** Import Your SSH Keys to the Git User
70 62Once the =git= user is created, you will need to copy your public SSH
71Once the `git` user is created, you will need to copy your 63key on your local development machine to the =git= user on the server.
72public SSH key on your local development machine to the `git`
73user on the server.
74 64
75If you don't have an SSH key yet, create one with this command: 65If you don't have an SSH key yet, create one with this command:
76 66
77```sh 67#+begin_src sh
78ssh-keygen 68ssh-keygen
79``` 69#+end_src
80 70
81Once you create the key pair, the public should be saved to 71Once you create the key pair, the public should be saved to
82`~/.ssh/id_rsa.pub`. 72=~/.ssh/id_rsa.pub=.
83 73
84If your server still has password-based authentication available, you 74If your server still has password-based authentication available, you
85can copy it over to your user's home directory like this: 75can copy it over to your user's home directory like this:
86 76
87```sh 77#+begin_src sh
88ssh-copy-id git@server 78ssh-copy-id git@server
89``` 79#+end_src
90 80
91Otherwise, copy it over to any user that you can access. 81Otherwise, copy it over to any user that you can access.
92 82
93```sh 83#+begin_src sh
94scp ~/.ssh/id_rsa.pub your_user@your_server: 84scp ~/.ssh/id_rsa.pub your_user@your_server:
95``` 85#+end_src
96 86
97Once on the server, you will need to copy the contents into the 87Once on the server, you will need to copy the contents into the =git=
98`git` user's `authorized_keys` file: 88user's =authorized_keys= file:
99 89
100```sh 90#+begin_src sh
101cat id_rsa.pub > /home/git/.ssh/authorized_keys 91cat id_rsa.pub > /home/git/.ssh/authorized_keys
102``` 92#+end_src
103
104### (Optional) Disable Password-Based SSH
105 93
94** (Optional) Disable Password-Based SSH
106If you want to lock down your server and ensure that no one can 95If you want to lock down your server and ensure that no one can
107authenticate in via SSH with a password, you will need to edit your SSH 96authenticate in via SSH with a password, you will need to edit your SSH
108configuration. 97configuration.
109 98
110```sh 99#+begin_src sh
111sudo nano /etc/ssh/sshd_config 100sudo nano /etc/ssh/sshd_config
112``` 101#+end_src
113 102
114Within this file, find the following settings and set them to the values 103Within this file, find the following settings and set them to the values
115I am showing below: 104I am showing below:
116 105
117```conf 106#+begin_src conf
118PermitRootLogin no 107PermitRootLogin no
119PasswordAuthentication no 108PasswordAuthentication no
120AuthenticationMethods publickey 109AuthenticationMethods publickey
121``` 110#+end_src
122 111
123You may have other Authentication Methods required in your personal 112You may have other Authentication Methods required in your personal
124set-up, so the key here is just to ensure that 113set-up, so the key here is just to ensure that =AuthenticationMethods=
125`AuthenticationMethods` does not allow passwords. 114does not allow passwords.
126
127## Setting up the Base Directory
128 115
129Now that we have set up a `git` user to handle all transport 116*** Setting up the Base Directory
130methods, we need to set up the directory that we will be using as our 117Now that we have set up a =git= user to handle all transport methods, we
131base of all repositories. 118need to set up the directory that we will be using as our base of all
119repositories.
132 120
133In my case, I am using `/git` as my source folder. To create 121In my case, I am using =/git= as my source folder. To create this folder
134this folder and assign it to the user we created, execute the following 122and assign it to the user we created, execute the following commands:
135commands:
136 123
137```sh 124#+begin_src sh
138sudo mkdir /git 125sudo mkdir /git
139sudo chown -R git:git /git 126sudo chown -R git:git /git
140``` 127#+end_src
141 128
142## Creating a Test Repository 129*** Creating a Test Repository
130On your server, switch over to the =git= user in order to start managing
131git files.
143 132
144On your server, switch over to the `git` user in order to 133#+begin_src sh
145start managing git files.
146
147```sh
148su git 134su git
149``` 135#+end_src
150 136
151Once logged-in as the `git` user, go to your base directory 137Once logged-in as the =git= user, go to your base directory and create a
152and create a test repository. 138test repository.
153 139
154```sh 140#+begin_src sh
155cd /git 141cd /git
156mkdir test.git && cd test.git 142mkdir test.git && cd test.git
157git init --bare 143git init --bare
158``` 144#+end_src
159 145
160If you want to make this repo viewable/cloneable to the public via the 146If you want to make this repo viewable/cloneable to the public via the
161`git://` protocol, you need to create a 147=git://= protocol, you need to create a =git-daemon-export-ok= file
162`git-daemon-export-ok` file inside the repository. 148inside the repository.
163 149
164```sh 150#+begin_src sh
165touch git-daemon-export-ok 151touch git-daemon-export-ok
166``` 152#+end_src
167
168## Change the Login Shell for `git`
169 153
170To make sure that the `git` user is only used for git 154* Change the Login Shell for =git=
171operations and nothing else, you need to change the user's login shell. 155To make sure that the =git= user is only used for git operations and
172To do this, simply use the `chsh` command: 156nothing else, you need to change the user's login shell. To do this,
157simply use the =chsh= command:
173 158
174```sh 159#+begin_src sh
175sudo chsh git 160sudo chsh git
176``` 161#+end_src
177 162
178The interactive prompt will ask which shell you want the 163The interactive prompt will ask which shell you want the =git= user to
179`git` user to use. You must use the following value: 164use. You must use the following value:
180 165
181```sh 166#+begin_src sh
182/usr/bin/git-shell 167/usr/bin/git-shell
183``` 168#+end_src
184
185Once done, no one will be able to SSH to the `git` user or
186execute commands other than the standard git commands.
187 169
188## Opening the Firewall 170Once done, no one will be able to SSH to the =git= user or execute
171commands other than the standard git commands.
189 172
173* Opening the Firewall
190Don't forget to open up ports on the device firewall and network 174Don't forget to open up ports on the device firewall and network
191firewall if you want to access these repositories publicly. If you're 175firewall if you want to access these repositories publicly. If you're
192using default ports, forward ports `22` (ssh) and 176using default ports, forward ports =22= (ssh) and =9418= (git) from your
193`9418` (git) from your router to your server's IP address. 177router to your server's IP address.
194 178
195If your server also has a firewall, ensure that the firewall allows the 179If your server also has a firewall, ensure that the firewall allows the
196same ports that are forwarded from the router. For example, if you use 180same ports that are forwarded from the router. For example, if you use
197`ufw`: 181=ufw=:
198 182
199```sh 183#+begin_src sh
200sudo ufw allow 22 184sudo ufw allow 22
201sudo ufw allow 9418 185sudo ufw allow 9418
202``` 186#+end_src
203 187
204### Non-Standard SSH Ports 188** Non-Standard SSH Ports
205 189If you use a non-standard port for SSH, such as =9876=, you will need to
206If you use a non-standard port for SSH, such as `9876`, you 190create an SSH configuration file on your local development machine in
207will need to create an SSH configuration file on your local development 191order to connect to your server's git repositories.
208machine in order to connect to your server's git repositories.
209 192
210To do this, you'll need to define your custom port on your client 193To do this, you'll need to define your custom port on your client
211machine in your `~/.ssh/config` file: 194machine in your =~/.ssh/config= file:
212 195
213```sh 196#+begin_src sh
214nano ~/.ssh/config 197nano ~/.ssh/config
215``` 198#+end_src
216 199
217```conf 200#+begin_src conf
218Host git.example.com 201Host git.example.com
219 # HostName can be a URL or an IP address 202 # HostName can be a URL or an IP address
220 HostName git.example.com 203 HostName git.example.com
221 Port 9876 204 Port 9876
222 User git 205 User git
223``` 206#+end_src
224
225### Testing SSH
226 207
208** Testing SSH
227There are two main syntaxes you can use to manage git over SSH: 209There are two main syntaxes you can use to manage git over SSH:
228 210
229- `git clone [user@]server:project.git` 211- =git clone [user@]server:project.git=
230- `git clone ssh://[user@]server/project.git` 212- =git clone ssh://[user@]server/project.git=
231 213
232I prefer the first, which is an `scp`-like syntax. To test 214I prefer the first, which is an =scp=-like syntax. To test it, try to
233it, try to clone the test repository you set up on the server: 215clone the test repository you set up on the server:
234 216
235```sh 217#+begin_src sh
236git clone git@git.example.com:/git/test.git 218git clone git@git.example.com:/git/test.git
237``` 219#+end_src
238
239## Enabling Read-Only Access
240 220
221* Enabling Read-Only Access
241If you want people to be able to clone any repository where you've 222If you want people to be able to clone any repository where you've
242placed a `git-daemon-export-ok` file, you will need to start 223placed a =git-daemon-export-ok= file, you will need to start the git
243the git daemon. 224daemon.
244 225
245To do this on a system with `systemd`, create a service file: 226To do this on a system with =systemd=, create a service file:
246 227
247```sh 228#+begin_src sh
248sudo nano /etc/systemd/system/git-daemon.service 229sudo nano /etc/systemd/system/git-daemon.service
249``` 230#+end_src
250 231
251Inside the `git-daemon.service` file, paste the following: 232Inside the =git-daemon.service= file, paste the following:
252 233
253```conf 234#+begin_src conf
254[Unit] 235[Unit]
255Description=Start Git Daemon 236Description=Start Git Daemon
256 237
@@ -269,24 +250,23 @@ Group=git
269 250
270[Install] 251[Install]
271WantedBy=multi-user.target 252WantedBy=multi-user.target
272``` 253#+end_src
273 254
274Once created, enable and start the service: 255Once created, enable and start the service:
275 256
276```sh 257#+begin_src sh
277sudo systemctl enable git-daemon.service 258sudo systemctl enable git-daemon.service
278sudo systemctl start git-daemon.service 259sudo systemctl start git-daemon.service
279``` 260#+end_src
280 261
281To clone read-only via the `git://` protocol, you can use the 262To clone read-only via the =git://= protocol, you can use the following
282following syntax: 263syntax:
283 264
284```sh 265#+begin_src sh
285git clone git://git.example.com/test.git 266git clone git://git.example.com/test.git
286``` 267#+end_src
287
288## Migrating Repositories
289 268
269* Migrating Repositories
290At this point, we have a working git server that works with both SSH and 270At this point, we have a working git server that works with both SSH and
291read-only access. 271read-only access.
292 272
@@ -296,49 +276,44 @@ my new source of truth:
296 276
297Server: 277Server:
298 278
299```sh 279#+begin_src sh
300su git 280su git
301mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git 281mkdir /git/<REPOSITORY_NAME>.git && cd /git/<REPOSITORY_NAME>.git
302git init --bare 282git init --bare
303 283
304# If you want to make this repo viewable/cloneable to the public 284# If you want to make this repo viewable/cloneable to the public
305touch git-daemon-export-ok 285touch git-daemon-export-ok
306``` 286#+end_src
307 287
308Client: 288Client:
309 289
310```sh 290#+begin_src sh
311git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME> 291git clone git@<PREVIOUS_HOST>:<REPOSITORY_NAME>
312git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git 292git remote set-url origin git@git.EXAMPLE.COM:/git/<REPOSITORY_NAME>.git
313git push 293git push
314``` 294#+end_src
315
316## Optional Web View: `cgit`
317 295
296* Optional Web View: =cgit=
318If you want a web viewer for your repositories, you can use various 297If you want a web viewer for your repositories, you can use various
319tools, such as `gitweb`, `cgit`, or 298tools, such as =gitweb=, =cgit=, or =klaus=. I chose =cgit= due to its
320`klaus`. I chose `cgit` due to its simple 299simple interface and fairly easy set-up (compared to others). Not to
321interface and fairly easy set-up (compared to others). Not to mention 300mention that the [[https://git.kernel.org/][Linux kernel uses =cgit=]].
322that the [Linux kernel uses `cgit`](https://git.kernel.org/).
323 301
324### Docker Compose 302** Docker Compose
303Instead of using my previous method of using a =docker run= command,
304I've updated this section to use =docker-compose= instead for an easier
305installation and simpler management and configuration.
325 306
326Instead of using my previous method of using a `docker run` 307In order to use Docker Compose, you will set up a =docker-compose.yml=
327command, I've updated this section to use `docker-compose` 308file to automatically connect resources like the repositories, =cgitrc=,
328instead for an easier installation and simpler management and 309and various files or folders to the =cgit= container you're creating:
329configuration.
330
331In order to use Docker Compose, you will set up a
332`docker-compose.yml` file to automatically connect resources
333like the repositories, `cgitrc`, and various files or folders
334to the `cgit` container you're creating:
335 310
336```sh 311#+begin_src sh
337mkdir ~/cgit && cd ~/cgit 312mkdir ~/cgit && cd ~/cgit
338nano docker-compose.yml 313nano docker-compose.yml
339``` 314#+end_src
340 315
341```conf 316#+begin_src conf
342# docker-compose.yml 317# docker-compose.yml
343version: '3' 318version: '3'
344 319
@@ -354,31 +329,29 @@ services:
354 ports: 329 ports:
355 - "8763:80" 330 - "8763:80"
356 restart: always 331 restart: always
357``` 332#+end_src
358 333
359Then, just start the container: 334Then, just start the container:
360 335
361```sh 336#+begin_src sh
362sudo docker-compose up -d 337sudo docker-compose up -d
363``` 338#+end_src
364 339
365Once it's finished installing, you can access the site at 340Once it's finished installing, you can access the site at
366`<SERVER_IP>:8763` or use a reverse-proxy service to forward 341=<SERVER_IP>:8763= or use a reverse-proxy service to forward =cgit= to a
367`cgit` to a URL, such as `git.example.com`. See 342URL, such as =git.example.com=. See the next section for more details on
368the next section for more details on reverse proxying a URL to a local 343reverse proxying a URL to a local port.
369port.
370
371### Nginx Reverse Proxy
372 344
373I am using Nginx as my reverse proxy so that the `cgit` 345** Nginx Reverse Proxy
374Docker container can use `git.example.com` as its URL. To do 346I am using Nginx as my reverse proxy so that the =cgit= Docker container
375so, I simply created the following configuration file: 347can use =git.example.com= as its URL. To do so, I simply created the
348following configuration file:
376 349
377```sh 350#+begin_src sh
378sudo nano /etc/nginx/sites-available/git.example.com 351sudo nano /etc/nginx/sites-available/git.example.com
379``` 352#+end_src
380 353
381```conf 354#+begin_src conf
382server { 355server {
383 listen 80; 356 listen 80;
384 server_name git.example.com; 357 server_name git.example.com;
@@ -412,67 +385,65 @@ server {
412 include /etc/letsencrypt/options-ssl-nginx.conf; 385 include /etc/letsencrypt/options-ssl-nginx.conf;
413 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 386 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
414} 387}
415``` 388#+end_src
416 389
417Once created, symlink it and restart the web server. 390Once created, symlink it and restart the web server.
418 391
419```sh 392#+begin_src sh
420sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/ 393sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
421sudo systemctl restart nginx.service 394sudo systemctl restart nginx.service
422``` 395#+end_src
423 396
424As we can see below, my site at `git.example.com` is 397As we can see below, my site at =git.example.com= is available and
425available and running: 398running:
426 399
427### Settings Up Git Details 400** Settings Up Git Details
401Once you have =cgit= running, you can add some small details, such as
402repository owners and descriptions by editing the following files within
403each repository.
428 404
429Once you have `cgit` running, you can add some small details, 405Alternatively, you can use the =cgitrc= file to edit these details if
430such as repository owners and descriptions by editing the following 406you only care to edit them for the purpose of seeing them on your
431files within each repository. 407website.
432 408
433Alternatively, you can use the `cgitrc` file to edit these 409The =description= file within the repository on your server will display
434details if you only care to edit them for the purpose of seeing them on 410the description online.
435your website.
436 411
437The `description` file within the repository on your server 412#+begin_src sh
438will display the description online.
439
440```sh
441cd /git/example.git 413cd /git/example.git
442nano description 414nano description
443``` 415#+end_src
444 416
445You can add a `[gitweb]` block to the `config` 417You can add a =[gitweb]= block to the =config= file in order to display
446file in order to display the owner of the repository. 418the owner of the repository.
447 419
448```sh 420#+begin_src sh
449cd /git/example.git 421cd /git/example.git
450nano config 422nano config
451``` 423#+end_src
452 424
453```conf 425#+begin_src conf
454[gitweb] 426[gitweb]
455 owner = "YourName" 427 owner = "YourName"
456``` 428#+end_src
457 429
458Note that you can ignore the configuration within each repository and 430Note that you can ignore the configuration within each repository and
459simply set up this information in the `cgitrc` file, if you 431simply set up this information in the =cgitrc= file, if you want to do
460want to do it that way. 432it that way.
461
462### Editing `cgit`
463 433
464In order to edit certain items within `cgit`, you need to 434** Editing =cgit=
465edit the `cgitrc` file. 435In order to edit certain items within =cgit=, you need to edit the
436=cgitrc= file.
466 437
467```sh 438#+begin_src sh
468nano ~/cgit/cgitrc 439nano ~/cgit/cgitrc
469``` 440#+end_src
470 441
471Below is an example configuration for `cgitrc`. You can find 442Below is an example configuration for =cgitrc=. You can find all the
472all the configuration options within the [configuration manual] 443configuration options within the [configuration manual]
473(<https://git.zx2c4.com/cgit/plain/cgitrc.5.txt>). 444([[https://git.zx2c4.com/cgit/plain/cgitrc.5.txt]]).
474 445
475```conf 446#+begin_src conf
476css=/cgit.css 447css=/cgit.css
477logo=/logo.png 448logo=/logo.png
478favicon=/favicon.png 449favicon=/favicon.png
@@ -544,111 +515,103 @@ repo.path=/git/test.git
544repo.readme=:README.md 515repo.readme=:README.md
545repo.owner=John Doe 516repo.owner=John Doe
546repo.desc=An example repository! 517repo.desc=An example repository!
547``` 518#+end_src
548
549### Final Fixes: Syntax Highlighting & README Rendering
550 519
520** Final Fixes: Syntax Highlighting & README Rendering
551After completing my initial install and playing around with it for a few 521After completing my initial install and playing around with it for a few
552days, I noticed two issues: 522days, I noticed two issues:
553 523
5541. Syntax highlighting did not work when viewing the source code within 5241. Syntax highlighting did not work when viewing the source code within
555 a file. 525 a file.
5562. The `about` tab within a repository was not rendered to 5262. The =about= tab within a repository was not rendered to HTML.
557 HTML.
558 527
559The following process fixes these issues. To start, let's go to the 528The following process fixes these issues. To start, let's go to the
560`cgit` directory where we were editing our configuration file 529=cgit= directory where we were editing our configuration file earlier.
561earlier.
562 530
563```sh 531#+begin_src sh
564cd ~/cgit 532cd ~/cgit
565``` 533#+end_src
566 534
567In here, create two folders that will hold our syntax files: 535In here, create two folders that will hold our syntax files:
568 536
569```sh 537#+begin_src sh
570mkdir filters && mkdir filters/html-converters && cd filters 538mkdir filters && mkdir filters/html-converters && cd filters
571``` 539#+end_src
572 540
573Next, download the default filters: 541Next, download the default filters:
574 542
575```sh 543#+begin_src sh
576curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh 544curl https://git.zx2c4.com/cgit/plain/filters/about-formatting.sh > about-formatting.sh
577chmod 755 about-formatting.sh 545chmod 755 about-formatting.sh
578curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py 546curl https://git.zx2c4.com/cgit/plain/filters/syntax-highlighting.py > syntax-highlighting.py
579chmod 755 syntax-highlighting.py 547chmod 755 syntax-highlighting.py
580``` 548#+end_src
581 549
582Finally, download the HTML conversion files you need. The example below 550Finally, download the HTML conversion files you need. The example below
583downloads the Markdown converter: 551downloads the Markdown converter:
584 552
585```sh 553#+begin_src sh
586cd html-converters 554cd html-converters
587curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html 555curl https://git.zx2c4.com/cgit/plain/filters/html-converters/md2html > md2html
588chmod 755 md2html 556chmod 755 md2html
589``` 557#+end_src
590 558
591If you need other filters or html-converters found within [the cgit 559If you need other filters or html-converters found within
592project files](https://git.zx2c4.com/cgit/tree/filters), repeat the 560[[https://git.zx2c4.com/cgit/tree/filters][the cgit project files]],
593`curl` and `chmod` process above for whichever 561repeat the =curl= and =chmod= process above for whichever files you
594files you need. 562need.
595 563
596However, formatting will not work quite yet since the Docker cgit 564However, formatting will not work quite yet since the Docker cgit
597container we're using doesn't have the formatting package installed. 565container we're using doesn't have the formatting package installed. You
598You can install this easily by install Python 3+ and the 566can install this easily by install Python 3+ and the =pygments= package:
599`pygments` package:
600 567
601```sh 568#+begin_src sh
602# Enter the container's command line 569# Enter the container's command line
603sudo docker exec -it cgit bash 570sudo docker exec -it cgit bash
604``` 571#+end_src
605 572
606```sh 573#+begin_src sh
607# Install the necessary packages and then exit 574# Install the necessary packages and then exit
608yum update -y && \ 575yum update -y && \
609yum upgrade -y && \ 576yum upgrade -y && \
610yum install python3 python3-pip -y && \ 577yum install python3 python3-pip -y && \
611pip3 install markdown pygments && \ 578pip3 install markdown pygments && \
612exit 579exit
613``` 580#+end_src
614 581
615**You will need to enter the cgit docker container and re-run these 582*You will need to enter the cgit docker container and re-run these =yum=
616`yum` commands every time you kill and restart the 583commands every time you kill and restart the container!*
617container!**
618 584
619If not done already, we need to add the following variables to our 585If not done already, we need to add the following variables to our
620`cgitrc` file in order for `cgit` to know where 586=cgitrc= file in order for =cgit= to know where our filtering files are:
621our filtering files are:
622 587
623```conf 588#+begin_src conf
624# Highlight source code with python pygments-based highlighter 589# Highlight source code with python pygments-based highlighter
625source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py 590source-filter=/var/www/htdocs/cgit/filters/syntax-highlighting.py
626 591
627# Format markdown, restructuredtext, manpages, text files, and html files 592# Format markdown, restructuredtext, manpages, text files, and html files
628# through the right converters 593# through the right converters
629about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh 594about-filter=/var/www/htdocs/cgit/filters/about-formatting.sh
630``` 595#+end_src
631 596
632Now you should see that syntax highlighting and README rendering to the 597Now you should see that syntax highlighting and README rendering to the
633`about` tab is fixed. 598=about= tab is fixed.
634
635### Theming
636 599
600** Theming
637I won't go into much detail in this section, but you can fully theme 601I won't go into much detail in this section, but you can fully theme
638your installation of `cgit` since you have access to the 602your installation of =cgit= since you have access to the =cgit.css= file
639`cgit.css` file in your web root. This is another file you 603in your web root. This is another file you can add as a volume to the
640can add as a volume to the `docker-compose.yml` file if you 604=docker-compose.yml= file if you want to edit this without entering the
641want to edit this without entering the container's command line. 605container's command line.
642
643## :warning: Remember to Back Up Your Data!
644 606
607*** :warning: Remember to Back Up Your Data!
645The last thing to note is that running services on your own equipment 608The last thing to note is that running services on your own equipment
646means that you're assuming a level of risk that exists regarding data 609means that you're assuming a level of risk that exists regarding data
647loss, catastrophes, etc. In order to reduce the impact of any such 610loss, catastrophes, etc. In order to reduce the impact of any such
648occurrence, I suggest backing up your data regularly. 611occurrence, I suggest backing up your data regularly.
649 612
650Backups can be automated via `cron`, by hooking your base 613Backups can be automated via =cron=, by hooking your base directory up
651directory up to a cloud provider, or even setting up hooks to push all 614to a cloud provider, or even setting up hooks to push all repository
652repository info to git mirrors on other git hosts. Whatever the method, 615info to git mirrors on other git hosts. Whatever the method, make sure
653make sure that your data doesn't vanish in the event that your drives 616that your data doesn't vanish in the event that your drives or servers
654or servers fail. 617fail.
content/blog/2022-07-14-gnupg.md → blog/gnupg/index.org renamed +97 −113
@@ -1,30 +1,27 @@
1+++ 1#+title: GNU Privacy Guard (GPG)
2date = 2022-07-14 2#+date: 2022-07-14
3title = "GNU Privacy Guard (GPG)" 3#+description: Learn how to create a PGP key with GNU Privacy Guard (GPG).
4description = "Learn how to create a PGP key with GNU Privacy Guard (GPG)." 4#+filetags: :privacy:
5+++
6 5
7## The History of GPG 6* The History of GPG
8 7[[https://gnupg.org/][GNU Privacy Guard]], also known as GnuPG and GPG,
9[GNU Privacy Guard](https://gnupg.org/), also known as GnuPG and GPG, is 8is a free ("free" as in both speech and beer) software that fully
10a free ("free" as in both speech and beer) software that fully 9implements the OpenPGP Message Format documented in
11implements the OpenPGP Message Format documented in [RFC 10[[https://www.rfc-editor.org/rfc/rfc4880][RFC 4880]].
124880](https://www.rfc-editor.org/rfc/rfc4880).
13 11
14I won't go in-depth on the full history of the software in this post, 12I won't go in-depth on the full history of the software in this post,
15but it is important to understand that GPG is not the same as PGP 13but it is important to understand that GPG is not the same as PGP
16(Pretty Good Privacy), which is a different implementation of RFC 4880. 14(Pretty Good Privacy), which is a different implementation of RFC 4880.
17However, GPG was designed to interoperate with PGP. 15However, GPG was designed to interoperate with PGP.
18 16
19GPG was originally developed in the late 1990s by [Werner 17GPG was originally developed in the late 1990s by
20Koch](https://en.wikipedia.org/wiki/Werner_Koch) and has historically 18[[https://en.wikipedia.org/wiki/Werner_Koch][Werner Koch]] and has
21been funded generously by the German government. 19historically been funded generously by the German government.
22
23Now that we have all the high-level info out of the way, let's dive
24into the different aspects of GPG and its uses.
25 20
26## Encryption Algorithms 21Now that we have all the high-level info out of the way, let's dive into
22the different aspects of GPG and its uses.
27 23
24* Encryption Algorithms
28GPG supports a wide range of different encryption algorithms, including 25GPG supports a wide range of different encryption algorithms, including
29public-key, cipher, hash, and compression algorithms. The support for 26public-key, cipher, hash, and compression algorithms. The support for
30these algorithms has grown since the adoption of the Libgcrypt library 27these algorithms has grown since the adoption of the Libgcrypt library
@@ -34,7 +31,7 @@ As you will be able to see below in an example of a full key generation
34with the GPG command line tool, GPG recommends the following algorithms 31with the GPG command line tool, GPG recommends the following algorithms
35to new users: 32to new users:
36 33
37```sh 34#+begin_src sh
38Please select what kind of key you want: 35Please select what kind of key you want:
39 (1) RSA and RSA 36 (1) RSA and RSA
40 (2) DSA and Elgamal 37 (2) DSA and Elgamal
@@ -42,72 +39,66 @@ Please select what kind of key you want:
42 (4) RSA (sign only) 39 (4) RSA (sign only)
43 (9) ECC (sign and encrypt) *default* 40 (9) ECC (sign and encrypt) *default*
44 (10) ECC (sign only) 41 (10) ECC (sign only)
45``` 42#+end_src
46 43
47I am not doing an in-depth explanation here in order to keep the focus 44I am not doing an in-depth explanation here in order to keep the focus
48on GPG and not encryption algorithms. If you want a deep dive into 45on GPG and not encryption algorithms. If you want a deep dive into
49cryptography or encryption algorithms, please read my other posts: 46cryptography or encryption algorithms, please read my other posts:
50 47
51- [AES Encryption](/blog/aes-encryption/) (2018) 48- [[../aes-encryption/][AES Encryption]] (2018)
52- [Cryptography Basics](/blog/cryptography-basics/) (2020) 49- [[../cryptography-basics/][Cryptography Basics]] (2020)
53
54### Vulnerabilities
55 50
51** Vulnerabilities
56As of 2022-07-14, there are a few different vulnerabilities associated 52As of 2022-07-14, there are a few different vulnerabilities associated
57with GPG or the libraries it uses: 53with GPG or the libraries it uses:
58 54
59- GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one 55- GPG versions 1.0.2--1.2.3 contains a bug where "as soon as one
60 (GPG-generated) ElGamal signature of an arbitrary message is 56 (GPG-generated) ElGamal signature of an arbitrary message is released,
61 released, one can recover the signer's private key in less than a 57 one can recover the signer's private key in less than a second on a
62 second on a PC." 58 PC." ([[https://www.di.ens.fr/~pnguyen/pub_Ng04.htm][Source]])
63 ([Source](https://www.di.ens.fr/~pnguyen/pub_Ng04.htm)) 59- GPG versions prior to 1.4.2.1 contain a false positive signature
64- GPG versions prior to 1.4.2.1 contain a false positive signature 60 verification bug.
65 verification bug. 61 ([[https://lists.gnupg.%20org/pipermail/gnupg-announce/2006q1/000211.html][Source]])
66 ([Source](https://lists.gnupg.%20org/pipermail/gnupg-announce/2006q1/000211.html)) 62- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned
67- GPG versions prior to 1.4.2.2 cannot detect injection of unsigned 63 data. (
68 data. ( 64 [[https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html][Source]])
69 [Source](https://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000218.html)) 65- Libgcrypt, a library used by GPG, contained a bug which enabled full
70- Libgcrypt, a library used by GPG, contained a bug which enabled full 66 key recovery for RSA-1024 and some RSA-2048 keys. This was resolved in
71 key recovery for RSA-1024 and some RSA-2048 keys. This was resolved 67 a GPG update in 2017. ([[https://lwn.net/Articles/727179/][Source]])
72 in a GPG update in 2017. 68- The [[https://en.wikipedia.org/wiki/ROCA_vulnerability][ROCA
73 ([Source](https://lwn.net/Articles/727179/)) 69 Vulnerability]] affects RSA keys generated by YubiKey 4 tokens.
74- The [ROCA 70 ([[https://crocs.fi.%20muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf][Source]])
75 Vulnerability](https://en.wikipedia.org/wiki/ROCA_vulnerability) 71- The [[https://en.wikipedia.org/wiki/SigSpoof][SigSpoof Attack]] allows
76 affects RSA keys generated by YubiKey 4 tokens. 72 an attacker to spoof digital signatures.
77 ([Source](https://crocs.fi.%20muni.cz/_media/public/papers/nemec_roca_ccs17_preprint.pdf)) 73 ([[https://arstechnica.%20com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/][Source]])
78- The [SigSpoof Attack](https://en.wikipedia.org/wiki/SigSpoof) allows 74- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer
79 an attacker to spoof digital signatures. 75 overflow, fixed in Libgcrypt 1.9.1
80 ([Source](https://arstechnica.%20com/information-technology/2018/06/decades-old-pgp-bug-allowed-hackers-to-spoof-just-about-anyones-signature/)) 76 ([[https://web.archive.%20org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/][Source]])
81- Libgcrypt 1.9.0 contains a severe flaw related to a heap buffer 77
82 overflow, fixed in Libgcrypt 1.9.1 78*** Platforms
83 ([Source](https://web.archive.%20org/web/20210221012505/https://www.theregister.com/2021/01/29/severe_libgcrypt_bug/)) 79Originally developed as a command-line program for *nix systems, GPG now
84 80has a wealth of front-end applications and libraries available for
85## Platforms
86
87Originally developed as a command-line program for *nix systems, GPG
88now has a wealth of front-end applications and libraries available for
89end-users. However, the most recommended programs remain the same: 81end-users. However, the most recommended programs remain the same:
90 82
91- [GnuPG](https://gnupg.org) for Linux (depending on distro) 83- [[https://gnupg.org][GnuPG]] for Linux (depending on distro)
92- [Gpg4win](https://gpg4win.org) for Windows 84- [[https://gpg4win.org][Gpg4win]] for Windows
93- [GPGTools](https://gpgtools.org) for macOS 85- [[https://gpgtools.org][GPGTools]] for macOS
94
95## Creating a Key Pair
96 86
87* Creating a Key Pair
97In order to create a GPG key pair, a user would first need to install 88In order to create a GPG key pair, a user would first need to install
98GPG on their system. If we're assuming that the user is on Fedora 89GPG on their system. If we're assuming that the user is on Fedora Linux,
99Linux, they would execute the following: 90they would execute the following:
100 91
101```sh 92#+begin_src sh
102sudo dnf install gpg 93sudo dnf install gpg
103``` 94#+end_src
104 95
105Once installed, a user can create a new key pair with the following 96Once installed, a user can create a new key pair with the following
106command(s): 97command(s):
107 98
108```sh 99#+begin_src sh
109gpg --full-generate-key 100gpg --full-generate-key
110``` 101#+end_src
111 102
112GPG will walk the user through an interactive setup that asks for an 103GPG will walk the user through an interactive setup that asks for an
113algorithm preference, expiration date, name, and email to associate with 104algorithm preference, expiration date, name, and email to associate with
@@ -116,7 +107,7 @@ this key.
116See the following example key set-up for a default key generation using 107See the following example key set-up for a default key generation using
117the GnuPG command-line interface: 108the GnuPG command-line interface:
118 109
119```sh 110#+begin_src sh
120gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc. 111gpg (GnuPG) 2.3.6; Copyright (C) 2021 Free Software Foundation, Inc.
121This is free software: you are free to change and redistribute it. 112This is free software: you are free to change and redistribute it.
122There is NO WARRANTY, to the extent permitted by law. 113There is NO WARRANTY, to the extent permitted by law.
@@ -168,20 +159,19 @@ pub ed25519 2022-07-14 [SC]
168 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C 159 E955B7700FFC11EF51C2BA1FE096AACDD4C32E9C
169uid John Doe (test key) <johndoe@example.com> 160uid John Doe (test key) <johndoe@example.com>
170sub cv25519 2022-07-14 [E] 161sub cv25519 2022-07-14 [E]
171``` 162#+end_src
172 163
173Please note that GUI apps may differ slightly from the GPG command-line 164Please note that GUI apps may differ slightly from the GPG command-line
174interface. 165interface.
175 166
176## Common Usage 167* Common Usage
177
178As noted in RFC 4880, the general functions of OpenPGP are as follows: 168As noted in RFC 4880, the general functions of OpenPGP are as follows:
179 169
180- digital signatures 170- digital signatures
181- encryption 171- encryption
182- compression 172- compression
183- Radix-64 conversion 173- Radix-64 conversion
184- key management and certificate services 174- key management and certificate services
185 175
186From this, you can probably gather that the main use of GPG is for 176From this, you can probably gather that the main use of GPG is for
187encrypting data and/or signing the data with a key. The purpose of 177encrypting data and/or signing the data with a key. The purpose of
@@ -190,8 +180,7 @@ recipient(s) can access the data.
190 180
191Let's explore some specific GPG use-cases. 181Let's explore some specific GPG use-cases.
192 182
193### Email 183** Email
194
195One of the more popular uses of GPG is to sign and/or encrypt emails. 184One of the more popular uses of GPG is to sign and/or encrypt emails.
196With the use of a GPG keypair, you can encrypt a message, its subject, 185With the use of a GPG keypair, you can encrypt a message, its subject,
197and even the attachments within. 186and even the attachments within.
@@ -206,8 +195,8 @@ The second process, regarding the actual encryption of the message and
206its contents, works by using a combination of the sender's keys and the 195its contents, works by using a combination of the sender's keys and the
207recipient's keys. This process may vary slightly by implementation, but 196recipient's keys. This process may vary slightly by implementation, but
208it most commonly uses asymmetric cryptography, also known as public-key 197it most commonly uses asymmetric cryptography, also known as public-key
209cryptography. In this version of encryption, the sender's private key 198cryptography. In this version of encryption, the sender's private key to
210to sign the message and a combination of the sender's keys and the 199sign the message and a combination of the sender's keys and the
211recipient's public key to encrypt the message. 200recipient's public key to encrypt the message.
212 201
213If two people each have their own private keys and exchange their public 202If two people each have their own private keys and exchange their public
@@ -219,61 +208,57 @@ Implementation of email encryption varies greatly between email clients,
219so you will need to reference your email client's documentation to 208so you will need to reference your email client's documentation to
220ensure you are setting it up correctly for that specific client. 209ensure you are setting it up correctly for that specific client.
221 210
222### File Encryption 211** File Encryption
223
224As noted in the section above regarding emails, GPG enables users to be 212As noted in the section above regarding emails, GPG enables users to be
225able to send a message to each other if they are both set-up with GPG 213able to send a message to each other if they are both set-up with GPG
226keys. In this example, I am going to show how a user could send a file 214keys. In this example, I am going to show how a user could send a file
227called `example_file.txt` to another user via the 215called =example_file.txt= to another user via the recipient's email.
228recipient's email.
229 216
230The sender would find the file they want to send and execute the 217The sender would find the file they want to send and execute the
231following command: 218following command:
232 219
233```sh 220#+begin_src sh
234gpg --encrypt --output example_file.txt.gpg --recipient \ 221gpg --encrypt --output example_file.txt.gpg --recipient \
235recipient@example.com example_file.txt 222recipient@example.com example_file.txt
236``` 223#+end_src
237 224
238Once received, the recipient can decrypt the file with the following 225Once received, the recipient can decrypt the file with the following
239command: 226command:
240 227
241```sh 228#+begin_src sh
242gpg --decrypt --output example_file.txt example_file.txt.gpg 229gpg --decrypt --output example_file.txt example_file.txt.gpg
243``` 230#+end_src
244
245### Ownership Signatures
246 231
232** Ownership Signatures
247One important aspect of GPG, especially for developers, is the ability 233One important aspect of GPG, especially for developers, is the ability
248to sign data without encrypting it. For example, developers often sign 234to sign data without encrypting it. For example, developers often sign
249code changes when they commit the changes back to a central repository, 235code changes when they commit the changes back to a central repository,
250in order to display ownership of who made the changes. This allows other 236in order to display ownership of who made the changes. This allows other
251users to look at a code change and determine that the change was valid. 237users to look at a code change and determine that the change was valid.
252 238
253In order to do this using [Git](https://git-scm.com), the developer 239In order to do this using [[https://git-scm.com][Git]], the developer
254simply needs to alter the `git commit` command to include the 240simply needs to alter the =git commit= command to include the =-S= flag.
255`-S` flag. Here's an example: 241Here's an example:
256 242
257```sh 243#+begin_src sh
258git commit -S -m "my commit message" 244git commit -S -m "my commit message"
259``` 245#+end_src
260 246
261As an expansion of the example above, Git users can configure their 247As an expansion of the example above, Git users can configure their
262environment with a default key to use by adding their GPG signature: 248environment with a default key to use by adding their GPG signature:
263 249
264```sh 250#+begin_src sh
265git config --global user.signingkey XXXXXXXXXXXXXXXX 251git config --global user.signingkey XXXXXXXXXXXXXXXX
266``` 252#+end_src
267 253
268If you're not sure what your signature is, you can find it titled 254If you're not sure what your signature is, you can find it titled =sig=
269`sig` in the output of this command: 255in the output of this command:
270 256
271```sh 257#+begin_src sh
272gpg --list-signatures 258gpg --list-signatures
273``` 259#+end_src
274
275### File Integrity
276 260
261** File Integrity
277When a person generates a signature for data, they are allowing users 262When a person generates a signature for data, they are allowing users
278the ability to verify the signature on that data in the future to ensure 263the ability to verify the signature on that data in the future to ensure
279the data has not been corrupted. This is most common with software 264the data has not been corrupted. This is most common with software
@@ -283,24 +268,23 @@ replaced with dangerous software.
283 268
284In order to verify signed data, a user needs to have: 269In order to verify signed data, a user needs to have:
285 270
2861. The signed data 2711. The signed data
2872. A signature file 2722. A signature file
2883. The public GPG key of the signer 2733. The public GPG key of the signer
289 274
290Once the signer's public key is imported on the user's system, and 275Once the signer's public key is imported on the user's system, and they
291they have the data and signature, they can verify the data with the 276have the data and signature, they can verify the data with the following
292following commands: 277commands:
293 278
294```sh 279#+begin_src sh
295# If the signature is attached to the data 280# If the signature is attached to the data
296gpg --verify [signature-file] 281gpg --verify [signature-file]
297 282
298# If the signature is detached as a separate file from the data 283# If the signature is detached as a separate file from the data
299gpg --verify [signature-file] [original-file] 284gpg --verify [signature-file] [original-file]
300``` 285#+end_src
301
302## Finding Public Keys
303 286
287*** Finding Public Keys
304In order to use GPG with others, a user needs to know the other user(s) 288In order to use GPG with others, a user needs to know the other user(s)
305keys. This is easy to do if the user knows the other user(s) in person, 289keys. This is easy to do if the user knows the other user(s) in person,
306but may be hard if the relationship is strictly digital. Luckily, there 290but may be hard if the relationship is strictly digital. Luckily, there
@@ -309,5 +293,5 @@ social pages if they have them.
309 293
310Otherwise, the best option is to use a keyserver, such as: 294Otherwise, the best option is to use a keyserver, such as:
311 295
312- [pgp.mit.edu](https://pgp.mit.edu) 296- [[https://pgp.mit.edu][pgp.mit.edu]]
313- [keys.openpgp.org](https://keys.openpgp.org) 297- [[https://keys.openpgp.org][keys.openpgp.org]]
content/blog/2023-06-08-goaccess-geoip.md → blog/goaccess-geoip/index.org renamed +27 −32
@@ -1,48 +1,44 @@
1+++ 1#+title: Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data
2date = 2023-06-08 2#+date: 2023-06-08
3title = "Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data" 3#+description: Learn how to use GoAccess and MaxMind to evaluate visitors to your web server.
4description = "Learn how to use GoAccess and MaxMind to evaluate visitors to your web server." 4#+filetags: :sysadmin:
5+++ 5
6 6* Overview
7## Overview 7[[https://goaccess.io/][GoAccess]] is an open source real-time web log
8 8analyzer and interactive viewer that runs in a terminal in *nix systems
9[GoAccess](https://goaccess.io/) is an open source real-time web log
10analyzer and interactive viewer that runs in a terminal in \*nix systems
11or through your browser. 9or through your browser.
12 10
13## Installation 11* Installation
14 12To start, you'll need to install GoAccess for your OS. Here's an example
15To start, you'll need to install GoAccess for your OS. Here's an 13for Debian-based distros:
16example for Debian-based distros:
17 14
18```sh 15#+begin_src sh
19sudo apt install goaccess 16sudo apt install goaccess
20``` 17#+end_src
21 18
22Next, find any number of the MaxMind GeoIP database files on GitHub or 19Next, find any number of the MaxMind GeoIP database files on GitHub or
23another file hosting website. We're going to use P3TERX's version in 20another file hosting website. We're going to use P3TERX's version in
24this example: 21this example:
25 22
26```sh 23#+begin_src sh
27wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb 24wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb
28``` 25#+end_src
29 26
30Be sure to save this file in an easy to remember location! 27Be sure to save this file in an easy to remember location!
31 28
32## Usage 29* Usage
33
34In order to utilize the full capabilities of GoAccess and MMDB, start 30In order to utilize the full capabilities of GoAccess and MMDB, start
35with the command template below and customize as necessary. This will 31with the command template below and customize as necessary. This will
36export an HTML view of the GoAccess dashboard, showing all relevant 32export an HTML view of the GoAccess dashboard, showing all relevant
37information related to that site's access log. You can also omit the 33information related to that site's access log. You can also omit the
38`-o output.html` parameter if you prefer to view the data 34=-o output.html= parameter if you prefer to view the data within the CLI
39within the CLI instead of creating an HTML file. 35instead of creating an HTML file.
40 36
41With the addition of the GeoIP Database parameter, section 37With the addition of the GeoIP Database parameter, section
42`16 - Geo Location` will be added with the various countries 38=16 - Geo Location= will be added with the various countries that are
43that are associated with the collected IP addresses. 39associated with the collected IP addresses.
44 40
45```sh 41#+begin_src sh
46zcat /var/log/nginx/example.access.log.*.gz | goaccess \ 42zcat /var/log/nginx/example.access.log.*.gz | goaccess \
47--geoip-database=/home/user/GeoLite2-City.mmdb \ 43--geoip-database=/home/user/GeoLite2-City.mmdb \
48--date-format=%d/%b/%Y \ 44--date-format=%d/%b/%Y \
@@ -50,20 +46,19 @@ zcat /var/log/nginx/example.access.log.*.gz | goaccess \
50--log-format=COMBINED \ 46--log-format=COMBINED \
51-o output.html \ 47-o output.html \
52/var/log/nginx/example.access.log - 48/var/log/nginx/example.access.log -
53``` 49#+end_src
54
55### Example Output
56 50
51** Example Output
57See below for an example of the HTML output: 52See below for an example of the HTML output:
58 53
59![GoAccess 54#+caption: GoAccess HTML
60HTML](https://img.cleberg.net/blog/20230608-goaccess/goaccess-dashboard.png) 55[[https://img.cleberg.net/blog/20230608-goaccess/goaccess-dashboard.png]]
61 56
62You can also see the GeoIP card created by the integration of the 57You can also see the GeoIP card created by the integration of the
63MaxMind database information. 58MaxMind database information.
64 59
65![GoAccess 60#+caption: GoAccess GeoIP
66GeoIP](https://img.cleberg.net/blog/20230608-goaccess/goaccess-geoip.png) 61[[https://img.cleberg.net/blog/20230608-goaccess/goaccess-geoip.png]]
67 62
68That's all there is to it! Informational data is provided in an 63That's all there is to it! Informational data is provided in an
69organized fashion with minimal effort. 64organized fashion with minimal effort.
blog/graphene-os/index.org added +154
@@ -0,0 +1,154 @@
1#+title: Installing Graphene OS on the Pixel 6 Pro
2#+date: 2022-09-21
3#+description: A retrospective on the successful command-line installation of Graphene OS on a Pixel 6 Pro.
4#+filetags: :privacy:
5
6* Introduction
7After using iOS for a couple of years, I finally took the plunge and
8purchased a Pixel 6 Pro in order to test and use [GrapheneOS]
9([[https://grapheneos.org]]).
10
11The installation process was rather quick once you have the tools and
12files you need. Overall, it can be done in just a few minutes.
13
14* Gathering Tools & Files
15** Android Tools
16First, in order to interact with the device, we will need the
17[[https://developer.android.com/studio/releases/platform-tools.html][Android
18platform tools]]. Find the Linux download and save the ZIP folder to
19your preferred location.
20
21Once we've downloaded the files, we will need to unzip them, enter the
22directory, and move the necessary executables to a central location,
23such as =/usr/bin/=. For this installation, we only need the =fastboot=
24and =adb= executables.
25
26#+begin_src sh
27cd ~/Downloads
28#+end_src
29
30#+begin_src sh
31unzip platform-tools_r33.0.3-linux.zip
32cd platform-tools
33sudo mv fastboot /usr/bin/
34sudo mv adb /usr/bin
35#+end_src
36
37** GrapheneOS Files
38Next, we need the [[https://grapheneos.org/releases][GrapheneOS files]]
39for our device and model. For example, the Pixel 6 Pro is codenamed
40=raven= on the release page.
41
42Once we have the links, let's download them to our working directory:
43
44#+begin_src sh
45curl -O https://releases.grapheneos.org/factory.pub
46curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
47curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
48#+end_src
49
501. Validate Integrity
51
52 In order to validate the integrity of the downloaded files, we will
53 need the =signify= package and Graphene's =factory.pub= file.
54
55 #+begin_src sh
56 sudo dnf install signify
57 #+end_src
58
59 #+begin_src sh
60 curl -O https://releases.grapheneos.org/factory.pub
61 #+end_src
62
63 Then we can validate the files and ensure that no data was corrupted
64 or modified before it was saved to our device.
65
66 #+begin_src sh
67 signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
68 #+end_src
69
702. Unzip Files
71
72 Once the files are verified, we can unzip the Graphene image and
73 enter the directory:
74
75 #+begin_src sh
76 unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
77 #+end_src
78
79* Installation Process
80** Enable Developer Debugging & OEM Unlock
81Before we can actually flash anything to the phone, we will need to
82enable OEM Unlocking, as well as either USB Debugging or Wireless
83Debugging, depending on which method we will be using.
84
85To start, enable developer mode by going to =Settings= > =About= and
86tapping =Build Number= seven (7) times. You may need to enter your PIN
87to enable this mode.
88
89Once developer mode is enabled, go to =Settings= > =System= >
90=Devloper Options= and enable OEM Unlocking, as well as USB or Wireless
91Debugging. In my case, I chose USB Debugging and performed all actions
92via USB cable.
93
94Once these options are enabled, plug the phone into the computer and
95execute the following command:
96
97#+begin_src sh
98adb devices
99#+end_src
100
101If an unauthorized error occurs, make sure the USB mode on the phone is
102changed from charging to something like "File Transfer" or "PTP." You
103can find the USB mode in the notification tray.
104
105** Reboot Device
106Once we have found the device via =adb=, we can either boot into the
107bootloader interface by holding the volume down button while the phone
108reboots or by executing the following command:
109
110#+begin_src sh
111adb reboot bootloader
112#+end_src
113
114** Unlock the Bootloader
115The phone will reboot and load the bootloader screen upon startup. At
116this point, we are ready to start the actual flashing of GrapheneOS onto
117the device.
118
119*NOTE*: In my situation, I needed to use =sudo= with every =fastboot=
120command, but not with =adb= commands. I am not sure if this is standard
121or a Fedora quirk, but I'm documenting my commands verbatim in this
122post.
123
124First, we start by unlocking the bootloader so that we can load other
125ROMs:
126
127#+begin_src sh
128sudo fastboot flashing unlock
129#+end_src
130
131** Flashing Factory Images
132Once the phone is unlocked, we can flash it with the =flash-all.sh=
133script found inside the =raven-factory-2022091400= folder we entered
134earlier:
135
136#+begin_src sh
137sudo ./flash-all.sh
138#+end_src
139
140This process should take a few minutes and will print informational
141messages as things progress. Avoid doing anything on the phone while
142this process is operating.
143
144** Lock the Bootloader
145If everything was successful, the phone should reboot a few times and
146finally land back on the bootloader screen. At this point, we can
147re-lock the bootloader to enable full verified boot and protect the
148device from unwanted flashing or erasure of data.
149
150#+begin_src sh
151sudo fastboot flashing lock
152#+end_src
153
154Once done, the device will be wiped and ready for a fresh set-up!
content/blog/2020-09-25-happiness-map.md → blog/happiness-map/index.org renamed +60 −68
@@ -1,52 +1,48 @@
1+++ 1#+title: Data Visualization: World Choropleth Map of Happiness
2date = 2020-09-25 2#+date: 2020-09-25
3title = "Data Visualization: World Choropleth Map of Happiness" 3#+description: Exploring and visualizing data with Python.
4description = "Exploring and visualizing data with Python." 4#+filetags: :data:
5+++
6
7# Background Information
8 5
6* Background Information
9The dataset (obtained from 7The dataset (obtained from
10[Kaggle](https://www.kaggle.com/unsdsn/world-happiness)) used in this 8[[https://www.kaggle.com/unsdsn/world-happiness][Kaggle]]) used in this
11article contains a list of countries around the world, their happiness 9article contains a list of countries around the world, their happiness
12rankings and scores, as well as other national scoring measures. 10rankings and scores, as well as other national scoring measures.
13 11
14Fields include: 12Fields include:
15 13
16- Overall rank 14- Overall rank
17- Country or region 15- Country or region
18- GDP per capita 16- GDP per capita
19- Social support 17- Social support
20- Healthy life expectancy 18- Healthy life expectancy
21- Freedom to make life choices 19- Freedom to make life choices
22- Generosity 20- Generosity
23- Perceptions of corruption 21- Perceptions of corruption
24 22
25There are 156 records. Since there are \~195 countries in the world, we 23There are 156 records. Since there are ~195 countries in the world, we
26can see that around 40 countries will be missing from this dataset. 24can see that around 40 countries will be missing from this dataset.
27 25
28# Install Packages 26* Install Packages
29 27As always, run the =install= command for all packages needed to perform
30As always, run the `install` command for all packages needed 28analysis.
31to perform analysis.
32 29
33```python 30#+begin_src python
34!pip install folium geopandas matplotlib numpy pandas 31!pip install folium geopandas matplotlib numpy pandas
35``` 32#+end_src
36
37# Import the Data
38 33
34* Import the Data
39We only need a couple packages to create a choropleth map. We will use 35We only need a couple packages to create a choropleth map. We will use
40[Folium](https://python-visualization.github.io/folium/), which provides 36[[https://python-visualization.github.io/folium/][Folium]], which
41map visualizations in Python. We will also use geopandas and pandas to 37provides map visualizations in Python. We will also use geopandas and
42wrangle our data before we put it on a map. 38pandas to wrangle our data before we put it on a map.
43 39
44```python 40#+begin_src python
45# Import the necessary Python packages 41# Import the necessary Python packages
46import folium 42import folium
47import geopandas as gpd 43import geopandas as gpd
48import pandas as pd 44import pandas as pd
49``` 45#+end_src
50 46
51To get anything to show up on a map, we need a file that will specify 47To get anything to show up on a map, we need a file that will specify
52the boundaries of each country. Luckily, GeoJSON files exist (for free!) 48the boundaries of each country. Luckily, GeoJSON files exist (for free!)
@@ -57,38 +53,37 @@ GeoPandas will take this data and load it into a dataframe so that we
57can easily match it to the data we're trying to analyze. Let's look at 53can easily match it to the data we're trying to analyze. Let's look at
58the GeoJSON dataframe: 54the GeoJSON dataframe:
59 55
60```python 56#+begin_src python
61# Load the GeoJSON data with geopandas 57# Load the GeoJSON data with geopandas
62geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson') 58geo_data = gpd.read_file('https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson')
63geo_data.head() 59geo_data.head()
64``` 60#+end_src
65 61
66![GeoJSON 62#+caption: GeoJSON Dataframe
67Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/geojson_df.png) 63[[https://img.cleberg.net/blog/20200925-world-choropleth-map/geojson_df.png]]
68 64
69Next, let's load the data from the Kaggle dataset. I've downloaded 65Next, let's load the data from the Kaggle dataset. I've downloaded this
70this file, so update the file path if you have it somewhere else. After 66file, so update the file path if you have it somewhere else. After
71loading, let's take a look at this dataframe: 67loading, let's take a look at this dataframe:
72 68
73```python 69#+begin_src python
74# Load the world happiness data with pandas 70# Load the world happiness data with pandas
75happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv') 71happy_data = pd.read_csv(r'~/Downloads/world_happiness_data_2019.csv')
76happy_data.head() 72happy_data.head()
77``` 73#+end_src
78
79![Happiness
80Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/happiness_df.png)
81 74
82# Clean the Data 75#+caption: Happiness Dataframe
76[[https://img.cleberg.net/blog/20200925-world-choropleth-map/happiness_df.png]]
83 77
78* Clean the Data
84Some countries need to be renamed, or they will be lost when you merge 79Some countries need to be renamed, or they will be lost when you merge
85the happiness and GeoJSON dataframes. This is something I discovered 80the happiness and GeoJSON dataframes. This is something I discovered
86when the map below showed empty countries. I searched both data frames 81when the map below showed empty countries. I searched both data frames
87for the missing countries to see the naming differences. Any countries 82for the missing countries to see the naming differences. Any countries
88that do not have records in the `happy_data` df will not show 83that do not have records in the =happy_data= df will not show up on the
89up on the map. 84map.
90 85
91```python 86#+begin_src python
92# Rename some countries to match our GeoJSON data 87# Rename some countries to match our GeoJSON data
93 88
94# Rename USA 89# Rename USA
@@ -106,38 +101,36 @@ happy_data.at[republic_congo_index, 'Country or region'] = 'Republic of Congo'
106# Rename the DRC 101# Rename the DRC
107democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)'] 102democratic_congo_index = happy_data.index[happy_data['Country or region'] == 'Congo (Kinshasa)']
108happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo' 103happy_data.at[democratic_congo_index, 'Country or region'] = 'Democratic Republic of the Congo'
109``` 104#+end_src
110
111# Merge the Data
112 105
106* Merge the Data
113Now that we have clean data, we need to merge the GeoJSON data with the 107Now that we have clean data, we need to merge the GeoJSON data with the
114happiness data. Since we've stored them both in dataframes, we just 108happiness data. Since we've stored them both in dataframes, we just need
115need to call the `.merge()` function. 109to call the =.merge()= function.
116 110
117We will also rename a couple columns, just so that they're a little 111We will also rename a couple columns, just so that they're a little
118easier to use when we create the map. 112easier to use when we create the map.
119 113
120```python 114#+begin_src python
121# Merge the two previous dataframes into a single geopandas dataframe 115# Merge the two previous dataframes into a single geopandas dataframe
122merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region') 116merged_df = geo_data.merge(happy_data,left_on='ADMIN', right_on='Country or region')
123 117
124# Rename columns for ease of use 118# Rename columns for ease of use
125merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'}) 119merged_df = merged_df.rename(columns = {'ADMIN':'GeoJSON_Country'})
126merged_df = merged_df.rename(columns = {'Country or region':'Country'}) 120merged_df = merged_df.rename(columns = {'Country or region':'Country'})
127``` 121#+end_src
128
129![Merged
130Dataframe](https://img.cleberg.net/blog/20200925-world-choropleth-map/merged_df.png)
131 122
132# Create the Map 123#+caption: Merged Dataframe
124[[https://img.cleberg.net/blog/20200925-world-choropleth-map/merged_df.png]]
133 125
126* Create the Map
134The data is finally ready to be added to a map. The code below shows the 127The data is finally ready to be added to a map. The code below shows the
135simplest way to find the center of the map and create a Folium map 128simplest way to find the center of the map and create a Folium map
136object. The important part is to remember to reference the merged 129object. The important part is to remember to reference the merged
137dataframe for our GeoJSON data and value data. The columns specify which 130dataframe for our GeoJSON data and value data. The columns specify which
138geo data and value data to use. 131geo data and value data to use.
139 132
140```python 133#+begin_src python
141# Assign centroids to map 134# Assign centroids to map
142x_map = merged_df.centroid.x.mean() 135x_map = merged_df.centroid.x.mean()
143y_map = merged_df.centroid.y.mean() 136y_map = merged_df.centroid.y.mean()
@@ -161,21 +154,20 @@ folium.Choropleth(
161 smooth_factor=0, 154 smooth_factor=0,
162 highlight=True 155 highlight=True
163).add_to(world_map) 156).add_to(world_map)
164``` 157#+end_src
165 158
166Let's look at the resulting map. 159Let's look at the resulting map.
167 160
168![Choropleth 161#+caption: Choropleth Map
169Map](https://img.cleberg.net/blog/20200925-world-choropleth-map/map.png) 162[[https://img.cleberg.net/blog/20200925-world-choropleth-map/map.png]]
170
171# Create a Tooltip on Hover
172 163
164* Create a Tooltip on Hover
173Now that we have a map set up, we could stop. However, I want to add a 165Now that we have a map set up, we could stop. However, I want to add a
174tooltip so that I can see more information about each country. The 166tooltip so that I can see more information about each country. The
175`tooltip_data` code below will show a popup on hover with all 167=tooltip_data= code below will show a popup on hover with all the data
176the data fields shown. 168fields shown.
177 169
178```python 170#+begin_src python
179 # Adding labels to map 171 # Adding labels to map
180 style_function = lambda x: {'fillColor': '#ffffff', 172 style_function = lambda x: {'fillColor': '#ffffff',
181 'color':'#000000', 173 'color':'#000000',
@@ -216,10 +208,10 @@ folium.LayerControl().add_to(world_map)
216 208
217# Display the map 209# Display the map
218world_map 210world_map
219``` 211#+end_src
220 212
221The final image below will show you what the tooltip looks like whenever 213The final image below will show you what the tooltip looks like whenever
222you hover over a country. 214you hover over a country.
223 215
224![Choropleth Map 216#+caption: Choropleth Map Tooltip
225Tooltip](https://img.cleberg.net/blog/20200925-world-choropleth-map/tooltip_map.png) 217[[https://img.cleberg.net/blog/20200925-world-choropleth-map/tooltip_map.png]]
content/blog/2020-05-03-homelab.md → blog/homelab/index.org renamed +55 −71
@@ -1,60 +1,55 @@
1+++ 1#+title: An Inside Look at My Homelab
2date = 2020-05-03 2#+date: 2020-05-03
3title = "An Inside Look at My Homelab" 3#+description: A retrospective on the first iteration of my home lab.
4description = "A retrospective on the first iteration of my home lab." 4#+filetags: :sysadmin:
5+++
6
7# What is a Homelab?
8 5
6* What is a Homelab?
9Starting as a developer, I have largely stayed away from hardware-based 7Starting as a developer, I have largely stayed away from hardware-based
10hobbies (other than building a gaming desktop). However, as the 8hobbies (other than building a gaming desktop). However, as the
11quarantine for COVID-19 stretches out further and further, I found 9quarantine for COVID-19 stretches out further and further, I found
12myself bored and in search of new hobbies. After spending the last few 10myself bored and in search of new hobbies. After spending the last few
13months browsing the [r/homelab](https://www.reddit.com/r/homelab/) 11months browsing the [[https://www.reddit.com/r/homelab/][r/homelab]]
14subreddit, I decided it was time to jump in and try things out for 12subreddit, I decided it was time to jump in and try things out for
15myself. 13myself.
16 14
17Since I am a beginner and just recently graduated from college, 15Since I am a beginner and just recently graduated from college,
18everything I've done so far in my homelab is fairly low-budget. 16everything I've done so far in my homelab is fairly low-budget.
19 17
20# Hardware 18* Hardware
21 19#+caption: HomeLab Diagram
22![HomeLab 20[[https://img.cleberg.net/blog/20200503-homelab/homelab-min.png]]
23Diagram](https://img.cleberg.net/blog/20200503-homelab/homelab-min.png)
24 21
25## Raspberry Pi 4 22*** Raspberry Pi 4
26 23Luckily, I had actually purchased a
27Luckily, I had actually purchased a [Raspberry Pi 24[[https://www.raspberrypi.org/products/raspberry-pi-4-model-b/][Raspberry
284](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) before 25Pi 4]] before the quarantine started so that I could try to keep Plex
29the quarantine started so that I could try to keep Plex Media Center 26Media Center running, even while my desktop computer was turned off. I
30running, even while my desktop computer was turned off. I started here, 27started here, using the Pi to hold Plex and Pi-hole until I grew tired
31using the Pi to hold Plex and Pi-hole until I grew tired with the slow 28with the slow performance.
32performance.
33 29
34Here are the specifications for the Pi 4: 30Here are the specifications for the Pi 4:
35 31
36- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz 32- Broadcom BCM2711, Quad core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5GHz
37- 4GB LPDDR4-3200 SDRAM 33- 4GB LPDDR4-3200 SDRAM
38- Gigabit Ethernet 34- Gigabit Ethernet
39- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode) 35- H.265 (4kp60 decode), H264 (1080p60 decode, 1080p30 encode)
40- 64 GB MicroSD Card 36- 64 GB MicroSD Card
41
42## Dell Optiplex 5040
43 37
38** Dell Optiplex 5040
44Since I wasn't happy with the Pi as my main server, I turned to 39Since I wasn't happy with the Pi as my main server, I turned to
45Craigslist. I know a lot of other homelabbers use Ebay, but I can't 40Craigslist. I know a lot of other homelabbers use Ebay, but I can't seem
46seem to ever trust it enough to purchase items on there. So I used 41to ever trust it enough to purchase items on there. So I used Craigslist
47Craigslist and found a Dell Optiplex 5040 desktop computer on sale for 42and found a Dell Optiplex 5040 desktop computer on sale for $90. While
48\$90. While this computer might be underpowered, it was one of the few 43this computer might be underpowered, it was one of the few computers
49computers under \$100 that was available during quarantine. 44under $100 that was available during quarantine.
50 45
51Here are the specifications for the Dell Optiplex 5040: 46Here are the specifications for the Dell Optiplex 5040:
52 47
53- Intel Core i3 6100 48- Intel Core i3 6100
54- 8GB RAM DDR3 49- 8GB RAM DDR3
55- Intel HD Graphics 50- Intel HD Graphics
56- Gigabit Ethernet 51- Gigabit Ethernet
57- 500GB Hard Drive 52- 500GB Hard Drive
58 53
59While this hardware would be awful for a work computer or a gaming rig, 54While this hardware would be awful for a work computer or a gaming rig,
60it turned out to be wonderful for my server purposes. The only 55it turned out to be wonderful for my server purposes. The only
@@ -63,36 +58,34 @@ power for a single 4k video transcode at a time. I haven't tested more
63than three 1080p streams at a time, but the maximum amount of streams 58than three 1080p streams at a time, but the maximum amount of streams
64I've ever actually used is two. 59I've ever actually used is two.
65 60
66## WD easystore 10TB & 8TB 61** WD easystore 10TB & 8TB
67
68Application storage and temporary files are stored on the internal hard 62Application storage and temporary files are stored on the internal hard
69drive of the server, but all media files (movies, tv, games, books, etc) 63drive of the server, but all media files (movies, tv, games, books, etc)
70are stored externally on my WD easystore hard drive. Creating auto-boot 64are stored externally on my WD easystore hard drive. Creating auto-boot
71configurations in the `/etc/fstab` file on my server allows 65configurations in the =/etc/fstab= file on my server allows the hard
72the hard drives to automatically mount whenever I need to restart my 66drives to automatically mount whenever I need to restart my server.
73server.
74 67
75> Update: In March 2022, I shucked the hard drives out of their external 68#+begin_quote
76> cases, put some Kapton tape on the third power pin to prevent power 69Update: In March 2022, I shucked the hard drives out of their external
77> shutdowns, and stuck them inside my server tower using internal SATA 70cases, put some Kapton tape on the third power pin to prevent power
78> cables. 71shutdowns, and stuck them inside my server tower using internal SATA
72cables.
79 73
80## Netgear Unmanaged Switch 74#+end_quote
81 75
76** Netgear Unmanaged Switch
82To manage all the ethernet cords used by my homelab, my desktop, and my 77To manage all the ethernet cords used by my homelab, my desktop, and my
83living room media center, I purchased an 8-port gigabit ethernet switch 78living room media center, I purchased an 8-port gigabit ethernet switch
84for \$50 at my local computer store. This is probably much more than I 79for $50 at my local computer store. This is probably much more than I
85should have spent on an unmanaged switch, but I am comfortable with the 80should have spent on an unmanaged switch, but I am comfortable with the
86choice. 81choice.
87 82
88## TP-Link Managed Switch 83** TP-Link Managed Switch
89
90Since I use the unmanaged switch to group all living room devices 84Since I use the unmanaged switch to group all living room devices
91together, I use the managed switch to configure VLANs and secure my 85together, I use the managed switch to configure VLANs and secure my
92network. 86network.
93 87
94## Arris TM1602A Modem & Sagecom Fast 5280 Router 88** Arris TM1602A Modem & Sagecom Fast 5280 Router
95
96My default modem and router, provided by my ISP, are fairly standard. 89My default modem and router, provided by my ISP, are fairly standard.
97The Arris modem supports DOCSIS 3.0, which is something that I 90The Arris modem supports DOCSIS 3.0, which is something that I
98definitely wanted as a minimum. The Sagecom router is also standard, no 91definitely wanted as a minimum. The Sagecom router is also standard, no
@@ -100,23 +93,19 @@ fancy bells or whistles. However, it does support DHCP and DHCPv6, which
100is something you can use to route all household traffic through a 93is something you can use to route all household traffic through a
101pi-hole or firewall. 94pi-hole or firewall.
102 95
103## TP-Link EAP 96** TP-Link EAP
104
105In order to gain better control over the network, I use my own wireless 97In order to gain better control over the network, I use my own wireless
106access point instead of the one included in the Sagecom router above. 98access point instead of the one included in the Sagecom router above.
107Now I can control and organize all of my ethernet connections through 99Now I can control and organize all of my ethernet connections through
108the VLANs on the managed switch and wireless connections through the 100the VLANs on the managed switch and wireless connections through the
109VLANS on the EAP. 101VLANS on the EAP.
110 102
111## Generic Printer 103** Generic Printer
112
113The last piece to my homelab is a standard wireless printer. Nothing 104The last piece to my homelab is a standard wireless printer. Nothing
114special here. 105special here.
115 106
116# Software 107* Software
117 108** Ubuntu Server 20.04
118## Ubuntu Server 20.04
119
120While the 20.04 version of Ubuntu was just released, I always like to 109While the 20.04 version of Ubuntu was just released, I always like to
121experiment with new features (and I don't mind breaking my system - it 110experiment with new features (and I don't mind breaking my system - it
122just gives me more experience learning how to fix things). So, I have 111just gives me more experience learning how to fix things). So, I have
@@ -124,31 +113,27 @@ Ubuntu Server 20.04 installed on the Dell Optiplex server and Ubuntu
124Server 19.10 installed on the Raspberry Pi. Once I find an acceptable 113Server 19.10 installed on the Raspberry Pi. Once I find an acceptable
125use for the Pi, I will most likely switch the operating system. 114use for the Pi, I will most likely switch the operating system.
126 115
127## Docker 116** Docker
128 117I am /very/ new to Docker, but I have had a lot of fun playing with it
129I am *very* new to Docker, but I have had a lot of fun playing with it
130so far. Docker is used to create containers that can hold all the 118so far. Docker is used to create containers that can hold all the
131contents of a system without interfering with other software on the same 119contents of a system without interfering with other software on the same
132system. So far, I have successfully installed pi-hole, GitLab, Gogs, and 120system. So far, I have successfully installed pi-hole, GitLab, Gogs, and
133Nextcloud in containers. However, I opted to delete all of those so that 121Nextcloud in containers. However, I opted to delete all of those so that
134I can reconfigure them more professionally at a later time. 122I can reconfigure them more professionally at a later time.
135 123
136## Plex Media Server 124** Plex Media Server
137
138Plex is a media center software that allows you to organize your movies, 125Plex is a media center software that allows you to organize your movies,
139TV shows, music, photos, and videos automatically. It will even download 126TV shows, music, photos, and videos automatically. It will even download
140metadata for you so that you can easily browse these collections. 127metadata for you so that you can easily browse these collections.
141 128
142## Pi-hole 129** Pi-hole
143
144Pi-hole is an alternative ad-blocker that runs at the DNS level, 130Pi-hole is an alternative ad-blocker that runs at the DNS level,
145allowing you to block traffic when it hits your network, so that you can 131allowing you to block traffic when it hits your network, so that you can
146reject any traffic you deem to be bad. Pi-hole uses blacklists and 132reject any traffic you deem to be bad. Pi-hole uses blacklists and
147whitelists to decide which traffic block and, luckily, there are a lot 133whitelists to decide which traffic block and, luckily, there are a lot
148of pre-made lists out there on Reddit, GitHub, etc. 134of pre-made lists out there on Reddit, GitHub, etc.
149 135
150## Nextcloud 136** Nextcloud
151
152While I had trouble with the Docker version of Nextcloud, I was very 137While I had trouble with the Docker version of Nextcloud, I was very
153successful when setting up the snap version. Using this, I was able to 138successful when setting up the snap version. Using this, I was able to
154map Nextcloud to a subdomain of a domain I own in Namecheap. 139map Nextcloud to a subdomain of a domain I own in Namecheap.
@@ -156,8 +141,7 @@ Additionally, Nextcloud has an integration with Let's Encrypt that
156allows me to issue certificates automatically to any new domain I 141allows me to issue certificates automatically to any new domain I
157authorize. 142authorize.
158 143
159## Webmin 144** Webmin
160
161To monitor my servers, and the processes running on them, I use the 145To monitor my servers, and the processes running on them, I use the
162Webmin dashboard. This was fairly painless to set up, and I currently 146Webmin dashboard. This was fairly painless to set up, and I currently
163access it straight through the server's IP address. In the future, I 147access it straight through the server's IP address. In the future, I
blog/index.org added +141
@@ -0,0 +1,141 @@
1#+title: Blog
2#+options: toc:nil
3
4Use =Ctrl + F= to search blog post titles for keywords.
5
6* TODO Create RSS Feed
7
8* 2024
9
10- 2024-02-26 [[./org-blog/][Blogging in Org-Mode]]
11- 2024-02-21 [[./self-hosting-otter-wiki/][Self-Hosting An Otter Wiki]]
12- 2024-02-13 [[./ubuntu-emergency-mode/][Stuck in Ubuntu's Emergency Mode? Try Fixing the Fstab File]]
13- 2024-02-06 [[./zfs/][How to Create a ZFS Pool on Ubuntu Linux]]
14- 2024-01-27 [[./tableau-dashboard/][Data Visualization: Mapping Omaha Crime Data with Tableau]]
15- 2024-01-26 [[./audit-dashboard/][Building an Audit Status Dashboard]]
16- 2024-01-13 [[./local-llm/][Running Local LLMs on macOS and iOS]]
17- 2024-01-09 [[./macos-customization/][Customizing macOS]]
18- 2024-01-08 [[./dont-say-hello/][Don't Say Hello]]
19
20* 2023
21
22- 2023-12-03 [[./unifi-nextdns/][How to Install NextDNS on the Unifi Dream Machine]]
23- 2023-11-08 [[./scli/][Installing scli on Alpine Linux (musl)]]
24- 2023-10-17 [[./self-hosting-anonymousoverflow/][Self-Hosting AnonymousOverflow]]
25- 2023-10-15 [[./alpine-ssh-hardening/][SSH Hardening for Alpine Linux]]
26- 2023-10-11 [[./self-hosting-authelia/][Self-Hosting Authelia]]
27- 2023-10-04 [[./digital-minimalism/][Digital Minimalism]]
28- 2023-09-19 [[./audit-sql-scripts/][Useful SQL Scripts for Auditing Logical Access]]
29- 2023-09-15 [[./self-hosting-gitweb/][Self-Hosting GitWeb via Nginx]]
30- 2023-08-18 [[./agile-auditing/][Agile Auditing: An Introduction]]
31- 2023-07-19 [[./plex-transcoder-errors/][How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.']]
32- 2023-07-12 [[./wireguard-lan/][Enable LAN Access in Mullvad Wireguard Conf Files]]
33- 2023-06-30 [[./self-hosting-voyager/][Self-Hosting Voyager - A Lemmy Web Client]]
34- 2023-06-28 [[./backblaze-b2/][Getting Started with Backblaze B2 Cloud Storage]]
35- 2023-06-23 [[./self-hosting-convos/][Self-Hosting Convos IRC Web Client]]
36- 2023-06-23 [[./byobu/][Byobu]]
37- 2023-06-20 [[./audit-review-template/][Audit Testing Review Template]]
38- 2023-06-18 [[./unifi-ip-blocklist/][Block IP Addresses and Subnets with Unifi Network Firewall]]
39- 2023-06-08 [[./goaccess-geoip/][Inspecting Nginx Logs with GoAccess and MaxMind GeoIP Data]]
40- 2023-06-08 [[./self-hosting-baikal/][Self-Hosting Baikal Server (CalDAV & CardDAV)]]
41- 2023-05-22 [[./burnout/][RE: Burnout]]
42- 2023-02-02 [[./exploring-hare/][Exploring the Hare Programming Language]]
43- 2023-01-28 [[./self-hosting-wger/][Self-Hosting Wger Workout Manager]]
44- 2023-01-23 [[./random-wireguard/][Connecting to a Random Mullvad Wireguard Host on Boot]]
45- 2023-01-21 [[./flatpak-symlinks/][Running Flatpak Apps with Symlinks]]
46- 2023-01-08 [[./fedora-login-manager/][How to Remove the Login Manager from Fedora i3]]
47- 2023-01-05 [[./mass-unlike-tumblr-posts/][How to Easily Mass Unlike Tumblr Posts with Javascript]]
48- 2023-01-03 [[./recent-website-changes/][Recent Website Changes]]
49
50* 2022
51
52- 2022-12-23 [[./alpine-desktop/][Alpine Linux as a Desktop OS]]
53- 2022-12-17 [[./st/][Simple Terminal]]
54- 2022-12-07 [[./nginx-wildcard-redirect/][Redirect Nginx Subdomains & Trailing Content with Regex]]
55- 2022-12-01 [[./nginx-compression/][Enable GZIP Compression in Nginx]]
56- 2022-11-29 [[./nginx-referrer-ban-list/][Creating a Referrer Ban List in Nginx]]
57- 2022-11-27 [[./server-build/][Building a Custom Rack-Mounted Server]]
58- 2022-11-11 [[./nginx-tmp-errors/][Fixing Permission Errors in /var/lib/nginx]]
59- 2022-10-30 [[./linux-display-manager/][How to Disable or Change the Display Manager on Void Linux]]
60- 2022-10-22 [[./alpine-linux/][Alpine Linux: My New Server OS]]
61- 2022-10-04 [[./syncthing/][Syncthing: A Minimal Self-Hosted Cloud Storage Solution]]
62- 2022-10-04 [[./mtp-linux/][How to Mount an MTP Mobile Device on Fedora Linux]]
63- 2022-09-21 [[./graphene-os/][Installing Graphene OS on the Pixel 6 Pro]]
64- 2022-09-17 [[./serenity-os/][Serenity OS: Testing Out a Unique System]]
65- 2022-08-31 [[./privacy-com-changes/][Concerning Changes on Privacy.com]]
66- 2022-03-23 [[./cloudflare-dns-api/][Dynamic DNS with Cloudflare API]]
67- 2022-07-31 [[./bash-it/][Upgrade Bash with Bash-It & Ble.sh]]
68- 2022-07-30 [[./flac-to-opus/][Recursive Command-Line FLAC to Opus Conversion]]
69- 2022-07-25 [[./curseradio/][CurseRadio: Listening to the Radio on the Command Line]]
70- 2022-07-14 [[./gnupg/][GNU Privacy Guard (GPG)]]
71- 2022-07-01 [[./git-server/][Self-Hosting a Personal Git Server]]
72- 2022-06-24 [[./fedora-i3/][Rebooting My Love Affair with Linux]]
73- 2022-06-22 [[./daily-poetry/][Daily Plaintext Poetry via Email]]
74- 2022-06-16 [[./terminal-lifestyle/][A Terminal Lifestyle]]
75- 2022-06-07 [[./self-hosting-freshrss/][Self-Hosting FreshRSS]]
76- 2022-06-01 [[./ditching-cloudflare/][Ditching Cloudflare for Njalla]]
77- 2022-04-09 [[./pinetime/][PineTime: An Open-Source Smart Watch]]
78- 2022-04-02 [[./nginx-reverse-proxy/][Set-Up a Reverse Proxy with Nginx]]
79- 2022-03-26 [[./ssh-mfa/][Enable TOTP MFA for SSH]]
80- 2022-03-24 [[./server-hardening/][Hardening a Public-Facing Home Server]]
81- 2022-03-23 [[./nextcloud-on-ubuntu/][Nextcloud on Ubuntu]]
82- 2022-03-08 [[./plex-migration/][Migrating Plex to a New Server (& Nvidia Transcoding)]]
83- 2022-03-03 [[./financial-database/][Maintaining a Personal Financial Database]]
84- 2022-03-02 [[./reliable-notes/][Easy, Reliable Note-Taking]]
85- 2022-02-22 [[./tuesday/][Tuesday]]
86- 2022-02-20 [[./nginx-caching/][Caching Static Content with Nginx]]
87- 2022-02-17 [[./exiftool/][Stripping Image Metadata with exiftool]]
88- 2022-02-16 [[./debian-and-nginx/][Migrating to a New Web Server Setup with Debian, Nginx, and Agate]]
89- 2022-02-10 [[./leaving-the-office/][Leaving Office-Based Work in the Past]]
90- 2022-02-10 [[./njalla-dns-api/][Dynamic DNS with Njalla API]]
91
92* 2021
93
94- 2021-12-04 [[./cisa/][I Passed the CISA!]]
95- 2021-10-09 [[./apache-redirect/][Apache Redirect HTML Files to a Directory]]
96- 2021-08-25 [[./audit-sampling/][Audit Sampling with Python]]
97- 2021-07-15 [[./delete-gitlab-repos/][How to Delete All GitLab Repositories]]
98- 2021-05-30 [[./changing-git-authors/][Changing Git Authors]]
99- 2021-04-28 [[./photography/][Jumping Back Into Photography]]
100- 2021-04-23 [[./php-comment-system/][Roll Your Own Static Commenting System in PHP]]
101- 2021-04-17 [[./gemini-server/][Hosting a Gemini Server]]
102- 2021-03-30 [[./vps-web-server/][How to Set Up a VPS Web Server]]
103- 2021-03-28 [[./vaporwave-vs-outrun/][Vaporwave vs Outrun]]
104- 2021-03-28 [[./gemini-capsule/][Launching a Gemini Capsule]]
105- 2021-03-19 [[./clone-github-repos/][How to Clone All Repositories from a GitHub or Sourcehut Account]]
106- 2021-02-19 [[./macos/][macOS: Testing Out A New OS]]
107- 2021-01-04 [[./fediverse/][A Simple Guide to the Fediverse]]
108- 2021-01-07 [[./ufw/][Secure Your Network with the Uncomplicated Firewall (ufw)]]
109- 2021-01-01 [[./seum/][SEUM: Speedrunners from Hell]]
110
111* 2020
112
113- 2020-12-29 [[./zork/][Zork: Let's Explore a Classic]]
114- 2020-12-27 [[./website-redesign/][Redesigning My Website: The 5 KB Result]]
115- 2020-12-28 [[./neon-drive/][Neon Drive: A Nostalgic 80s Arcade Racing Game]]
116- 2020-10-12 [[./mediocrity/][On the Pursuit of Mediocrity]]
117- 2020-09-25 [[./happiness-map/][Data Visualization: World Choropleth Map of Happiness]]
118- 2020-09-22 [[./internal-audit/][What is Internal Audit?]]
119- 2020-09-01 [[./visual-recognition/][IBM Watson Visual Recognition]]
120- 2020-08-29 [[./php-auth-flow/][PHP Authentication Flow]]
121- 2020-08-22 [[./redirect-github-pages/][Redirect GitHub Pages from Subdomain to the Top-Level Domain]]
122- 2020-07-26 [[./business-analysis/][Algorithmically Analyzing Local Businesses]]
123- 2020-07-20 [[./video-game-sales/][Data Exploration: Video Game Sales]]
124- 2020-05-19 [[./customizing-ubuntu/][Beginner's Guide: Customizing Ubuntu]]
125- 2020-05-03 [[./homelab/][An Inside Look at My Homelab]]
126- 2020-03-25 [[./session-manager/][Session Private Messenger]]
127- 2020-02-09 [[./cryptography-basics/][Cryptography Basics]]
128- 2020-01-26 [[./steam-on-ntfs/][Linux Gaming Tweak: Steam on NTFS Drives]]
129- 2020-01-25 [[./linux-software/][Linux Software]]
130
131* 2019
132
133- 2019-12-16 [[./password-security//][Password Security]]
134- 2019-12-03 [[./the-ansoff-matrix//][The Ansoff Matrix]]
135- 2019-09-09 [[./audit-analytics//][Data Analysis in Auditing]]
136- 2019-01-07 [[./useful-css//][Useful CSS Snippets]]
137
138* 2018
139
140- 2018-12-08 [[./aes-encryption//][AES Encryption]]
141- 2018-11-28 [[./cpp-compiler//][The C++ Compiler]]
content/blog/2020-09-22-internal-audit.md → blog/internal-audit/index.org renamed +54 −70
@@ -1,33 +1,32 @@
1+++ 1#+title: What is Internal Audit?
2date = 2020-09-22 2#+date: 2020-09-22
3title = "What is Internal Audit?" 3#+description: Learn about the Internal Audit function and their purpose.
4description = "Learn about the Internal Audit function and their purpose." 4#+filetags: :audit:
5+++
6 5
7![Internal Audit 6#+caption: Internal Audit Overview
8Overview](https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-overview.jpg) 7[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-overview.jpg]]
9
10# Definitions
11 8
9* Definitions
12One of the many reasons that Internal Audit needs such thorough 10One of the many reasons that Internal Audit needs such thorough
13explaining to non-auditors is that Internal Audit can serve many 11explaining to non-auditors is that Internal Audit can serve many
14purposes, depending on the organization's size and needs. However, the 12purposes, depending on the organization's size and needs. However, the
15Institute of Internal Auditors (IIA) defines Internal Auditing as: 13Institute of Internal Auditors (IIA) defines Internal Auditing as:
16 14
17> Internal auditing is an independent, objective assurance and 15#+begin_quote
18> consulting activity designed to add value and improve an 16Internal auditing is an independent, objective assurance and consulting
19> organization's operations. It helps an organization accomplish its 17activity designed to add value and improve an organization's operations.
20> objectives by bringing a systematic, disciplined approach to evaluate 18It helps an organization accomplish its objectives by bringing a
21> and improve the effectiveness of risk management, control, and 19systematic, disciplined approach to evaluate and improve the
22> governance processes. 20effectiveness of risk management, control, and governance processes.
23 21
24However, this definition uses quite a few terms that aren't clear 22#+end_quote
25unless the reader already has a solid understanding of the auditing
26profession. To further explain, the following is a list of definitions
27that can help supplement understanding of internal auditing.
28 23
29## Independent 24However, this definition uses quite a few terms that aren't clear unless
25the reader already has a solid understanding of the auditing profession.
26To further explain, the following is a list of definitions that can help
27supplement understanding of internal auditing.
30 28
29** Independent
31Independence is the freedom from conditions that threaten the ability of 30Independence is the freedom from conditions that threaten the ability of
32the internal audit activity to carry out internal audit responsibilities 31the internal audit activity to carry out internal audit responsibilities
33in an unbiased manner. To achieve the degree of independence necessary 32in an unbiased manner. To achieve the degree of independence necessary
@@ -38,8 +37,7 @@ dual-reporting relationship. Threats to independence must be managed at
38the individual auditor, engagement, functional, and organizational 37the individual auditor, engagement, functional, and organizational
39levels. 38levels.
40 39
41## Objective 40** Objective
42
43Objectivity is an unbiased mental attitude that allows internal auditors 41Objectivity is an unbiased mental attitude that allows internal auditors
44to perform engagements in such a manner that they believe in their work 42to perform engagements in such a manner that they believe in their work
45product and that no quality compromises are made. Objectivity requires 43product and that no quality compromises are made. Objectivity requires
@@ -47,8 +45,7 @@ that internal auditors do not subordinate their judgment on audit
47matters to others. Threats to objectivity must be managed at the 45matters to others. Threats to objectivity must be managed at the
48individual auditor, engagement, functional, and organizational levels. 46individual auditor, engagement, functional, and organizational levels.
49 47
50## Assurance 48** Assurance
51
52Assurance services involve the internal auditor's objective assessment 49Assurance services involve the internal auditor's objective assessment
53of evidence to provide opinions or conclusions regarding an entity, 50of evidence to provide opinions or conclusions regarding an entity,
54operation, function, process, system, or other subject matters. The 51operation, function, process, system, or other subject matters. The
@@ -59,8 +56,7 @@ operation, function, process, system, or other subject - (the process
59owner), (2) the person or group making the assessment - (the internal 56owner), (2) the person or group making the assessment - (the internal
60auditor), and (3) the person or group using the assessment - (the user). 57auditor), and (3) the person or group using the assessment - (the user).
61 58
62## Consulting 59** Consulting
63
64Consulting services are advisory in nature and are generally performed 60Consulting services are advisory in nature and are generally performed
65at the specific request of an engagement client. The nature and scope of 61at the specific request of an engagement client. The nature and scope of
66the consulting engagement are subject to agreement with the engagement 62the consulting engagement are subject to agreement with the engagement
@@ -70,23 +66,21 @@ person or group seeking and receiving the advice (the engagement
70client). When performing consulting services, the internal auditor 66client). When performing consulting services, the internal auditor
71should maintain objectivity and not assume management responsibility. 67should maintain objectivity and not assume management responsibility.
72 68
73## Governance, Risk Management, & Compliance (GRC) 69** Governance, Risk Management, & Compliance (GRC)
74
75The integrated collection of capabilities that enable an organization to 70The integrated collection of capabilities that enable an organization to
76reliably achieve objectives, address uncertainty and act with integrity. 71reliably achieve objectives, address uncertainty and act with integrity.
77 72
78# Audit Charter & Standards 73* Audit Charter & Standards
79 74First, it's important to note that not every organization needs internal
80First, it's important to note that not every organization needs 75auditors. In fact, it's unwise for an organization to hire internal
81internal auditors. In fact, it's unwise for an organization to hire 76auditors unless they have regulatory requirements for auditing and have
82internal auditors unless they have regulatory requirements for auditing 77the capital to support the department. Internal audit is a cost center
83and have the capital to support the department. Internal audit is a cost 78that can only affect revenue indirectly.
84center that can only affect revenue indirectly.
85 79
86Once an organization determines the need for internal assurance 80Once an organization determines the need for internal assurance
87services, they will hire a Chief Audit Executive and create the audit 81services, they will hire a Chief Audit Executive and create the audit
88charter. This charter is a document, approved by the company's 82charter. This charter is a document, approved by the company's governing
89governing body, that will define internal audit's purpose, authority, 83body, that will define internal audit's purpose, authority,
90responsibility, and position within the organization. Fortunately, the 84responsibility, and position within the organization. Fortunately, the
91IIA has model charters available to IIA members for those developing or 85IIA has model charters available to IIA members for those developing or
92improving their charter. 86improving their charter.
@@ -101,21 +95,20 @@ assurance. Finally, additional standards such as FASB, GAAP, and
101industry-specific standards are used when performing internal audit 95industry-specific standards are used when performing internal audit
102work. 96work.
103 97
104# Three Lines of Defense 98* Three Lines of Defense
105 99[[https://theiia.org][The IIA]] released the original Three Lines of
106[The IIA](https://theiia.org) released the original Three Lines of
107Defense model in 2013, but have released an updated version in 2020. 100Defense model in 2013, but have released an updated version in 2020.
108Here is what the Three Lines of Defense model has historically looked 101Here is what the Three Lines of Defense model has historically looked
109like: 102like:
110 103
111![2013 Three Lines of Defense 104#+caption: 2013 Three Lines of Defense Model
112Model](https://img.cleberg.net/blog/20200922-what-is-internal-audit/three_lines_model.png) 105[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/three_lines_model.png]]
113 106
114I won't go into depth about the changes made to the model in this 107I won't go into depth about the changes made to the model in this
115article. Instead, let's take a look at the most current model. 108article. Instead, let's take a look at the most current model.
116 109
117![2020 Three Lines of Defense 110#+caption: 2020 Three Lines of Defense Model
118Model](https://img.cleberg.net/blog/20200922-what-is-internal-audit/updated_three_lines_model.png) 111[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/updated_three_lines_model.png]]
119 112
120The updated model forgets the strict idea of areas performing their own 113The updated model forgets the strict idea of areas performing their own
121functions or line of defense. Instead of talking about management, risk, 114functions or line of defense. Instead of talking about management, risk,
@@ -126,10 +119,10 @@ Looking at this model from an auditing perspective shows us that
126auditors will need to align, communicate, and collaborate with 119auditors will need to align, communicate, and collaborate with
127management, including business area managers and chief officers, as well 120management, including business area managers and chief officers, as well
128as reporting to the governing body. The governing body will instruct 121as reporting to the governing body. The governing body will instruct
129internal audit *functionally* on their goals and track their progress 122internal audit /functionally/ on their goals and track their progress
130periodically. 123periodically.
131 124
132However, the internal audit department will report *administratively* to 125However, the internal audit department will report /administratively/ to
133a chief officer in the company for the purposes of collaboration, 126a chief officer in the company for the purposes of collaboration,
134direction, and assistance with the business. Note that in most 127direction, and assistance with the business. Note that in most
135situations, the governing body is the audit committee on the company's 128situations, the governing body is the audit committee on the company's
@@ -139,22 +132,20 @@ The result of this structure is that internal audit is an independent
139and objective function that can provide assurance over the topics they 132and objective function that can provide assurance over the topics they
140audit. 133audit.
141 134
142# Audit Process 135* Audit Process
143
144A normal audit will generally follow the same process, regardless of the 136A normal audit will generally follow the same process, regardless of the
145topic. However, certain special projects or abnormal business areas may 137topic. However, certain special projects or abnormal business areas may
146call for changes to the audit process. The audit process is not set in 138call for changes to the audit process. The audit process is not set in
147stone, it's simply a set of best practices so that audits can be 139stone, it's simply a set of best practices so that audits can be
148performed consistently. 140performed consistently.
149 141
150![The Internal Audit 142#+caption: The Internal Audit Process
151Process](https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-process.jpg) 143[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/internal-audit-process.jpg]]
152 144
153While different organizations may tweak the process, it will generally 145While different organizations may tweak the process, it will generally
154follow this flow: 146follow this flow:
155 147
156## 1. Risk Assessment 148** 1. Risk Assessment
157
158The risk assessment part of the process has historically been performed 149The risk assessment part of the process has historically been performed
159annually, but many organizations have moved to performing this process 150annually, but many organizations have moved to performing this process
160much more frequently. In fact, some organizations are moving to an agile 151much more frequently. In fact, some organizations are moving to an agile
@@ -168,8 +159,7 @@ Once a risk assessment has been documented, the audit department has a
168prioritized list of risks that can be audited. This is usually in the 159prioritized list of risks that can be audited. This is usually in the
169form of auditable entities, such as business areas or departments. 160form of auditable entities, such as business areas or departments.
170 161
171## 2. Planning 162** 2. Planning
172
173During the planning phase of an audit, auditors will meet with the 163During the planning phase of an audit, auditors will meet with the
174business area to discuss the various processes, controls, and risks 164business area to discuss the various processes, controls, and risks
175applicable to the business. This helps the auditors determine the scope 165applicable to the business. This helps the auditors determine the scope
@@ -177,8 +167,7 @@ limits for the audit, as well as timing and subject-matter experts.
177Certain documents will be created in this phase that will be used to 167Certain documents will be created in this phase that will be used to
178keep the audit on-track an in-scope as it goes forward. 168keep the audit on-track an in-scope as it goes forward.
179 169
180## 3. Testing 170** 3. Testing
181
182The testing phase, also known as fieldwork or execution, is where 171The testing phase, also known as fieldwork or execution, is where
183internal auditors will take the information they've discovered and test 172internal auditors will take the information they've discovered and test
184it against regulations, industry standards, company rules, best 173it against regulations, industry standards, company rules, best
@@ -192,8 +181,7 @@ well as compared against regulations such as the Equal Employment
192Opportunity (EEO), American with Disabilities Act, and National Labor 181Opportunity (EEO), American with Disabilities Act, and National Labor
193Relations Act. 182Relations Act.
194 183
195## 4. Reporting 184** 4. Reporting
196
197Once all the tests have been completed, the audit will enter the 185Once all the tests have been completed, the audit will enter the
198reporting phase. This is when the audit team will conclude on the 186reporting phase. This is when the audit team will conclude on the
199evidence they've collected, interviews they've held, and any opinions 187evidence they've collected, interviews they've held, and any opinions
@@ -204,16 +192,14 @@ opportunity to respond to the report and submit an action plan and time
204frame. These responses become part of the final report which is 192frame. These responses become part of the final report which is
205distributed to the appropriate level of administration. 193distributed to the appropriate level of administration.
206 194
207## 5. Follow-Up 195** 5. Follow-Up
208
209After audits have been completed and management has formed action plans 196After audits have been completed and management has formed action plans
210and time frames for audit issues, internal audit will follow up once 197and time frames for audit issues, internal audit will follow up once
211that due date has arrived. In most cases, the follow-up will simply 198that due date has arrived. In most cases, the follow-up will simply
212consist of a meeting to discuss how the action plan has been completed 199consist of a meeting to discuss how the action plan has been completed
213and to request documentation to prove it. 200and to request documentation to prove it.
214 201
215# Audit Department Structure 202* Audit Department Structure
216
217While an internal audit department is most often thought of as a team of 203While an internal audit department is most often thought of as a team of
218full-time employees, there are actually many different ways in which a 204full-time employees, there are actually many different ways in which a
219department can be structured. As the world becomes more digital and 205department can be structured. As the world becomes more digital and
@@ -230,12 +216,11 @@ large organizations, but organizations tend to rotate lower-level audit
230staff instead of managers. This helps prevent any significant knowledge 216staff instead of managers. This helps prevent any significant knowledge
231loss as auditors rotate out to business areas. 217loss as auditors rotate out to business areas.
232 218
233# Consulting 219* Consulting
234
235Consulting is not an easy task at any organization, especially for a 220Consulting is not an easy task at any organization, especially for a
236department that can have negative perceptions within the organization as 221department that can have negative perceptions within the organization as
237the "compliance police." However, once an internal audit department 222the "compliance police." However, once an internal audit department has
238has delivered value to organization, adding consulting to their suite of 223delivered value to organization, adding consulting to their suite of
239services is a smart move. In most cases, Internal Audit can insert 224services is a smart move. In most cases, Internal Audit can insert
240themselves into a consulting role without affecting the process of 225themselves into a consulting role without affecting the process of
241project management at the company. This means that internal audit can 226project management at the company. This means that internal audit can
@@ -243,10 +228,9 @@ add objective assurance and opinions to business areas as they develop
243new processes, instead of coming in periodically to audit an area and 228new processes, instead of coming in periodically to audit an area and
244file issues that could have been fixed at the beginning. 229file issues that could have been fixed at the beginning.
245 230
246# Data Science & Data Analytics 231* Data Science & Data Analytics
247 232#+caption: Data Science Skill Set
248![Data Science Skill 233[[https://img.cleberg.net/blog/20200922-what-is-internal-audit/data-science-skillset.png]]
249Set](https://img.cleberg.net/blog/20200922-what-is-internal-audit/data-science-skillset.png)
250 234
251One major piece of the internal audit function in the modern world is 235One major piece of the internal audit function in the modern world is
252data science. While the process is data science, most auditors will 236data science. While the process is data science, most auditors will
content/blog/2022-02-10-leaving-the-office.md → blog/leaving-the-office/index.org renamed +54 −56
@@ -1,11 +1,9 @@
1+++ 1#+title: Leaving Office-Based Work in the Past
2date = 2022-02-10 2#+date: 2022-02-10
3title = "Leaving Office-Based Work in the Past" 3#+description: My thoughts on the current surge of remote work and what that means for full-time office-based roles.
4description = "My thoughts on the current surge of remote work and what that means for full-time office-based roles." 4#+filetags: :audit:
5+++
6
7## The Working World is Changing
8 5
6* The Working World is Changing
9There has been a trend for the past few years of companies slowly 7There has been a trend for the past few years of companies slowly
10realizing that the pandemic is not just a temporary state that will go 8realizing that the pandemic is not just a temporary state that will go
11away eventually and let everything return to the way it was before. In 9away eventually and let everything return to the way it was before. In
@@ -23,10 +21,8 @@ I decided to take a look back at my relatively short career so far and
23compare the positive and negative effects of the different work 21compare the positive and negative effects of the different work
24environments I've been in. 22environments I've been in.
25 23
26## In-Person Offices 24* In-Person Offices
27 25** Retail Internship
28### Retail Internship
29
30I started my first job as a management intern at a busy retail pharmacy, 26I started my first job as a management intern at a busy retail pharmacy,
31working my 40-hour weeks on my feet. As these retail stores don't 27working my 40-hour weeks on my feet. As these retail stores don't
32believe in resting or sitting down, you can guarantee that you will 28believe in resting or sitting down, you can guarantee that you will
@@ -42,10 +38,9 @@ However, if you are able to operate a retail store with a limited crew
42and provide enough comfort and support, I believe these jobs could be 38and provide enough comfort and support, I believe these jobs could be
43both comfortable and efficient. 39both comfortable and efficient.
44 40
45### Semi-Private Cubicles 41** Semi-Private Cubicles
46 42#+caption: Semi-Private Cubicles
47![Semi-Private 43[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/private_cubicles.png]]
48Cubicles](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/private_cubicles.png)
49 44
50After about a year, I was able to find another internship - this time, 45After about a year, I was able to find another internship - this time,
51it was in my field of interest: internal auditing. This was for a life 46it was in my field of interest: internal auditing. This was for a life
@@ -60,8 +55,8 @@ formed into cubicles that house 4 desks/employees. These "pods" of
604-person cubicles are linked throughout each floor of the headquarters 554-person cubicles are linked throughout each floor of the headquarters
61(except the sales people, who had that open-floor concept going on). The 56(except the sales people, who had that open-floor concept going on). The
62walls of the cubicle were tall and provided a lot of privacy and 57walls of the cubicle were tall and provided a lot of privacy and
63sound-proofing, except when I used the standing desk feature (I'm over 58sound-proofing, except when I used the standing desk feature (I'm over 6
646 feet tall, so probably not an issue for most people). 59feet tall, so probably not an issue for most people).
65 60
66I loved this environment, it allowed me to focus on my work with minimal 61I loved this environment, it allowed me to focus on my work with minimal
67distractions, but also allowed easy access, so I could spin around in my 62distractions, but also allowed easy access, so I could spin around in my
@@ -69,10 +64,9 @@ chair and chat with my friends without leaving my chair. This is the
69closest I've been to a home office environment (which is my personal 64closest I've been to a home office environment (which is my personal
70favorite, as I'll get to later in this post). 65favorite, as I'll get to later in this post).
71 66
72### Semi-Open Floor Concept 67** Semi-Open Floor Concept
73 68#+caption: Semi-Open Floor Concept
74![Semi-Open Floor 69[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/semi_open_office.png]]
75Concept](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/semi_open_office.png)
76 70
77When I shifted to my first full-time internal audit job out of college, 71When I shifted to my first full-time internal audit job out of college,
78I was working at a company that was headquartered on a floor in a 72I was working at a company that was headquartered on a floor in a
@@ -80,8 +74,8 @@ downtown high-rise building. The company was only about 20 years old
80when I worked there and were trying a lot of new things to attract young 74when I worked there and were trying a lot of new things to attract young
81talent, one of which was a semi-open floor concept for the office. My 75talent, one of which was a semi-open floor concept for the office. My
82department worked just around the hallway corner from the executive 76department worked just around the hallway corner from the executive
83offices and used that "modern" layout young tech companies started 77offices and used that "modern" layout young tech companies started using
84using in the 2000s/2010s. 78in the 2000s/2010s.
85 79
86Each desk was brief, and you could look most coworkers in the face 80Each desk was brief, and you could look most coworkers in the face
87without moving from your chair, I hated this so much. Directly to my 81without moving from your chair, I hated this so much. Directly to my
@@ -97,17 +91,15 @@ conversation would be constant throughout the day while you try to work.
97For someone like me, who needs silence to get work done, that was a 91For someone like me, who needs silence to get work done, that was a
98non-starter. 92non-starter.
99 93
100### Hotel Office Concept 94** Hotel Office Concept
101 95#+caption: Hotel Office Concept
102![Hotel Office 96[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/hotel_desks.png]]
103Concept](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/hotel_desks.png)
104 97
105I currently work for a company remotely (for now) and travel to the 98I currently work for a company remotely (for now) and travel to the
106office every once in a while for events and to help coach the staff 99office every once in a while for events and to help coach the staff
107underneath me. The office I visit uses the hotel desk concept, where you 100underneath me. The office I visit uses the hotel desk concept, where you
108need to check in at a touch screen when you enter the office and 101need to check in at a touch screen when you enter the office and "rent"
109"rent" a desk for the day. The same goes for offices and meeting 102a desk for the day. The same goes for offices and meeting rooms.
110rooms.
111 103
112These desks are flat-top only and do not have any walls at all. In 104These desks are flat-top only and do not have any walls at all. In
113addition, they're stacked with one row of 4 desks facing another row of 105addition, they're stacked with one row of 4 desks facing another row of
@@ -125,10 +117,9 @@ Luckily, you can rent offices with doors that offer quiet and privacy,
125which can be very nice if you have a lot of meetings or webinars on a 117which can be very nice if you have a lot of meetings or webinars on a
126certain day. 118certain day.
127 119
128## Home Office 120* Home Office
129 121#+caption: Home Office
130![Home 122[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/home_office.png]]
131Office](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/home_office.png)
132 123
133Okay, now let's finally get to the home office concept. I have worked 124Okay, now let's finally get to the home office concept. I have worked
134from home for a little over two years at this point, across three 125from home for a little over two years at this point, across three
@@ -140,11 +131,13 @@ These things might not apply to you, and that's fine. Everyone has a
140different situation, and I really don't think you'll know what works 131different situation, and I really don't think you'll know what works
141until you try. 132until you try.
142 133
143### Tip #1 134** Tip #1
144
145Let's start with my top rule for a home office: 135Let's start with my top rule for a home office:
146 136
147> If you live with others, working in a shared space is not effective. 137#+begin_quote
138If you live with others, working in a shared space is not effective.
139
140#+end_quote
148 141
149It just does not work. If you have another person sleeping in your 142It just does not work. If you have another person sleeping in your
150bedroom, it is difficult to manage your work schedule with their 143bedroom, it is difficult to manage your work schedule with their
@@ -157,12 +150,14 @@ likely won't work either. Distractions will come far more frequently:
157televisions, cooking, cleaning, deliveries, etc. If you're like me, 150televisions, cooking, cleaning, deliveries, etc. If you're like me,
158you'll end up playing a game instead of actually doing any work. 151you'll end up playing a game instead of actually doing any work.
159 152
160### Tip #2 153** Tip #2
161
162Okay, the second thing I've discovered that works for me: 154Okay, the second thing I've discovered that works for me:
163 155
164> Use the pomodoro method (or something similar) to balance work tasks 156#+begin_quote
165> with personal tasks. 157Use the pomodoro method (or something similar) to balance work tasks
158with personal tasks.
159
160#+end_quote
166 161
167I use a very casual version of the pomodoro method where I will work for 162I use a very casual version of the pomodoro method where I will work for
1681-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and 1631-2 hours (usually set in strict intervals like 1, 1.5, 2 hours) and
@@ -176,13 +171,15 @@ clothes in the washer, get the mail, etc. If you're in a convenient
176location, this usually gives time for things like getting groceries (as 171location, this usually gives time for things like getting groceries (as
177long as you're not a slow shopper). 172long as you're not a slow shopper).
178 173
179### Tip #3 174** Tip #3
180
181While I listed this one as number three, I don't think I'd accomplish 175While I listed this one as number three, I don't think I'd accomplish
182anything without it: 176anything without it:
183 177
184> Document everything: even things you didn't before - such as task 178#+begin_quote
185> lists and notes from casual calls or meetings. 179Document everything: even things you didn't before - such as task lists
180and notes from casual calls or meetings.
181
182#+end_quote
186 183
187I've noticed that staying in an office gave me more constant reminders 184I've noticed that staying in an office gave me more constant reminders
188of outstanding tasks or facts I had learned in a conversation. 185of outstanding tasks or facts I had learned in a conversation.
@@ -196,11 +193,13 @@ has improved my retention immensely. Beyond helping my mental
196recollection, it has saved me numerous times when I need to do a keyword 193recollection, it has saved me numerous times when I need to do a keyword
197search for some topic that was discussed 6+ months ago. 194search for some topic that was discussed 6+ months ago.
198 195
199### Tip #4 196** Tip #4
200
201Okay, last one for now. 197Okay, last one for now.
202 198
203> Keep your work area clean. 199#+begin_quote
200Keep your work area clean.
201
202#+end_quote
204 203
205This one is straightforward, but I know some people struggle with 204This one is straightforward, but I know some people struggle with
206cleanliness or may not believe it makes a difference. Trust me, keeping 205cleanliness or may not believe it makes a difference. Trust me, keeping
@@ -211,8 +210,8 @@ Just think about it, you walk into your home office and see a clean desk
211with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen 210with a laptop, dock, monitors, keyboard, mouse, and a notepad with a pen
212on top. 211on top.
213 212
214Now imagine the opposite, there's an office with the same equipment, 213Now imagine the opposite, there's an office with the same equipment, but
215but there are clothes hanging on the chair, empty drink bottles, candy 214there are clothes hanging on the chair, empty drink bottles, candy
216wrappers and dirty plates. This can take both a mental and emotional 215wrappers and dirty plates. This can take both a mental and emotional
217toll by bringing constant disarray and stress into your working 216toll by bringing constant disarray and stress into your working
218environment. 217environment.
@@ -220,12 +219,11 @@ environment.
220Just keep things clean each day, and you won't need to do any big 219Just keep things clean each day, and you won't need to do any big
221cleaning days to recover. 220cleaning days to recover.
222 221
223## My Preferences 222* My Preferences
224
225I've talked about the different environments I've worked in and 223I've talked about the different environments I've worked in and
226expressed some honest thoughts on pros or cons to each, but what do I 224expressed some honest thoughts on pros or cons to each, but what do I
227prefer? Well, if you're reading along, you should be able to tell that 225prefer? Well, if you're reading along, you should be able to tell that I
228I much prefer a home office above all else. 226much prefer a home office above all else.
229 227
230Being able to control my own day and allot my time as needed has brought 228Being able to control my own day and allot my time as needed has brought
231a calmness to my life and has allowed me to maximize each day. I feel 229a calmness to my life and has allowed me to maximize each day. I feel
@@ -238,5 +236,5 @@ my work done.
238 236
239Cubicles are good! I agree with Alice (from the comic Dilbert): 237Cubicles are good! I agree with Alice (from the comic Dilbert):
240 238
241![Dilbert comic 239#+caption: Dilbert comic strip
242strip](https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/dilbert_120109.png) 240[[https://img.cleberg.net/blog/20220210-leaving-office-based-work-in-the-past/dilbert_120109.png]]
blog/linux-display-manager/index.org added +72
@@ -0,0 +1,72 @@
1#+title: How to Disable or Change the Display Manager on Void Linux
2#+date: 2022-10-30
3#+description: Learn how to remove or modify the display manager on Void Linux.
4#+filetags: :linux:
5
6* Display Manager Services
7In order to change the
8[[https://en.wikipedia.org/wiki/Display_manager][display manager]] on
9Void Linux - or any other Linux distro - you need to identify the
10currently enabled display manager.
11
12** Disabling the Current Display Manager
13Void Linux only has one ISO available for download with a pre-built
14display manager at the time of this post: the XFCE ISO. If you've
15installed this version, the pre-assigned display manager is =lxdm=. If
16you installed another display manager, replace =lxdm= in the following
17command with the display manager you have installed.
18
19To disable =lxdm=, simply remove the service symlink:
20
21#+begin_src sh
22sudo rm /var/service/lxdm
23#+end_src
24
25** Enabling a New Display Manager
26If you want to enable a new display manager, you can do so after =lxdm=
27is disabled. Make sure to replace =<new_display_manager>= with your new
28DM, such as =gdm=, =xdm=, etc.
29
30#+begin_src sh
31sudo ln -s /etc/sv/<new_display_manager> /var/service
32#+end_src
33
34* Set Up =.xinitrc=
35Depending on your setup, you may need to create a few X files, such as
36=~/.xinitrc=. For my personal set-up, I created this file to launch the
37i3wm as my desktop.
38
39#+begin_src sh
40nano ~/.xinitrc
41#+end_src
42
43#+begin_src sh
44#!/bin/sh
45
46exec i3
47#+end_src
48
49If you run a desktop other than i3, simply replace =i3= with the shell
50command that launches that desktop.
51
52* Set Up Your Shell Profile
53Finally, in order to automatically launch an X session upon login, you
54will need to edit the =.bash_profile= (bash) or =.zprofile= (zsh) files
55for your shell:
56
57#+begin_src sh
58nano ~/.zprofile
59#+end_src
60
61Add the following snippet to the end of the shell profile file. This
62will execute the =startx= command upon login.
63
64#+begin_src sh
65if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
66 exec startx
67fi
68#+end_src
69
70Alternatively, you can ignore this step and simply choose to manually
71execute =startx= upon login. This can be useful if you have issues with
72your desktop or like to manually launch different desktops by choice.
content/blog/2020-01-25-linux-software.md → blog/linux-software/index.org renamed +123 −127
@@ -1,172 +1,169 @@
1+++ 1#+title: Linux Software
2date = 2020-01-25 2#+date: 2020-01-25
3title = "Linux Software" 3#+description: A look at some useful Linux applications.
4description = "A look at some useful Linux applications." 4#+filetags: :linux:
5+++ 5
6 6* GUI Applications
7# GUI Applications 7** Etcher
8 8#+caption: Etcher Screenshot
9## Etcher 9[[https://img.cleberg.net/blog/20200125-the-best-linux-software/etcher.png]]
10 10
11![Etcher Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/etcher.png) 11[[https://www.balena.io/etcher/][Etcher]] is a quick and easy way to
12 12burn ISO images to CDs and USB devices. There are two different ways you
13[Etcher](https://www.balena.io/etcher/) is a quick and easy way to burn 13can install this program. First, you can navigate to the
14ISO images to CDs and USB devices. There are two different ways you can 14[[https://www.balena.io/etcher/][official website]] and download the
15install this program. First, you can navigate to the [official 15AppImage file, which can run without installation.
16website](https://www.balena.io/etcher/) and download the AppImage file,
17which can run without installation.
18 16
19However, AppImage files are not executable by default, so you'll either 17However, AppImage files are not executable by default, so you'll either
20need to right-click to open the properties of the file and click the 18need to right-click to open the properties of the file and click the
21"Allow executing file as program" box in the Permissions tab or use 19"Allow executing file as program" box in the Permissions tab or use the
22the following command: 20following command:
23 21
24```sh 22#+begin_src sh
25chmod u+x FILE_NAME 23chmod u+x FILE_NAME
26``` 24#+end_src
27 25
28If you don't like AppImage files or just prefer repositories, you can 26If you don't like AppImage files or just prefer repositories, you can
29use the following commands to add the author's repository and install 27use the following commands to add the author's repository and install it
30it through the command-line only. 28through the command-line only.
31 29
32First, you'll have to echo the repo and write it to a list file: 30First, you'll have to echo the repo and write it to a list file:
33 31
34```sh 32#+begin_src sh
35echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list 33echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
36``` 34#+end_src
37 35
38Next, add the application keys to Ubuntu's keyring: 36Next, add the application keys to Ubuntu's keyring:
39 37
40```sh 38#+begin_src sh
41sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61 39sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
42``` 40#+end_src
43 41
44Finally, update the repositories and install the app. 42Finally, update the repositories and install the app.
45 43
46```sh 44#+begin_src sh
47sudo apt update && sudo apt install balena-etcher-electron 45sudo apt update && sudo apt install balena-etcher-electron
48``` 46#+end_src
49 47
50Using Arch, Manjaro, or another distro using the AUR? Use this command 48Using Arch, Manjaro, or another distro using the AUR? Use this command
51instead: 49instead:
52 50
53```sh 51#+begin_src sh
54sudo pacman -S etcher 52sudo pacman -S etcher
55``` 53#+end_src
56
57## Atom
58 54
59![Atom Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/atom.png) 55** Atom
56#+caption: Atom Screenshot
57[[https://img.cleberg.net/blog/20200125-the-best-linux-software/atom.png]]
60 58
61[Atom](https://atom.io) is the self-proclaimed "hackable text editor 59[[https://atom.io][Atom]] is the self-proclaimed "hackable text editor
62for the 21st century". This text editor is made by GitHub, [now owned 60for the 21st century". This text editor is made by GitHub,
63by 61[[https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/][now
64Microsoft](https://news.microsoft.com/2018/06/04/microsoft-to-acquire-github-for-7-5-billion/), 62owned by Microsoft]], and has some of the best add-ons available to
65and has some of the best add-ons available to customize the layout and 63customize the layout and abilities of the app.
66abilities of the app.
67 64
68First, add the Atom repository to your sources. 65First, add the Atom repository to your sources.
69 66
70```sh 67#+begin_src sh
71sudo add-apt-repository ppa:webupd8team/atom 68sudo add-apt-repository ppa:webupd8team/atom
72``` 69#+end_src
73 70
74Next, update your package listings and install atom. 71Next, update your package listings and install atom.
75 72
76```sh 73#+begin_src sh
77sudo apt update && sudo apt install atom 74sudo apt update && sudo apt install atom
78``` 75#+end_src
79 76
80If you have issues updating your packages with the Atom repository, 77If you have issues updating your packages with the Atom repository,
81you'll need to use the snap package described below instead of the 78you'll need to use the snap package described below instead of the
82repository. To remove the repository we just added, use this command: 79repository. To remove the repository we just added, use this command:
83 80
84```sh 81#+begin_src sh
85sudo add-apt-repository -r ppa:webupd8team/atom 82sudo add-apt-repository -r ppa:webupd8team/atom
86``` 83#+end_src
87 84
88You can also install Atom as a snap package, but it must be installed 85You can also install Atom as a snap package, but it must be installed
89with the `--classic` flag. A [full explanation is 86with the =--classic= flag. A
90available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement) 87[[https://language-bash.com/blog/how-to-snap-introducing-classic-confinement][full
91if you'd like to read more about why you need the classic flag. 88explanation is available]] if you'd like to read more about why you need
89the classic flag.
92 90
93```sh 91#+begin_src sh
94snap install atom --classic 92snap install atom --classic
95``` 93#+end_src
96 94
97Using Arch, Manjaro, or another distro using the AUR? Use this command 95Using Arch, Manjaro, or another distro using the AUR? Use this command
98instead: 96instead:
99 97
100```sh 98#+begin_src sh
101sudo pacman -S atom 99sudo pacman -S atom
102``` 100#+end_src
103
104---
105 101
106## Visual Studio Code 102** Visual Studio Code
103#+caption: Visual Studio Code Code
104[[https://img.cleberg.net/blog/20200125-the-best-linux-software/vscode.png]]
107 105
108![Visual Studio Code Code](https://img.cleberg.net/blog/20200125-the-best-linux-software/vscode.png) 106[[https://code.visualstudio.com][Visual Studio Code]] is yet another
109
110[Visual Studio Code](https://code.visualstudio.com) is yet another
111fantastic choice for programming on Linux, especially if you need those 107fantastic choice for programming on Linux, especially if you need those
112extra add-ons to spice up your late-night coding sessions. The theme 108extra add-ons to spice up your late-night coding sessions. The theme
113used in the screenshot is 109used in the screenshot is
114[Mars](https://marketplace.visualstudio.com/items?itemName=EliverLara.mars) 110[[https://marketplace.visualstudio.com/items?itemName=EliverLara.mars][Mars]]
115by theme creator [Eliver Lara](https://github.com/EliverLara), who makes 111by theme creator [[https://github.com/EliverLara][Eliver Lara]], who
116a ton of great themes for VS Code, Atom, and various Linux desktop 112makes a ton of great themes for VS Code, Atom, and various Linux desktop
117environments. 113environments.
118 114
119To install VS Code, you'll need to download the `.deb` file from the 115To install VS Code, you'll need to download the =.deb= file from the
120official website. Once you've downloaded the file, either double-click 116official website. Once you've downloaded the file, either double-click
121it to install through the Software Center or run the following command: 117it to install through the Software Center or run the following command:
122 118
123```sh 119#+begin_src sh
124sudo dpkg -i FILE_NAME.deb 120sudo dpkg -i FILE_NAME.deb
125``` 121#+end_src
126 122
127You can also install VS Code as a snap package, but it must be installed 123You can also install VS Code as a snap package, but it must be installed
128with the `--classic` flag. A [full explanation is 124with the =--classic= flag. A
129available](https://language-bash.com/blog/how-to-snap-introducing-classic-confinement) 125[[https://language-bash.com/blog/how-to-snap-introducing-classic-confinement][full
130if you'd like to read more about why you need the classic flag. 126explanation is available]] if you'd like to read more about why you need
127the classic flag.
131 128
132```sh 129#+begin_src sh
133snap install code --classic 130snap install code --classic
134``` 131#+end_src
135 132
136Using Arch, Manjaro, or another distro using the AUR? Use these commands 133Using Arch, Manjaro, or another distro using the AUR? Use these commands
137instead: 134instead:
138 135
139```sh 136#+begin_src sh
140sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin 137sudo pacman -S yay binutils make gcc pkg-config fakeroot yay -S visual-studio-code-bin
141``` 138#+end_src
142
143## GNOME Tweaks
144 139
145![Gnome Tweaks Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/gnome-tweaks.png) 140** GNOME Tweaks
141#+caption: Gnome Tweaks Screenshot
142[[https://img.cleberg.net/blog/20200125-the-best-linux-software/gnome-tweaks.png]]
146 143
147[Gnome Tweaks](https://gitlab.gnome.org/GNOME/gnome-tweaks) is the 144[[https://gitlab.gnome.org/GNOME/gnome-tweaks][Gnome Tweaks]] is the
148ultimate tool to use if you want to customize your GNOME desktop 145ultimate tool to use if you want to customize your GNOME desktop
149environment. This is how you can switch application themes (GTK), shell 146environment. This is how you can switch application themes (GTK), shell
150themes, icons, fonts, and more. To install GNOME Tweaks on Ubuntu, you 147themes, icons, fonts, and more. To install GNOME Tweaks on Ubuntu, you
151just need to install the official package. 148just need to install the official package.
152 149
153```sh 150#+begin_src sh
154sudo apt install gnome-tweaks 151sudo apt install gnome-tweaks
155``` 152#+end_src
156 153
157If you've installed Manjaro or Arch with Gnome, you should have the 154If you've installed Manjaro or Arch with Gnome, you should have the
158tweak tool pre-installed. If you're on Fedora, this tool is available 155tweak tool pre-installed. If you're on Fedora, this tool is available as
159as an official package: 156an official package:
160 157
161```sh 158#+begin_src sh
162sudo dnf install gnome-tweaks 159sudo dnf install gnome-tweaks
163``` 160#+end_src
164
165## Steam
166 161
167![Steam Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png) 162** Steam
163#+caption: Steam Screenshot
164[[https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png]]
168 165
169[Steam](https://steampowered.com) is one of the most popular gaming 166[[https://steampowered.com][Steam]] is one of the most popular gaming
170libraries for computers and is one of the main reasons that many people 167libraries for computers and is one of the main reasons that many people
171have been able to switch to Linux in recent years, thanks to Steam 168have been able to switch to Linux in recent years, thanks to Steam
172Proton, which makes it easier to play games not officially created for 169Proton, which makes it easier to play games not officially created for
@@ -175,21 +172,21 @@ Linux platforms.
175To install Steam on Ubuntu, you just need to install the official 172To install Steam on Ubuntu, you just need to install the official
176package. 173package.
177 174
178```sh 175#+begin_src sh
179sudo apt install steam-installer 176sudo apt install steam-installer
180``` 177#+end_src
181 178
182For Arch-based systems, you'll simply need to install the 179For Arch-based systems, you'll simply need to install the =steam=
183`steam` package. However, this requires that you enable the 180package. However, this requires that you enable the =multilib= source.
184`multilib` source. To do so, use the following command: 181To do so, use the following command:
185 182
186```sh 183#+begin_src sh
187sudo nano /etc/pacman.conf 184sudo nano /etc/pacman.conf
188``` 185#+end_src
189 186
190Now, scroll down and uncomment the `multilib` section. 187Now, scroll down and uncomment the =multilib= section.
191 188
192```config 189#+begin_src config
193# Before: 190# Before:
194#[multilib] 191#[multilib]
195#Include = /etc/pacman.d/mirrorlist 192#Include = /etc/pacman.d/mirrorlist
@@ -197,24 +194,23 @@ Now, scroll down and uncomment the `multilib` section.
197# After: 194# After:
198[multilib] 195[multilib]
199Include = /etc/pacman.d/mirrorlist 196Include = /etc/pacman.d/mirrorlist
200``` 197#+end_src
201 198
202Finally, install the program: 199Finally, install the program:
203 200
204```sh 201#+begin_src sh
205sudo pacman -S steam 202sudo pacman -S steam
206``` 203#+end_src
207 204
208[Problem Launching Steam Games? Click 205[[./2020-01-26-steam-on-ntfs-drives.html][Problem Launching Steam Games?
209Here.](./2020-01-26-steam-on-ntfs-drives.html) 206Click Here.]]
210 207
211# Command-Line Packages 208* Command-Line Packages
209** neofetch
210#+caption: Neofetch Screenshot
211[[https://img.cleberg.net/blog/20200125-the-best-linux-software/neofetch.png]]
212 212
213## neofetch 213[[https://github.com/dylanaraps/neofetch][Neofetch]] is a customizable
214
215![Neofetch Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/neofetch.png)
216
217[Neofetch](https://github.com/dylanaraps/neofetch) is a customizable
218tool used in the command-line to show system information. This is 214tool used in the command-line to show system information. This is
219exceptionally useful if you want to see your system's information 215exceptionally useful if you want to see your system's information
220quickly without the clutter of some resource-heavy GUI apps. 216quickly without the clutter of some resource-heavy GUI apps.
@@ -222,54 +218,54 @@ quickly without the clutter of some resource-heavy GUI apps.
222This is an official package if you're running Ubuntu 17.04 or later, so 218This is an official package if you're running Ubuntu 17.04 or later, so
223simply use the following command: 219simply use the following command:
224 220
225```sh 221#+begin_src sh
226sudo apt install neofetch 222sudo apt install neofetch
227``` 223#+end_src
228 224
229If you're running Ubuntu 16.10 or earlier, you'll have to use a series 225If you're running Ubuntu 16.10 or earlier, you'll have to use a series
230of commands: 226of commands:
231 227
232```sh 228#+begin_src sh
233sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch 229sudo add-apt-repository ppa:dawidd0811/neofetch; sudo apt update; sudo apt install neofetch
234``` 230#+end_src
235 231
236Using Arch, Manjaro, or another distro using the AUR? Use this command 232Using Arch, Manjaro, or another distro using the AUR? Use this command
237instead: 233instead:
238 234
239```sh 235#+begin_src sh
240sudo pacman -S neofetch 236sudo pacman -S neofetch
241``` 237#+end_src
242
243## yt-dlp
244 238
245![yt-dlp Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/yt-dlp.png) 239** yt-dlp
240#+caption: yt-dlp Screenshot
241[[https://img.cleberg.net/blog/20200125-the-best-linux-software/yt-dlp.png]]
246 242
247[yt-dlp](https://github.com/yt-dlp/yt-dlp) is an extremely handy 243[[https://github.com/yt-dlp/yt-dlp][yt-dlp]] is an extremely handy
248command-line tool that allows you to download video or audio files from 244command-line tool that allows you to download video or audio files from
249various websites, such as YouTube. There are a ton of different options 245various websites, such as YouTube. There are a ton of different options
250when running this package, so be sure to run `yt-dlp --help` first to 246when running this package, so be sure to run =yt-dlp --help= first to
251look through everything you can do (or give up and search for the best 247look through everything you can do (or give up and search for the best
252config online). 248config online).
253 249
254While this shouldn't be a problem for most users, yt-dlp requires 250While this shouldn't be a problem for most users, yt-dlp requires Python
255Python 2.6, 2.7, or 3.2+ to work correctly, so install Python if you 2512.6, 2.7, or 3.2+ to work correctly, so install Python if you don't have
256don't have it already. You can check to see if you have Python 252it already. You can check to see if you have Python installed by
257installed by running: 253running:
258 254
259```sh 255#+begin_src sh
260python -V 256python -V
261``` 257#+end_src
262 258
263To get the youtube-dl package, simply curl the URL and output the 259To get the youtube-dl package, simply curl the URL and output the
264results. 260results.
265 261
266```sh 262#+begin_src sh
267sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp 263sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
268``` 264#+end_src
269 265
270Finally, make the file executable so that it can be run from the 266Finally, make the file executable so that it can be run from the
271command-line. 267command-line.
272 268
273```sh 269#+begin_src sh
274sudo chmod a+rx /usr/local/bin/yt-dlp 270sudo chmod a+rx /usr/local/bin/yt-dlp
275``` 271#+end_src
blog/local-llm/index.org added +108
@@ -0,0 +1,108 @@
1#+title: Running Local LLMs on macOS and iOS
2#+date: 2024-01-13
3#+description: Finding some useful applications for running local LLMs on macOS and iOS.
4#+filetags: :apple:
5
6* Requirements
7I've recently started playing with large language models (LLMs), mostly
8in the popular chatbot form, as part of my job and have decided to see
9if there's a consistent and reliable way to interact with these models
10on Apple devices without sacrificing privacy or requiring in-depth
11technical setup.
12
13My requirements for this test:
14
15- Open source platform
16- On-device model files
17- Minimal required configuration
18- Preferably pre-built, but a simple build process is acceptable
19
20I tested a handful of apps and have summarized my favorite (so far) for
21macOS and iOS below.
22
23#+begin_quote
24TL;DR - Here are the two that met my requirements and I have found the
25easiest to install and use so far:
26
27#+end_quote
28
29- macOS: [[https://ollama.ai/][Ollama]]
30- iOS : [[https://llmfarm.site/][LLM Farm]]
31
32* macOS
33[[https://ollama.ai/][Ollama]] is a simple Go application for macOS and
34Linux that can run various LLMs locally.
35
36For macOS, you can download the pplication on the
37[[https://ollama.ai/download/mac][Ollama download page]] and install it
38by unzipping the =Ollama.app= file and moving it to the =Applications=
39folder.
40
41If you prefer the command line, you can run these commands after the
42download finished:
43
44#+begin_src sh
45cd ~/Downloads && \
46unzip Ollama-darwin.zip && \
47mv ~/Downloads/Ollama.app /Applications/
48#+end_src
49
50After running the app, the app will ask you to open a terminal and run
51the default =llama2= model, which will open an interactive chat session
52in the terminal. You can startfully using the application at this point.
53
54#+caption: Ollama
55[[https://img.cleberg.net/blog/20240113-local-llm/ollama.png]]
56
57If you don't want to use the default =llama2= model, you can download
58and run additional models found on the
59[[https://ollama.ai/library][Models]] page.
60
61To see the information for the currently-used model, you can run the
62=/show info= command in the chat.
63
64#+caption: Model Info
65[[https://img.cleberg.net/blog/20240113-local-llm/ollama_info.png]]
66
67** Community Integrations
68I highly recommend browsing the
69[[https://github.com/jmorganca/ollama#community-integrations][Community
70Integrations]] section of the project to see how you would prefer to
71extend Ollama beyond a simple command-line interface. There are options
72for APIs, browser UIs, advanced terminal configurations, and more.
73
74#+caption: Ollama SwiftUI
75[[https://img.cleberg.net/blog/20240113-local-llm/ollama-swiftui.png]]
76
77* iOS
78While there are a handful of decent macOS options, it was quite
79difficult to find an iOS app that offered an open source platform
80without an extensive configuration and building process. I found LLM
81Farm to be decent enough in quality to sit at the top of my list -
82however, it's definitely not user friendly enough for me to consider
83using it on a daily basis.
84
85[[https://llmfarm.site/][LLM Farm]] is available on TestFlight, so
86there's no manual build process required. However, you can view the
87[[https://github.com/guinmoon/LLMFarm][LLMFarm repository]] if you wish.
88
89The caveat is that you will have to manually download the model files
90from the links in the
91[[https://github.com/guinmoon/LLMFarm/blob/main/models.md][models.md]]
92file to your iPhone to use the app - there's currently no option in the
93app to reach out and grab the latest version of any supported model.
94
95Once you have a file downloaded, you simply create a new chat and select
96the downloaded model file and ensure the inference matches the
97requirement in the =models.md= file.
98
99See below for a test of the ORCA Mini v3 model:
100
101| Chat List | Chat |
102|------------------------------------------------------------------------+------------------------------------------------------------------|
103| [[https://img.cleberg.net/blog/20240113-local-llm/llm_farm_chats.png]] | [[https://img.cleberg.net/blog/20240113-local-llm/llm_farm.png]] |
104
105[[https://github.com/AugustDev/enchanted][Enchanted]] is also an iOS for
106private AI models, but it requires a public-facing Ollama API, which did
107not meet my "on device requirement." Nonetheless, it's an interesting
108looking app and I will likely set it up to test soon.
blog/macos-customization/index.org added +170
@@ -0,0 +1,170 @@
1#+title: Customizing macOS
2#+date: 2024-01-09
3#+description: Learn how to customize macOS beyond the standard, built-in options provided by Apple.
4#+filetags: :apple:
5
6I have been using macOS more than Linux lately, so I wrote this post to
7describe some simple options to customize macOS beyond the normal
8built-in settings menu.
9
10While not all-encompassing, the options below should be a good start for
11anyone looking to dive down the rabbit hole.
12
13* Basics
14** Package Management
15To install a lot of software on macOS, you will need
16[[https://brew.sh/][Homebrew]]. You can use their installation script to
17get started. Simply open the =Terminal= application and paste the
18following snippet:
19
20#+begin_src sh
21/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
22#+end_src
23
24This will allow you to easily install and manage applications and other
25software easily through the =brew= command.
26
27** Terminal
28If you're serious about customizing your macOS system, I highly
29recommend installing a terminal emulator that you like and if you're not
30comfortable on the command line yet, start learning. A lot of
31customization options require you to edit hidden files, which is easiest
32in a terminal.
33
34There are options like iTerm2, Kitty, Alacritty, Hyper, Warp, or the
35built-in Terminal app.
36
37I use [[https://iterm2.com/][iTerm2]], which can be installed with
38Homebrew:
39
40#+begin_src sh
41brew install iterm2
42#+end_src
43
44#+caption: iTerm2
45[[https://img.cleberg.net/blog/20240109-macos-customization/iterm2.png]]
46
47To install color schemes, such as the Dracula scheme shown in the
48screenshot above, you visit [[https://iterm2colorschemes.com/][iTerm
49Themes]] and follow their installation instructions to install any of
50the themes.
51
52* Desktop
53** Window Management
54[[https://github.com/koekeishiya/yabai][yabai]] is a tiling window
55manager for macOS. While other window managers exist, I found that most
56of them struggled to create logical layouts and to allow me to easily
57move windows around the screen.
58
59Some advanced settings for yabai are only available if partially disable
60System Integrity Protection (SIP). However, I chose not to do this and
61it hasn't affected my basic usage of yabai at all.
62
63Refer to the
64[[https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release)][yabai
65wiki]] for installation instructions. You will need to ensure that yabai
66is allowed to access the accessibility and screen recording APIs.
67
68You can see a basic three-pane layout atuomatically configured by yabai
69for me as I opened the windows below.
70
71#+caption: yabai window manager
72[[https://img.cleberg.net/blog/20240109-macos-customization/yabai.png]]
73
74** Keyboard Shortcuts
75[[https://github.com/koekeishiya/skhd][skhd]] is a simple hotkey daemon
76that allows you to define hotkeys in a file for usage on your system.
77
78Installation is simple:
79
80#+begin_src sh
81brew install koekeishiya/formulae/skhd
82skhd --start-service
83#+end_src
84
85After installation, be sure to allow =skhd= access to the accessibility
86API in the macOS privacy settings.
87
88You can configure your hotkeys in the =~/.config/skhd/skhdrc= file:
89
90#+begin_src sh
91nano ~/.config/skhd/skhdrc
92#+end_src
93
94For example, I have hotkeys to open my browser and terminal:
95
96#+begin_src conf
97# Terminal
98cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
99
100# Browser
101cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
102#+end_src
103
104** Widgets
105[[https://github.com/felixhageloh/uebersicht/][uebersicht]] is a handy
106desktop-based widget tool with a plethora of community-made widgets
107available in the [[https://tracesof.net/uebersicht-widgets/][widgets
108gallery]]. You can also write your own widgets with this tool.
109
110To install, simply download the latest release from the
111[[https://tracesof.net/uebersicht/][uebersicht website]] and copy it to
112the Applications folder.
113
114See below for an example of the
115[[https://tracesof.net/uebersicht-widgets/#Mond][Mond]] widget in
116action.
117
118#+caption: uebersicht desktop widgets
119[[https://img.cleberg.net/blog/20240109-macos-customization/uebersicht.png]]
120
121** Status Bar
122[[https://github.com/FelixKratz/SketchyBar][SketchyBar]] is a
123customizable replacement for the macOS status or menu bar.
124
125You can browse a discussion where various users shared their
126[[https://github.com/FelixKratz/SketchyBar/discussions/47?sort=top][configurations]]
127for inspiration or to copy their dotfiles.
128
129See below for a quick (& slightly broken) copy of
130[[https://github.com/zer0yu/dotfiles][zer0yu's]] SketchyBar
131configuration.
132
133#+caption: SketchyBar
134[[https://img.cleberg.net/blog/20240109-macos-customization/sketchybar.png]]
135
136** Dock
137The easiest way to customize the dock is to install
138[[https://ubarapp.com/][uBar]], which uses a Windows-like menu bar as
139the default style.
140
141However, the built-in macOS dock cannot be disabled and can only be set
142to "always hidden". This can be annoying as it will pop out any time
143your mouse cursor passes closely to the dock edge of the screen. Because
144of this, I simply use the built-in dock instead of customizing it with
145third-party software.
146
147Regardless, see below for the default installation style of uBar.
148
149#+caption: uBar
150[[https://img.cleberg.net/blog/20240109-macos-customization/ubar.png]]
151
152** Application Icons
153You can also customize the icon of any application in macOS, which will
154show up in Finder, the Dock, Launchpad, search results, etc. I recommend
155using [[https://macosicons.com/][macOSicons]] to download icons you
156want, and then apply them by following this process.
157
1581. Open the Finder application.
1592. Navigate to the =Applications= folder.
1603. Right-click an application of your choice, and select =Get Info=.
1614. Drag the image you downloaded on top of the application's icon at the
162 top of information window (you will see a green "plus" symbol when
163 you're hovering over it).
1645. Release the new icon on top of the old icon and it will update!
165
166You can see an example of me dragging a new =signal.icns= file onto my
167Signal.app information window to update it below:
168
169#+caption: replace macOS icons
170[[https://img.cleberg.net/blog/20240109-macos-customization/replace_icon.png]]
content/blog/2021-02-19-macos.md → blog/macos/index.org renamed +83 −97
@@ -1,11 +1,9 @@
1+++ 1#+title: macOS: Testing Out A New OS
2date = 2021-02-19 2#+date: 2021-02-19
3title = "macOS: Testing Out A New OS" 3#+description: A retrospective on my migration from Linux to macOS.
4description = "A retrospective on my migration from Linux to macOS." 4#+filetags: :apple:
5+++
6
7## Diving into macOS
8 5
6* Diving into macOS
9After spending nearly 15 years working with Windows and 8 years on 7After spending nearly 15 years working with Windows and 8 years on
10Linux, I have experienced macOS for the first time. By chance, my spouse 8Linux, I have experienced macOS for the first time. By chance, my spouse
11happened to buy a new MacBook and gifted me their 2013 model. Of course, 9happened to buy a new MacBook and gifted me their 2013 model. Of course,
@@ -14,22 +12,21 @@ around for gaming needs, but over the past week I've found myself using
14the MacBook more and more for things that don't require gaming specs or 12the MacBook more and more for things that don't require gaming specs or
15advanced dev tools. 13advanced dev tools.
16 14
17## Initial Thoughts 15* Initial Thoughts
18
19Before I move on to the technical aspects of my set-up, I want to take 16Before I move on to the technical aspects of my set-up, I want to take
20some time and express my thoughts on the overall OS. 17some time and express my thoughts on the overall OS.
21 18
22![macOS 19#+caption: macOS Desktop
23Desktop](https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/macos-desktop.png) 20[[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/macos-desktop.png]]
24 21
25As expected, the initial computer setup is a breeze with Mac's guided 22As expected, the initial computer setup is a breeze with Mac's guided
26GUI installer. 23GUI installer.
27 24
28The desktop itself reminds me of GNOME more than anything else I've 25The desktop itself reminds me of GNOME more than anything else I've
29seen: even Pantheon from [ElementaryOS](https://elementary.io/), which 26seen: even Pantheon from [[https://elementary.io/][ElementaryOS]], which
30people commonly refer to as the closest Linux distro to macOS. The 27people commonly refer to as the closest Linux distro to macOS. The
31desktop toolbar is great and far surpasses the utility of the GNOME 28desktop toolbar is great and far surpasses the utility of the GNOME
32toolbar due to the fact that the extensions and icons *actually work*. I 29toolbar due to the fact that the extensions and icons /actually work/. I
33launch macOS and immediately see my shortcuts for Tresorit, Bitwarden, 30launch macOS and immediately see my shortcuts for Tresorit, Bitwarden,
34and Mullvad pop up as the computer loads. 31and Mullvad pop up as the computer loads.
35 32
@@ -41,7 +38,7 @@ can't stand items taking up precious screen space if I'm not currently
41using them. On that same note, it's taking me some time to get use to 38using them. On that same note, it's taking me some time to get use to
42the fact that I have to manually quit an app or else it will still stay 39the fact that I have to manually quit an app or else it will still stay
43open/active in the dock, even if I've closed out all windows for that 40open/active in the dock, even if I've closed out all windows for that
44app (e.g. Firefox). 41app (e.g. Firefox).
45 42
46Overall, I'm having a lot of fun and for users who spend a large 43Overall, I'm having a lot of fun and for users who spend a large
47majority of their time performing basic tasks like web browsing, 44majority of their time performing basic tasks like web browsing,
@@ -51,8 +48,7 @@ The rest of this post explains the technicalities of how I set up my CLI
51environment to make me feel more at-home, similar to the environments I 48environment to make me feel more at-home, similar to the environments I
52set up on Fedora, Ubuntu, etc. 49set up on Fedora, Ubuntu, etc.
53 50
54## Making it Feel Like Home 51* Making it Feel Like Home
55
56If you're someone who uses Linux primarily, no doubt your first thought 52If you're someone who uses Linux primarily, no doubt your first thought
57when booting macOS will be the same as mine was: "Where is the terminal 53when booting macOS will be the same as mine was: "Where is the terminal
58and how do I set up my favorite utilities?" 54and how do I set up my favorite utilities?"
@@ -60,155 +56,145 @@ and how do I set up my favorite utilities?"
60Luckily, macOS hasn't completely hidden away the development tools from 56Luckily, macOS hasn't completely hidden away the development tools from
61the average user. You can easily find the Terminal app in the Launchpad 57the average user. You can easily find the Terminal app in the Launchpad
62area, but it's probably not what you're used to. I was surprised (and 58area, but it's probably not what you're used to. I was surprised (and
63happy) to see that the default shell is `zsh`, the shell I 59happy) to see that the default shell is =zsh=, the shell I use on all of
64use on all of my Linux distros. However, the commands are not the same - 60my Linux distros. However, the commands are not the same - even the ones
65even the ones you may think are native to the shell. Commands like 61you may think are native to the shell. Commands like =dir= do not exist,
66`dir` do not exist, so other native commands like 62so other native commands like =ls -la= or =pwd= are more useful here.
67`ls -la` or `pwd` are more useful here.
68 63
69With only a few minutes of installing and tweaking a few packages, I was 64With only a few minutes of installing and tweaking a few packages, I was
70able to recreate a terminal environment that I feel very comfortable 65able to recreate a terminal environment that I feel very comfortable
71using. See the image below for a preview of the iTerm2 app with a split 66using. See the image below for a preview of the iTerm2 app with a split
72view between my macOS desktop shell and an SSH session into my server. 67view between my macOS desktop shell and an SSH session into my server.
73 68
74![iTerm2](https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/iterm2.png) 69#+caption: iTerm2
75 70[[https://img.cleberg.net/blog/20210219-macos-testing-out-a-new-os/iterm2.png]]
76## Xcode
77 71
78My first step was to search the web for any hints on how to get 72* Xcode
79`zsh` back up to the state I like, with extensions, themes, 73My first step was to search the web for any hints on how to get =zsh=
80etc. My first step was to install the CLI tools for 74back up to the state I like, with extensions, themes, etc. My first step
81[Xcode](https://developer.apple.com/xcode/), Apple's suite of 75was to install the CLI tools for
76[[https://developer.apple.com/xcode/][Xcode]], Apple's suite of
82development tools. 77development tools.
83 78
84```sh 79#+begin_src sh
85sudo xcode-select -r 80sudo xcode-select -r
86``` 81#+end_src
87 82
88```sh 83#+begin_src sh
89sudo xcode-select --install 84sudo xcode-select --install
90``` 85#+end_src
91 86
92## Homebrew 87* Homebrew
93 88Next up is to install [[https://brew.sh][Homebrew]], a nifty package
94Next up is to install [Homebrew](https://brew.sh), a nifty package
95manager for macOS. 89manager for macOS.
96 90
97```sh 91#+begin_src sh
98/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 92/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
99``` 93#+end_src
100 94
101I ran into a permission error when installing Homebrew: 95I ran into a permission error when installing Homebrew:
102 96
103```sh 97#+begin_src sh
104Error: Failed to link all completions, docs and manpages: 98Error: Failed to link all completions, docs and manpages:
105 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew) 99 Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew)
106Failed during: /usr/local/bin/brew update --force --quiet 100Failed during: /usr/local/bin/brew update --force --quiet
107``` 101#+end_src
108 102
109I found that the following permission modification worked like a charm. 103I found that the following permission modification worked like a charm.
110However, I noted that some users online discussed the fact that this 104However, I noted that some users online discussed the fact that this
111solution may not work if your system has multiple users who use 105solution may not work if your system has multiple users who use
112Homebrew. 106Homebrew.
113 107
114```sh 108#+begin_src sh
115sudo chown -R $(whoami) $(brew --prefix)/* 109sudo chown -R $(whoami) $(brew --prefix)/*
116``` 110#+end_src
117 111
118Next up is to ensure Homebrew is updated and cleaned. 112Next up is to ensure Homebrew is updated and cleaned.
119 113
120```sh 114#+begin_src sh
121brew update 115brew update
122``` 116#+end_src
123 117
124```sh 118#+begin_src sh
125brew cleanup 119brew cleanup
126``` 120#+end_src
127
128## iTerm2
129 121
122* iTerm2
130Now that I've installed the basic utilities for development, I moved 123Now that I've installed the basic utilities for development, I moved
131onto installing iTerm2, a much better terminal than the default. 124onto installing iTerm2, a much better terminal than the default.
132 125
133```sh 126#+begin_src sh
134brew install --cask iterm2 127brew install --cask iterm2
135``` 128#+end_src
136 129
137I also used the `Make iTerm2 Default Term` and 130I also used the =Make iTerm2 Default Term= and
138`Install Shell Integration` options in the iTerm2 application 131=Install Shell Integration= options in the iTerm2 application menu to
139menu to make sure I don't run into any issues later on with different 132make sure I don't run into any issues later on with different terminals.
140terminals.
141 133
142We will also install `zsh` so we can use it in iTerm2. 134We will also install =zsh= so we can use it in iTerm2.
143 135
144```sh 136#+begin_src sh
145brew install zsh 137brew install zsh
146``` 138#+end_src
147
148## Oh-My-Zsh
149 139
150I've shown the great aspects of [Oh My Zsh](https://ohmyz.sh) in other 140* Oh-My-Zsh
151blog posts, so I'll skip over that speech for now. Simply install it 141I've shown the great aspects of [[https://ohmyz.sh][Oh My Zsh]] in other
152and run an update. 142blog posts, so I'll skip over that speech for now. Simply install it and
143run an update.
153 144
154```sh 145#+begin_src sh
155sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" 146sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
156``` 147#+end_src
157 148
158```sh 149#+begin_src sh
159omz update 150omz update
160``` 151#+end_src
161 152
162Finally, restart the iTerm2 application to ensure all changes go into 153Finally, restart the iTerm2 application to ensure all changes go into
163effect. 154effect.
164 155
165## Oh-My-Zsh Themes 156* Oh-My-Zsh Themes
166
167Let's change the theme of the terminal to make it a little more 157Let's change the theme of the terminal to make it a little more
168friendly. 158friendly.
169 159
170```sh 160#+begin_src sh
171open ~/.zshrc 161open ~/.zshrc
172``` 162#+end_src
173 163
174The third section of this file should contain a line like the code 164The third section of this file should contain a line like the code
175below. Change that theme to [any theme you 165below. Change that theme to
176want](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes), save the file, 166[[https://github.com/ohmyzsh/ohmyzsh/wiki/Themes][any theme you want]],
177and exit. 167save the file, and exit.
178 168
179```sh 169#+begin_src sh
180ZSH_THEME="af-magic" 170ZSH_THEME="af-magic"
181``` 171#+end_src
182
183After changing the `.zshrc` file, you'll need to close your
184terminal and re-open it to see the changes. Optionally, just open a new
185tab if you're using iTerm2, and you'll see the new shell config.
186 172
187## Oh-My-Zsh Plugins 173After changing the =.zshrc= file, you'll need to close your terminal and
174re-open it to see the changes. Optionally, just open a new tab if you're
175using iTerm2, and you'll see the new shell config.
188 176
189Of course, my customization of `zsh` would not be complete 177* Oh-My-Zsh Plugins
190without 178Of course, my customization of =zsh= would not be complete without
191[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions). 179[[https://github.com/zsh-users/zsh-autosuggestions][zsh-autosuggestions]].
192This will bring up commands you've run in the past as you type them. 180This will bring up commands you've run in the past as you type them. For
193For example, if you've run `ssh user@192.168.1.99` before, 181example, if you've run =ssh user@192.168.1.99= before, the terminal will
194the terminal will show this command as soon as you start typing it 182show this command as soon as you start typing it (e.g. =zsh u=), and you
195(e.g. =zsh u=), and you can hit the right arrow to autocomplete the 183can hit the right arrow to autocomplete the command.
196command.
197 184
198```sh 185#+begin_src sh
199git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 186git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
200``` 187#+end_src
201 188
202```sh 189#+begin_src sh
203open ~/.zshrc 190open ~/.zshrc
204``` 191#+end_src
205 192
206```sh 193#+begin_src sh
207# Scroll down the script and edit this line to add zsh-autosuggestions 194# Scroll down the script and edit this line to add zsh-autosuggestions
208plugins=(git zsh-autosuggestions) 195plugins=(git zsh-autosuggestions)
209``` 196#+end_src
210 197
211Remember: After changing the `.zshrc` file, you'll need to 198Remember: After changing the =.zshrc= file, you'll need to close your
212close your terminal and re-open it to see the changes. Optionally, just 199terminal and re-open it to see the changes. Optionally, just open a new
213open a new tab if you're using iTerm2, and you'll see the new shell 200tab if you're using iTerm2, and you'll see the new shell config.
214config.
content/blog/2023-01-05-mass-unlike-tumblr-posts.md → blog/mass-unlike-tumblr-posts/index.org renamed +34 −41
@@ -1,18 +1,15 @@
1+++ 1#+title: How to Easily Mass Unlike Tumblr Posts with Javascript
2date = 2023-01-05 2#+date: 2023-01-05
3title = "How to Easily Mass Unlike Tumblr Posts with Javascript" 3#+description: Learn how to unlike Tumblr posts en masse in the browser.
4description = "Learn how to unlike Tumblr posts en masse in the browser." 4#+filetags: :dev:
5+++
6
7## The Dilemma
8 5
6* The Dilemma
9The dilemma I had was pretty simple: I wanted to unlike all the posts I 7The dilemma I had was pretty simple: I wanted to unlike all the posts I
10have liked on Tumblr so that I could follow a new focus on blogs and 8have liked on Tumblr so that I could follow a new focus on blogs and
11start fresh. Otherwise, Tumblr will keep recommending content based on 9start fresh. Otherwise, Tumblr will keep recommending content based on
12your previous likes. 10your previous likes.
13 11
14## The Solution 12* The Solution
15
16I searched the web for a while and noted that most solutions referenced 13I searched the web for a while and noted that most solutions referenced
17Tumblr setting and dashboard pages that no longer exist. Additionally, I 14Tumblr setting and dashboard pages that no longer exist. Additionally, I
18did not want to install a third party extension to do this, as some 15did not want to install a third party extension to do this, as some
@@ -22,13 +19,12 @@ Luckily, I used Javascript for a while a few years ago and figured it
22would be easy enough to script a solution, as long as Tumblr had a 19would be easy enough to script a solution, as long as Tumblr had a
23system for the unlike buttons. 20system for the unlike buttons.
24 21
25### Identifying Unlike Buttons 22** Identifying Unlike Buttons
26
27Tumblr's unlike buttons are structured as you can see in the following 23Tumblr's unlike buttons are structured as you can see in the following
28code block. All unlike buttons have an `aria-label` with a 24code block. All unlike buttons have an =aria-label= with a value of
29value of `Unlike`. 25=Unlike=.
30 26
31```html 27#+begin_src html
32<button class="TRX6J" aria-label="Unlike"> 28<button class="TRX6J" aria-label="Unlike">
33 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1"> 29 <span class="EvhBA B1Z5w ztpfZ" tabindex="-1">
34 <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23" 30 <svg xmlns="http://www.w3.org/2000/svg" height="21" width="23"
@@ -37,56 +33,53 @@ value of `Unlike`.
37 </svg> 33 </svg>
38 </span> 34 </span>
39</button> 35</button>
40``` 36#+end_src
41 37
42### Running a Script to Unlike All Likes 38** Running a Script to Unlike All Likes
43 39To run this script, you will need to load the
44To run this script, you will need to load the [Likes \| 40[[https://www.tumblr.com/likes][Likes | Tumblr]] page while logged in to
45Tumblr](https://www.tumblr.com/likes) page while logged in to your 41your account.
46account.
47 42
48Further, be sure to scroll down to the bottom and force Tumblr to load 43Further, be sure to scroll down to the bottom and force Tumblr to load
49more posts so that this script unlikes more posts at a time. 44more posts so that this script unlikes more posts at a time.
50 45
51Once you are logged in and the page is loaded, open the Developer Tools 46Once you are logged in and the page is loaded, open the Developer Tools
52and be sure you're on the "Console" tab. It should look something 47and be sure you're on the "Console" tab. It should look something like
53like this (this is in Firefox, Chromium should be similar): 48this (this is in Firefox, Chromium should be similar):
54 49
55![Firefox Dev 50#+caption: Firefox Dev !Tools
56!Tools](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png) 51[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/dev_console.png]]
57 52
58All you need to do is paste the following snippet into the dev console. 53All you need to do is paste the following snippet into the dev console.
59This code will collect all unlike buttons (`elements`) and 54This code will collect all unlike buttons (=elements=) and then click
60then click each button to unlike it. 55each button to unlike it.
61 56
62Optionally, you can comment-out the line 57Optionally, you can comment-out the line =elements[i].click();= and
63`elements[i].click();` and uncomment the 58uncomment the =console.log()= lines to simply print out information
64`console.log()` lines to simply print out information without 59without performing any actions. This can be useful to debug issues or
65performing any actions. This can be useful to debug issues or confirm 60confirm that the code below isn't doing anything you don't want it to.
66that the code below isn't doing anything you don't want it to.
67 61
68```javascript 62#+begin_src javascript
69const elements = document.querySelectorAll('[aria-label="Unlike"]'); 63const elements = document.querySelectorAll('[aria-label="Unlike"]');
70// console.log(elements); // 👉 [button] 64// console.log(elements); // 👉 [button]
71  65
72for (let i=0; i < elements.length; i++) { 66for (let i=0; i < elements.length; i++) {
73 // console.log(elements[i]); 67 // console.log(elements[i]);
74 elements[i].click(); 68 elements[i].click();
75} 69}
76``` 70#+end_src
77
78## Results
79 71
80The results were quick for my situation, as it unliked \~200 posts 72* Results
81within 2-3 seconds. I am not sure how this will perform on larger sets 73The results were quick for my situation, as it unliked ~200 posts within
82of likes (or if Tumblr has a limit to unliking posts). 742-3 seconds. I am not sure how this will perform on larger sets of likes
75(or if Tumblr has a limit to unliking posts).
83 76
84You can see the below screenshot showing that I pasted the snippet into 77You can see the below screenshot showing that I pasted the snippet into
85the console, pressed Enter, and then the posts are automatically 78the console, pressed Enter, and then the posts are automatically
86unliked. 79unliked.
87 80
88![Script 81#+caption: Script !Results
89!Results](https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png) 82[[https:///img.cleberg.net/blog/20230105-mass-unlike-tumblr-posts/script_results.png]]
90 83
91Thinking about this further, I would bet that this would be fairly 84Thinking about this further, I would bet that this would be fairly
92simple to package into a browser add-on so that users could install the 85simple to package into a browser add-on so that users could install the
content/blog/2020-10-12-mediocrity.md → blog/mediocrity/index.org renamed +47 −45
@@ -1,11 +1,9 @@
1+++ 1#+title: On the Pursuit of Mediocrity
2date = 2020-10-12 2#+date: 2020-10-12
3title = "On the Pursuit of Mediocrity" 3#+description: Musings on mediocrity.
4description = "Musings on mediocrity." 4#+filetags: :personal:
5+++
6
7# Perfect is the Enemy of Good
8 5
6* Perfect is the Enemy of Good
9As the saying goes, "the best is the enemy of the good." As we strive 7As the saying goes, "the best is the enemy of the good." As we strive
10for perfection, we often fail to realize the implications of such an 8for perfection, we often fail to realize the implications of such an
11undertaking. Attempting to reach perfection is often unrealistic. Even 9undertaking. Attempting to reach perfection is often unrealistic. Even
@@ -15,36 +13,37 @@ burned the bridges that would have allowed us to reach a lesser yet
15still superb solution. 13still superb solution.
16 14
17Philosophers throughout history have inspected this plight from many 15Philosophers throughout history have inspected this plight from many
18viewpoints. Greek mythology speaks of the [golden 16viewpoints. Greek mythology speaks of the
19mean](https://en.wikipedia.org/wiki/Golden_mean_(philosophy)), which 17[[https://en.wikipedia.org/wiki/Golden_mean_(philosophy)][golden mean]],
20uses the story of Icarus to illustrate that sometimes "the middle 18which uses the story of Icarus to illustrate that sometimes "the middle
21course" is the best solution. In this story, Daedalus, a famous artist 19course" is the best solution. In this story, Daedalus, a famous artist
22of his time, built feathered wings for himself and his son so that they 20of his time, built feathered wings for himself and his son so that they
23might escape the clutches of King Minos. Daedalus warns his beloved son 21might escape the clutches of King Minos. Daedalus warns his beloved son
24whom he loved so much to "fly the middle course", between the sea 22whom he loved so much to "fly the middle course", between the sea spray
25spray and the sun's heat. Icarus did not heed his father; he flew up 23and the sun's heat. Icarus did not heed his father; he flew up and up
26and up until the sun melted the wax off his wings. For not heeding the 24until the sun melted the wax off his wings. For not heeding the middle
27middle course, he fell into the sea and drowned. 25course, he fell into the sea and drowned.
28 26
29More recently, management scholars have explored the [Pareto 27More recently, management scholars have explored the
30principle](https://en.wikipedia.org/wiki/Pareto_principle) and found 28[[https://en.wikipedia.org/wiki/Pareto_principle][Pareto principle]] and
31that as we increase the frequency of something, or strive to perform 29found that as we increase the frequency of something, or strive to
32actions to achieve some form of perfection, we run into [diminishing 30perform actions to achieve some form of perfection, we run into
33returns](https://en.wikipedia.org/wiki/Diminishing_returns). 31[[https://en.wikipedia.org/wiki/Diminishing_returns][diminishing
34 32returns]].
35Even further, Harold Demsetz is noted as coining the term [the Nirvana 33
36fallacy](https://en.wikipedia.org/wiki/Nirvana_fallacy) in 1969, which 34Even further, Harold Demsetz is noted as coining the term
37shows the fallacy of comparing actual things with unrealistic, idealized 35[[https://en.wikipedia.org/wiki/Nirvana_fallacy][the Nirvana fallacy]]
38alternatives. This is another trap that we may fall into, where we are 36in 1969, which shows the fallacy of comparing actual things with
39constantly thinking of the ultimate solutions to problems, when 37unrealistic, idealized alternatives. This is another trap that we may
40something more realistic needs to be considered. 38fall into, where we are constantly thinking of the ultimate solutions to
39problems, when something more realistic needs to be considered.
41 40
42Over and over throughout history, we've found that perfection is often 41Over and over throughout history, we've found that perfection is often
43unrealistic and unachievable. However, we push ourselves and our peers 42unrealistic and unachievable. However, we push ourselves and our peers
44to "give 100%" or "go the extra mile," while it may be that the 43to "give 100%" or "go the extra mile," while it may be that the better
45better course is to give a valuable level of effort while considering 44course is to give a valuable level of effort while considering the
46the effects of further effort on the outcome. Working harder does not 45effects of further effort on the outcome. Working harder does not always
47always help us achieve loftier goals. 46help us achieve loftier goals.
48 47
49This has presented itself to me most recently during my time studying at 48This has presented itself to me most recently during my time studying at
50my university. I was anxious and feeling the stresses of my courses, 49my university. I was anxious and feeling the stresses of my courses,
@@ -53,8 +52,10 @@ how well I was doing at school and my level of effort at work. One day,
53I happened to be talking to my father when he said something simple that 52I happened to be talking to my father when he said something simple that
54hit home: 53hit home:
55 54
56> All you can do is show up and do your best. Worrying about the 55#+begin_quote
57> outcomes won't affect the outcome itself. 56All you can do is show up and do your best. Worrying about the outcomes
57won't affect the outcome itself.
58#+end_quote
58 59
59The thought was extremely straightforward and uncomplicated, yet it was 60The thought was extremely straightforward and uncomplicated, yet it was
60something that I had lost sight of during my stress-filled years at 61something that I had lost sight of during my stress-filled years at
@@ -63,8 +64,7 @@ quote every time I get anxious or stressed. It helps to stop and think
63"Can I do anything to affect the outcome, or am I simply worrying over 64"Can I do anything to affect the outcome, or am I simply worrying over
64something I can't change?" 65something I can't change?"
65 66
66# When Mediocrity Isn't Enough 67* When Mediocrity Isn't Enough
67
68One problem with the philosophies presented in this post is that they 68One problem with the philosophies presented in this post is that they
69are implemented far too often in situations where mediocrity simply 69are implemented far too often in situations where mediocrity simply
70isn't adequate. For example, let's take a look at digital user data, 70isn't adequate. For example, let's take a look at digital user data,
@@ -73,21 +73,23 @@ cybersecurity auditor in the United States, I have found that most
73companies are concerned more with compliance than any actual safeguards 73companies are concerned more with compliance than any actual safeguards
74over the privacy or protection of user data. Other than companies who 74over the privacy or protection of user data. Other than companies who
75have built their reputation on privacy and security, most companies will 75have built their reputation on privacy and security, most companies will
76use [satisficing](https://en.wikipedia.org/wiki/Satisficing) as their 76use [[https://en.wikipedia.org/wiki/Satisficing][satisficing]] as their
77primary decision-making strategy around user data. 77primary decision-making strategy around user data.
78 78
79> Satisficing is a decision-making strategy or cognitive heuristic that 79#+begin_quote
80> entails searching through the available alternatives until an 80Satisficing is a decision-making strategy or cognitive heuristic that
81> acceptability threshold is met. 81entails searching through the available alternatives until an
82acceptability threshold is met.
83#+end_quote
82 84
83This means that each decision will be met with certain possible 85This means that each decision will be met with certain possible
84solutions until one of the solutions meets their minimum acceptable 86solutions until one of the solutions meets their minimum acceptable
85standards. For companies that deal with user data, the 87standards. For companies that deal with user data, the
86minimum-acceptable standards come from three areas: 88minimum-acceptable standards come from three areas:
87 89
881. Laws and regulations 901. Laws and regulations
892. Competitive pressure 912. Competitive pressure
903. Risk of monetary or reputation loss 923. Risk of monetary or reputation loss
91 93
92Working with project management or auditing, the primary concern here is 94Working with project management or auditing, the primary concern here is
93the risk of legal ramifications. Since the primary risk comes from laws 95the risk of legal ramifications. Since the primary risk comes from laws
@@ -110,9 +112,9 @@ measure of how well a company or manager is performing, so we often
110don't think about our responsibilities beyond these basic standards. 112don't think about our responsibilities beyond these basic standards.
111 113
112Not all situations demand excellence, but I believe that applying any 114Not all situations demand excellence, but I believe that applying any
113philosophy as a broad stroke across one's life can be a mistake. We 115philosophy as a broad stroke across one's life can be a mistake. We must
114must be able to think critically about what we are doing as we do it and 116be able to think critically about what we are doing as we do it and ask
115ask ourselves a few questions. Have I done everything I can in this 117ourselves a few questions. Have I done everything I can in this
116situation? Is mediocrity an acceptable outcome, or should we strive for 118situation? Is mediocrity an acceptable outcome, or should we strive for
117perfection, even if we can't attain it? 119perfection, even if we can't attain it?
118 120
content/blog/2022-10-04-mtp-linux.md → blog/mtp-linux/index.org renamed +28 −35
@@ -1,16 +1,14 @@
1+++ 1#+title: How to Mount an MTP Mobile Device on Fedora Linux
2date = 2022-10-04 2#+date: 2022-10-04
3title = "How to Mount an MTP Mobile Device on Fedora Linux" 3#+description: Learn how to mount an MTP mobile device on Fedora Linux.
4description = "Learn how to mount an MTP mobile device on Fedora Linux." 4#+filetags: :linux:
5+++
6 5
7I recently ran into trouble attempting to mount my GrapheneOS phone to 6I recently ran into trouble attempting to mount my GrapheneOS phone to
8my laptop running Fedora Linux via the [Media Transfer 7my laptop running Fedora Linux via the
9Protocol](https://en.wikipedia.org/wiki/Media_transfer_protocol) (MTP) 8[[https://en.wikipedia.org/wiki/Media_transfer_protocol][Media Transfer
10and discovered a simple and effective solution. 9Protocol]] (MTP) and discovered a simple and effective solution.
11
12## Use a USB 3.0 Port
13 10
11* Use a USB 3.0 Port
14First, ensure that the device was plugged in to the laptop through a USB 12First, ensure that the device was plugged in to the laptop through a USB
153.0 port, if possible. From a brief glance online, it seems that USB 2.0 133.0 port, if possible. From a brief glance online, it seems that USB 2.0
16ports may cause issues with dropped connections over MTP. This is purely 14ports may cause issues with dropped connections over MTP. This is purely
@@ -18,63 +16,58 @@ anecdotal since I don't have any evidence to link showing that USB 2.0
18causes issues, but I can confirm that switching to a USB 3.0 port seemed 16causes issues, but I can confirm that switching to a USB 3.0 port seemed
19to cut out most of my issues. 17to cut out most of my issues.
20 18
21## Switch USB Preferences to MTP 19* Switch USB Preferences to MTP
22
23Secondly, you need to ensure that the phone's USB preferences/mode is 20Secondly, you need to ensure that the phone's USB preferences/mode is
24changed to MTP or File Transfer once the phone is plugged in. Other 21changed to MTP or File Transfer once the phone is plugged in. Other
25modes will not allow you to access the phone's file system. 22modes will not allow you to access the phone's file system.
26 23
27## Install `jmtpfs` 24* Install =jmtpfs=
28 25Next, I used the =jmtpfs= package to mount my phone to my laptop. There
29Next, I used the `jmtpfs` package to mount my phone to my 26are other packages that exist, but this one worked perfectly for me. On
30laptop. There are other packages that exist, but this one worked 27Fedora Linux, you can install it like this:
31perfectly for me. On Fedora Linux, you can install it like this:
32 28
33```sh 29#+begin_src sh
34sudo dnf install jmtpfs -y 30sudo dnf install jmtpfs -y
35``` 31#+end_src
36
37## Create a Mount Point
38 32
33* Create a Mount Point
39Once you have the package installed, you just need to create a folder 34Once you have the package installed, you just need to create a folder
40for the device to use as a mount point. In my case, I used 35for the device to use as a mount point. In my case, I used =/mnt/pixel=:
41`/mnt/pixel`:
42 36
43```sh 37#+begin_src sh
44sudo mkdir /mnt/pixel 38sudo mkdir /mnt/pixel
45sudo chown -R $USER:$USER /mnt/pixel 39sudo chown -R $USER:$USER /mnt/pixel
46``` 40#+end_src
47
48## Mount & Access the Phone's File System
49 41
42* Mount & Access the Phone's File System
50Finally, plug-in and mount the device, and you should be able to see all 43Finally, plug-in and mount the device, and you should be able to see all
51storage (internal and external) inside your new folder! 44storage (internal and external) inside your new folder!
52 45
53```sh 46#+begin_src sh
54jmtpfs /mnt/pixel 47jmtpfs /mnt/pixel
55``` 48#+end_src
56 49
57The output should look something like this: 50The output should look something like this:
58 51
59```sh 52#+begin_src sh
60Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP). 53Device 0 (VID=18d1 and PID=4ee1) is a Google Inc Nexus/Pixel (MTP).
61Android device detected, assigning default bug flags 54Android device detected, assigning default bug flags
62``` 55#+end_src
63 56
64Now you are mounted and can do anything you'd like with the device's 57Now you are mounted and can do anything you'd like with the device's
65files: 58files:
66 59
67```sh 60#+begin_src sh
68cd /mnt/pixel 61cd /mnt/pixel
69ls -lha 62ls -lha
70``` 63#+end_src
71 64
72From here, you will be able to see any internal or external storage 65From here, you will be able to see any internal or external storage
73available on the device: 66available on the device:
74 67
75```sh 68#+begin_src sh
76total 0 69total 0
77drwxr-xr-x. 3 user user 0 Jan 1 1970 . 70drwxr-xr-x. 3 user user 0 Jan 1 1970 .
78drwxr-xr-x. 1 root root 10 Oct 4 13:29 .. 71drwxr-xr-x. 1 root root 10 Oct 4 13:29 ..
79drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage' 72drwxr-xr-x. 16 user user 0 Apr 21 4426383 'Internal shared storage'
80``` 73#+end_src
blog/neon-drive/index.org added +93
@@ -0,0 +1,93 @@
1#+title: Neon Drive: A Nostalgic 80s Arcade Racing Game
2#+date: 2020-12-28
3#+description: A video game review for Neon Drive.
4#+filetags: :gaming:
5
6* Game Description
7[[https://store.steampowered.com/app/433910/Neon_Drive/][Neon Drive]]
8presents itself as a simple arcade-style game inspired by the arcade
9race games of the 1980s, yet it has managed to take up hours of my life
10without much effort. The game description, directly from the Steam page,
11is intriguing enough to entice anyone who's been looking for a good
12arcade racing game:
13
14#+begin_quote
15Neon Drive is a slick retro-futuristic arcade game that will make your
16brain melt. You've been warned. From beautiful cityscapes and ocean
17roads to exploding enemy spaceships, Neon Drive has it all.
18
19#+end_quote
20
21* Gameplay
22The game holds true to the
23[[https://en.wikipedia.org/wiki/Retrofuturism][retro-futurism]] style,
24including chrome female robots, pixelated arcade machines, and
25[[https://teddit.net/r/outrun/][outrun]] aesthetics.
26
27Each level of the game is shown as a separate arcade machine. Each
28arcade machine lets you play on Normal, Hard, Insane, Practice, and Free
29Run. To beat each arcade, you must reach the end of the level without
30crashing your car into the various obstacles on the course. Basic levels
31let you move left or right to avoid blocks in the road. Later levels put
32you through other tests, such as dodging traffic or blasting asteroids.
33
34The game uses synthwave music to keep you on track to make the correct
35moves by timing the beats of the songs to the correct moves on the
36screen. It reminds me of the early Guitar Hero games, as well as mobile
37apps like VOEZ - repetition and staying on-beat is the only way to win.
38
39* In-Game Screenshots
40Taking a look at the main menu, you can see that Neon Drive plays into
41every stereotype you can think of around retro-futuristic, synthwave
42arcades (in a good way).
43
44#+caption: Neon Drive Menu
45[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_menu.png]]
46
47Once you get into the first level, we see that the choice of car fits
48right in with the stereotypical cars of the 80s, like the
49[[https://en.wikipedia.org/wiki/DMC_DeLorean][DeLorean]] or the
50[[https://en.wikipedia.org/wiki/Ferrari_F40][Ferrari F40]]. Each new
51level comes with new color schemes and cars, so you should never get
52tired of the aesthetic.
53
54#+caption: Neon Drive Race
55[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_race.png]]
56
57Personally, I love the orange and blue colors used in level 2:
58
59#+caption: Level 2
60[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_level_2.png]]
61
62If you're the competitive type and getting 100% on all arcade machines
63isn't enough, there are leaderboards for the regular part of the game,
64and the endurance game mode.
65
66#+caption: Leaderboard
67[[https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_leaderboard.png]]
68
69* Other Suggestions
70Neon Drive sits nicely within the well-founded cult genre of Outrun.
71Other games that I've enjoyed in this same spectrum are:
72
73- [[https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/][Far
74 Cry 3: Blood Dragon]]
75- [[https://store.steampowered.com/app/1239690/Retrowave/][Retrowave]]
76- [[https://store.steampowered.com/app/732810/Slipstream/][Slipstream]]
77
78Although these games aren't necessarily in the same genre, they do have
79aspects that place them close enough to interest gamers that enjoyed
80Neon Drive:
81
82- [[https://store.steampowered.com/app/311800/Black_Ice/][Black Ice]]
83- [[https://store.steampowered.com/app/746850/Cloudpunk/][Cloudpunk]]
84- [[https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/][Need
85 for Speed: Heat]]
86- [[https://store.steampowered.com/app/1019310/VirtuaVerse/][VirtuaVerse]]
87
88Of course, if all you really care about is the arcade aspect of these
89games, you can check out the
90[[https://store.steampowered.com/app/400020/Atari_Vault/][Atari Vault]]
91or any of the other classic games sold on Steam by companies like Namco,
92Atari. For something like Nintendo, you'd have to settle for buying used
93classic consoles or delve into the world of emulation.
content/blog/2022-03-23-nextcloud-on-ubuntu.md → blog/nextcloud-on-ubuntu/index.org renamed +58 −68
@@ -1,83 +1,77 @@
1+++ 1#+title: Nextcloud on Ubuntu
2date = 2022-03-23 2#+date: 2022-03-23
3title = "Installing Nextcloud on Ubuntu" 3#+description: A guide to self-hosting the NextCloud application on your own server.
4description = "A guide to self-hosting the NextCloud application on your own server." 4#+filetags: :selfhosting:
5+++
6 5
7## What is Nextcloud? 6* What is Nextcloud?
8 7[[https://nextcloud.com/][Nextcloud]] is a self-hosted solution for
9[Nextcloud](https://nextcloud.com/) is a self-hosted solution for
10storage, communications, editing, calendar, contacts, and more. 8storage, communications, editing, calendar, contacts, and more.
11 9
12This tutorial assumes that you have an Ubuntu server and a domain name 10This tutorial assumes that you have an Ubuntu server and a domain name
13configured to point toward the server. 11configured to point toward the server.
14 12
15## Install Dependencies 13* Install Dependencies
16
17To start, you will need to install the packages that Nextcloud requires: 14To start, you will need to install the packages that Nextcloud requires:
18 15
19```sh 16#+begin_src sh
20sudo apt install apache2 mariadb-server libapache2-mod-php7.4 17sudo apt install apache2 mariadb-server libapache2-mod-php7.4
21sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl 18sudo apt install php7.4-gd php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl
22sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip 19sudo apt install php7.4-gmp php7.4-bcmath php-imagick php7.4-xml php7.4-zip
23``` 20#+end_src
24 21
25## Set Up MySQL 22* Set Up MySQL
23Next, you will need to log in to MySQL as the =root= user of the
24machine.
26 25
27Next, you will need to log in to MySQL as the `root` user of 26#+begin_src sh
28the machine.
29
30```sh
31sudo mysql -uroot -p 27sudo mysql -uroot -p
32``` 28#+end_src
33 29
34Once you've logged in, you must create a new user so that Nextcloud can 30Once you've logged in, you must create a new user so that Nextcloud can
35manage the database. You will also create a `nextcloud` 31manage the database. You will also create a =nextcloud= database and
36database and assign privileges: 32assign privileges:
37 33
38```sql 34#+begin_src sql
39CREATE USER 'username'@'localhost' IDENTIFIED BY 'password'; 35CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
40CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 36CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
41GRANT ALL PRIVILEGES ON nextcloud.* TO 'username'@'localhost'; 37GRANT ALL PRIVILEGES ON nextcloud.** TO 'username'@'localhost';
42FLUSH PRIVILEGES; 38FLUSH PRIVILEGES;
43quit; 39quit;
44``` 40#+end_src
45
46## Download & Install Nextcloud
47 41
48To download Nextcloud, go the [Nextcloud downloads 42* Download & Install Nextcloud
49page](https://nextcloud.com/install/#instructions-server), click on 43To download Nextcloud, go the
50`Archive File` and right-click the big blue button to copy 44[[https://nextcloud.com/install/#instructions-server][Nextcloud
51the link. 45downloads page]], click on =Archive File= and right-click the big blue
46button to copy the link.
52 47
53Then, go to your server and enter the following commands to download, 48Then, go to your server and enter the following commands to download,
54unzip, and move the files to your destination directory. This example 49unzip, and move the files to your destination directory. This example
55uses `example.com` as the destination, but you can put it 50uses =example.com= as the destination, but you can put it wherever you
56wherever you want to server your files from. 51want to server your files from.
57 52
58```sh 53#+begin_src sh
59wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip 54wget https://download.nextcloud.com/server/releases/nextcloud-23.0.3.zip
60sudo apt install unzip 55sudo apt install unzip
61unzip nextcloud-23.0.3.zip 56unzip nextcloud-23.0.3.zip
62sudo cp -r nextcloud /var/www/example.com 57sudo cp -r nextcloud /var/www/example.com
63``` 58#+end_src
64
65## Configure the Apache Web Server
66 59
60* Configure the Apache Web Server
67Now that the database is set up and Nextcloud is installed, you need to 61Now that the database is set up and Nextcloud is installed, you need to
68set up the Apache configuration files to tell the server how to handle 62set up the Apache configuration files to tell the server how to handle
69requests for `example.com/nextcloud`. 63requests for =example.com/nextcloud=.
70 64
71First, open the following file in the editor: 65First, open the following file in the editor:
72 66
73```sh 67#+begin_src sh
74sudo nano /etc/apache2/sites-available/nextcloud.conf 68sudo nano /etc/apache2/sites-available/nextcloud.conf
75``` 69#+end_src
76 70
77Once the editor is open, paste the following information in. Then, save 71Once the editor is open, paste the following information in. Then, save
78and close the file. 72and close the file.
79 73
80```config 74#+begin_src config
81<VirtualHost *:80> 75<VirtualHost *:80>
82 DocumentRoot /var/www/example.com 76 DocumentRoot /var/www/example.com
83 ServerName example.com 77 ServerName example.com
@@ -96,74 +90,70 @@ and close the file.
96 </IfModule> 90 </IfModule>
97 </Directory> 91 </Directory>
98</VirtualHost> 92</VirtualHost>
99``` 93#+end_src
100 94
101Once the file is saved, enable it with Apache: 95Once the file is saved, enable it with Apache:
102 96
103```sh 97#+begin_src sh
104sudo a2ensite nextcloud.conf 98sudo a2ensite nextcloud.conf
105``` 99#+end_src
106 100
107Next, enable the Apache mods required by Nextcloud: 101Next, enable the Apache mods required by Nextcloud:
108 102
109```sh 103#+begin_src sh
110sudo a2enmod rewrite headers env dir mime 104sudo a2enmod rewrite headers env dir mime
111``` 105#+end_src
112 106
113Finally, restart Apache. If any errors arise, you must solve those 107Finally, restart Apache. If any errors arise, you must solve those
114before continuing. 108before continuing.
115 109
116```sh 110#+begin_src sh
117sudo systemctl restart apache2 111sudo systemctl restart apache2
118``` 112#+end_src
119 113
120For the app to work, you must have the correct file permissions on your 114For the app to work, you must have the correct file permissions on your
121`nextcloud` directory. Set the owner to be 115=nextcloud= directory. Set the owner to be =www-data=:
122`www-data`:
123 116
124```sh 117#+begin_src sh
125sudo chown -R www-data:www-data /var/www/example.com/nextcloud/ 118sudo chown -R www-data:www-data /var/www/example.com/nextcloud/
126``` 119#+end_src
127
128## DNS
129 120
121* DNS
130If you do not have a static IP address, you will need to update your DNS 122If you do not have a static IP address, you will need to update your DNS
131settings (at your DNS provider) whenever your dynamic IP address 123settings (at your DNS provider) whenever your dynamic IP address
132changes. 124changes.
133 125
134For an example on how I do that with Cloudflare, see my other post: 126For an example on how I do that with Cloudflare, see my other post:
135[Updating Dynamic DNS with Cloudflare 127[[../updating-dynamic-dns-with-cloudflare-api/][Updating Dynamic DNS
136API](/blog/updating-dynamic-dns-with-cloudflare-api/) 128with Cloudflare API]]
137
138## Certbot
139 129
130* Certbot
140If you want to serve Nextcloud from HTTPS rather than plain HTTP, use 131If you want to serve Nextcloud from HTTPS rather than plain HTTP, use
141the following commands to issue Let's Encrypt SSL certificates: 132the following commands to issue Let's Encrypt SSL certificates:
142 133
143```sh 134#+begin_src sh
144sudo apt install snapd 135sudo apt install snapd
145sudo snap install core 136sudo snap install core
146sudo snap refresh core 137sudo snap refresh core
147sudo snap install --classic certbot 138sudo snap install --classic certbot
148sudo ln -s /snap/bin/certbot /usr/bin/certbot 139sudo ln -s /snap/bin/certbot /usr/bin/certbot
149sudo certbot --apache 140sudo certbot --apache
150``` 141#+end_src
151
152## Results
153 142
143* Results
154Voilà! You're all done and should be able to access Nextcloud from your 144Voilà! You're all done and should be able to access Nextcloud from your
155domain or IP address. 145domain or IP address.
156 146
157See the screenshots below for the dashboard and a settings page on my 147See the screenshots below for the dashboard and a settings page on my
158instance of Nextcloud, using the `Breeze Dark` theme I 148instance of Nextcloud, using the =Breeze Dark= theme I installed from
159installed from the Apps page. 149the Apps page.
160 150
161![Nextcloud 151#+caption: Nextcloud Dashboard
162Dashboard](https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_dashboard.png) 152[[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_dashboard.png]]
163 153
164*Figure 01: Nextcloud Dashboard* 154/Figure 01: Nextcloud Dashboard/
165 155
166![Nextcloud 156#+caption: Nextcloud Settings
167Settings](https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_settings.png) 157[[https://img.cleberg.net/blog/20220323-installing-nextcloud-on-ubuntu/nextcloud_settings.png]]
168 158
169*Figure 02: Nextcloud Security Settings* 159/Figure 02: Nextcloud Security Settings/
blog/nginx-caching/index.org added +68
@@ -0,0 +1,68 @@
1#+title: Caching Static Content with Nginx
2#+date: 2022-02-20
3#+description: Learn how to enable the static content cache in Nginx.
4#+filetags: :nginx:
5
6* Update Your Nginx Config to Cache Static Files
7If you run a website on Nginx that serves static content (i.e., content
8that is not dynamic and changing with interactions from the user), you
9would likely benefit from caching that content on the client-side. If
10you're used to Apache and looking for the Nginx equivalent, this post
11should help.
12
13Luckily, setting up the cache is as easy as identifying the file types
14you want to cache and determining the expiration length. To include more
15file types, simply use the bar separator (=|=) and type the new file
16extension you want to include.
17
18#+begin_src config
19server {
20 ...
21
22 location ~** .(css|js|jpg|jpeg|gif|png|ico)$ {
23 expires 30d;
24 }
25
26 ...
27}
28#+end_src
29
30I have seen some people who prefer to set =expires= as =365d= or even
31=max=, but that is only for stable, infrequently changing websites. As
32my site often changes (i.e., I'm never content with my website), I need
33to know that my readers are seeing the new content without waiting too
34long.
35
36So, I went ahead and set the expiration date at =30d=, which is short
37enough to refresh for readers but long enough that clients/browsers
38won't be re-requesting the static files too often, hopefully resulting
39in faster loading times, as images should be the only thing slowing down
40my site.
41
42* Testing Results
43To test my changes to the Nginx configuration, I used the
44[[https://addons.mozilla.org/en-US/firefox/addon/http-header-live/][HTTP
45Header Live]] extension on my Gecko browser and used the sidebar to
46inspect the headers of a recent image from my blog.
47
48In the image below, you can see that the =Cache-Control= header is now
49present and set to 2592000, which is 30 days represented in seconds (30
50days _ 24 hours/day _ 60 minutes/hour ** 60 seconds/minute = 2,592,000
51seconds).
52
53The =Expires= field is now showing 22 March 2022, which is 30 days from
54the day of this post, 20 February 2022.
55
56#+caption: Image Headers
57[[https://img.cleberg.net/blog/20220220-caching-static-content-with-nginx/image_headers.png]]
58
59* Caveats
60Remember that this caching system is *client-side*, which means that
61content is only cached for as long as a client allows it. For example,
62my browser purges all caches, data, etc. upon exit, so this caching
63policy will only work as long as my browser remains open and running.
64
65If you need to test updates to your site, you'll need to clear the cache
66to see updates for any file extension you configured. This can often be
67done with the =Shift + F5= or =Ctrl + F5= key combinations in most
68browsers.
blog/nginx-compression/index.org added +73
@@ -0,0 +1,73 @@
1#+title: Enable GZIP Compression in Nginx
2#+date: 2022-12-01
3#+description: Learn how to enable compression in Nginx.
4#+filetags: :nginx:
5
6* Text Compression
7Text compression allows a web server to serve text-based resources
8faster than uncompressed data. This can speed up things like First
9Contentful Paint, Tie to Interactive, and Speed Index.
10
11* Enable Nginx Compression with gzip
12In order to enable text compression on Nginx, we need to enable it
13within the configuration file:
14
15#+begin_src sh
16nano /etc/nginx/nginx.conf
17#+end_src
18
19Within the =http= block, find the section that shows something like the
20block below. This is the default gzip configuration I found in my
21=nginx.conf= file on Alpine Linux 3.17. Yours may look slightly
22different, just make sure that you're not creating any duplicate gzip
23options.
24
25#+begin_src conf
26# Enable gzipping of responses.
27#gzip on;
28
29# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
30gzip_vary on;
31#+end_src
32
33Remove the default gzip lines and replace them with the following:
34
35#+begin_src conf
36# Enable gzipping of responses.
37gzip on;
38gzip_vary on;
39gzip_min_length 10240;
40gzip_proxied expired no-cache no-store private auth;
41gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
42gzip_disable "MSIE [1-6]";
43#+end_src
44
45* Explanations of ngx_{httpgzipmodule} Options
46Each of the lines above enables a different aspect of the gzip response
47for Nginx. Here are the full explanations:
48
49- =gzip= -- Enables or disables gzipping of responses.
50- =gzip_vary= -- Enables or disables inserting the "Vary:
51 Accept-Encoding" response header field if the directives gzip,
52 gzip_{static}, or gunzip are active.
53- =gzip_min_length= -- Sets the minimum length of a response that will
54 be gzipped. The length is determined only from the "Content-Length"
55 response header field.
56- =gzip_proxied= -- Enables or disables gzipping of responses for
57 proxied requests depending on the request and response. The fact that
58 the request is proxied is determined by the presence of the "Via"
59 request header field.
60- =gzip_types= -- Enables gzipping of responses for the specified MIME
61 types in addition to "text/html". The special value “*” matches any
62 MIME type (0.8.29). Responses with the "text/html" type are always
63 compressed.
64- =gzip_disable= -- Disables gzipping of responses for requests with
65 "User-Agent" header fields matching any of the specified regular
66 expressions.
67 - The special mask "msie6" (0.7.12) corresponds to the regular
68 expression "MSIE [4-6].", but works faster. Starting from version
69 0.8.11, "MSIE 6.0; ... SV1" is excluded from this mask.
70
71More information on these directives and their options can be found on
72the [[https://nginx.org/en/docs/http/ngx_http_gzip_module.html][Module
73ngx_{httpgzipmodule}]] page in Nginx's documentation.
content/blog/2022-11-29-nginx-referrer-ban-list.md → blog/nginx-referrer-ban-list/index.org renamed +46 −53
@@ -1,25 +1,23 @@
1+++ 1#+title: Creating a Referrer Ban List in Nginx
2date = 2022-11-29 2#+date: 2022-11-29
3title = "Creating a Referrer Ban List in Nginx" 3#+description: Learn how to create a ban list for referring sites in Nginx.
4description = "Learn how to create a ban list for referring sites in Nginx." 4#+filetags: :nginx:
5+++
6
7## Creating the Ban List
8 5
6* Creating the Ban List
9In order to ban list referral domains or websites with Nginx, you need 7In order to ban list referral domains or websites with Nginx, you need
10to create a ban list file. The file below will accept regexes for 8to create a ban list file. The file below will accept regexes for
11different domains or websites you wish to block. 9different domains or websites you wish to block.
12 10
13First, create the file in your nginx directory: 11First, create the file in your nginx directory:
14 12
15```sh 13#+begin_src sh
16doas nano /etc/nginx/banlist.conf 14doas nano /etc/nginx/banlist.conf
17``` 15#+end_src
18 16
19Next, paste the following contents in and fill out the regexes with 17Next, paste the following contents in and fill out the regexes with
20whichever domains you're blocking. 18whichever domains you're blocking.
21 19
22```conf 20#+begin_src conf
23# /etc/nginx/banlist.conf 21# /etc/nginx/banlist.conf
24 22
25map $http_referer $bad_referer { 23map $http_referer $bad_referer {
@@ -30,21 +28,20 @@ map $http_referer $bad_referer {
30 # Put regexes for undesired referrers here 28 # Put regexes for undesired referrers here
31 "~news.ycombinator.com" 1; 29 "~news.ycombinator.com" 1;
32} 30}
33``` 31#+end_src
34
35## Configuring Nginx
36 32
33* Configuring Nginx
37In order for the ban list to work, Nginx needs to know it exists and how 34In order for the ban list to work, Nginx needs to know it exists and how
38to handle it. For this, edit the `nginx.conf` file. 35to handle it. For this, edit the =nginx.conf= file.
39 36
40```sh 37#+begin_src sh
41doas nano /etc/nginx/nginx.conf 38doas nano /etc/nginx/nginx.conf
42``` 39#+end_src
43 40
44Within this file, find the `http` block and add your ban list 41Within this file, find the =http= block and add your ban list file
45file location to the end of the block. 42location to the end of the block.
46 43
47```conf 44#+begin_src conf
48# /etc/nginx/nginx.conf 45# /etc/nginx/nginx.conf
49 46
50http { 47http {
@@ -53,30 +50,28 @@ http {
53 # Include ban list 50 # Include ban list
54 include /etc/nginx/banlist.conf; 51 include /etc/nginx/banlist.conf;
55} 52}
56``` 53#+end_src
57
58## Enabling the Ban List
59 54
55* Enabling the Ban List
60Finally, we need to take action when a bad referral site is found. To do 56Finally, we need to take action when a bad referral site is found. To do
61so, edit the configuration file for your website. For example, I have 57so, edit the configuration file for your website. For example, I have
62all website configuration files in the `http.d` directory. 58all website configuration files in the =http.d= directory. You may have
63You may have them in the `sites-available` directory on some 59them in the =sites-available= directory on some distributions.
64distributions.
65 60
66```sh 61#+begin_src sh
67doas nano /etc/nginx/http.d/example.com.conf 62doas nano /etc/nginx/http.d/example.com.conf
68``` 63#+end_src
69 64
70Within each website's configuration file, edit the `server` 65Within each website's configuration file, edit the =server= blocks that
71blocks that are listening to ports 80 and 443 and create a check for the 66are listening to ports 80 and 443 and create a check for the
72`$bad_referrer` variable we created in the ban list file. 67=$bad_referrer= variable we created in the ban list file.
73 68
74If a matching site is found, you can return any [HTTP Status 69If a matching site is found, you can return any
75Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) you want. 70[[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes][HTTP Status
76Code 403 (Forbidden) is logical in this case since you are preventing a 71Code]] you want. Code 403 (Forbidden) is logical in this case since you
77client connection due to a banned domain. 72are preventing a client connection due to a banned domain.
78 73
79```conf 74#+begin_src conf
80server { 75server {
81 ... 76 ...
82 77
@@ -87,42 +82,40 @@ server {
87 82
88 ... 83 ...
89} 84}
90``` 85#+end_src
91
92## Restart Nginx
93 86
87* Restart Nginx
94Lastly, restart Nginx to enable all changes made. 88Lastly, restart Nginx to enable all changes made.
95 89
96```sh 90#+begin_src sh
97doas rc-service nginx restart 91doas rc-service nginx restart
98``` 92#+end_src
99
100## Testing Results
101 93
94* Testing Results
102In order to test the results, let's curl the contents of our site. To 95In order to test the results, let's curl the contents of our site. To
103start, I'll curl the site normally: 96start, I'll curl the site normally:
104 97
105```sh 98#+begin_src sh
106curl https://cleberg.net 99curl https://cleberg.net
107``` 100#+end_src
108 101
109The HTML contents of the page come back successfully: 102The HTML contents of the page come back successfully:
110 103
111```html 104#+begin_src html
112<!doctype html>...</html> 105<!doctype html>...</html>
113``` 106#+end_src
114 107
115Next, let's include a banned referrer: 108Next, let's include a banned referrer:
116 109
117```sh 110#+begin_src sh
118curl --referer https://news.ycombinator.com https://cleberg.net 111curl --referer https://news.ycombinator.com https://cleberg.net
119``` 112#+end_src
120 113
121This time, I'm met with a 403 Forbidden response page. That means we 114This time, I'm met with a 403 Forbidden response page. That means we are
122are successful and any clients being referred from a banned domain will 115successful and any clients being referred from a banned domain will be
123be met with this same response code. 116met with this same response code.
124 117
125```html 118#+begin_src html
126<html> 119<html>
127<head><title>403 Forbidden</title></head> 120<head><title>403 Forbidden</title></head>
128<body> 121<body>
@@ -130,4 +123,4 @@ be met with this same response code.
130<hr><center>nginx</center> 123<hr><center>nginx</center>
131</body> 124</body>
132</html> 125</html>
133``` 126#+end_src
content/blog/2022-04-02-nginx-reverse-proxy.md → blog/nginx-reverse-proxy/index.org renamed +97 −111
@@ -1,43 +1,40 @@
1+++ 1#+title: Set-Up a Reverse Proxy with Nginx
2date = 2022-04-02 2#+date: 2022-04-02
3title = "Set-Up a Reverse Proxy with Nginx" 3#+description: Learn how to set-up an Nginx reverse proxy from scratch.
4description = "Learn how to set-up an Nginx reverse proxy from scratch." 4#+filetags: :nginx:
5+++
6
7## What is a Reverse Proxy?
8 5
6* What is a Reverse Proxy?
9A reverse proxy is a server that is placed between local servers or 7A reverse proxy is a server that is placed between local servers or
10services and clients/users (e.g., the internet). The reverse proxy 8services and clients/users (e.g., the internet). The reverse proxy
11intercepts all requests from clients at the network edge and uses its 9intercepts all requests from clients at the network edge and uses its
12configuration files to determine where each request should be sent. 10configuration files to determine where each request should be sent.
13 11
14### A Brief Example 12** A Brief Example
15
16For example, let's say that I run three servers in my home: 13For example, let's say that I run three servers in my home:
17 14
18- Server~01~ (`example.com`) 15- Server_{01} (=example.com=)
19- Server~02~ (`service01.example.com`) 16- Server_{02} (=service01.example.com=)
20- Server~03~ (`service02.example.com`) 17- Server_{03} (=service02.example.com=)
21 18
22I also run a reverse proxy in my home that intercepts all public 19I also run a reverse proxy in my home that intercepts all public
23traffic: 20traffic:
24 21
25- Reverse Proxy 22- Reverse Proxy
26 23
27Assume that I have a domain name (`example.com`) that allows 24Assume that I have a domain name (=example.com=) that allows clients to
28clients to request websites or services from my home servers. 25request websites or services from my home servers.
29 26
30In this case, the reverse proxy will intercept all traffic from 27In this case, the reverse proxy will intercept all traffic from
31`example.com` that enters my network and determine if the 28=example.com= that enters my network and determine if the client is
32client is requesting valid data, based on my configuration. 29requesting valid data, based on my configuration.
33 30
34If the user is requesting `example.com` and my configuration 31If the user is requesting =example.com= and my configuration files say
35files say that Server~01~ holds that data, Nginx will send the user to 32that Server_{01} holds that data, Nginx will send the user to
36Server~01~. If I were to change the configuration so that 33Server_{01}. If I were to change the configuration so that =example.com=
37`example.com` is routed to Server~02~, that same user would 34is routed to Server_{02}, that same user would be sent to Server_{02}
38be sent to Server~02~ instead. 35instead.
39 36
40```txt 37#+begin_src txt
41┌──────┐ ┌───────────┐ 38┌──────┐ ┌───────────┐
42│ User │─┐ ┌──► Server_01 │ 39│ User │─┐ ┌──► Server_01 │
43└──────┘ │ │ └───────────┘ 40└──────┘ │ │ └───────────┘
@@ -47,140 +44,130 @@ be sent to Server~02~ instead.
47┌──────┐ │ │ ┌───────────┐ 44┌──────┐ │ │ ┌───────────┐
48│ User │─┘ └──► Server_03 │ 45│ User │─┘ └──► Server_03 │
49└──────┘ └───────────┘ 46└──────┘ └───────────┘
50``` 47#+end_src
51
52## Reverse Proxy Options
53 48
49* Reverse Proxy Options
54There are a lot of options when it comes to reverse proxy servers, so 50There are a lot of options when it comes to reverse proxy servers, so
55I'm just going to list a few of the options I've heard recommended 51I'm just going to list a few of the options I've heard recommended over
56over the last few years: 52the last few years:
57 53
58- [Nginx](https://nginx.com) 54- [[https://nginx.com][Nginx]]
59- [Caddy](https://caddyserver.com) 55- [[https://caddyserver.com][Caddy]]
60- [Traefik](https://traefik.io/) 56- [[https://traefik.io/][Traefik]]
61- [HAProxy](https://www.haproxy.org/) 57- [[https://www.haproxy.org/][HAProxy]]
62- [Squid](https://ubuntu.com/server/docs/proxy-servers-squid) 58- [[https://ubuntu.com/server/docs/proxy-servers-squid][Squid]]
63 59
64In this post, we will be using Nginx as our reverse proxy, running on 60In this post, we will be using Nginx as our reverse proxy, running on
65Ubuntu Server 20.04.4 LTS. 61Ubuntu Server 20.04.4 LTS.
66 62
67## Nginx Reverse Proxy Example 63* Nginx Reverse Proxy Example
68 64** Local Applications
69### Local Applications
70
71You may be like me and have a lot of applications running on your local 65You may be like me and have a lot of applications running on your local
72network that you'd like to expose publicly with a domain. 66network that you'd like to expose publicly with a domain.
73 67
74In my case, I have services running in multiple Docker containers within 68In my case, I have services running in multiple Docker containers within
75a single server and want a way to visit those services from anywhere 69a single server and want a way to visit those services from anywhere
76with a URL. For example, on my local network, [Dashy](https://dashy.to) 70with a URL. For example, on my local network,
77runs through port 4000 (`localhost:4000`) and [Uptime 71[[https://dashy.to][Dashy]] runs through port 4000 (=localhost:4000=)
78Kuma](https://github.com/louislam/uptime-kuma) runs through port 3001 72and [[https://github.com/louislam/uptime-kuma][Uptime Kuma]] runs
79(`localhost:3001`). 73through port 3001 (=localhost:3001=).
80 74
81In order to expose these services to the public, I will need to do the 75In order to expose these services to the public, I will need to do the
82following: 76following:
83 77
841. Set up DNS records for a domain or subdomain (one per service) to 781. Set up DNS records for a domain or subdomain (one per service) to
85 point toward the IP address of the server. 79 point toward the IP address of the server.
862. Open up the server network's HTTP and HTTPS ports (80 & 443) so 802. Open up the server network's HTTP and HTTPS ports (80 & 443) so that
87 that the reverse proxy can accept traffic and determine where to 81 the reverse proxy can accept traffic and determine where to send it.
88 send it. 823. Install the reverse proxy software.
893. Install the reverse proxy software. 834. Configure the reverse proxy to recognize which service should get
904. Configure the reverse proxy to recognize which service should get 84 traffic from any of the domains or subdomains.
91 traffic from any of the domains or subdomains.
92 85
93### Step 1: DNS Configuration 86** Step 1: DNS Configuration
87To start, update your DNS configuration so that you have an =A= record
88for each domain or subdomain.
94 89
95To start, update your DNS configuration so that you have an 90The =A= records should point toward the public IP address of the server.
96`A` record for each domain or subdomain. 91If you don't know the public IP address, log in to the server and run
92the following command:
97 93
98The `A` records should point toward the public IP address of 94#+begin_src sh
99the server. If you don't know the public IP address, log in to the
100server and run the following command:
101
102```sh
103curl ifconfig.co 95curl ifconfig.co
104``` 96#+end_src
105 97
106In the DNS example below, `xxx.xxx.xxx.xxx` is the public IP 98In the DNS example below, =xxx.xxx.xxx.xxx= is the public IP address of
107address of the server. 99the server.
108 100
109```config 101#+begin_src config
110example.com A xxx.xxx.xxx.xxx 102example.com A xxx.xxx.xxx.xxx
111uptime.example.com A xxx.xxx.xxx.xxx 103uptime.example.com A xxx.xxx.xxx.xxx
112dashy.example.com A xxx.xxx.xxx.xxx 104dashy.example.com A xxx.xxx.xxx.xxx
113www CNAME example.com 105www CNAME example.com
114``` 106#+end_src
115
116Finally, ensure the DNS has propagated correctly with [DNS
117Checker](https://dnschecker.org) by entering your domains or subdomains
118in the search box and ensuring the results are showing the correct IP
119address.
120 107
121### Step 2: Open Network Ports 108Finally, ensure the DNS has propagated correctly with
109[[https://dnschecker.org][DNS Checker]] by entering your domains or
110subdomains in the search box and ensuring the results are showing the
111correct IP address.
122 112
113** Step 2: Open Network Ports
123This step will be different depending on which router you have in your 114This step will be different depending on which router you have in your
124home. If you're not sure, try to visit 115home. If you're not sure, try to visit
125[192.168.1.1](http://192.168.1.1) in your browser. Login credentials are 116[[http://192.168.1.1][192.168.1.1]] in your browser. Login credentials
126usually written on a sticker somewhere on your modem/router. 117are usually written on a sticker somewhere on your modem/router.
127 118
128Once you're able to log in to your router, find the Port Forwarding 119Once you're able to log in to your router, find the Port Forwarding
129settings. You will need to forward ports `80` and 120settings. You will need to forward ports =80= and =443= to whichever
130`443` to whichever machine is running the reverse proxy. 121machine is running the reverse proxy.
131 122
132In my case, the table below shows the port-forwarding rules I've 123In my case, the table below shows the port-forwarding rules I've
133created. In this table, `xxx.xxx.xxx.xxx` is the local device 124created. In this table, =xxx.xxx.xxx.xxx= is the local device IP of the
134IP of the reverse proxy server, it will probably be an IP between 125reverse proxy server, it will probably be an IP between =192.168.1.1=
135`192.168.1.1` and `192.168.1.255`. 126and =192.168.1.255=.
136 127
137 NAME FROM PORT DEST PORT/IP ENABLED 128| NAME | FROM | PORT | DEST PORT/IP | ENABLED |
138 ------- ------ ------ ----------------- --------- 129|-------+------+------+-----------------+---------|
139 HTTP ​\* 80 xxx.xxx.xxx.xxx TRUE 130| HTTP | ​** | 80 | xxx.xxx.xxx.xxx | TRUE |
140 HTTPS ​\* 443 xxx.xxx.xxx.xxx TRUE 131| HTTPS | ​** | 443 | xxx.xxx.xxx.xxx | TRUE |
141 132
142Once configured, these rules will direct all web traffic to your reverse 133Once configured, these rules will direct all web traffic to your reverse
143proxy. 134proxy.
144 135
145### Step 3: Nginx Installation 136** Step 3: Nginx Installation
146
147To install Nginx, simply run the following command: 137To install Nginx, simply run the following command:
148 138
149```sh 139#+begin_src sh
150sudo apt install nginx 140sudo apt install nginx
151``` 141#+end_src
152 142
153If you have a firewall enabled, open up ports `80` and 143If you have a firewall enabled, open up ports =80= and =443= on your
154`443` on your server so that Nginx can accept web traffic 144server so that Nginx can accept web traffic from the router.
155from the router.
156 145
157For example, if you want to use `ufw` for web traffic and 146For example, if you want to use =ufw= for web traffic and SSH, run the
158SSH, run the following commands: 147following commands:
159 148
160```sh 149#+begin_src sh
161sudo ufw allow 'Nginx Full' 150sudo ufw allow 'Nginx Full'
162sudo ufw allow SSH 151sudo ufw allow SSH
163sudo ufw enable 152sudo ufw enable
164``` 153#+end_src
165
166### Step 4: Nginx Configuration
167 154
155** Step 4: Nginx Configuration
168Now that we have domains pointing toward the server, the only step left 156Now that we have domains pointing toward the server, the only step left
169is to configure the reverse proxy to direct traffic from domains to 157is to configure the reverse proxy to direct traffic from domains to
170local services. 158local services.
171 159
172To start, you'll need to create a configuration file for each domain in 160To start, you'll need to create a configuration file for each domain in
173`/etc/nginx/sites-available/`. They will look identical 161=/etc/nginx/sites-available/=. They will look identical except for the
174except for the `server_name` variable and the 162=server_name= variable and the =proxy_pass= port.
175`proxy_pass` port.
176 163
177Dashy: 164Dashy:
178 165
179```sh 166#+begin_src sh
180nano /etc/nginx/sites-available/dashy.example.com 167nano /etc/nginx/sites-available/dashy.example.com
181``` 168#+end_src
182 169
183```config 170#+begin_src config
184server { 171server {
185 listen 80; 172 listen 80;
186 server_name dashy.example.com; 173 server_name dashy.example.com;
@@ -189,15 +176,15 @@ server {
189 proxy_pass http://localhost:4000; 176 proxy_pass http://localhost:4000;
190 } 177 }
191} 178}
192``` 179#+end_src
193 180
194Uptime: 181Uptime:
195 182
196```sh 183#+begin_src sh
197nano /etc/nginx/sites-available/uptime.example.com 184nano /etc/nginx/sites-available/uptime.example.com
198``` 185#+end_src
199 186
200```config 187#+begin_src config
201server { 188server {
202 listen 80; 189 listen 80;
203 server_name uptime.example.com; 190 server_name uptime.example.com;
@@ -206,29 +193,28 @@ server {
206 proxy_pass http://localhost:3001; 193 proxy_pass http://localhost:3001;
207 } 194 }
208} 195}
209``` 196#+end_src
210 197
211Once the configuration files are created, you will need to enable them 198Once the configuration files are created, you will need to enable them
212with the `symlink` command: 199with the =symlink= command:
213 200
214```sh 201#+begin_src sh
215sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/ 202sudo ln -s /etc/nginx/sites-available/dashy.example.com /etc/nginx/sites-enabled/
216``` 203#+end_src
217 204
218Voilà! Your local services should now be available through their URLs. 205Voilà! Your local services should now be available through their URLs.
219 206
220## HTTPS with Certbot 207* HTTPS with Certbot
221
222If you've followed along, you'll notice that your services are only 208If you've followed along, you'll notice that your services are only
223available via HTTP (not HTTPS). 209available via HTTP (not HTTPS).
224 210
225If you want to enable HTTPS for your new domains, you will need to 211If you want to enable HTTPS for your new domains, you will need to
226generate SSL/TLS certificates for them. The easiest way to generate 212generate SSL/TLS certificates for them. The easiest way to generate
227certificates on Nginx is [Certbot](https://certbot.eff.org): 213certificates on Nginx is [[https://certbot.eff.org][Certbot]]:
228 214
229```sh 215#+begin_src sh
230sudo apt install snapd; sudo snap install core; sudo snap refresh core 216sudo apt install snapd; sudo snap install core; sudo snap refresh core
231sudo snap install --classic certbot 217sudo snap install --classic certbot
232sudo ln -s /snap/bin/certbot /usr/bin/certbot 218sudo ln -s /snap/bin/certbot /usr/bin/certbot
233sudo certbot --nginx 219sudo certbot --nginx
234``` 220#+end_src
content/blog/2022-11-11-nginx-tmp-errors.md → blog/nginx-tmp-errors/index.org renamed +31 −35
@@ -1,14 +1,12 @@
1+++ 1#+title: Fixing Permission Errors in /var/lib/nginx
2date = 2022-11-11 2#+date: 2022-11-11
3title = "Fixing Permission Errors in /var/lib/nginx" 3#+description: Learn how to fix permission errors related to the Nginx temporary file storage.
4description = "Learn how to fix permission errors related to the Nginx temporary file storage." 4#+filetags: :nginx:
5+++
6 5
7*This is a brief post so that I personally remember the solution as it 6/This is a brief post so that I personally remember the solution as it
8has occurred multiple times for me.* 7has occurred multiple times for me./
9
10## The Problem
11 8
9* The Problem
12After migrating to a new server OS, I started receiving quite a few 10After migrating to a new server OS, I started receiving quite a few
13permission errors like the one below. These popped up for various 11permission errors like the one below. These popped up for various
14different websites I'm serving via Nginx on this server, but did not 12different websites I'm serving via Nginx on this server, but did not
@@ -16,46 +14,44 @@ prevent the website from loading.
16 14
17I found the errors in the standard log file: 15I found the errors in the standard log file:
18 16
19```sh 17#+begin_src sh
20cat /var/log/nginx/error.log 18cat /var/log/nginx/error.log
21``` 19#+end_src
22 20
23```sh 21#+begin_src sh
242022/11/11 11:30:34 [crit] 8970#8970: *10 open() "/var/lib/nginx/tmp/proxy/3/00/0000000003" failed (13: Permission denied) while reading upstream, client: 169.150.203.10, server: cyberchef.example.com, request: "GET /assets/main.css HTTP/2.0", upstream: "http://127.0.0.1:8111/assets/main.css", host: "cyberchef.example.com", referrer: "https://cyberchef.example.com/" 222022/11/11 11:30:34 [crit] 8970#8970: *10 open() "/var/lib/nginx/tmp/proxy/3/00/0000000003" failed (13: Permission denied) while reading upstream, client: 169.150.203.10, server: cyberchef.example.com, request: "GET /assets/main.css HTTP/2.0", upstream: "http://127.0.0.1:8111/assets/main.css", host: "cyberchef.example.com", referrer: "https://cyberchef.example.com/"
25``` 23#+end_src
26 24
27You can see that the error is `13: Permission denied` and it 25You can see that the error is =13: Permission denied= and it occurs in
28occurs in the `/var/lib/nginx/tmp/` directory. In my case, I 26the =/var/lib/nginx/tmp/= directory. In my case, I had thousands of
29had thousands of errors where Nginx was denied permission to read/write 27errors where Nginx was denied permission to read/write files in this
30files in this directory. 28directory.
31 29
32So how do I fix it? 30So how do I fix it?
33 31
34## The Solution 32* The Solution
35 33In order to resolve the issue, I had to ensure the =/var/lib/nginx=
36In order to resolve the issue, I had to ensure the 34directory is owned by Nginx. Mine was owned by the =www= user and Nginx
37`/var/lib/nginx` directory is owned by Nginx. Mine was owned 35was not able to read or write files within that directory. This
38by the `www` user and Nginx was not able to read or write 36prevented Nginx from caching temporary files.
39files within that directory. This prevented Nginx from caching temporary
40files.
41 37
42```sh 38#+begin_src sh
43# Alpine Linux 39# Alpine Linux
44doas chown -R nginx:nginx /var/lib/nginx 40doas chown -R nginx:nginx /var/lib/nginx
45 41
46# Other Distros 42# Other Distros
47sudo chown -R nginx:nginx /var/lib/nginx 43sudo chown -R nginx:nginx /var/lib/nginx
48``` 44#+end_src
49 45
50You *may* also be able to change the `proxy_temp_path` in 46You /may/ also be able to change the =proxy_temp_path= in your Nginx
51your Nginx config, but I did not try this. Here's a suggestion I found 47config, but I did not try this. Here's a suggestion I found online that
52online that may work if the above solution does not: 48may work if the above solution does not:
53 49
54```sh 50#+begin_src sh
55nano /etc/nginx/http.d/example.com.conf 51nano /etc/nginx/http.d/example.com.conf
56``` 52#+end_src
57 53
58```conf 54#+begin_src conf
59server { 55server {
60 ... 56 ...
61 57
@@ -65,15 +61,15 @@ server {
65 61
66 ... 62 ...
67} 63}
68``` 64#+end_src
69 65
70Finally, restart Nginx and your server should be able to cache temporary 66Finally, restart Nginx and your server should be able to cache temporary
71files again. 67files again.
72 68
73```sh 69#+begin_src sh
74# Alpine Linux (OpenRC) 70# Alpine Linux (OpenRC)
75doas rc-service nginx restart 71doas rc-service nginx restart
76 72
77# Other Distros (systemd) 73# Other Distros (systemd)
78sudo systemctl restart nginx 74sudo systemctl restart nginx
79``` 75#+end_src
content/blog/2022-12-07-nginx-wildcard-redirect.md → blog/nginx-wildcard-redirect/index.org renamed +30 −36
@@ -1,11 +1,9 @@
1+++ 1#+title: Redirect Nginx Subdomains & Trailing Content with Regex
2date = 2022-12-07 2#+date: 2022-12-07
3title = "Redirect Nginx Subdomains & Trailing Content with Regex" 3#+description: A simple Nginx configuration to redirect all subdomains and trailing content.
4description = "A simple Nginx configuration to redirect all subdomains and trailing content." 4#+filetags: :nginx:
5+++
6
7## Problem
8 5
6* Problem
9I recently migrated domains and replaced the old webpage with a simple 7I recently migrated domains and replaced the old webpage with a simple
10info page with instructions to users on how to edit their bookmarks and 8info page with instructions to users on how to edit their bookmarks and
11URLs to get to the page they were seeking. 9URLs to get to the page they were seeking.
@@ -13,13 +11,12 @@ URLs to get to the page they were seeking.
13This was not ideal as it left the work up to the user and may have 11This was not ideal as it left the work up to the user and may have
14caused friction for users who accessed my RSS feed. 12caused friction for users who accessed my RSS feed.
15 13
16## Solution 14* Solution
17
18Instead, I finally found a solution that allows me to redirect both 15Instead, I finally found a solution that allows me to redirect both
19subdomains AND trailing content. For example, both of these URLs now 16subdomains AND trailing content. For example, both of these URLs now
20redirect properly using the logic I'll explain below: 17redirect properly using the logic I'll explain below:
21 18
22```txt 19#+begin_src txt
23# Example 1 - Simple base domain redirect with trailing content 20# Example 1 - Simple base domain redirect with trailing content
24https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/ 21https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
25 22
@@ -27,23 +24,22 @@ https://domain1.com/blog/alpine-linux/ -> https://domain2.com/blog/alpine-linux/
27https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 24https://libreddit.domain1.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
28-> 25->
29https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/ 26https://libreddit.domain2.com/r/history/comments/7z8cbg/new_discovery_mode_turns_video_game_assassins/
30``` 27#+end_src
31 28
32Go ahead, try the URLs if you want to test them. 29Go ahead, try the URLs if you want to test them.
33 30
34### Nginx Config 31** Nginx Config
35
36To make this possible. I needed to configure a proper redirect scheme in 32To make this possible. I needed to configure a proper redirect scheme in
37my Nginx configuration. 33my Nginx configuration.
38 34
39```sh 35#+begin_src sh
40doas nano /etc/nginx/http.d/domain1.conf 36doas nano /etc/nginx/http.d/domain1.conf
41``` 37#+end_src
42 38
43Within this file, I had one block configured to redirect HTTP requests 39Within this file, I had one block configured to redirect HTTP requests
44to HTTPS for the base domain and all subdomains. 40to HTTPS for the base domain and all subdomains.
45 41
46```conf 42#+begin_src conf
47server { 43server {
48 listen [::]:80; 44 listen [::]:80;
49 listen 80; 45 listen 80;
@@ -59,14 +55,14 @@ server {
59 55
60 return 404; 56 return 404;
61} 57}
62``` 58#+end_src
63 59
64For the base domain, I have another `server` block dedicated 60For the base domain, I have another =server= block dedicated to
65to redirecting all base domain requests. You can see that the 61redirecting all base domain requests. You can see that the =rewrite=
66`rewrite` line is instructing Nginx to gather all trailing 62line is instructing Nginx to gather all trailing content and append it
67content and append it to the new `domain2.com` URL. 63to the new =domain2.com= URL.
68 64
69```conf 65#+begin_src conf
70server { 66server {
71 listen [::]:443 ssl http2; 67 listen [::]:443 ssl http2;
72 listen 443 ssl http2; 68 listen 443 ssl http2;
@@ -78,23 +74,21 @@ server {
78 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 74 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
79 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 75 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
80} 76}
81``` 77#+end_src
82 78
83Finally, the tricky part is figuring out how to tell Nginx to redirect 79Finally, the tricky part is figuring out how to tell Nginx to redirect
84while keeping both a subdomain and trailing content intact. I found that 80while keeping both a subdomain and trailing content intact. I found that
85the easiest way to do this is to give it a `server` block of 81the easiest way to do this is to give it a =server= block of its own.
86its own.
87 82
88Within this block, we need to do some regex on the 83Within this block, we need to do some regex on the =server_name= line
89`server_name` line before we can rewrite anything. This 84before we can rewrite anything. This creates a variable called
90creates a variable called `subdomain`. 85=subdomain=.
91 86
92Once the server gets to the `rewrite` line, it pulls the 87Once the server gets to the =rewrite= line, it pulls the =subdomain=
93`subdomain` variable from above and uses it on the new 88variable from above and uses it on the new =domain2.com= domain before
94`domain2.com` domain before appending the trailing content 89appending the trailing content (=$request_uri=).
95(`$request_uri`).
96 90
97```conf 91#+begin_src conf
98server { 92server {
99 listen [::]:443 ssl http2; 93 listen [::]:443 ssl http2;
100 listen 443 ssl http2; 94 listen 443 ssl http2;
@@ -106,14 +100,14 @@ server {
106 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem; 100 ssl_certificate /etc/letsencrypt/live/domain1.com/fullchain.pem;
107 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem; 101 ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;
108} 102}
109``` 103#+end_src
110 104
111That's all there is to it. With this, I simply restarted Nginx and 105That's all there is to it. With this, I simply restarted Nginx and
112watched the redirections work in-action. 106watched the redirections work in-action.
113 107
114```sh 108#+begin_src sh
115doas rc-service nginx restart 109doas rc-service nginx restart
116``` 110#+end_src
117 111
118Looking back on it, I wish I had done this sooner. Who knows how many 112Looking back on it, I wish I had done this sooner. Who knows how many
119people went looking for my sites or bookmarks and gave up when they saw 113people went looking for my sites or bookmarks and gave up when they saw
content/blog/2022-06-04-njalla-dns-api.md → blog/njalla-dns-api/index.org renamed +55 −67
@@ -1,15 +1,13 @@
1+++ 1#+title: Dynamic DNS with Njalla API
2date = 2022-02-10 2#+date: 2022-02-10
3title = "Dynamic DNS with Njalla API" 3#+description: Learn how to dynamically update DNS records for changing IPs with Njalla.
4description = "Learn how to dynamically update DNS records for changing IPs with Njalla." 4#+filetags: :sysadmin:
5+++
6 5
7## Njalla's API 6* Njalla's API
8 7As noted in my recent post about [[/blog/ditching-cloudflare/][switching
9As noted in my recent post about [switching to Njalla from 8to Njalla from Cloudflare]], I was searching for a way to replace my
10Cloudflare](/blog/ditching-cloudflare/), I was searching for a 9very easy-to-use bash script to [[/blog/cloudflare-dns-api/][update
11way to replace my very easy-to-use bash script to [update Cloudflare's 10Cloudflare's DNS via their API]].
12DNS via their API](/blog/cloudflare-dns-api/).
13 11
14To reiterate what I said in those posts, this is a common necessity for 12To reiterate what I said in those posts, this is a common necessity for
15those of us who have non-static IP addresses that can change at any 13those of us who have non-static IP addresses that can change at any
@@ -22,88 +20,81 @@ update their domain's DNS records if it changes.
22This post explains how to use Python to update Njalla's DNS records 20This post explains how to use Python to update Njalla's DNS records
23whenever a machine's public IP address changes. 21whenever a machine's public IP address changes.
24 22
25### Creating a Token 23** Creating a Token
26
27To use Njalla's API, you will first need to create a token that will be 24To use Njalla's API, you will first need to create a token that will be
28used to authenticate you every time you call the API. Luckily, this is 25used to authenticate you every time you call the API. Luckily, this is
29very easy to do if you have an account with Njalla. 26very easy to do if you have an account with Njalla.
30 27
31Simply go the [API Settings](https://njal.la/settings/api/) page and 28Simply go the [[https://njal.la/settings/api/][API Settings]] page and
32click the `Add Token` button. Next, enter a name for the 29click the =Add Token= button. Next, enter a name for the token and click
33token and click `Add`. 30=Add=.
34 31
35Finally, click the `Manage` button next to your newly created 32Finally, click the =Manage= button next to your newly created token and
36token and copy the `API Token` field. 33copy the =API Token= field.
37
38### Finding the Correct API Request
39 34
35** Finding the Correct API Request
40Once you have a token, you're ready to call the Njalla API for any 36Once you have a token, you're ready to call the Njalla API for any
41number of requests. For a full listing of available requests, see the 37number of requests. For a full listing of available requests, see the
42[Njalla API Documentation](https://njal.la/api/). 38[[https://njal.la/api/][Njalla API Documentation]].
43 39
44For this demo, we are using the `list-records` and 40For this demo, we are using the =list-records= and =edit-record=
45`edit-record` requests. 41requests.
46 42
47The `list-records` request requires the following payload to 43The =list-records= request requires the following payload to be sent
48be sent when calling the API: 44when calling the API:
49 45
50```txt 46#+begin_src txt
51params: { 47params: {
52 domain: string 48 domain: string
53} 49}
54``` 50#+end_src
55 51
56The `edit-record` request requires the following payload to 52The =edit-record= request requires the following payload to be sent when
57be sent when calling the API: 53calling the API:
58 54
59```txt 55#+begin_src txt
60params: { 56params: {
61 domain: string 57 domain: string
62 id: int 58 id: int
63 content: string 59 content: string
64} 60}
65``` 61#+end_src
66
67## Server Set-Up
68 62
63* Server Set-Up
69To create this script, we will be using Python. By default, I use Python 64To create this script, we will be using Python. By default, I use Python
703 on my servers, so please note that I did not test this in Python 2, 653 on my servers, so please note that I did not test this in Python 2,
71and I do not know if Python 2 will work for this. 66and I do not know if Python 2 will work for this.
72 67
73### Creating the Script 68** Creating the Script
74
75First, find a suitable place to create your script. Personally, I just 69First, find a suitable place to create your script. Personally, I just
76create a directory called `ddns` in my home directory: 70create a directory called =ddns= in my home directory:
77 71
78```sh 72#+begin_src sh
79mkdir ~/ddns 73mkdir ~/ddns
80``` 74#+end_src
81 75
82Next, create a Python script file: 76Next, create a Python script file:
83 77
84```sh 78#+begin_src sh
85nano ~/ddns/ddns.py 79nano ~/ddns/ddns.py
86``` 80#+end_src
87 81
88The following code snippet is quite long, so I won't go into depth on 82The following code snippet is quite long, so I won't go into depth on
89each part. However, I suggest you read through the entire script before 83each part. However, I suggest you read through the entire script before
90running it; it is quite simple and contains comments to help explain 84running it; it is quite simple and contains comments to help explain
91each code block. 85each code block.
92 86
93:warning: **Note**: You will need to update the following variables for 87:warning: *Note*: You will need to update the following variables for
94this to work: 88this to work:
95 89
96- `token`: This is the Njalla API token you created 90- =token=: This is the Njalla API token you created earlier.
97 earlier. 91- =user_domain=: This is the top-level domain you want to modify.
98- `user_domain`: This is the top-level domain you want to 92- =include_subdomains=: Set this to =True= if you also want to modify
99 modify. 93 subdomains found under the TLD.
100- `include_subdomains`: Set this to `True` if 94- =subdomains=: If =include_subdomains= = =True=, you can include your
101 you also want to modify subdomains found under the TLD. 95 list of subdomains to be modified here.
102- `subdomains`: If `include_subdomains` = 96
103 `True`, you can include your list of subdomains to be 97#+begin_src python
104 modified here.
105
106```python
107#!/usr/bin/python 98#!/usr/bin/python
108# -*- coding: utf-8 -*- 99# -*- coding: utf-8 -*-
109# Import Python modules 100# Import Python modules
@@ -182,30 +173,27 @@ for record in data['records']:
182 'does not match Njalla's value of', 173 'does not match Njalla's value of',
183 record['content'], '. Updating...') 174 record['content'], '. Updating...')
184 update_record(user_domain, record['id'], ipv6) 175 update_record(user_domain, record['id'], ipv6)
185``` 176#+end_src
186
187### Running the Script
188 177
178** Running the Script
189Once you've created the script and are ready to test it, run the 179Once you've created the script and are ready to test it, run the
190following command: 180following command:
191 181
192```sh 182#+begin_src sh
193python3 ~/ddns/ddns.py 183python3 ~/ddns/ddns.py
194``` 184#+end_src
195
196### Setting the Script to Run Automatically
197 185
198To make sure the scripts run automatically, add it to the 186** Setting the Script to Run Automatically
199`cron` file so that it will run on a schedule. To do this, 187To make sure the scripts run automatically, add it to the =cron= file so
200open the `cron` file: 188that it will run on a schedule. To do this, open the =cron= file:
201 189
202```sh 190#+begin_src sh
203crontab -e 191crontab -e
204``` 192#+end_src
205 193
206In the cron file, paste the following at the bottom of the editor in 194In the cron file, paste the following at the bottom of the editor in
207order to check the IP every five minutes: 195order to check the IP every five minutes:
208 196
209```sh 197#+begin_src sh
210*/5 * * * * python3 /home/<your_username>/ddns/ddns.py 198,*/5 ** ** ** ** python3 /home/<your_username>/ddns/ddns.py
211``` 199#+end_src
blog/org-blog/index.org added +71
@@ -0,0 +1,71 @@
1#+title: Blogging in Org-Mode
2#+date: 2024-02-26
3#+description: A guide to blogging with org-mode, no third-party tools required.
4#+filetags: :dev:
5
6* TODO Write a post on emacs first?
7
8- Could write-up my Doom Emacs config and workflow first, then reference here.
9
10* TODO Configure Emacs
11
12#+begin_src sh
13emacs -nw
14#+end_src
15
16=SPC f f= ---> =~/.doom.d/config.el=
17
18#+begin_src lisp
19;; org-publish
20(require 'ox-publish)
21
22(setq org-publish-project-alist
23 `(("blog"
24 :base-directory "~/Source/cleberg.net/"
25 :base-extension "org"
26 :recursive t
27 :publishing-directory "~/Source/cleberg.net/public/"
28 :publishing-function org-html-publish-to-html
29 ;; HTML5
30 :html-doctype "html5"
31 :html-html5-fancy t
32 ;; Disable some Org's HTML defaults
33 :html-head-include-scripts nil
34 :html-head-include-default-style nil
35 :section-numbers nil
36 :with-title nil
37 ;; Generate sitemap
38 :auto-sitemap t
39 :sitemap-filename "sitemap.org"
40 ;; Customize HTML output
41 :html-divs ((preamble "header" "preamble")
42 (content "main" "content")
43 (postamble "footer" "postamble"))
44 :html-head "<link rel='stylesheet' href='/styles.css' type='text/css'>"
45 :html-preamble "<nav class='site-nav' aria-label='site-nav' role='navigation'>
46 <ul>
47 <li><a href='/'>Home</a></li>
48 <li><a href='/blog/'>Blog</a></li>
49 <li><a href='/services/'>Services</a></li>
50 <li><a href='/wiki/'>Wiki</a></li>
51 </ul></nav>
52 <h1>%t</h1>
53 <time datetime='%d'>%d</time>"
54 :html-postamble "
55 <p>Last build: %T</p>
56 <p>Created with %c</p>"
57 )
58
59 ("static"
60 :base-directory "~/Source/cleberg.net/static/"
61 :base-extension "css\\|txt\\|jpg\\|gif\\|png"
62 :recursive t
63 :publishing-directory "~/Source/cleberg.net/public/"
64 :publishing-function org-publish-attachment)
65
66 ("cleberg.net" :components ("blog" "static"))))
67#+end_src
68
69* TODO Build Process
70
71* TODO Deploy Process
blog/password-security/index.org added +121
@@ -0,0 +1,121 @@
1#+title: Password Security
2#+date: 2019-12-16
3#+description: Password security basics.
4#+filetags: :security:
5
6* Users
7** Why Does It Matter?
8Information security, including passwords and identities, has become one
9of the most important digital highlights of the last decade. With
10[[https://www.usatoday.com/story/money/2018/12/28/data-breaches-2018-billions-hit-growing-number-cyberattacks/2413411002/][billions
11of people affected by data breaches each year]], there's a greater need
12to introduce strong information security systems. If you think you've
13been part of a breach, or you want to check and see, you can use
14[[https://haveibeenpwned.com/][Have I Been Pwned]] to see if your email
15has been involved in any public breaches. Remember that there's a
16possibility that a company experienced a breach and did not report it to
17anyone.
18
19** How Do I Protect Myself?
20The first place to start with any personal security check-up is to
21gather a list of all the different websites, apps, or programs that
22require you to have login credentials. Optionally, once you know where
23your information is being stored, you can sort the list from the
24most-important items such as banks or government logins to less
25important items such as your favorite meme site. You will want to ensure
26that your critical logins are secure before getting to the others.
27
28Once you think you have a good idea of all your different authentication
29methods, I recommend using a password manager such as
30[[https://bitwarden.com/][Bitwarden]]. Using a password manager allows
31you to automatically save your logins, create randomized passwords, and
32transfer passwords across devices. However, you'll need to memorize your
33"vault password" that allows you to open the password manager. It's
34important to make this something hard to guess since it would allow
35anyone who has it to access every password you've stored in there.
36
37Personally, I recommend using a
38[[https://en.wikipedia.org/wiki/Passphrase][passphrase]] instead of a
39[[https://en.wikipedia.org/wiki/Password][password]] for your vault
40password. Instead of using a string of characters (whether random or
41simple), use a phrase and add in symbols and a number. For example, your
42vault password could be =Racing-Alphabet-Gourd-Parrot3=. Swap the
43symbols out for whichever symbol you want, move the number around, and
44fine-tune the passphrase until you are confident that you can remember
45it whenever necessary.
46
47Once you've stored your passwords, make sure you continually check up on
48your account and make sure you aren't following bad password practices.
49Krebs on Security has a great
50[[https://krebsonsecurity.com/password-dos-and-donts/][blog post on
51password recommendations]]. Any time that a data breach happens, make
52sure you check to see if you were included, and if you need to reset any
53account passwords.
54
55* Developers
56** What Are the Basic Requirements?
57When developing any password-protected application, there are a few
58basic rules that anyone should follow even if they do not follow any
59official guidelines such as NIST. The foremost practice is to require
60users to use passwords that are at least 8 characters and cannot easily
61be guessed. This sounds extremely simple, but it requires quite a few
62different strategies. First, the application should check the potential
63passwords against a dictionary of insecure passwords such =password=,
64=1234abc=, or =application_name=.
65
66Next, the application should offer guidance on the strength of passwords
67being entered during enrollment. Further, NIST officially recommends
68*not** implementing any composition rules that make passwords hard to
69remember (e.g. passwords with letters, numbers, and special characters)
70and instead encouraging the use of long pass phrases which can include
71spaces. It should be noted that to be able to keep spaces within
72passwords, all unicode characters should be supported, and passwords
73should not be truncated.
74
75** What Does NIST Recommend?
76The National Institute of Standards and Technology
77([[https://www.nist.gov][NIST]]) in the US Department of Commerce
78regularly publishes information around information security and digital
79identity guidelines. Recently, NIST published
80[[https://pages.nist.gov/800-63-3/sp800-63b.html][Special Publication
81800-63b]]: Digital Identity Guidelines and Authentication and Lifecycle
82Management.
83
84#+begin_quote
85A Memorized Secret authenticator - commonly referred to as a password
86or, if numeric, a PIN - is a secret value intended to be chosen and
87memorized by the user. Memorized secrets need to be of sufficient
88complexity and secrecy that it would be impractical for an attacker to
89guess or otherwise discover the correct secret value. A memorized secret
90is something you know.
91
92- NIST Special Publication 800-63B
93#+end_quote
94
95NIST offers a lot of guidance on passwords, but I'm going to highlight
96just a few of the important factors:
97
98- Require passwords to be a minimum of 8 characters (6 characters if
99 randomly generated and be generated using an approved random bit
100 generator).
101- Compare potential passwords against a list that contains values known
102 to be commonly-used, expected, or compromised.
103- Offer guidance on password strength, such as a strength meter.
104- Implement a rate-limiting mechanism to limit the number of failed
105 authentication attempts for each user account.
106- Do not require composition rules for passwords and do not require
107 passwords to be changed periodically (unless compromised).
108- Allow pasting of user identification and passwords to facilitate the
109 use of password managers.
110- Allow users to view the password as it is being entered.
111- Use secure forms of communication and storage, including salting and
112 hashing passwords using a one-way key derivation function.
113
114NIST offers further guidance on other devices that require specific
115security policies, querying for passwords, and more. All the information
116discussed so far comes from
117[[https://pages.nist.gov/800-63-3/sp800-63b.html][NIST SP800-63b]] but
118NIST offers a lot of information on digital identities, enrollment,
119identity proofing, authentication, lifecycle management, federation, and
120assertions in the total [[https://pages.nist.gov/800-63-3/][NIST
121SP800-63 Digital Identity Guidelines]].
content/blog/2021-04-28-photography.md → blog/photography/index.org renamed +30 −34
@@ -1,68 +1,64 @@
1+++ 1#+title: Jumping Back Into Photography
2date = 2021-04-28 2#+date: 2021-04-28
3title = "Jumping Back Into Photography" 3#+description: Some thoughts on photography.
4description = "Some thoughts on photography." 4#+filetags: :personal:
5+++
6 5
7## Why Photography? 6* Why Photography?
8 7I've often wondered why photography is as enticing as it is. You can see
9I've often wondered why photography is as enticing as it is. You can 8billions of people around the world taking photographs every single
10see billions of people around the world taking photographs every single
11moment of the day. New technology often boasts about their photographic 9moment of the day. New technology often boasts about their photographic
12capabilities, especially smartphones. I would even assume that we live 10capabilities, especially smartphones. I would even assume that we live
13in a world where there is never a moment in which a photograph is not 11in a world where there is never a moment in which a photograph is not
14being taken somewhere on Earth. 12being taken somewhere on Earth.
15 13
16As for myself, I would simply say that I enjoy preserving a memory in 14As for myself, I would simply say that I enjoy preserving a memory in
17physical (or digital) form. I've never had the best memory when it 15physical (or digital) form. I've never had the best memory when it comes
18comes to recalling details of places and people gone by, so it helps to 16to recalling details of places and people gone by, so it helps to have a
19have a frame of reference lying around. 17frame of reference lying around.
20 18
21Regardless of the reason, I think most people would agree that you 19Regardless of the reason, I think most people would agree that you
22simply cannot have too many hobbies. 20simply cannot have too many hobbies.
23 21
24## Older Cameras 22* Older Cameras
25
26I started playing around with the idea of photography when my family 23I started playing around with the idea of photography when my family
27purchased a Fujifilm camera for family-specific events. I don't recall 24purchased a Fujifilm camera for family-specific events. I don't recall
28the specific model, but I do recall it was a point-and-shoot camera 25the specific model, but I do recall it was a point-and-shoot camera
29without an interchangeable lens. However, it was of great value to 26without an interchangeable lens. However, it was of great value to
30someone, like myself, who couldn't afford any other camera. I took 27someone, like myself, who couldn't afford any other camera. I took about
31about 10,000 shots with that camera over a 3-5 year span. Most notably, 2810,000 shots with that camera over a 3-5 year span. Most notably, all of
32all of my trips to California were documented through this camera. 29my trips to California were documented through this camera.
33 30
34When possible, I would borrow my sister's camera, which is a Sony 31When possible, I would borrow my sister's camera, which is a Sony
35SLT-A58. This camera was great and allowed for some of my best early 32SLT-A58. This camera was great and allowed for some of my best early
36shots, especially those taken in Utah's and Nevada's parks. 33shots, especially those taken in Utah's and Nevada's parks.
37 34
38## My Current Kit 35* My Current Kit
39
40I've finally come to a point in my life where I have the disposable 36I've finally come to a point in my life where I have the disposable
41income to invest in a solid photography kit. I played around with the 37income to invest in a solid photography kit. I played around with the
42idea of a lot of different cameras, different types, new vs used, etc. 38idea of a lot of different cameras, different types, new vs used, etc.
43Finally, I settled on the [Sony α7 39Finally, I settled on the
44III](https://en.wikipedia.org/wiki/Sony_%CE%B17_III). This camera is 40[[https://en.wikipedia.org/wiki/Sony_%CE%B17_III][Sony α7 III]]. This
45mirror-less and uses a full-frame image sensor at 24 megapixels. I 41camera is mirror-less and uses a full-frame image sensor at 24
46don't create large prints, and I am mostly focused on preserving 42megapixels. I don't create large prints, and I am mostly focused on
47memories in high quality for the next 5-10 years with this camera, so 43preserving memories in high quality for the next 5-10 years with this
48the specifications here are just perfect for me. 44camera, so the specifications here are just perfect for me.
49 45
50For lenses, I decided to buy two lenses that could carry me through most 46For lenses, I decided to buy two lenses that could carry me through most
51situations: 47situations:
52 48
53- [Vario-Tessar T\* FE 24-70 mm F4 ZA 49- [[https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z][Vario-Tessar
54 OSS](https://electronics.sony.com/imaging/lenses/full-frame-e-mount/p/sel2470z) 50 T** FE 24-70 mm F4 ZA OSS]]
55- [Tamron 70-300mm f4.5-6.3 Di III 51- [[https://www.tamron-usa.com/product/lenses/a047.html][Tamron 70-300mm
56 RXD](https://www.tamron-usa.com/product/lenses/a047.html) 52 f4.5-6.3 Di III RXD]]
57 53
58In addition, I grabbed a couple [HGX Prime 54In addition, I grabbed a couple
5967mm](https://www.promaster.com/Product/6725) protection filters for the 55[[https://www.promaster.com/Product/6725][HGX Prime 67mm]] protection
60lenses. 56filters for the lenses.
61 57
62As I delve further into photography and pick up more skills, I will most 58As I delve further into photography and pick up more skills, I will most
63likely go back and grab a lens with a higher f-stop value, such as 59likely go back and grab a lens with a higher f-stop value, such as
64f/1.8. I toyed with the idea of grabbing a 50 mm at `f/1.8`, 60f/1.8. I toyed with the idea of grabbing a 50 mm at =f/1.8=, but decided
65but decided to keep things in a reasonable price range instead. 61to keep things in a reasonable price range instead.
66 62
67Finally, I made sure to buy a photography-specific backpack with a rain 63Finally, I made sure to buy a photography-specific backpack with a rain
68guard, and the zipper on the back panel, to protect the equipment while 64guard, and the zipper on the back panel, to protect the equipment while
content/blog/2020-08-29-php-auth-flow.md → blog/php-auth-flow/index.org renamed +35 −42
@@ -1,11 +1,9 @@
1+++ 1#+title: PHP Authentication Flow
2date = 2020-08-29 2#+date: 2020-08-29
3title = "PHP Authentication Flow" 3#+description: Learn how to establish and maintain a basic user authentication flow in PHP.
4description = "Learn how to establish and maintain a basic user authentication flow in PHP." 4#+filetags: :dev:
5+++
6
7# Introduction
8 5
6* Introduction
9When creating websites that will allow users to create accounts, the 7When creating websites that will allow users to create accounts, the
10developer always needs to consider the proper authentication flow for 8developer always needs to consider the proper authentication flow for
11their app. For example, some developers will utilize an API for 9their app. For example, some developers will utilize an API for
@@ -17,7 +15,7 @@ problem of copying and pasting the code from their library's
17documentation. For example, here's the code I use to authenticate users 15documentation. For example, here's the code I use to authenticate users
18with the Tumblr OAuth API for my Tumblr client, Vox Populi: 16with the Tumblr OAuth API for my Tumblr client, Vox Populi:
19 17
20```php 18#+begin_src php
21// Start the session 19// Start the session
22session_start(); 20session_start();
23 21
@@ -38,7 +36,7 @@ $client = new Tumblr\API\Client(
38 $token, 36 $token,
39 $token_secret 37 $token_secret
40); 38);
41``` 39#+end_src
42 40
43However, developers creating authentication flows from scratch will need 41However, developers creating authentication flows from scratch will need
44to think carefully about when to make sure a web page will check the 42to think carefully about when to make sure a web page will check the
@@ -47,8 +45,7 @@ user's authenticity.
47In this article, we're going to look at a simple authentication flow 45In this article, we're going to look at a simple authentication flow
48using a MySQL database and PHP. 46using a MySQL database and PHP.
49 47
50# Creating User Accounts 48* Creating User Accounts
51
52The beginning to any type of user authentication is to create a user 49The beginning to any type of user authentication is to create a user
53account. This process can take many formats, but the simplest is to 50account. This process can take many formats, but the simplest is to
54accept user input from a form (e.g., username and password) and send it 51accept user input from a form (e.g., username and password) and send it
@@ -56,10 +53,10 @@ over to your database. For example, here's a snippet that shows how to
56get username and password parameters that would come when a user submits 53get username and password parameters that would come when a user submits
57a form to your PHP script. 54a form to your PHP script.
58 55
59**Note**: Ensure that your password column is large enough to hold the 56*Note*: Ensure that your password column is large enough to hold the
60hashed value (at least 60 characters or longer). 57hashed value (at least 60 characters or longer).
61 58
62```php 59#+begin_src php
63// Get the values from the URL 60// Get the values from the URL
64$username = $_POST['username']; 61$username = $_POST['username'];
65$raw_password = $_POST['password']; 62$raw_password = $_POST['password'];
@@ -92,15 +89,14 @@ if ($conn->query($sql) === TRUE) {
92} 89}
93 90
94$conn->close(); 91$conn->close();
95``` 92#+end_src
96
97# Validate Returning Users
98 93
94** Validate Returning Users
99To be able to verify that a returning user has a valid username and 95To be able to verify that a returning user has a valid username and
100password in your database is as simple as having users fill out a form 96password in your database is as simple as having users fill out a form
101and comparing their inputs to your database. 97and comparing their inputs to your database.
102 98
103```php 99#+begin_src php
104// Query the database for username and password 100// Query the database for username and password
105// ... 101// ...
106 102
@@ -111,42 +107,40 @@ if(password_verify($password_input, $hashed_password)) {
111 107
112// Else, Redirect them back to the login page. 108// Else, Redirect them back to the login page.
113... 109...
114``` 110#+end_src
115 111
116# Storing Authentication State 112* Storing Authentication State
113Once you've created the user's account, now you're ready to initialize
114the user's session. *You will need to do this on every page you load
115while the user is logged in.** To do so, simply enter the following code
116snippet:
117 117
118Once you've created the user's account, now you're ready to 118#+begin_src php
119initialize the user's session. **You will need to do this on every page
120you load while the user is logged in.** To do so, simply enter the
121following code snippet:
122
123```php
124session_start(); 119session_start();
125``` 120#+end_src
126 121
127Once you've initialized the session, the next step is to store the 122Once you've initialized the session, the next step is to store the
128session in a cookie so that you can access it later. 123session in a cookie so that you can access it later.
129 124
130```php 125#+begin_src php
131setcookie(session_name()); 126setcookie(session_name());
132``` 127#+end_src
133 128
134Now that the session name has been stored, you'll be able to check if 129Now that the session name has been stored, you'll be able to check if
135there's an active session whenever you load a page. 130there's an active session whenever you load a page.
136 131
137```php 132#+begin_src php
138if(isset(session_name())) { 133if(isset(session_name())) {
139 // The session is active 134 // The session is active
140} 135}
141``` 136#+end_src
142
143# Removing User Authentication
144 137
138** Removing User Authentication
145The next logical step is to give your users the option to log out once 139The next logical step is to give your users the option to log out once
146they are done using your application. This can be tricky in PHP since a 140they are done using your application. This can be tricky in PHP since a
147few of the standard ways do not always work. 141few of the standard ways do not always work.
148 142
149```php 143#+begin_src php
150// Initialize the session. 144// Initialize the session.
151// If you are using session_name("something"), don't forget it now! 145// If you are using session_name("something"), don't forget it now!
152session_start(); 146session_start();
@@ -178,18 +172,17 @@ session_write_close();
178// Go back to sign-in page 172// Go back to sign-in page
179header('Location: https://example.com/logged-out/'); 173header('Location: https://example.com/logged-out/');
180die(); 174die();
181``` 175#+end_src
182
183# Wrapping Up
184 176
177* Wrapping Up
185Now you should be ready to begin your authentication programming with 178Now you should be ready to begin your authentication programming with
186PHP. You can create user accounts, create sessions for users across 179PHP. You can create user accounts, create sessions for users across
187different pages of your site, and then destroy the user data when 180different pages of your site, and then destroy the user data when
188they're ready to leave. 181they're ready to leave.
189 182
190For more information on this subject, I recommend reading the [PHP 183For more information on this subject, I recommend reading the
191Documentation](https://www.php.net/). Specifically, you may want to look 184[[https://www.php.net/][PHP Documentation]]. Specifically, you may want
192at [HTTP Authentication with 185to look at [[https://www.php.net/manual/en/features.http-auth.php][HTTP
193PHP](https://www.php.net/manual/en/features.http-auth.php), [session 186Authentication with PHP]],
194handling](https://www.php.net/manual/en/book.session.php), and 187[[https://www.php.net/manual/en/book.session.php][session handling]],
195[hash](https://www.php.net/manual/en/function.hash.php). 188and [[https://www.php.net/manual/en/function.hash.php][hash]].
content/blog/2021-04-23-php-comment-system.md → blog/php-comment-system/index.org renamed +84 −95
@@ -1,11 +1,9 @@
1+++ 1#+title: Roll Your Own Static Commenting System in PHP
2date = 2021-04-23 2#+date: 2021-04-23
3title = "Roll Your Own Static Commenting System in PHP" 3#+description: A simple guide to creating a commenting system in PHP.
4description = "A simple guide to creating a commenting system in PHP." 4#+filetags: :dev:
5+++
6
7## The Terrible-ness of Commenting Systems
8 5
6* The Terrible-ness of Commenting Systems
9The current state of affairs regarding interactive comment systems is, 7The current state of affairs regarding interactive comment systems is,
10well, terrible. It is especially awful if you're a privacy conscious 8well, terrible. It is especially awful if you're a privacy conscious
11person who does not generally load third-party scripts or frames on the 9person who does not generally load third-party scripts or frames on the
@@ -16,24 +14,23 @@ something that should be standard.
16 14
17Of course, there are some really terrible options: 15Of course, there are some really terrible options:
18 16
19- Facebook Comments 17- Facebook Comments
20- Discourse 18- Discourse
21 19
22There are some options that are better but still use too many scripts, 20There are some options that are better but still use too many scripts,
23frames, or social integrations on your web page that could impact some 21frames, or social integrations on your web page that could impact some
24users: 22users:
25 23
26- Disqus 24- Disqus
27- Isso 25- Isso
28- Remark42 26- Remark42
29 27
30Lastly, I looked into a few unique ways of generating blog comments, 28Lastly, I looked into a few unique ways of generating blog comments,
31such as using Twitter threads or GitHub issues to automatically post 29such as using Twitter threads or GitHub issues to automatically post
32issues. However, these both rely on external third-party sites that I 30issues. However, these both rely on external third-party sites that I
33don't currently use. 31don't currently use.
34 32
35## Stay Static with Server-Side Comments 33* Stay Static with Server-Side Comments
36
37The main issue for my personal use-case is that my blog is completely, 34The main issue for my personal use-case is that my blog is completely,
38100% static. I use PHP on the back-end but website visitors only see 35100% static. I use PHP on the back-end but website visitors only see
39HTML and a single CSS file. No external javascript and no embedded 36HTML and a single CSS file. No external javascript and no embedded
@@ -41,15 +38,14 @@ frames.
41 38
42So, how do we keep a site static and still allow users to interact with 39So, how do we keep a site static and still allow users to interact with
43blog posts? The key actually pretty simple - I'm already using PHP, so 40blog posts? The key actually pretty simple - I'm already using PHP, so
44why not rely on the classic HTML `<form>` and a PHP script to 41why not rely on the classic HTML =<form>= and a PHP script to save the
45save the comments somewhere? As it turns out, this was a perfect 42comments somewhere? As it turns out, this was a perfect solution for me.
46solution for me.
47 43
48The second issue for my personal use-case is that I am trying to keep 44The second issue for my personal use-case is that I am trying to keep
49the contents of my website accessible over time, as described by 45the contents of my website accessible over time, as described by
50@brandur, in his post entitled [Blog with Markdown + Git, and degrade 46[cite/t:@brandur], in his post entitled
51gracefully through 47[[https://brandur.org/fragments/graceful-degradation-time][Blog with
52time](https://brandur.org/fragments/graceful-degradation-time) . 48Markdown + Git, and degrade gracefully through time]] .
53 49
54This means I cannot rely on a database for comments, since I do not rely 50This means I cannot rely on a database for comments, since I do not rely
55on a database for any other part of my websites. 51on a database for any other part of my websites.
@@ -59,35 +55,34 @@ that future readers will be able to see the source data long after I'm
59gone, or the website has gone offline. However, I still haven't 55gone, or the website has gone offline. However, I still haven't
60committed any images served on my blog to Git, as I'm not entirely sold 56committed any images served on my blog to Git, as I'm not entirely sold
61on Git LFS yet - for now, images can be found at 57on Git LFS yet - for now, images can be found at
62[img.cleberg.net](https://img.cleberg.net). 58[[https://img.cleberg.net][img.cleberg.net]].
63 59
64Saving my comments back to the Git repository ensures that another 60Saving my comments back to the Git repository ensures that another
65aspect of my site will degrade gracefully. 61aspect of my site will degrade gracefully.
66 62
67## Create a Comment Form 63* Create a Comment Form
68
69Okay, let's get started. The first step is to create an HTML form that 64Okay, let's get started. The first step is to create an HTML form that
70users can see and utilize to submit comments. This is fairly easy and 65users can see and utilize to submit comments. This is fairly easy and
71can be changed depending on your personal preferences. 66can be changed depending on your personal preferences.
72 67
73Take a look at the code block below for the form I currently use. Note 68Take a look at the code block below for the form I currently use. Note
74that `<current-url>` is replaced automatically in PHP with 69that =<current-url>= is replaced automatically in PHP with the current
75the current post's URL, so that my PHP script used later will know 70post's URL, so that my PHP script used later will know which blog post
76which blog post the comment is related to. 71the comment is related to.
77 72
78The form contains the following structure: 73The form contains the following structure:
79 74
801. `<form>` - This is the form and will determine which PHP 751. =<form>= - This is the form and will determine which PHP script to
81 script to send the comment to. 76 send the comment to.
822. `<section hidden>` - This section is hidden from the user 772. =<section hidden>= - This section is hidden from the user and is used
83 and is used to ensure that we know which blog post sent the comment. 78 to ensure that we know which blog post sent the comment.
843. `<section>` Display Name (Optional) - Used to accept a 793. =<section>= Display Name (Optional) - Used to accept a display name,
85 display name, if entered. 80 if entered.
864. `<section>` Comment (Required) - Used to accept the 814. =<section>= Comment (Required) - Used to accept the user's full
87 user's full comment. Markdown is allowed. 82 comment. Markdown is allowed.
885. `<button>` - A button to submit the form. 835. =<button>= - A button to submit the form.
89 84
90```html 85#+begin_src html
91<form action="/comment.php" method="POST"> 86<form action="/comment.php" method="POST">
92 <h3>Leave a Comment</h3> 87 <h3>Leave a Comment</h3>
93 <section hidden> 88 <section hidden>
@@ -127,32 +122,30 @@ The form contains the following structure:
127 </section> 122 </section>
128 <button type="submit">Submit</button> 123 <button type="submit">Submit</button>
129</form> 124</form>
130``` 125#+end_src
131
132## Handle Comments via POST
133 126
127* Handle Comments via POST
134Now that we have a form and can submit comments, we need to create a PHP 128Now that we have a form and can submit comments, we need to create a PHP
135script so that the server can fetch the comment data and save it. Make 129script so that the server can fetch the comment data and save it. Make
136sure your PHP script name matches the name you entered in the 130sure your PHP script name matches the name you entered in the =action=
137`action` field in your form. 131field in your form.
138 132
139See the code block below for the contents of my `comment.php` 133See the code block below for the contents of my =comment.php= script. We
140script. We perform the following tasks in this script: 134perform the following tasks in this script:
141 135
1421. Grab the POST data from the HTML form. 1361. Grab the POST data from the HTML form.
1432. Sanitize the comment data with `htmlentities`. 1372. Sanitize the comment data with =htmlentities=.
1443. Set the display name to `Anonymous` if it was left blank. 1383. Set the display name to =Anonymous= if it was left blank.
1454. Create a PHP object that combines all of this data. 1394. Create a PHP object that combines all of this data.
1465. Check if our destination file `comments.json` exists. 1405. Check if our destination file =comments.json= exists.
1476. If so, convert the PHP object to JSON and save it to the file. 1416. If so, convert the PHP object to JSON and save it to the file.
1487. If the `comments.json` file does not exist, the script 1427. If the =comments.json= file does not exist, the script will exit with
149 will exit with an error. You can alter this to ensure it creates the 143 an error. You can alter this to ensure it creates the script, but my
150 script, but my source code includes the file by default, so it will 144 source code includes the file by default, so it will always exist.
151 always exist. 1458. Finally, send the user back to the =#comments= section of the blog
1528. Finally, send the user back to the `#comments` section of 146 post they just read.
153 the blog post they just read. 147
154 148#+begin_src php
155```php
156// Get the content sent from the comment form 149// Get the content sent from the comment form
157$comment = htmlentities($_POST['userContent']); 150$comment = htmlentities($_POST['userContent']);
158$post_url = $_POST['postURL']; 151$post_url = $_POST['postURL'];
@@ -187,33 +180,32 @@ if (file_exists($file_name)) {
187 180
188// Send the user back 181// Send the user back
189header('Location: ' . $post_url . '#comments'); 182header('Location: ' . $post_url . '#comments');
190``` 183#+end_src
191 184
192If you're using Apache, make sure the `www-data` user on 185If you're using Apache, make sure the =www-data= user on your server has
193your server has the correct permissions to your website directory or 186the correct permissions to your website directory or else it will not be
194else it will not be able to write to `comments.json`. 187able to write to =comments.json=.
195 188
196```sh 189#+begin_src sh
197chgrp -R www-data /path/to/website/ 190chgrp -R www-data /path/to/website/
198chmod -R g+w comments.json 191chmod -R g+w comments.json
199``` 192#+end_src
200
201## Display User Comments
202 193
194* Display User Comments
203Now that we can submit comments, and they are saved to the 195Now that we can submit comments, and they are saved to the
204`comments.json` file, let's make sure we can show those 196=comments.json= file, let's make sure we can show those comments in each
205comments in each blog post. 197blog post.
206 198
207The code block below shows the function I use to decode my 199The code block below shows the function I use to decode my
208`comments.json` file, check if the comments apply to the 200=comments.json= file, check if the comments apply to the current post,
209current post, and then display them. 201and then display them.
210 202
211This piece of code should **really** be inside a function (or at least 203This piece of code should *really** be inside a function (or at least in
212in an organized PHP workflow). Don't just copy-and-paste and expect it 204an organized PHP workflow). Don't just copy-and-paste and expect it to
213to work. You need to at least supply a `$query` variable 205work. You need to at least supply a =$query= variable depending on the
214depending on the page visited. 206page visited.
215 207
216```php 208#+begin_src php
217$query = 'your-blog-post.html'; 209$query = 'your-blog-post.html';
218 210
219// Load saved comments 211// Load saved comments
@@ -245,32 +237,29 @@ foreach ($comments as $comment) {
245} 237}
246 238
247echo $comment_section; 239echo $comment_section;
248``` 240#+end_src
249
250## Bonus: Create a 'Recent Comments' Page
251 241
242* Bonus: Create a 'Recent Comments' Page
252Finally, the last part of my current system is to create a Recent 243Finally, the last part of my current system is to create a Recent
253Comments page so that I can easily check-in on my blog and moderate any 244Comments page so that I can easily check-in on my blog and moderate any
254spam. As an alternative, you could use PHP's `mail()` 245spam. As an alternative, you could use PHP's =mail()= function to email
255function to email you for each blog comment. 246you for each blog comment.
256 247
257The code to do this is literally the same code as the previous section, 248The code to do this is literally the same code as the previous section,
258I just make sure it is printed when someone visits 249I just make sure it is printed when someone visits
259`https://example.com/comments/`. 250=https://example.com/comments/=.
260
261## Possible Enhancements
262 251
252* Possible Enhancements
263This comment system is by no means a fully-developed system. I have 253This comment system is by no means a fully-developed system. I have
264noted a few possible enhancements here that I may implement in the 254noted a few possible enhancements here that I may implement in the
265future: 255future:
266 256
267- Create a secure moderator page with user authentication at 257- Create a secure moderator page with user authentication at
268 `https://blog.example.com/mod/`. This page could have the 258 =https://blog.example.com/mod/=. This page could have the option to
269 option to edit or delete any comment found in 259 edit or delete any comment found in =comments.json=.
270 `comments.json`. 260- Create a temporary file, such as =pending_comments.json=, that will
271- Create a temporary file, such as `pending_comments.json`, 261 store newly-submitted comments and won't display on blog posts until
272 that will store newly-submitted comments and won't display on blog 262 approved by a moderator.
273 posts until approved by a moderator. 263- Create a =/modlog/= page with a chronological log, showing which
274- Create a `/modlog/` page with a chronological log, 264 moderator approved which comments and why certain comments were
275 showing which moderator approved which comments and why certain 265 rejected.
276 comments were rejected.
content/blog/2022-04-09-pinetime.md → blog/pinetime/index.org renamed +59 −70
@@ -1,44 +1,39 @@
1+++ 1#+title: PineTime: An Open-Source Smart Watch
2date = 2022-04-09 2#+date: 2022-04-09
3title = "PineTime: An Open-Source Smart Watch" 3#+description: Playing with the PineTime smart watch.
4description = "Playing with the PineTime smart watch." 4#+filetags: :hardware:
5+++ 5
6 6* PineTime Product Information
7## PineTime Product Information 7** Overview
8 8The [[https://www.pine64.org/pinetime/][PineTime]] is an open-source
9### Overview 9smartwatch, created by [[https://www.pine64.org][PINE64]]. Originally
10
11The [PineTime](https://www.pine64.org/pinetime/) is an open-source
12smartwatch, created by [PINE64](https://www.pine64.org). Originally
13announced in September 2019, this ARM-based watch is a fantastic option 10announced in September 2019, this ARM-based watch is a fantastic option
14for users who want the benefits of a modern smartwatch with the backing 11for users who want the benefits of a modern smartwatch with the backing
15of open-source components and software. 12of open-source components and software.
16 13
17### Product Specifications 14** Product Specifications
15I won't dive into too many details that you can find on
16[[https://www.pine64.org/pinetime/][the product page]], but I wanted to
17point out the prices for each watch and the primary functions:
18 18
19I won't dive into too many details that you can find on [the product 191. Price:
20page](https://www.pine64.org/pinetime/), but I wanted to point out the
21prices for each watch and the primary functions:
22 20
231. Price: 21 - $26.99 (Sealed)
22 - $24.99 (Dev Kit)
23 - $51.98 (One Sealed + One Dev Kit)
24 24
25 - \$26.99 (Sealed) 252. Primary Functionality:
26 - \$24.99 (Dev Kit)
27 - \$51.98 (One Sealed + One Dev Kit)
28 26
292. Primary Functionality: 27 - Clock (+ Smartphone Sync)
30 28 - Pedometer
31 - Clock (+ Smartphone Sync) 29 - Heart Rate Monitor
32 - Pedometer 30 - Sleep Monitor
33 - Heart Rate Monitor 31 - Calories burned
34 - Sleep Monitor 32 - Messaging
35 - Calories burned 33 - Smartphone Notifications
36 - Messaging 34 - Media Controls
37 - Smartphone Notifications
38 - Media Controls
39
40## Unboxing
41 35
36* Unboxing
42Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and 37Now, my PineTime was ordered on 2022-02-17, shipped on 2022-02-22, and
43was delivered on 2022-03-23. With the current delays on shipping times 38was delivered on 2022-03-23. With the current delays on shipping times
44around the world (and the semiconductor shortage), a month for delivery 39around the world (and the semiconductor shortage), a month for delivery
@@ -51,31 +46,29 @@ USB wall adapter).
51The watch itself was able to turn on immediately when I pulled it out of 46The watch itself was able to turn on immediately when I pulled it out of
52the box, but the battery was depleted and required charging right away. 47the box, but the battery was depleted and required charging right away.
53 48
54![PineTime Box 49#+caption: PineTime Box Contents
55Contents](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime_box_contents.png) 50[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime_box_contents.png]]
56
57## Software
58
59### Watch OS: InfiniTime
60 51
52* Software
53** Watch OS: InfiniTime
61While turning on the watch for the first time, some of the main design 54While turning on the watch for the first time, some of the main design
62choices you can see in the watch OS, 55choices you can see in the watch OS,
63[InfiniTime](https://wiki.pine64.org/wiki/InfiniTime), are: 56[[https://wiki.pine64.org/wiki/InfiniTime][InfiniTime]], are:
64 57
65- A square bezel, not too thin against the sides of the watch. 58- A square bezel, not too thin against the sides of the watch.
66- A simple, rubber band. 59- A simple, rubber band.
67- Basic font and screen pixel design. 60- Basic font and screen pixel design.
68- Swipe gestures to access other screens. 61- Swipe gestures to access other screens.
69 62
70![PineTime 63#+caption: PineTime Screens
71Screens](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime.png) 64[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/pinetime.png]]
72 65
73The OS itself is fantastic in terms of functionality for me. It does 66The OS itself is fantastic in terms of functionality for me. It does
74exactly what a smartwatch should do - track time, steps, heart rates, 67exactly what a smartwatch should do - track time, steps, heart rates,
75and connect to another smart device, without being overly burdensome to 68and connect to another smart device, without being overly burdensome to
76the user. 69the user.
77 70
78My only gripe so far is that it's *really* difficult to swipe to 71My only gripe so far is that it's /really/ difficult to swipe to
79different screens, such as pulling down the notification tray. I'm not 72different screens, such as pulling down the notification tray. I'm not
80sure if this is an OS or hardware issue, but it makes it quite hard to 73sure if this is an OS or hardware issue, but it makes it quite hard to
81quickly move around the screens. 74quickly move around the screens.
@@ -86,46 +79,43 @@ accidentally turn on the screen. With other watches, I absolutely hated
86not being able to turn off the raise-to-wake or wave features (i.e., 79not being able to turn off the raise-to-wake or wave features (i.e.,
87blinding myself while wearing a watch at night because I moved my arm). 80blinding myself while wearing a watch at night because I moved my arm).
88 81
89### iOS App: InfiniLink 82** iOS App: InfiniLink
90
91Since I am using iOS as my primary mobile device OS, I am using the 83Since I am using iOS as my primary mobile device OS, I am using the
92[InfiniLink](https://github.com/xan-m/InfiniLink) app to connect my 84[[https://github.com/xan-m/InfiniLink][InfiniLink]] app to connect my
93watch. 85watch.
94 86
95This app provides the following for PineTime owners: 87This app provides the following for PineTime owners:
96 88
97- Firmware updates 89- Firmware updates
98- Steps 90- Steps
99- Charts 91- Charts
100- Notifications 92- Notifications
101 93
102I mashed up a few screenshots to show off the home page, menu, firmware 94I mashed up a few screenshots to show off the home page, menu, firmware
103update, and notification test screens: 95update, and notification test screens:
104 96
105![InfiniLink 97#+caption: InfiniLink Home
106Home](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_home.png) 98[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_home.png]]
107 99
108Another big feature of InfiniLink is the ability to track pedometer 100Another big feature of InfiniLink is the ability to track pedometer
109steps in a collection of beautiful graphs, with the option to change 101steps in a collection of beautiful graphs, with the option to change
110your step goal and add in manual steps. 102your step goal and add in manual steps.
111 103
112![InfiniLink 104#+caption: InfiniLink Steps
113Steps](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_steps.png) 105[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_steps.png]]
114 106
115Finally, there are charts to display the battery percentage and heart 107Finally, there are charts to display the battery percentage and heart
116rates over time. This area also comes with an option to clear data. 108rates over time. This area also comes with an option to clear data.
117 109
118![InfiniLink 110#+caption: InfiniLink Charts
119Charts](https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_charts.png) 111[[https://img.cleberg.net/blog/20220409-pinetime-smart-watch/infinilink_charts.png]]
120
121## Final Thoughts
122
123### Pros
124 112
113* Final Thoughts
114** Pros
125After wearing my watch for a few weeks, I have mostly positive thoughts 115After wearing my watch for a few weeks, I have mostly positive thoughts
126about the watch so far. In the past, I have owned smartwatches by 116about the watch so far. In the past, I have owned smartwatches by
127FitBit, Fossil, Apple, etc. - **but I prefer the PineTime over all of 117FitBit, Fossil, Apple, etc. - *but I prefer the PineTime over all of
128those watches**. 118those watches*.
129 119
130The PineTime strips out all the unnecessary features and performs the 120The PineTime strips out all the unnecessary features and performs the
131functions that it provides effectively and efficiently. 121functions that it provides effectively and efficiently.
@@ -133,12 +123,11 @@ functions that it provides effectively and efficiently.
133The battery life is amazing on this device. By default, the watch seems 123The battery life is amazing on this device. By default, the watch seems
134to last anywhere from a few days to a week before dying. 124to last anywhere from a few days to a week before dying.
135 125
136And of course, it's open source and backed by some of the most 126And of course, it's open source and backed by some of the most dedicated
137dedicated enthusiasts and developers I've seen. Watching the Matrix 127enthusiasts and developers I've seen. Watching the Matrix channel,
138channel, forums, and website have been exciting to see. 128forums, and website have been exciting to see.
139
140### Cons
141 129
130** Cons
142If I had to complain about anything, it would simply be the small bugs 131If I had to complain about anything, it would simply be the small bugs
143in some features that can be contributed to the companion apps more than 132in some features that can be contributed to the companion apps more than
144the watch itself. 133the watch itself.
blog/plex-migration/index.org added +230
@@ -0,0 +1,230 @@
1#+title: Migrating Plex to a New Server (& Nvidia Transcoding)
2#+date: 2022-03-08
3#+description: A retrospective on migrating Plex between servers and enabling Nvidia GPU transcoding.
4#+filetags: :selfhosting:
5
6* Migration Phases
7I recently decided to migrate my server from an old OptiPlex desktop
8machine to a custom-built tower with better hardware in every category.
9In order to do this, I would need to properly migrate a full Plex
10installation.
11
12The second part of this migration is that the new server uses an Nvidia
13GPU and does not have any integrated graphics, which requires extra work
14for installation, but provides much better hardware transcoding options
15for Plex.
16
17Therefore, I have broken this migration down into three phases:
18
191. Configure the New Server
202. Migrate Plex Data & Devices
213. Configure GPU Transcoding
22
23* Phase 1: Configure the New Server
24** Choosing an OS
25In order to migrate Plex to my new server, I first needed to choose an
26appropriate operating system (OS) and install it on the machine. Given
27that I have encountered numerous issues installing other Linux
28distributions properly with Nvidia graphics, I chose
29[[https://ubuntu.com/download/server][Ubuntu Server]].
30
31The first step is to create a bootable USB with Ubuntu Server. This is
32easy with [[https://www.balena.io/etcher/][Etcher]], an app that runs on
33many different platforms. Just download the Ubuntu Server =.iso= image,
34launch Etcher, and install the =.iso= on the USB.
35
36Once the USB is created, insert it into my server, reboot, and click
37=Esc= (or any of the =F1-12= keys) until the BIOS menu appears. Finally,
38launch the USB boot drive.
39
40** Booting with Nvidia
41In order to install Ubuntu Server with an Nvidia Graphics card (and no
42integrated graphics on this device for some reason), you'll have to
43configure the boot menu to allow different graphics drivers to be
44loaded.
45
46When booting from the USB, the machine will launch the initial
47installation menu. From this menu, type =e= to view the default command
48options that come with the device - it's a good idea to take a photo of
49this screen, so you can enter these commands on the next screen (along
50with adding support for Nvidia).
51
52Finally, type =Ctrl + C= to enter the command line. From this command
53line, enter the commands found on the =e= screen. *Remember to add
54=nomodeset= to the =linux ...= line so that your Nvidia device will
55display the installation screens properly!*
56
57Here's an example of the commands I pulled from the =e= screen and
58entered on the command line.
59
60#+begin_src sh
61setparams 'Install Ubuntu Server'
62setgfxpayload=keep
63linux /casper/vmlinuz quiet nomodeset ---
64initrd /casper/initrd
65boot
66#+end_src
67
68#+caption: Ubuntu Server Installation 01
69[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_01.png]]
70
71#+caption: Ubuntu Server Installation 02
72[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_02.png]]
73
74Once the machine is rebooted, enter the =e= screen again and add
75=nomodeset= to the =linux ...= line again and press =Ctrl + X= to save
76the boot options.
77
78The machine is now fully installed and can properly display on an
79external display using the Nvidia GPU.
80
81Always remember to update and upgrade on a new installation:
82
83#+begin_src sh
84sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
85#+end_src
86
87* Phase 2: Migrate Plex Data & Devices
88This phase uses the great Plex article on migrations
89([[https://support.plex.tv/articles/201370363-move-an-install-to-another-system/][Move
90an Installation to Another System]]) and adds a bit more information to
91help with commands and context.
92
93** Terminology
94*Source:** The original server that is being replaced.\\
95*Destination:** The new server.\\
96*Client:** Any application that can be used to modify settings for both
97source/destination.
98
99** Step 01: [Client] Update Settings
100Open up a Plex app and /disable/ the =Account= > =Library= >
101=Empty trash automatically after every scan= preference for the source
102server.
103
104** Step 02: [Destination] Install Plex
105Open up the [[https://www.plex.tv/media-server-downloads/][Plex Media
106Server download page]] and copy the link for the appropriate platform.
107
108Execute the following commands on the destination server to install
109Plex:
110
111#+begin_src sh
112wget <url>
113sudo dpkg -i <filename>
114sudo systemctl stop plexmediaserver.service
115#+end_src
116
117** Step 03: [Source] Stop Plex & Migrate Data
118First, stop the Plex service so that no data is created or modified
119during the migration.
120
121#+begin_src sh
122sudo systemctl stop plexmediaserver.service
123#+end_src
124
125Next, copy the data to the new server. To find where the Plex data
126directory is located, Plex has another excellent article available:
127[[https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/][Where
128is the Plex Media Server data directory located?]].
129
130There are many ways to copy the data to the new server and will largely
131depend on the size of the folder being copied. Personally, my data
132folder was ~23GB and I opted to simply use the =scp= command to copy the
133files over SSH.
134
135This process was throttled by the old server's slow HDD and ports and
136took approximately 90 minutes to complete. In comparison, moving the
137data from the new server's =home/user/= directory to the
138=/var/.../Plex Media Server= directory took 2-3 minutes.
139
140#+begin_src sh
141scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
142#+end_src
143
144** Step 04: [Destination] Update File Permissions
145In case you move the data directory to a common area on the new server,
146it will have to be moved to the proper location before Plex can function
147properly:
148
149#+begin_src sh
150mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
151#+end_src
152
153To ensure permissions were retained properly, the server will need to
154show that all files and folders in the data directory are owned by
155=plex:plex= (or whichever user is running the Plex application).
156
157#+begin_src sh
158sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
159#+end_src
160
161Finally, start the service and check the status.
162
163#+begin_src sh
164sudo systemctl start plexmediaserver.service
165sudo systemctl status plexmediaserver.service
166#+end_src
167
168** Step 05: [Client] Update Libraries & Metadata
169The first step - now that the new server is up and running - is to sign
170out of the client and sign back in. Once this is done, update any
171library locations, if necessary. This was unnecessary in my case since I
172simply moved my storage drives from the source server to the destination
173server.
174
175Next, perform the following actions in the client:
176
1771. On the left sidebar, click =More= > Three-Dot Menu >
178 =Scan Library Files=
1792. /Enable/ the =Account= > =Library= >
180 =Empty trash automatically after every scan= preference for the
181 source server.
1823. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
183 > =Empty Trash=
1844. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
185 > =Clean Bundles=
1865. On the left sidebar, click =More= > Three-Dot Menu > =Manage Server=
187 > =Optimize Database=
188
189Finally, double-check the Remote Access settings to make sure no changes
190have caused issues with accessing the server from outside the network.
191
192In my case, I use a single port forwarding rule in my router and needed
193to update the Local LAN IP Address to the new server IP address.
194
195#+caption: Router port forwarding
196[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/port_forwarding.png]]
197
198* Phase 3: Configure GPU Transcoding
199The final piece to the migration is enabling hardware transcoding so
200that Plex can fully utilize the new Nvidia GPU available in the server.
201The first step is to install Nvidia graphics drivers. This process may
202take a few minutes, but the commands are pretty simple:
203
204#+begin_src sh
205sudo add-apt-repository ppa:graphics-drivers/ppa
206sudo apt update
207sudo apt-get install ubuntu-drivers-common
208sudo ubuntu-drivers autoinstall
209#+end_src
210
211Finally, reboot so that the changes are loaded:
212
213#+begin_src sh
214sudo reboot now
215#+end_src
216
217To ensure that the Nvidia graphics drivers are working properly, run the
218following command to view the available GPUs, statistics, and processes:
219
220#+begin_src sh
221sudo nvidia-smi
222#+end_src
223
224#+caption: nvidia-smi
225[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/nvidia_smi.png]]
226
227Finally, enable hardware transcoding settings in the Plex application:
228
229#+caption: Plex transcoding settings
230[[https://img.cleberg.net/blog/20220308-plex-media-server-migration/plex_transcoding.png]]
content/blog/2023-07-19-plex-transcoder-errors.md → blog/plex-transcoder-errors/index.org renamed +27 −33
@@ -1,63 +1,57 @@
1+++ 1#+title: How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.'
2date = 2023-07-19 2#+date: 2023-07-19
3title = "How to Avoid Plex Error: 'Conversion failed. The transcoder failed to start up.'" 3#+description: Learn how to avoid Plex conversion errors caused by subtitles.
4description = "Learn how to avoid Plex conversion errors caused by subtitles." 4#+filetags: :sysadmin:
5+++
6
7## Plex Transcoder Error
8 5
6* Plex Transcoder Error
9Occasionally, you may see an error in your Plex client that references a 7Occasionally, you may see an error in your Plex client that references a
10failure with the transcoder conversion process. The specific error 8failure with the transcoder conversion process. The specific error
11wording is: 9wording is:
12 10
13```txt 11#+begin_src txt
14Conversion failed. The transcoder failed to start up. 12Conversion failed. The transcoder failed to start up.
15``` 13#+end_src
16
17## Debugging the Cause
18 14
19In order to get a better look at what is causing the error, I'm going 15* Debugging the Cause
20to observe the Plex console while the error occurs. To do this, open the 16In order to get a better look at what is causing the error, I'm going to
21Plex web client, go to `Settings` > `Manage` > 17observe the Plex console while the error occurs. To do this, open the
22`Console`. Now, try to play the title again and watch to see 18Plex web client, go to =Settings= > =Manage= > =Console=. Now, try to
23which errors occur. 19play the title again and watch to see which errors occur.
24 20
25In my case, you can see the errors below are related to a subtitle file 21In my case, you can see the errors below are related to a subtitle file
26(`.srt`) causing the transcoder to crash. 22(=.srt=) causing the transcoder to crash.
27 23
28```txt 24#+begin_src txt
29Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt 25Jul 19, 2023 16:49:34.945 [140184571120440] Error — Couldn't find the file to stream: /movies/Movie Title (2021)/Movie Title (2021).srt
30Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file 26Jul 19, 2023 16:49:34.947 [140184532732728] Error — [Req#7611/Transcode/42935159-67C1-4192-9336-DDC6F7BC9330] Error configuring transcoder: TPU: Failed to download sub-stream to temporary file
31Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID. 27Jul 19, 2023 16:49:35.225 [140184532732728] Warning — [Req#760d/Transcode] Got a request to stop a transcode session without a valid session GUID.
32Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available 28Jul 19, 2023 16:49:45.561 [140184532732728] Info — [Req#7648] AutoUpdate: no updates available
33Jul 19, 2023 16:51:23.402 [140184510081848] Info — Library section 1 (Movies) will be updated because of a change in "/movies/Movie Title (2021)/Movie Title (2021).srt" 29Jul 19, 2023 16:51:23.402 [140184510081848] Info — Library section 1 (Movies) will be updated because of a change in "/movies/Movie Title (2021)/Movie Title (2021).srt"
34``` 30#+end_src
35
36## Solving the Error
37 31
32* Solving the Error
38In my case, I simply removed the subtitle file because it was not 33In my case, I simply removed the subtitle file because it was not
39critical to keep. You may also avoid this by turning off subtitles if 34critical to keep. You may also avoid this by turning off subtitles if
40you don't want to delete the file. 35you don't want to delete the file.
41 36
42```sh 37#+begin_src sh
43rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt" 38rm -rf "/movies/Movie Title (2021)/Movie Title (2021).srt"
44``` 39#+end_src
45 40
46Once the subtitle is removed from the directory or subtitles are turned 41Once the subtitle is removed from the directory or subtitles are turned
47off, try to play the title again. At this point, it should play without 42off, try to play the title again. At this point, it should play without
48error. If not, reopen or refresh your Plex client and the Plex server: 43error. If not, reopen or refresh your Plex client and the Plex server:
49 44
50```sh 45#+begin_src sh
51sudo systemctl restart plexmediaserver.service 46sudo systemctl restart plexmediaserver.service
52``` 47#+end_src
53 48
54## Related Discussion 49* Related Discussion
55 50Looking at
56Looking at [a related Plex forum 51[[https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2][a
57post](https://forums.plex.tv/t/subtitles-crashing-plex-transcoder-samsung-q80-tv-with-or-without-hardware-transcode/741441/2), 52related Plex forum post]], it seems that =.srt= subtitles are the core
58it seems that `.srt` subtitles are the core issue here. 53issue here. However, Plex does not seem to have a resolution that fixes
59However, Plex does not seem to have a resolution that fixes these 54these errors.
60errors.
61 55
62Unfortunately, I would suggest converting subtitle formats, burning the 56Unfortunately, I would suggest converting subtitle formats, burning the
63subtitles into the title, or simply removing subtitles when they cause 57subtitles into the title, or simply removing subtitles when they cause
content/blog/2022-08-31-privacy.com-changes.md → blog/privacy-com-changes/index.org renamed +30 −34
@@ -1,24 +1,21 @@
1+++ 1#+title: Concerning Changes on Privacy.com
2date = 2022-08-31 2#+date: 2022-08-31
3title = "Concering Changes on Privacy.com" 3#+description: My thoughts on Privacy.com changing from a prepaid model to a credit charge model.
4description = "My thoughts on Privacy.com changing from a prepaid model to a credit charge model." 4#+filetags: :privacy:
5+++
6
7## Privacy.com Changes Their Terms
8 5
6* Privacy.com Changes Their Terms
9Recently, Privacy.com reached out to their customers regarding a change 7Recently, Privacy.com reached out to their customers regarding a change
10in their terms of use. Further, all customers are required to agree to 8in their terms of use. Further, all customers are required to agree to
11the changes in order to continue using their accounts. 9the changes in order to continue using their accounts.
12 10
13[You can view the new cardholder agreement 11[[https://privacy.com/commercial-cardholder-agreement][You can view the
14here](https://privacy.com/commercial-cardholder-agreement). 12new cardholder agreement here]].
15 13
16When you log in, you'll be greeted with a pop-up window asking you to 14When you log in, you'll be greeted with a pop-up window asking you to
17review and agree to the new terms of use. You will also not be able to 15review and agree to the new terms of use. You will also not be able to
18open any new cards until the terms are agreed to. 16open any new cards until the terms are agreed to.
19 17
20### Changing from a "Prepaid Debit" Model to a "Charge Card" Model 18** Changing from a "Prepaid Debit" Model to a "Charge Card" Model
21
22The actual content of the changes is interesting. While the historical 19The actual content of the changes is interesting. While the historical
23model of using Privacy.com was akin to prepaid debit cards, the new 20model of using Privacy.com was akin to prepaid debit cards, the new
24model is very similar to a credit card (they use the term "charge 21model is very similar to a credit card (they use the term "charge
@@ -27,7 +24,7 @@ card").
27I have used Privacy.com for 1-2 years, and the process was always that 24I have used Privacy.com for 1-2 years, and the process was always that
28you would create a single-use or merchant-locked card. This card could 25you would create a single-use or merchant-locked card. This card could
29be used for any dollar limit you set and would immediately draw the 26be used for any dollar limit you set and would immediately draw the
30funds from whatever funding source you connected, e.g. PayPal account or 27funds from whatever funding source you connected, e.g. PayPal account or
31a bank account. 28a bank account.
32 29
33The benefit this service provides with a premium account is masking the 30The benefit this service provides with a premium account is masking the
@@ -37,15 +34,14 @@ charges through Privacy.com and set the merchant as one of their pre-set
37options, such as "Smiley's Corner Store" or "NSA Gift Shop." 34options, such as "Smiley's Corner Store" or "NSA Gift Shop."
38 35
39The new model still works with a bank account as a funding source, but 36The new model still works with a bank account as a funding source, but
40the model is changed so that you get a "line of credit" set according 37the model is changed so that you get a "line of credit" set according to
41to a 14-day billing cycle. It seems that Privacy.com will now allow 38a 14-day billing cycle. It seems that Privacy.com will now allow charges
42charges to be incurred without being immediately paid. 39to be incurred without being immediately paid.
43
44### Daily Payments and Available Credit
45 40
41** Daily Payments and Available Credit
46Instead of paying as charges are incurred, you must make a "Daily 42Instead of paying as charges are incurred, you must make a "Daily
47Payment" and your "Available Credit" will be locked until you make 43Payment" and your "Available Credit" will be locked until you make that
48that payment. There are also "End of Billing Cycle Payments" that are 44payment. There are also "End of Billing Cycle Payments" that are
49assigned a due date. 45assigned a due date.
50 46
51Further, Privacy.com will decline charges that would cause you to exceed 47Further, Privacy.com will decline charges that would cause you to exceed
@@ -53,18 +49,20 @@ your Available Credit or Credit Limit.
53 49
54One particular interesting section states the following: 50One particular interesting section states the following:
55 51
56> YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER 52#+begin_quote
57> OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT 53YOUR OBLIGATION TO PAY US BACK FOR ALL CARD TRANSACTIONS AND OTHER
58> PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE 54OBLIGATIONS YOU INCUR IS SECURED BY THE SECURED ACCOUNT. IF YOU DO NOT
59> WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL 55PAY US BACK FOR ANY AMOUNT YOU OWE US WHEN YOUR PAYMENTS ARE DUE, WE
60> LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY 56WILL EXERCISE OUR INTEREST AND DEBIT THE SECURED ACCOUNT, AND YOU WILL
61> AGREEMENT FOR MORE INFORMATION. 57LOSE THE MONEY IN THE SECURED ACCOUNT. SEE SECTION 8: SECURITY AGREEMENT
58FOR MORE INFORMATION.
62 59
63### Personal Information 60#+end_quote
64 61
62** Personal Information
65Now that Privacy.com is more of a financial institution, they are 63Now that Privacy.com is more of a financial institution, they are
66obligated to comply with the [know your 64obligated to comply with the
67customer](https://en.wikipedia.org/wiki/Know_your_customer) 65[[https://en.wikipedia.org/wiki/Know_your_customer][know your customer]]
68guidelines/laws. 66guidelines/laws.
69 67
70I did not proceed with the change to my Privacy.com account, but I have 68I did not proceed with the change to my Privacy.com account, but I have
@@ -73,13 +71,11 @@ to be submitted, such as SSN. I am not aware of all new personal
73information required or if the funding source is now required to only be 71information required or if the funding source is now required to only be
74a bank account. 72a bank account.
75 73
76### Fees 74** Fees
77
78Luckily, the fees section did not change much. The subscription fees for 75Luckily, the fees section did not change much. The subscription fees for
79a premium account are still the only fees. 76a premium account are still the only fees.
80 77
81## My Thoughts 78* My Thoughts
82
83Personally, I wiped my personal information from my account and then 79Personally, I wiped my personal information from my account and then
84permanently deleted it when I heard about these changes. I have no 80permanently deleted it when I heard about these changes. I have no
85interest in yet another method of credit lending offered by private 81interest in yet another method of credit lending offered by private
@@ -93,6 +89,6 @@ This type of change toward a "buy it now, pay us later" model is
93concerning, and I will be watching Privacy.com to see if they further 89concerning, and I will be watching Privacy.com to see if they further
94their interests in the credit model as time goes on. 90their interests in the credit model as time goes on.
95 91
96Could we see them start charging interest, fees, etc.? I'm not sure, 92Could we see them start charging interest, fees, etc.? I'm not sure, but
97but this change does not inspire confidence in their mission as a 93this change does not inspire confidence in their mission as a
98privacy-focused company. 94privacy-focused company.
content/blog/2023-01-23-random-mullvad-wireguard.md → blog/random-wireguard/index.org renamed +41 −45
@@ -1,17 +1,16 @@
1+++ 1#+title: Connecting to a Random Mullvad Wireguard Host on Boot
2date = 2023-01-23 2#+date: 2023-01-23
3title = "Connecting to a Random Mullvad Wireguard Host on Boot" 3#+description: Learn how to connect to a random Mullvad Wireguard host on boot.
4description = "Learn how to connect to a random Mullvad Wireguard host on boot." 4#+filetags: :linux:
5+++
6
7## Mullvad Wireguard
8 5
6* Mullvad Wireguard
9If you're using an OS that does not support one of Mullvad's apps, 7If you're using an OS that does not support one of Mullvad's apps,
10you're likely using the Wireguard configuration files instead. 8you're likely using the Wireguard configuration files instead.
11 9
12If not, the first step is to visit Mullvad's [Wireguard configuration 10If not, the first step is to visit Mullvad's
13files](https://mullvad.net/en/account/#/wireguard-config) page and 11[[https://mullvad.net/en/account/#/wireguard-config][Wireguard
14download a ZIP of the configuration files you want to use. 12configuration files]] page and download a ZIP of the configuration files
13you want to use.
15 14
16Personally, I downloaded all configuration files across the world and 15Personally, I downloaded all configuration files across the world and
17chose my connections using the script below. 16chose my connections using the script below.
@@ -19,32 +18,31 @@ chose my connections using the script below.
19Once the files are downloaded, unzip them and move them to your 18Once the files are downloaded, unzip them and move them to your
20preferred location: 19preferred location:
21 20
22```sh 21#+begin_src sh
23cd Downloads 22cd Downloads
24unzip mullvad_wireguard_linux_all_all.zip 23unzip mullvad_wireguard_linux_all_all.zip
25mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/ 24mkdir ~/mullvad && mv ~/Downloads/*.conf ~/mullvad/
26``` 25#+end_src
27
28## Creating a Script to Connect to a Random Host
29 26
27*** Creating a Script to Connect to a Random Host
30Once you have a folder of Wireguard configuration files from Mullvad, 28Once you have a folder of Wireguard configuration files from Mullvad,
31you can create a script to randomly connect to any one of the locations. 29you can create a script to randomly connect to any one of the locations.
32 30
33Start by creating a shell script - mine is called `vpn.sh`. 31Start by creating a shell script - mine is called =vpn.sh=.
34 32
35```sh 33#+begin_src sh
36nano ~/vpn.sh 34nano ~/vpn.sh
37``` 35#+end_src
38 36
39Within this script, you can paste the following info. Note that I 37Within this script, you can paste the following info. Note that I
40specify `us-*` in my script, which means that it will only 38specify =us-*= in my script, which means that it will only consider
41consider US-based VPN locations. You can alter this or simply change it 39US-based VPN locations. You can alter this or simply change it =*= to
42`*` to consider all locations. 40consider all locations.
43 41
44```sh 42#+begin_src sh
45#!/bin/sh 43#!/bin/sh
46 44
47ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do 45ls /home/$USER/mullvad/us-** |sort -R |tail -n 1 |while read file; do
48 # Replace `doas` with `sudo` if your machine uses `sudo`, 46 # Replace `doas` with `sudo` if your machine uses `sudo`,
49 # or remove `doas` if users don't need to su to run wg-quick 47 # or remove `doas` if users don't need to su to run wg-quick
50 doas wg-quick up $file; 48 doas wg-quick up $file;
@@ -52,19 +50,19 @@ ls /home/$USER/mullvad/us-* |sort -R |tail -n 1 |while read file; do
52 printf "\n\nPrinting new IP info:\n" 50 printf "\n\nPrinting new IP info:\n"
53 curl https://am.i.mullvad.net/connected 51 curl https://am.i.mullvad.net/connected
54done 52done
55``` 53#+end_src
56 54
57Once you've modified the script to your liking, add executable 55Once you've modified the script to your liking, add executable
58permissions and run the script: 56permissions and run the script:
59 57
60```sh 58#+begin_src sh
61chmod +x ~/vpn.sh 59chmod +x ~/vpn.sh
62~/vpn.sh 60~/vpn.sh
63``` 61#+end_src
64 62
65The output should look like the following: 63The output should look like the following:
66 64
67```txt 65#+begin_src txt
68doas (user@host) password: 66doas (user@host) password:
69 67
70# ... The script will process all of the iptables and wg commands here 68# ... The script will process all of the iptables and wg commands here
@@ -73,44 +71,42 @@ Created Mullvad wireguard connection with file: /home/user/mullvad/us-nyc-wg-210
73 71
74Printing new IP info: 72Printing new IP info:
75You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99 73You are connected to Mullvad (server country-city-wg-num). Your IP address is 12.345.678.99
76``` 74#+end_src
77 75
78That's all there is to it. You can see your new location and IP via the 76That's all there is to it. You can see your new location and IP via the
79`printf` and `curl` commands included in the 77=printf= and =curl= commands included in the script.
80script.
81
82You can also go to the [Connection Check ​\|
83Mullvad](https://mullvad.net/en/check/) page to see if you are fully
84connected to Mullvad and if any leaks exist.
85 78
86![Mullvad Connection 79You can also go to the [[https://mullvad.net/en/check/][Connection Check
87Check](https://img.cleberg.net/blog/20230123-random-mullvad-wireguard/mullvad_check.png) 80​| Mullvad]] page to see if you are fully connected to Mullvad and if any
81leaks exist.
88 82
89## Disconnecting from the Wireguard Connection 83#+caption: Mullvad Connection Check
84[[https://img.cleberg.net/blog/20230123-random-mullvad-wireguard/mullvad_check.png]]
90 85
86* Disconnecting from the Wireguard Connection
91If you forget which connection you're using, you can execute the 87If you forget which connection you're using, you can execute the
92following command to see where Wireguard is currently connected: 88following command to see where Wireguard is currently connected:
93 89
94```sh 90#+begin_src sh
95wg show 91wg show
96``` 92#+end_src
97 93
98This command will show you the Wireguard interfaces and should output a 94This command will show you the Wireguard interfaces and should output a
99connection like so: `interface: us-lax-wg-104`. 95connection like so: =interface: us-lax-wg-104=.
100 96
101Once you have this, just disconnect using that files' full path: 97Once you have this, just disconnect using that files' full path:
102 98
103```sh 99#+begin_src sh
104wg-quick down /home/user/mullvad/us-lax-wg-104.conf 100wg-quick down /home/user/mullvad/us-lax-wg-104.conf
105``` 101#+end_src
106 102
107I have a TODO item on figuring out how to easily export an environment 103I have a TODO item on figuring out how to easily export an environment
108variable that contains the configuration file's full name, so that I 104variable that contains the configuration file's full name, so that I can
109can just execute the following: 105just execute the following:
110 106
111```sh 107#+begin_src sh
112# Ideal situation if I can export the $file variable to the environment 108# Ideal situation if I can export the $file variable to the environment
113wg-quick down $file 109wg-quick down $file
114``` 110#+end_src
115 111
116If you have an idea on how to do this, email me! 112If you have an idea on how to do this, email me!
content/blog/2023-01-03-recent-website-changes.md → blog/recent-website-changes/index.org renamed +31 −42
@@ -1,43 +1,36 @@
1+++ 1#+title: Recent Website Changes
2date = 2023-01-03 2#+date: 2023-01-03
3title = "Recent Website Changes" 3#+description: A retrospective of recent changes to my website.
4description = "A retrospective of recent changes to my website." 4#+filetags: :dev:
5+++
6
7## The State of This Website
8 5
6* The State of This Website
9Over the years, this website has changed URLs, styles, content focus, 7Over the years, this website has changed URLs, styles, content focus,
10and much more. However, it seems that I am never done, as I am yet again 8and much more. However, it seems that I am never done, as I am yet again
11changing this page to fit my current needs and wants. 9changing this page to fit my current needs and wants.
12 10
13While this site was already minimal (\~12kb), it contained a lot of 11While this site was already minimal (~12kb), it contained a lot of
14disorganized content and some poorly chosen color schemes. 12disorganized content and some poorly chosen color schemes.
15 13
16The recent updates attempt to fix these items while focusing on what I 14The recent updates attempt to fix these items while focusing on what I
17truly care about here: the content within each page. 15truly care about here: the content within each page.
18 16
19## Recent Changes 17* Recent Changes
20
21I've made quite a few changes to the style of this website today, both 18I've made quite a few changes to the style of this website today, both
22in the CSS and in the HTML. 19in the CSS and in the HTML.
23 20
24Here are some of the key takeaways from today's changes: 21Here are some of the key takeaways from today's changes:
25 22
261. Reduce the width of the website from `60em` to 231. Reduce the width of the website from =60em= to =40em=.
27 `40em`. 242. Remove breadcrumb navigation and replaced with a simple "Return Home"
282. Remove breadcrumb navigation and replaced with a simple "Return 25 link when visiting anything other than the homepage.
29 Home" link when visiting anything other than the homepage. 263. Remove syntax highlighting from code blocks. CSS now loads from a
303. Remove syntax highlighting from code blocks. CSS now loads from a 27 single file.
31 single file. 284. Move blog posts on the homepage to the top, pushing tertiary content
324. Move blog posts on the homepage to the top, pushing tertiary content 29 down.
33 down. 305. Update font-family from =monospace= to =sans-serif= for readability.
345. Update font-family from `monospace` to 31
35 `sans-serif` for readability. 32* Future Focus
36 33** Accessibility
37## Future Focus
38
39### Accessibility
40
41My main focus amidst all the changes this past year was accessibility. I 34My main focus amidst all the changes this past year was accessibility. I
42tried to improve the accessibility of all pages on this site as well as 35tried to improve the accessibility of all pages on this site as well as
43I know how. For example, I added aria labels to identify different 36I know how. For example, I added aria labels to identify different
@@ -46,8 +39,7 @@ navigation menus in November.
46I want to continue this in 2023 and learn more about the accessibility 39I want to continue this in 2023 and learn more about the accessibility
47features I can utilize to help those who may need them. 40features I can utilize to help those who may need them.
48 41
49### Content First 42** Content First
50
51Beyond accessibility, I am making a concerted effort to bring the 43Beyond accessibility, I am making a concerted effort to bring the
52content of this site to the forefront and push the tertiary information 44content of this site to the forefront and push the tertiary information
53(e.g., About Me, Services, etc.) down below the content. 45(e.g., About Me, Services, etc.) down below the content.
@@ -56,30 +48,27 @@ Further, I want to review and edit previous blog posts for grammar,
56accuracy, dead links, and more. Where necessary, I may even add a blurb 48accuracy, dead links, and more. Where necessary, I may even add a blurb
57at the top of old posts that are no longer accurate or helpful. 49at the top of old posts that are no longer accurate or helpful.
58 50
59### Style 51** Style
60
61As always, I will be searching for ways to reduce distractions and bring 52As always, I will be searching for ways to reduce distractions and bring
62a minimalistic approach to this site. While there are certainly more 53a minimalistic approach to this site. While there are certainly more
63drastic measures I could take, such as making this a [Shinobi 54drastic measures I could take, such as making this a
64Website](https://shinobi.bt.ht) or a [No CSS 55[[https://shinobi.bt.ht][Shinobi Website]] or a [[https://nocss.club][No
65Website](https://nocss.club), I prefer to keep some modern features 56CSS Website]], I prefer to keep some modern features here.
66here.
67 57
68Two ideas have been floating around in my head, but I am not sure how I 58Two ideas have been floating around in my head, but I am not sure how I
69feel about these approaches yet: 59feel about these approaches yet:
70 60
711. Going all-in with Emacs org-mode and creating a script to 611. Going all-in with Emacs org-mode and creating a script to
72 auto-publish all my blog posts from org-mode to plain HTML files and 62 auto-publish all my blog posts from org-mode to plain HTML files and
73 then letting my CSS style it after the fact. 63 then letting my CSS style it after the fact.
742. Focus on publishing for Gemini or Gopher and then utilize a 642. Focus on publishing for Gemini or Gopher and then utilize a
75 conversion program to translate the pages to HTML. 65 conversion program to translate the pages to HTML.
76
77## Looking Back
78 66
67* Looking Back
79As I sit here and finalize the changes, as well as this post, I am happy 68As I sit here and finalize the changes, as well as this post, I am happy
80with the current state of this website. It's certainly a long way from 69with the current state of this website. It's certainly a long way from
81my first attempts (parallax background images, anyone?) and it's good 70my first attempts (parallax background images, anyone?) and it's good to
82to see the progress. 71see the progress.
83 72
84I can only hope and work hard to ensure that I make more meaningful 73I can only hope and work hard to ensure that I make more meaningful
85progress by this time next year. 74progress by this time next year.
blog/redirect-github-pages/index.org added +120
@@ -0,0 +1,120 @@
1#+title: Redirect GitHub Pages from Subdomain to the Top-Level Domain
2#+date: 2020-08-22
3#+description: Learn how to redirect Github pages to the TLD.
4#+filetags: :sysadmin:
5
6* Short answer
7** Step 1
8Add a new file CNAME to your GitHub Pages repository containing only one
9line: your top-level domain name. E.g.: =example.com=
10
11** Step 2
12[Optional] but highly recommended
13
142.1: Remove all other top-level records (prefixed with @) of type A from
15your DNS configuration.
16
172.2: Remove a CNAME record for the second-level domain www if it is
18present.
19
20** Step 3
21Add these 5 entries to the very top of your DNS configuration:
22
23#+begin_src txt
24@ A 185.199.108.153
25@ A 185.199.109.153
26@ A 185.199.110.153
27@ A 185.199.111.153
28www CNAME your_github_username.github.io.
29#+end_src
30
31Replace =your_github_username= with your actual GitHub username.
32
33** Step 4
34Wait for your DNS changes to propagate. DNS changes aren't effective
35immediately. They can take up to a full day to propagate.
36
37* Long answer
38This issue has two sides. One is the DNS configuration itself. Another
39one is the way GitHub Pages will forward HTTP requests.
40
41We need to know a few things to understand what GitHub is trying to say
42in their documentation.
43
44** DNS Entry Types
45There are two types of DNS records which interest us: CNAME and A.
46
47=A= is also known as =Apex= or sometimes as =root entry=. It forwards
48requests to a specified fixed IP address. =CNAME= entry forwards
49requests to a specified URL (actual valid plain text URL, not an IP
50address).
51
52** DNS Load balancing
53GitHub has one central URL address which accepts all DNS requests for
54GitHub Pages: =http://username.github.io=. That URL is resolved to
55different IP addresses based on your geographical location. Website
56hosted on GitHub Pages is a simple collection of =HTML=, =CSS= and =JS=
57files. GitHub distributes these files to different servers across the
58globe. So that when your browser sends a request from Europe, it
59receives data from a server in Europe. The same is valid for the
60requests from Asia and the USA.
61
62** What GitHub is trying to say
63Since =A= records in DNS must contain IP addresses, and they must be
64either =185.199.108.153= or =185.199.109.153= or =185.199.110.153= or
65=185.199.111.153=, there is no way to forward requests to a server
66located somewhere in Europe or Asia. Your website hosted at GitHub Pages
67will be downloaded from a central GitHub Pages server. There is a minor
68risk that if GitHub Pages DNS servers (=x.x.x.153=) are down for some
69reason, all custom domains which use fixed GitHub Pages IP addresses
70will not be accessible (their DNS requests will not be resolvable).
71
72That is why GitHub strongly suggests to either use a second-level domain
73for your GitHub Pages (e.g. =blog.example.com=) or use a DNS service
74provider that supports a record type =ALIAS= that acts as =A= record but
75forwards request to a URL address (e.g. =username.github.io=) instead of
76a fixed IP address.
77
78** How GitHub Pages treats HTTP requests
79After a DNS request for =your_github_username.github.io= is resolved
80into an IP address, e.g. =185.199.108.153= your browser sends an HTTP
81request to that server with an HTTP header =Host=. Below are =curl=
82examples that load the same website (these examples might not work if
83you are behind a proxy server):
84
85#+begin_src sh
86curl --header "Host: your_github_username.github.io" http://185.199.108.153/
87curl --header "Host: www.example.com" http://185.199.108.153/
88curl --header "Host: example.com" http://185.199.108.153/
89#+end_src
90
91This way GitHub Pages servers know which user website to serve.
92
93#+begin_quote
94GitHub Pages server will automatically redirect HTTP requests to the
95top-level domain if your =CNAME= file contains =example.com= but
96=www.example.com= is requested.
97
98The same is valid if your =CNAME= file contains =www.example.com= but
99the header =Host= in the =HTTP= request contains =example.com=.
100#+end_quote
101
102** Why can't I add a =CNAME= record entry that accepts a top-level request (=@=) to my DNS configuration?
103Quote from the GitHub Pages documentation:
104
105#+begin_quote
106Warning: Do not create a CNAME record for your custom apex domain! Doing
107so may cause issues with other services, such as email, on that domain.
108#+end_quote
109
110** References:
1111. [[https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site][Setting
112 up a custom domain with GitHub Pages]]
1132. [[https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages][My
114 custom domain isn't working]]
1153. [[https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address][Cannot
116 access my GitHub Pages website by IP Address]]
1174. [[https://stackoverflow.com/questions/23375422/how-do-i-set-up-github-pages-to-redirect-dns-requests-from-a-subdomain-e-g-www][How
118 do I set up GitHub Pages to redirect DNS requests from a subdomain
119 (e.g. www) to the top-level domain (TLD, Apex record)?]]
120
content/blog/2022-03-02-note-taking.md → blog/reliable-notes/index.org renamed +49 −49
@@ -1,16 +1,17 @@
1+++ 1#+title: Easy, Reliable Note-Taking
2date = 2022-03-02 2#+date: 2022-03-02
3title = "Easy, Reliable Note-Taking" 3#+description: My personal preferences to ensure notes are clear, portable, and long-lasting.
4description = "My personal preferences to ensure notes are clear, portable, and long-lasting." 4#+filetags: :personal:
5+++
6 5
7## Choosing Durable File Formats 6* Choosing Durable File Formats
8 7
9> TL;DR: Write in a format that can be easily rendered and read in 8#+begin_quote
10> plain-text mode (e.g., `.txt`, `.md`, etc.). 9TL;DR: Write in a format that can be easily rendered and read in
10plain-text mode (e.g., =.txt=, =.md=, etc.).
11#+end_quote
11 12
12As I've written more and more over the years, I've found that my love 13As I've written more and more over the years, I've found that my love of
13of note-taking is always growing. Everything I learn or need to remember 14note-taking is always growing. Everything I learn or need to remember
14can be written down in a note and saved digitally, with no cost to 15can be written down in a note and saved digitally, with no cost to
15myself. Unlike paper copies that need physical storage space, digital 16myself. Unlike paper copies that need physical storage space, digital
16files simply need space on your local disk or cloud storage, which is 17files simply need space on your local disk or cloud storage, which is
@@ -22,8 +23,8 @@ styling. This meant that I had to go through each note during migration
22and edit the file to look presentable again. 23and edit the file to look presentable again.
23 24
24For the last year or two, I have written everything exclusively in 25For the last year or two, I have written everything exclusively in
25[Markdown](https://en.wikipedia.org/wiki/Markdown) format. Small notes, 26[[https://en.wikipedia.org/wiki/Markdown][Markdown]] format. Small
26long-form writings, and even these blog posts are all written in 27notes, long-form writings, and even these blog posts are all written in
27Markdown. 28Markdown.
28 29
29Why Markdown? While I do appreciate the simplicity of plain-text files 30Why Markdown? While I do appreciate the simplicity of plain-text files
@@ -31,19 +32,18 @@ without any formatting, I often need visual cues such as heading and
31code blocks to keep my thoughts straight. Markdown provides a minimal 32code blocks to keep my thoughts straight. Markdown provides a minimal
32set of styling indicators for me to style my notes without adding any 33set of styling indicators for me to style my notes without adding any
33proprietary, embedded data into the files. If I want a top-level 34proprietary, embedded data into the files. If I want a top-level
34heading, I simply add a hash (`#`) before the line. An added 35heading, I simply add a hash (=#=) before the line. An added bonus is
35bonus is that even if a system doesn't understand Markdown, it will 36that even if a system doesn't understand Markdown, it will render it as
36render it as plain-text and I can read it just as easily. 37plain-text and I can read it just as easily.
37 38
38For example, here's how TextEdit on macOS will open and display a 39For example, here's how TextEdit on macOS will open and display a
39Markdown file in plain-text, since it does contain any features to 40Markdown file in plain-text, since it does contain any features to
40preview Markdown as HTML: 41preview Markdown as HTML:
41 42
42![Plain Text 43#+caption: Plain Text Markdown
43Markdown](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png) 44[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/plain_markdown.png]]
44
45## Saving & Syncing Files
46 45
46** Saving & Syncing Files
47In order to read and edit my notes across platforms, I use my personal 47In order to read and edit my notes across platforms, I use my personal
48cloud storage through Tresorit due to its native integration with macOS 48cloud storage through Tresorit due to its native integration with macOS
49and iOS file managers. In addition, Tresorit works well on Debian-based 49and iOS file managers. In addition, Tresorit works well on Debian-based
@@ -59,19 +59,17 @@ me to sync anything manually or kick-off a sync job to update my files.
59This means that I can edit on mobile, and it takes about 5-10 seconds to 59This means that I can edit on mobile, and it takes about 5-10 seconds to
60see the changes on desktop. 60see the changes on desktop.
61 61
62### Version Control with Git 62*** Version Control with Git
63
64A different approach I've contemplated is storing my notes and 63A different approach I've contemplated is storing my notes and
65attachments is using a hosted Git repository to track changes to the 64attachments is using a hosted Git repository to track changes to the
66files. However, I don't want to rely on an external service that could 65files. However, I don't want to rely on an external service that could
67potentially see into my data, even if the repository is private. 66potentially see into my data, even if the repository is private.
68 67
69I might just do `git init` locally and then commit my changes 68I might just do =git init= locally and then commit my changes each time
70each time I write or update a note, but that seems to be a lot of work 69I write or update a note, but that seems to be a lot of work just for
71just for tracking changes - which I don't necessarily care to know. 70tracking changes - which I don't necessarily care to know.
72
73### Backups!
74 71
72*** Backups!
75One small addition to the storage/sync conversation is the idea of 73One small addition to the storage/sync conversation is the idea of
76backups. Personally, I manually create periodic backups of my entire 74backups. Personally, I manually create periodic backups of my entire
77cloud storage, compress it into an archive, and store it on my home 75cloud storage, compress it into an archive, and store it on my home
@@ -81,12 +79,15 @@ To improve my workflow, I am going to be exploring options to
81automatically compress the mounted cloud directory and send it over to 79automatically compress the mounted cloud directory and send it over to
82my server on a set schedule. 80my server on a set schedule.
83 81
84## Writing on Desktop 82** Writing on Desktop
85 83
86> **Update (06.14.22)**: Since writing this post, I have reverted to 84#+begin_quote
87> simply keeping my `notes` folder open and opening notes 85*Update (06.14.22)*: Since writing this post, I have reverted to simply
88> individually in TextEdit for a more minimal and relaxing writing 86keeping my =notes= folder open and opening notes individually in
89> experience on the desktop. 87TextEdit for a more minimal and relaxing writing experience on the
88desktop.
89
90#+end_quote
90 91
91The bulk of my writing occurs in a desktop environment, with a full 92The bulk of my writing occurs in a desktop environment, with a full
92keyboard layout and wide screen. I don't illustrate with a smart pen, I 93keyboard layout and wide screen. I don't illustrate with a smart pen, I
@@ -94,31 +95,30 @@ rarely use embedded images, and I love being able to see all of my
94notes/directories in a sidebar. 95notes/directories in a sidebar.
95 96
96With this simple set of requirements, I chose 97With this simple set of requirements, I chose
97[Obsidian](https://obsidian.md) as my desktop text editor. Obsidian has 98[[https://obsidian.md][Obsidian]] as my desktop text editor. Obsidian
98some in-depth tools like a graph view, command palette, mentions, etc., 99has some in-depth tools like a graph view, command palette, mentions,
99but I've found that using it as a simple Markdown editor is incredibly 100etc., but I've found that using it as a simple Markdown editor is
100easy and straightforward. 101incredibly easy and straightforward.
101 102
102Here's an example of how my Markdown notes look when opened in 103Here's an example of how my Markdown notes look when opened in
103plain-text mode: 104plain-text mode:
104 105
105![Obsidian Markdown Source 106#+caption: Obsidian Markdown Source Mode
106Mode](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png) 107[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_source_mode.png]]
107
108Here's the "live preview" version, where the Markdown is rendered
109into its HTML format:
110 108
111![Obsidian Markdown Live 109Here's the "live preview" version, where the Markdown is rendered into
112Preview](https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png) 110its HTML format:
113 111
114### Programming on Desktop 112#+caption: Obsidian Markdown Live Preview
113[[https://img.cleberg.net/blog/20220302-easy-reliable-note-taking/obsidian_live_preview.png]]
115 114
115*** Programming on Desktop
116While I was writing this, I realized I should specify that I don't use 116While I was writing this, I realized I should specify that I don't use
117the same editor for writing notes and for writing code. For programming 117the same editor for writing notes and for writing code. For programming
118purposes, I use [VSCodium](https://vscodium.com) as my development IDE. 118purposes, I use [[https://vscodium.com][VSCodium]] as my development
119 119IDE.
120## Writing on Mobile
121 120
121** Writing on Mobile
122Personally, I write very little on mobile, except when needing to take 122Personally, I write very little on mobile, except when needing to take
123important notes on-the-go. Any long-form writing, journals, etc. are 123important notes on-the-go. Any long-form writing, journals, etc. are
124done at home, where I always have my laptop available. 124done at home, where I always have my laptop available.
@@ -126,9 +126,9 @@ done at home, where I always have my laptop available.
126I wanted a simple and foolproof editor for iOS, preferably open-source. 126I wanted a simple and foolproof editor for iOS, preferably open-source.
127After a long journey of testing the few (& terrible) open-source iOS 127After a long journey of testing the few (& terrible) open-source iOS
128note-taking apps, I finally found a phenomenal one: 128note-taking apps, I finally found a phenomenal one:
129[Runestone](https://github.com/simonbs/runestone). This app is fantastic 129[[https://github.com/simonbs/runestone][Runestone]]. This app is
130for note-taking, has plenty of optional features, and integrates 130fantastic for note-taking, has plenty of optional features, and
131natively with the iOS file manager. 131integrates natively with the iOS file manager.
132 132
133This app opens the iOS file manager and allows you to click any file you 133This app opens the iOS file manager and allows you to click any file you
134want, opens it up in an editor, and lets me save and close out of that 134want, opens it up in an editor, and lets me save and close out of that
blog/scli/index.org added +145
@@ -0,0 +1,145 @@
1#+title: Installing scli on Alpine Linux (musl)
2#+date: 2023-11-08
3#+description: Learn how to properly configure scli for musl-based systems.
4#+filetags: :linux:
5
6[[https://github.com/isamert/scli][scli]] is a command-line tool that
7allows you to connect to your Signal messenger account. This program
8utilizes a two-pane display that shows you chats on the left and the
9focused conversation on the right.
10
11This guide will show you how to install =scli= and its dependencies on
12Alpine Linux, which requires some extra work due to musl.
13
14If you're using a non-musl system, you can likely following the =scli=
15README and download the packaged binaries for an easier installation
16process.
17
18* Dependencies
19In order to use =scli=, you need a few dependencies:
20
21- =openjdk17-jre= - Used as a dependency for the =signal-cli= tool.
22 Version may vary.
23- =signal-cli= - Used as the backbone of the =scli= tool.
24- =findutils= - Replaces the standard Busybox version of =xargs=.
25- =urwid= - A console user interface library for Python.
26- =urwid-readline= - For GNU emacs-like keybinds on the input line.
27- =qrencode= - Displays a QR code in the terminal to link the device
28 using your phone. Not necessary if you're only linking on desktop and
29 can copy/paste the connection URL.
30
31Let's start by installing the packages available via Alpine's
32repositories. Be sure to install the latest version of =openjdk=. If you
33run into Java-related issues, uninstall =openjdk= and install an older
34version.
35
36#+begin_src sh
37doas apk add openjdk17-jre findutils qrencode
38#+end_src
39
40Next, let's install =signal-cli=. Be sure to export the version of
41=signal-cli= that you want. I use version =0.12.4= below, but that may
42be outdated by the time you're reading this.
43
44#+begin_src sh
45export VERSION="0.12.4"
46wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
47doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
48doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
49#+end_src
50
51Finally, install the =urwid= packages using the Python packaging
52utility.
53
54#+begin_src sh
55pip3 install urwid urwid-readline
56#+end_src
57
58* Installation
59Now that we have all of the dependencies we need, we can install =scli=.
60Start by simply cloning the repository.
61
62#+begin_src sh
63git clone https://github.com/isamert/scli
64#+end_src
65
66When I cloned this repository on 2023-11-08, I found a bug in the logic
67that required a fix. You must edit the =scli= file and replace the one
68instance of =RLIMIT_OFILE= with =RLIMIT_NOFILE=.
69
70#+begin_src sh
71cd scli
72nano scli
73#+end_src
74
75Once complete, you can move this program to anywhere on your =$PATH=. I
76chose the following directory.
77
78#+begin_src sh
79doas mv scli /usr/local/bin/scli
80#+end_src
81
82* Initial Setup
83Now that everything is installed, we can login and configure the client.
84Start by generating a connection link.
85
86#+begin_src sh
87signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
88#+end_src
89
90This will generate a connection link and related QR code for you to use
91to link the devices together. Once complete, *wait patiently** for the
92connection process to finish.
93
94Once it completes, it will exit and return you to the prompt. From here,
95you need to perform an initial =receive= command to start things off.
96The =USERNAME= variable should be your phone number, such as
97=+15551237890=.
98
99#+begin_src sh
100signal-cli -u USERNAME receive
101#+end_src
102
103Also be sure to test the daemon to ensure it works properly. If no
104errors occur, it's working. If you run into errors because you're not
105running a DBUS session, see my notes below.
106
107#+begin_src sh
108signal-cli -u USERNAME daemon
109#+end_src
110
111Once the initial reception is complete, you are ready to use =scli=.
112
113This process will differ depending on your desktop environment (DE). If
114you are running a DE, you likely have a DBUS session running already and
115can simply launch the program.
116
117However, if you're like me and running your computer straight on the TTY
118without a DE, you'll need to start a DBUS session for this program.
119
120#+begin_src sh
121# If you're not running a DBUS session yet, you need to start one for scli
122dbus-run-session -- scli
123
124# OR - If you're already running a DBUS session, simply run scli
125scli
126#+end_src
127
128* Configuration
129Lastly, there are a number of configuration options that you can pass
130via the command or in the =~/.config/sclirc= file. See the Github README
131for more information on configuration options.
132
133#+begin_src sh
134nano ~/.config/sclirc
135#+end_src
136
137#+begin_src conf
138# ~/.config/sclirc
139
140wrap-at = 80
141enable-notifications = true
142#+end_src
143
144That's it! Following this guide, I have a functional =scli= program that
145successfully sends messages to my contacts and myself!
content/blog/2023-10-17-self-hosting-anonymousoverflow.md → blog/self-hosting-anonymousoverflow/index.org renamed +27 −33
@@ -1,35 +1,30 @@
1+++ 1#+title: Self-Hosting AnonymousOverflow
2date = 2023-10-17 2#+date: 2023-10-17
3title = "Self-Hosting AnonymousOverflow" 3#+description: A guide to self-hosting the AnonymousOverflow application on your own server.
4description = "A guide to self-hosting the AnonymousOverflow application on your own server." 4#+filetags: :selfhosting:
5+++
6
7## Overview
8 5
6* Overview
9I recently launched an instance of AnonymousOverflow at 7I recently launched an instance of AnonymousOverflow at
10[ao.cleberg.net](https://ao.cleberg.net) and wanted to write a brief 8[[https://ao.cleberg.net][ao.cleberg.net]] and wanted to write a brief
11post on how easy it is to install with Docker Compose and Nginx. 9post on how easy it is to install with Docker Compose and Nginx.
12 10
13This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse 11This guide uses Ubuntu server, Docker Compose, and Nginx as a reverse
14proxy. 12proxy.
15 13
16## Installation 14* Installation
17 15** Docker Compose
18### Docker Compose
19
20To install AnonymousOverflow, start by creating a directory for the 16To install AnonymousOverflow, start by creating a directory for the
21application and create its `docker-compose.yml` file. 17application and create its =docker-compose.yml= file.
22 18
23```sh 19#+begin_src sh
24mkdir ~/anonymousoverflow && cd ~/anonymousoverflow 20mkdir ~/anonymousoverflow && cd ~/anonymousoverflow
25nano docker-compose.yml 21nano docker-compose.yml
26``` 22#+end_src
27 23
28Within this file, paste the following information. Be sure to change the 24Within this file, paste the following information. Be sure to change the
29`APP_URL`, `JWT_SIGNING_SECRET`, and 25=APP_URL=, =JWT_SIGNING_SECRET=, and =ports= to match your needs.
30`ports` to match your needs.
31 26
32```yaml 27#+begin_src yaml
33version: '3' 28version: '3'
34 29
35services: 30services:
@@ -42,32 +37,30 @@ services:
42 ports: 37 ports:
43 - '9380:8080' 38 - '9380:8080'
44 restart: 'always' 39 restart: 'always'
45``` 40#+end_src
46 41
47Save and exit the file when complete. You can now launch the container 42Save and exit the file when complete. You can now launch the container
48and access it via your local network. 43and access it via your local network.
49 44
50```sh 45#+begin_src sh
51sudo docker-compose up -d 46sudo docker-compose up -d
52``` 47#+end_src
53
54### Nginx Reverse Proxy
55 48
49** Nginx Reverse Proxy
56If you want to access this service outside the local network, I 50If you want to access this service outside the local network, I
57recommend using Nginx as a reverse proxy. 51recommend using Nginx as a reverse proxy.
58 52
59Let's start by creating a configuration file. 53Let's start by creating a configuration file.
60 54
61```sh 55#+begin_src sh
62sudo nano /etc/nginx/sites-available/ao 56sudo nano /etc/nginx/sites-available/ao
63``` 57#+end_src
64 58
65Within this file, paste the following content and repace 59Within this file, paste the following content and repace
66`ao.example.com` with your URL. You may need to update the 60=ao.example.com= with your URL. You may need to update the SSL
67SSL certificate statements if your certificates are in a different 61certificate statements if your certificates are in a different location.
68location.
69 62
70```conf 63#+begin_src conf
71server { 64server {
72 if ($host ~ ^[^.]+\.cleberg\.net$) { 65 if ($host ~ ^[^.]+\.cleberg\.net$) {
73 return 301 https://$host$request_uri; 66 return 301 https://$host$request_uri;
@@ -119,15 +112,16 @@ server {
119 proxy_buffers 64 256k; 112 proxy_buffers 64 256k;
120 } 113 }
121} 114}
122``` 115#+end_src
123 116
124Save and exit the file when complete. On Ubuntu, you will need to 117Save and exit the file when complete. On Ubuntu, you will need to
125symlink the configuration file before it will be recognized by Nginx. 118symlink the configuration file before it will be recognized by Nginx.
126Once complete, simply restart the web server. 119Once complete, simply restart the web server.
127 120
128```sh 121#+begin_src sh
129sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao 122sudo ln -s /etc/nginx/sites-available/ao /etc/nginx/sites-enabled/ao
130sudo systemctl restart nginx.service 123sudo systemctl restart nginx.service
131``` 124#+end_src
132 125
133The website will now be available publicly. Visit [my instance](https://ao.cleberg.net) for an example. 126The website will now be available publicly. Visit
127[[https://ao.cleberg.net][my instance]] for an example.
content/blog/2023-10-11-self-hosting-authelia.md → blog/self-hosting-authelia/index.org renamed +84 −98
@@ -1,12 +1,10 @@
1+++ 1#+title: Self-Hosting Authelia
2date = 2023-10-11 2#+date: 2023-10-11
3title = "Self-Hosting Authelia" 3#+description: A guide to self-hosting the Authelia application on your own server.
4description = "A guide to self-hosting the Authelia application on your own server." 4#+filetags: :selfhosting:
5+++
6 5
7## Overview 6* Overview
8 7[[https://www.authelia.com/][Authelia]] is an open-source authentication
9[Authelia](https://www.authelia.com/) is an open-source authentication
10service that allows you to place a portal between end users on the 8service that allows you to place a portal between end users on the
11internet and self-hosted services on your server. 9internet and self-hosted services on your server.
12 10
@@ -15,42 +13,38 @@ authentication for any such user before allowing them to access a
15specific service on your domain. 13specific service on your domain.
16 14
17This guide will walk through a standard installation of Authelia for 15This guide will walk through a standard installation of Authelia for
18`example.com`, using `auth.example.com` as 16=example.com=, using =auth.example.com= as Authelia's authentication
19Authelia's authentication domain and `teddit.example.com` as 17domain and =teddit.example.com= as the website we want to protect behind
20the website we want to protect behind the authentication portal. 18the authentication portal.
21
22## Prerequisites
23 19
20* Prerequisites
24This guide assumes you have the following already set-up: 21This guide assumes you have the following already set-up:
25 22
26- A registered domain with DNS pointing to your server. 23- A registered domain with DNS pointing to your server.
27- A subdomain for Authelia (`auth.example.com`) and a 24- A subdomain for Authelia (=auth.example.com=) and a subdomain to
28 subdomain to protect via Authelia (`app.example.com`). 25 protect via Authelia (=app.example.com=).
29- A working Nginx web server. 26- A working Nginx web server.
30- Docker and docker-compose installed. 27- Docker and docker-compose installed.
31
32## Installation
33 28
29* Installation
34This guide will walk through each installation step one-by-one, starting 30This guide will walk through each installation step one-by-one, starting
35with the container and finishing by cleaning up external access via an 31with the container and finishing by cleaning up external access via an
36Nginx reverse proxy. 32Nginx reverse proxy.
37 33
38### Docker-Compose 34** Docker-Compose
39
40To start, create a directory for Authelia and create a 35To start, create a directory for Authelia and create a
41`docker-compose.yml` file. 36=docker-compose.yml= file.
42 37
43```sh 38#+begin_src sh
44mkdir ~/authelia 39mkdir ~/authelia
45nano ~/authelia/docker-compose.yml 40nano ~/authelia/docker-compose.yml
46``` 41#+end_src
47 42
48Within this file, paste the following content. If you prefer a different 43Within this file, paste the following content. If you prefer a different
49local port, modify the port on the left side of the colon on the 44local port, modify the port on the left side of the colon on the
50`9091:9091` line. Be sure to modify the `TZ` 45=9091:9091= line. Be sure to modify the =TZ= variable to your timezone.
51variable to your timezone.
52 46
53```yml 47#+begin_src yml
54version: '3.3' 48version: '3.3'
55 49
56services: 50services:
@@ -63,55 +57,52 @@ services:
63 - 9091:9091 57 - 9091:9091
64 environment: 58 environment:
65 - TZ=America/Chicago 59 - TZ=America/Chicago
66``` 60#+end_src
67 61
68Start the container with docker-compose: 62Start the container with docker-compose:
69 63
70```sh 64#+begin_src sh
71sudo docker-compose up -d 65sudo docker-compose up -d
72``` 66#+end_src
73 67
74After the first start, the container will automatically exit and require 68After the first start, the container will automatically exit and require
75you to modify the app's configuration files before continuing. Read on 69you to modify the app's configuration files before continuing. Read on
76to learn more. 70to learn more.
77 71
78### Authelia Configuration 72** Authelia Configuration
79
80To configure Authelia before we restart the container, we need to open 73To configure Authelia before we restart the container, we need to open
81the `config` directory and modify the files. Start by editing 74the =config= directory and modify the files. Start by editing the
82the `configuration.yml` file, where all of Authelia's 75=configuration.yml= file, where all of Authelia's settings are stored.
83settings are stored.
84 76
85My personal preference is to copy the original configuration file to a 77My personal preference is to copy the original configuration file to a
86backup file and edit a fresh copy. 78backup file and edit a fresh copy.
87 79
88```sh 80#+begin_src sh
89sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk 81sudo cp ~/authelia/config/configuration.yml ~/authelia/config/configuration.yml.bk
90sudo nano ~/authelia/config/configuration.yml 82sudo nano ~/authelia/config/configuration.yml
91``` 83#+end_src
92 84
93Within the blank `configuration.yml` file, paste the 85Within the blank =configuration.yml= file, paste the following
94following information. You will need to make quite a few updates, so be 86information. You will need to make quite a few updates, so be sure to
95sure to read each line carefully and modify as necessary. 87read each line carefully and modify as necessary.
96 88
97The major required changes are: 89The major required changes are:
98 90
99- Any instances of `example.com` should be replaced by your 91- Any instances of =example.com= should be replaced by your domain.
100 domain. 92- =jwt_secret= - Use the =pwgen 40 1= command to generate a secret for
101- `jwt_secret` - Use the `pwgen 40 1` command to 93 yourself.
102 generate a secret for yourself. 94- =access_control= - Set the Authelia domain to bypass here, as well as
103- `access_control` - Set the Authelia domain to bypass 95 any subdomains you want to protect.
104 here, as well as any subdomains you want to protect. 96- =session= > =secret= - Use the =pwgen 40 1= command to generate a
105- `session` > `secret` - Use the 97 secret for yourself.
106 `pwgen 40 1` command to generate a secret for yourself. 98- =regulation= - Set the variables here to restrict login attempts and
107- `regulation` - Set the variables here to restrict login 99 bans.
108 attempts and bans. 100- =storage= > =encryption_key= - Use the =pwgen 40 1= command to
109- `storage` > `encryption_key` - Use the 101 generate a secret for yourself.
110 `pwgen 40 1` command to generate a secret for yourself. 102- =smtp= - If you have access to an SMTP service, set up the information
111- `smtp` - If you have access to an SMTP service, set up 103 here to active outgoing emails.
112 the information here to active outgoing emails. 104
113 105#+begin_src yml
114```yml
115# yamllint disable rule:comments-indentation 106# yamllint disable rule:comments-indentation
116--- 107---
117############################################################################### 108###############################################################################
@@ -200,26 +191,25 @@ notifier:
200 skip_verify: false 191 skip_verify: false
201 minimum_version: TLS1.2 192 minimum_version: TLS1.2
202... 193...
203``` 194#+end_src
204
205### Authelia Users
206 195
196** Authelia Users
207Next, create the users file for authentication. 197Next, create the users file for authentication.
208 198
209```sh 199#+begin_src sh
210sudo nano ~/authelia/config/users_database.yml 200sudo nano ~/authelia/config/users_database.yml
211``` 201#+end_src
212 202
213Within the file, you will need to create an entry for each user that 203Within the file, you will need to create an entry for each user that
214needs access to Authelia. The `my_username` entry will be the 204needs access to Authelia. The =my_username= entry will be the username
215username used on the login page. 205used on the login page.
216 206
217To generate the password, go to [Argon2 Hash 207To generate the password, go to [[https://argon2.online][Argon2 Hash
218Generator](https://argon2.online), generate a random salt, and make sure 208Generator]], generate a random salt, and make sure the rest of the
219the rest of the settings match the `authentication_backend` 209settings match the =authentication_backend= section of
220section of `configuration.yml` file. 210=configuration.yml= file.
221 211
222```yaml 212#+begin_src yaml
223users: 213users:
224 my_username: 214 my_username:
225 displayname: "My User" 215 displayname: "My User"
@@ -230,32 +220,30 @@ users:
230 groups: 220 groups:
231 - admins 221 - admins
232 - dev 222 - dev
233``` 223#+end_src
234 224
235Once the app is configured, restart the container from scratch. 225Once the app is configured, restart the container from scratch.
236 226
237```sh 227#+begin_src sh
238cd ~/authelia 228cd ~/authelia
239sudo docker-compose down && sudo docker-compose up -d 229sudo docker-compose down && sudo docker-compose up -d
240``` 230#+end_src
241
242### Nginx: Authelia Domain
243 231
232** Nginx: Authelia Domain
244Once the container is running and configured, the final step is to 233Once the container is running and configured, the final step is to
245configure external access to the server via Nginx reverse proxy. 234configure external access to the server via Nginx reverse proxy.
246 235
247Start by creating the Authelia domain. 236Start by creating the Authelia domain.
248 237
249```sh 238#+begin_src sh
250sudo nano /etc/nginx/sites-available/auth 239sudo nano /etc/nginx/sites-available/auth
251``` 240#+end_src
252 241
253Within this file, paste the following information and be sure to update 242Within this file, paste the following information and be sure to update
254`example.com` to your domain. Make sure the 243=example.com= to your domain. Make sure the =$upstream_authelia=
255`$upstream_authelia` variable matches the location of your 244variable matches the location of your Authelia container.
256Authelia container.
257 245
258```conf 246#+begin_src conf
259server { 247server {
260 if ($host ~ ^[^.]+\.example\.com$) { 248 if ($host ~ ^[^.]+\.example\.com$) {
261 return 301 https://$host$request_uri; 249 return 301 https://$host$request_uri;
@@ -307,30 +295,29 @@ server {
307 } 295 }
308 296
309} 297}
310``` 298#+end_src
311 299
312Next, symlink the file and restart Nginx. If there are errors, be sure 300Next, symlink the file and restart Nginx. If there are errors, be sure
313to resolve those before moving on. 301to resolve those before moving on.
314 302
315```sh 303#+begin_src sh
316sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth 304sudo ln -s /etc/nginx/sites-available/auth /etc/nginx/sites-enabled/auth
317sudo systemctl restart nginx.service 305sudo systemctl restart nginx.service
318``` 306#+end_src
319
320### Nginx: Protected Domain(s)
321 307
308** Nginx: Protected Domain(s)
322Now that Authelia is accessible externally, you need to configure the 309Now that Authelia is accessible externally, you need to configure the
323domain you intend to protect with Authelia. In this example, I'm 310domain you intend to protect with Authelia. In this example, I'm
324protecting `teddit.example.com`. 311protecting =teddit.example.com=.
325 312
326Similar to the process above, paste the content and update the relevant 313Similar to the process above, paste the content and update the relevant
327variables. 314variables.
328 315
329```sh 316#+begin_src sh
330sudo nano /etc/nginx/sites-available/teddit 317sudo nano /etc/nginx/sites-available/teddit
331``` 318#+end_src
332 319
333```conf 320#+begin_src conf
334server { 321server {
335 if ($host ~ ^[^.]+\.example\.com$) { 322 if ($host ~ ^[^.]+\.example\.com$) {
336 return 301 https://$host$request_uri; 323 return 301 https://$host$request_uri;
@@ -432,26 +419,25 @@ server {
432 proxy_buffers 64 256k; 419 proxy_buffers 64 256k;
433 } 420 }
434} 421}
435``` 422#+end_src
436 423
437Same as before, symlink the file and restart Nginx. 424Same as before, symlink the file and restart Nginx.
438 425
439```sh 426#+begin_src sh
440sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit 427sudo ln -s /etc/nginx/sites-available/teddit /etc/nginx/sites-enabled/teddit
441sudo systemctl restart nginx.service 428sudo systemctl restart nginx.service
442``` 429#+end_src
443
444## Results
445 430
431* Results
446When visiting the protected domain, you will now be redirected to your 432When visiting the protected domain, you will now be redirected to your
447authentication domain and presented with the Authelia login portal. 433authentication domain and presented with the Authelia login portal.
448 434
449![Authelia 435#+caption: Authelia Portal
450Portal](https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png "Authelia Portal") 436[[https://img.cleberg.net/blog/20231010-authelia/authelia_portal.png]]
451 437
452Once you've successfully authenticated, you can visit your 438Once you've successfully authenticated, you can visit your
453authentication domain directly and see that you're currently 439authentication domain directly and see that you're currently
454authenticated to any domain protected by Authelia. 440authenticated to any domain protected by Authelia.
455 441
456![Authelia 442#+caption: Authelia Success
457Success](https://img.cleberg.net/blog/20231010-authelia/authelia_success.png "Authelia Success") 443[[https://img.cleberg.net/blog/20231010-authelia/authelia_success.png]]
content/blog/2023-06-08-self-hosting-baikal-server.md → blog/self-hosting-baikal/index.org renamed +48 −57
@@ -1,34 +1,30 @@
1+++ 1#+title: Self-Hosting Baikal Server (CalDAV & CardDAV)
2date = 2023-06-08 2#+date: 2023-06-08
3title = "Self-Hosting Baikal Server (CalDAV & CardDAV)" 3#+description: A guide to self-hosting the Baikal application on your own server.
4description = "A guide to self-hosting the Baikal application on your own server." 4#+filetags: :selfhosting:
5+++
6 5
7## What is Baikal? 6* What is Baikal?
8 7[[https://sabre.io/baikal/][Baikal]] is a lightweight CalDAV + CardDAV
9[Baikal](https://sabre.io/baikal/) is a lightweight CalDAV + CardDAV
10server that you can self-host on your own machine. While I have tried (& 8server that you can self-host on your own machine. While I have tried (&
11failed) to get this CalDAV + CardDAV server running before, it was quite 9failed) to get this CalDAV + CardDAV server running before, it was quite
12easy this time. Not really sure what I did differently this time, but 10easy this time. Not really sure what I did differently this time, but
13I'm documenting my process here to ensure I don't forget. 11I'm documenting my process here to ensure I don't forget.
14 12
15## Installation 13* Installation
16 14First, create a folder on your server and open a =docker-compose.yml=
17First, create a folder on your server and open a 15file for editing:
18`docker-compose.yml` file for editing:
19 16
20```sh 17#+begin_src sh
21mkdir baikal && cd baikal 18mkdir baikal && cd baikal
22nano docker-compose.yml 19nano docker-compose.yml
23``` 20#+end_src
24 21
25Within this file, you'll need to paste the information below. You can 22Within this file, you'll need to paste the information below. You can
26customize the `ports` section to use any port on your server 23customize the =ports= section to use any port on your server to pass
27to pass through to port 80 in the container. You can also edit the 24through to port 80 in the container. You can also edit the =volumes=
28`volumes` section to use docker volumes instead of local 25section to use docker volumes instead of local folders.
29folders.
30 26
31```conf 27#+begin_src conf
32version: "2" 28version: "2"
33services: 29services:
34 baikal: 30 baikal:
@@ -39,45 +35,41 @@ services:
39 volumes: 35 volumes:
40 - ./config:/var/www/baikal/config 36 - ./config:/var/www/baikal/config
41 - ./data:/var/www/baikal/Specific 37 - ./data:/var/www/baikal/Specific
42``` 38#+end_src
43 39
44Once finished with editing, save and close the file. Then, launch the 40Once finished with editing, save and close the file. Then, launch the
45docker container: 41docker container:
46 42
47```sh 43#+begin_src sh
48sudo docker-compose up -d 44sudo docker-compose up -d
49``` 45#+end_src
50
51## Intial Setup
52 46
47* Intial Setup
53As long as no issues came up when starting the container, you should be 48As long as no issues came up when starting the container, you should be
54able to visit the server's set-up page at 49able to visit the server's set-up page at =http://<server_ip>:<port>=.
55`http://<server_ip>:<port>`. The application will ask you to 50The application will ask you to create an administrator account and
56create an administrator account and choose the database type for your 51choose the database type for your storage. Personally, I opted to use
57storage. Personally, I opted to use SQLite. 52SQLite.
58 53
59Make sure the administrator credentials are adequate to protect against 54Make sure the administrator credentials are adequate to protect against
60common attacks. 55common attacks.
61 56
62## Creating Users 57* Creating Users
63 58Once you've set up the application, you will be greeted by the Dashboard
64Once you've set up the application, you will be greeted by the 59page, which will show the version of the app, status of the
65Dashboard page, which will show the version of the app, status of the
66admin/CalDAV/CardDAV services, and the number of users, calendars, 60admin/CalDAV/CardDAV services, and the number of users, calendars,
67events, address books, and contacts. 61events, address books, and contacts.
68 62
69![Baikal 63#+caption: Baikal Dashboard
70Dashboard](https://img.cleberg.net/blog/20230608-baikal/baikal-dashboard.png) 64[[https://img.cleberg.net/blog/20230608-baikal/baikal-dashboard.png]]
71 65
72To create a new user, navigate to the `Users and resources` 66To create a new user, navigate to the =Users and resources= page. This
73page. This process is as simple as entering a username, password, and 67process is as simple as entering a username, password, and email.
74email.
75 68
76Once a user has been created, you can create any number of calendars and 69Once a user has been created, you can create any number of calendars and
77address books for user, as well as inspect their information. 70address books for user, as well as inspect their information.
78 71
79## Setting Up a Public URL 72* Setting Up a Public URL
80
81Once your application is working locally, you can open access remotely 73Once your application is working locally, you can open access remotely
82via a URL by using a reverse-proxy like Nginx. 74via a URL by using a reverse-proxy like Nginx.
83 75
@@ -87,15 +79,15 @@ point a domain name to the server hosting Baikal.
87Start by navigating to your web server's configuration directory and 79Start by navigating to your web server's configuration directory and
88create a new file for this application. 80create a new file for this application.
89 81
90```sh 82#+begin_src sh
91cd /etc/nginx/sites-available/ 83cd /etc/nginx/sites-available/
92nano dav 84nano dav
93``` 85#+end_src
94 86
95Within this file, paste in the configuration from below and change 87Within this file, paste in the configuration from below and change
96`dav.example.com` to match the URL you'll be using. 88=dav.example.com= to match the URL you'll be using.
97 89
98```conf 90#+begin_src conf
99server { 91server {
100 if ($host ~ ^[^.]+\.example\.com$) { 92 if ($host ~ ^[^.]+\.example\.com$) {
101 return 301 https://$host$request_uri; 93 return 301 https://$host$request_uri;
@@ -125,35 +117,34 @@ server {
125 include /etc/letsencrypt/options-ssl-nginx.conf; 117 include /etc/letsencrypt/options-ssl-nginx.conf;
126 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 118 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
127} 119}
128``` 120#+end_src
129 121
130For Nginx on Ubuntu, you'll need to symlink the configuration file to 122For Nginx on Ubuntu, you'll need to symlink the configuration file to
131the `sites-enabled` directory and then restart Nginx. 123the =sites-enabled= directory and then restart Nginx.
132 124
133```sh 125#+begin_src sh
134sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav 126sudo ln -s /etc/nginx/sites-available/dav /etc/nginx/sites-enabled/dav
135sudo systemctl restart nginx.service 127sudo systemctl restart nginx.service
136``` 128#+end_src
137 129
138At this point, the Baikal server should be available over the internet 130At this point, the Baikal server should be available over the internet
139at the URL configured above! 131at the URL configured above!
140 132
141## Configuring Clients 133* Configuring Clients
142
143Lastly, configuring clients and applications is essential to making sure 134Lastly, configuring clients and applications is essential to making sure
144the DAV server is being fully utilized. 135the DAV server is being fully utilized.
145 136
146You can also use the `Users and resources` page to inspect a 137You can also use the =Users and resources= page to inspect a user's
147user's personal link to their calendars and address books by clicking 138personal link to their calendars and address books by clicking the info
148the info (i) button. It will show a URI like 139(i) button. It will show a URI like
149`/dav.php/calendars/your-user/default/`. 140=/dav.php/calendars/your-user/default/=.
150 141
151However, I found that the following URL works for most applications: 142However, I found that the following URL works for most applications:
152`/dav.php/principals/your-user/`. 143=/dav.php/principals/your-user/=.
153 144
154I used the `principals` URL above for Thunderbird (calendar, 145I used the =principals= URL above for Thunderbird (calendar, tasks, and
155tasks, and contacts), as well as iOS (calendar, tasks, and contacts) and 146contacts), as well as iOS (calendar, tasks, and contacts) and everything
156everything works flawlessly so far. 147works flawlessly so far.
157 148
158Syncing is quick between the server and clients, and I haven't seen any 149Syncing is quick between the server and clients, and I haven't seen any
159disruptions in the service or data integrity. 150disruptions in the service or data integrity.
content/blog/2023-06-23-self-hosting-convos-irc.md → blog/self-hosting-convos/index.org renamed +62 −68
@@ -1,43 +1,39 @@
1+++ 1#+title: Self-Hosting Convos IRC Web Client
2date = 2023-06-23 2#+date: 2023-06-23
3title = "Self-Hosting Convos IRC Web Client" 3#+description: A guide to self-hosting the Convos application on your own server.
4description = "A guide to self-hosting the Convos application on your own server." 4#+filetags: :selfhosting:
5+++
6 5
7## Convos 6* Convos
8 7[[https://convos.chat/][Convos]] is an always-online web client for IRC.
9[Convos](https://convos.chat/) is an always-online web client for IRC.
10It has a few features that made it attractive to me as a self-hosted 8It has a few features that made it attractive to me as a self-hosted
11option: 9option:
12 10
13- Extremely simple Docker Compose installation method. 11- Extremely simple Docker Compose installation method.
14- Runs in the background and monitors chats even while you're not 12- Runs in the background and monitors chats even while you're not logged
15 logged in. 13 in.
16- Neatly organized sidebar for conversation and client settings. 14- Neatly organized sidebar for conversation and client settings.
17- Ability to connect to different hosts and create profiles for hosts. 15- Ability to connect to different hosts and create profiles for hosts.
18- By default, registration is closed to the public. You can enable 16- By default, registration is closed to the public. You can enable
19 public registration on the Settings page or generate invitation 17 public registration on the Settings page or generate invitation links
20 links on the Users page. 18 on the Users page.
21- Customization of the client theme, organization name and URL, admin 19- Customization of the client theme, organization name and URL, admin
22 email, and video service. 20 email, and video service.
23 21
24## Docker Installation 22* Docker Installation
25 23To install Convos, simply create a directory and a =docker-compose.yml=
26To install Convos, simply create a directory and a 24file:
27`docker-compose.yml` file: 25
28 26#+begin_src sh
29```sh
30mkdir ~/convos && cd ~/convos 27mkdir ~/convos && cd ~/convos
31nano docker-compose.yml 28nano docker-compose.yml
32``` 29#+end_src
33 30
34With the `docker-compose.yml` file open, paste the 31With the =docker-compose.yml= file open, paste the configuration below
35configuration below into the file. You can customize the host port to be 32into the file. You can customize the host port to be something unique,
36something unique, such as `21897:3000`. You can also change 33such as =21897:3000=. You can also change the =data= folder to be a
37the `data` folder to be a docker volume instead, if you 34docker volume instead, if you prefer.
38prefer.
39 35
40```config 36#+begin_src config
41version: '3' 37version: '3'
42 38
43services: 39services:
@@ -50,33 +46,31 @@ services:
50 environment: 46 environment:
51 - CONVOS_REVERSE_PROXY=1 47 - CONVOS_REVERSE_PROXY=1
52 restart: always 48 restart: always
53``` 49#+end_src
54 50
55Save the `docker-compose.yml` file and bring the container 51Save the =docker-compose.yml= file and bring the container up:
56up:
57 52
58```sh 53#+begin_src sh
59sudo docker-compose up -d 54sudo docker-compose up -d
60``` 55#+end_src
61 56
62At this point, Convos is available at `<server_ip>:3000` but 57At this point, Convos is available at =<server_ip>:3000= but not
63not available to the public. 58available to the public.
64
65## Nginx Reverse Proxy
66 59
60* Nginx Reverse Proxy
67If you're using Nginx, you can create a configuration file for the 61If you're using Nginx, you can create a configuration file for the
68convos application. Start by opening the file: 62convos application. Start by opening the file:
69 63
70```sh 64#+begin_src sh
71nano /etc/nginx/sites-available/convos 65nano /etc/nginx/sites-available/convos
72``` 66#+end_src
73 67
74Within the nginx configuration file, paste the following content and be 68Within the nginx configuration file, paste the following content and be
75sure to update `convos.example.com` to match your domain and 69sure to update =convos.example.com= to match your domain and
76`127.0.0.1:3000` to match the port you opened in the 70=127.0.0.1:3000= to match the port you opened in the
77`docker-compose.yml` file. 71=docker-compose.yml= file.
78 72
79```config 73#+begin_src config
80# Host and port where convos is running 74# Host and port where convos is running
81upstream convos_upstream { server 127.0.0.1:3000; } 75upstream convos_upstream { server 127.0.0.1:3000; }
82 76
@@ -114,53 +108,53 @@ server {
114 include /etc/letsencrypt/options-ssl-nginx.conf; 108 include /etc/letsencrypt/options-ssl-nginx.conf;
115 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 109 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
116} 110}
117``` 111#+end_src
118 112
119Once the file is saved, link it to the `sites-enabled` 113Once the file is saved, link it to the =sites-enabled= directory and
120directory and restart Nginx. 114restart Nginx.
121 115
122```sh 116#+begin_src sh
123sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos 117sudo ln -s /etc/nginx/sites-available/convos /etc/nginx/sites-enabled/convos
124sudo systemctl restart nginx.service 118sudo systemctl restart nginx.service
125``` 119#+end_src
126
127## Screenshots
128 120
121* Screenshots
129See the screenshots below for an example of the Convos homepage and an 122See the screenshots below for an example of the Convos homepage and an
130excerpt of the chat screen. 123excerpt of the chat screen.
131 124
132There are numerous themes to choose from; the theme shown in the images 125There are numerous themes to choose from; the theme shown in the images
133below is Dracula. 126below is Dracula.
134 127
135![Convos Home](https://img.cleberg.net/blog/20230623-convos/convos_home.png "Convos Home") 128#+caption: Convos Home
136 129[[https://img.cleberg.net/blog/20230623-convos/convos_home.png]]
137![Convos Chat](https://img.cleberg.net/blog/20230623-convos/convos_chat.png "Convos Chat")
138 130
139## Registering a Nickname 131#+caption: Convos Chat
132[[https://img.cleberg.net/blog/20230623-convos/convos_chat.png]]
140 133
134* Registering a Nickname
141Hop into the server channel so that you can message NickServ. In the 135Hop into the server channel so that you can message NickServ. In the
142case of Convos, the default sever is libera.chat. Simply click the 136case of Convos, the default sever is libera.chat. Simply click the
143`libera` conversation at the top of the sidebar to open it. 137=libera= conversation at the top of the sidebar to open it. Once the
144Once the chat is open, you can claim a nickname by typing: 138chat is open, you can claim a nickname by typing:
145 139
146```txt 140#+begin_src txt
147/nick <nick> 141/nick <nick>
148``` 142#+end_src
149 143
150If the nickname is available, and you'd like to register the nickname 144If the nickname is available, and you'd like to register the nickname to
151to yourself, you'll need to type another command: 145yourself, you'll need to type another command:
152 146
153```txt 147#+begin_src txt
154/msg NickServ REGISTER 148/msg NickServ REGISTER
155<password> <email> 149<password> <email>
156``` 150#+end_src
157 151
158On libera.chat, the server will send a confirmation email with a command 152On libera.chat, the server will send a confirmation email with a command
159that you must message in IRC to verify registration of the nickname: 153that you must message in IRC to verify registration of the nickname:
160 154
161```txt 155#+begin_src txt
162/msg NickServ VERIFY REGISTER <nick> <verification_code> 156/msg NickServ VERIFY REGISTER <nick> <verification_code>
163``` 157#+end_src
164 158
165Once entered, the server should confirm registration of the nickname to 159Once entered, the server should confirm registration of the nickname to
166the supplied email with the password specified. 160the supplied email with the password specified.
content/blog/2022-06-07-freshrss.md → blog/self-hosting-freshrss/index.org renamed +90 −112
@@ -1,11 +1,9 @@
1+++ 1#+title: Self-Hosting FreshRSS
2date = 2022-06-07 2#+date: 2022-06-07
3title = "Self-Hosting FreshRSS" 3#+description: A guide to self-hosting the FreshRSS application on your own server.
4description = "A guide to self-hosting the FreshRSS application on your own server." 4#+filetags: :selfhosting:
5+++
6
7## Why RSS?
8 5
6* Why RSS?
9After noticing that I have collected 50+ blogs as bookmarks, I decided 7After noticing that I have collected 50+ blogs as bookmarks, I decided
10to migrate back to using RSS feeds to stay up-to-date with my favorite 8to migrate back to using RSS feeds to stay up-to-date with my favorite
11websites. Using RSS allows me to read all of these posts in a single app 9websites. Using RSS allows me to read all of these posts in a single app
@@ -16,56 +14,49 @@ However, I ran into one issue: syncing subscriptions and read/unread
16posts across devices. Since I want to be able to easily read on both 14posts across devices. Since I want to be able to easily read on both
17mobile and desktop, I decided to look for a self-hosted RSS solution. 15mobile and desktop, I decided to look for a self-hosted RSS solution.
18 16
19Thus, I found [FreshRSS](https://www.freshrss.org/) and was able to 17Thus, I found [[https://www.freshrss.org/][FreshRSS]] and was able to
20successfully install it on my server in about 30 minutes. 18successfully install it on my server in about 30 minutes.
21 19
22## Documentation 20* Documentation
23 21While it's certainly not robust, the
24While it's certainly not robust, the [FreshRSS 22[[https://freshrss.github.io/FreshRSS/][FreshRSS documentation]] is
25documentation](https://freshrss.github.io/FreshRSS/) is helpful for 23helpful for figuring out basic information about the service.
26figuring out basic information about the service.
27 24
28However, I wanted to install this service as a Docker container and 25However, I wanted to install this service as a Docker container and
29stumbled across the [Docker 26stumbled across the
30README](https://github.com/FreshRSS/FreshRSS/tree/edge/Docker) within 27[[https://github.com/FreshRSS/FreshRSS/tree/edge/Docker][Docker README]]
31the GitHub repository. 28within the GitHub repository.
32 29
33This README was the documentation I actually needed. However, as you'll 30This README was the documentation I actually needed. However, as you'll
34see below, I still had to manually edit one file 31see below, I still had to manually edit one file (=config.php=) to
35(`config.php`) to access the API externally via my RSS apps. 32access the API externally via my RSS apps.
36
37## Installation
38
39### DNS
40 33
34* Installation
35** DNS
41The first step, as required by any external web service, was assigning a 36The first step, as required by any external web service, was assigning a
42domain name to use. I chose to use a subdomain, like 37domain name to use. I chose to use a subdomain, like =rss.example.com=.
43`rss.example.com`.
44 38
45To assign this, I created an `A` record in my DNS settings 39To assign this, I created an =A= record in my DNS settings with the IPv4
46with the IPv4 address of the server and an `AAAA` record with 40address of the server and an =AAAA= record with the IPv6 address of the
47the IPv6 address of the server. Note: assigning an IPv6 41server. Note: assigning an IPv6 (=AAAA=) record is optional, but I like
48(`AAAA`) record is optional, but I like to enable IPV6 for my 42to enable IPV6 for my services.
49services.
50 43
51```config 44#+begin_src config
52rss.example.com A xxx.xxx.xxx.xxx 45rss.example.com A xxx.xxx.xxx.xxx
53rss.example.com AAAA xxxx:xxxx: ... :xxxx 46rss.example.com AAAA xxxx:xxxx: ... :xxxx
54``` 47#+end_src
55
56### Docker
57 48
58I initially tried to set up a `docker-compose.yml` file with 49** Docker
59a `.env` file because I prefer to have a file I can look back 50I initially tried to set up a =docker-compose.yml= file with a =.env=
60at later to see how I initially started the container, but it simply 51file because I prefer to have a file I can look back at later to see how
61wouldn't work for me. I'm not sure why, but I assume I wasn't telling 52I initially started the container, but it simply wouldn't work for me.
62`docker-compose` where the `.env` file was. 53I'm not sure why, but I assume I wasn't telling =docker-compose= where
54the =.env= file was.
63 55
64Regardless, I chose to simply run the service with 56Regardless, I chose to simply run the service with =docker run=. See the
65`docker run`. See the following command for my 57following command for my =docker run= configuration:
66`docker run` configuration:
67 58
68```sh 59#+begin_src sh
69sudo docker run -d --restart unless-stopped --log-opt max-size=10m \ 60sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
70 -p 8080:80 \ 61 -p 8080:80 \
71 -e TZ=America/Chicago \ 62 -e TZ=America/Chicago \
@@ -74,44 +65,40 @@ sudo docker run -d --restart unless-stopped --log-opt max-size=10m \
74 -v freshrss_extensions:/var/www/FreshRSS/extensions \ 65 -v freshrss_extensions:/var/www/FreshRSS/extensions \
75 --name freshrss \ 66 --name freshrss \
76 freshrss/freshrss 67 freshrss/freshrss
77``` 68#+end_src
78 69
79This started the container successfully and allowed me to visit the 70This started the container successfully and allowed me to visit the
80FreshRSS instance at `localhost:8080`. 71FreshRSS instance at =localhost:8080=.
81
82### Fresh RSS Set-Up
83 72
84I **HIGHLY** suggest that you set up your user account prior to exposing 73** Fresh RSS Set-Up
74I *HIGHLY** suggest that you set up your user account prior to exposing
85this service to the public. It's unlikely that someone is trying to 75this service to the public. It's unlikely that someone is trying to
86access the exact domain or IP/port you're assigning here, but as soon 76access the exact domain or IP/port you're assigning here, but as soon as
87as you expose this service, the first person to open the URL will be 77you expose this service, the first person to open the URL will be able
88able to create the admin user. 78to create the admin user.
89 79
90In order to set up your FreshRSS service, open the 80In order to set up your FreshRSS service, open the =localhost:8080= URL
91`localhost:8080` URL in your browser (you may need to use a 81in your browser (you may need to use a local IP instead of =localhost=
92local IP instead of `localhost` if you're accessing the page 82if you're accessing the page from a different machine on the network -
93from a different machine on the network - e.g., 83e.g., =192.168.1.20:8080=).
94`192.168.1.20:8080`).
95 84
96Once the page loads, set up your default user with a strong username and 85Once the page loads, set up your default user with a strong username and
97password. You may also choose to configure other settings prior to 86password. You may also choose to configure other settings prior to
98exposing this service. 87exposing this service.
99 88
100### Nginx Reverse-Proxy 89** Nginx Reverse-Proxy
101
102In order to access this service outside my home, I needed to set up a 90In order to access this service outside my home, I needed to set up a
103reverse-proxy to connect `localhost:8080` to 91reverse-proxy to connect =localhost:8080= to =rss.example.com=.
104`rss.example.com`.
105 92
106First, I created a new Nginx configuration file: 93First, I created a new Nginx configuration file:
107 94
108```sh 95#+begin_src sh
109sudo nano /etc/nginx/sites-available/rss.example.com 96sudo nano /etc/nginx/sites-available/rss.example.com
110``` 97#+end_src
111 98
112Within the config file, I pasted the following code: 99Within the config file, I pasted the following code:
113 100
114```config 101#+begin_src config
115upstream freshrss { 102upstream freshrss {
116 server 127.0.0.1:8080; 103 server 127.0.0.1:8080;
117 keepalive 64; 104 keepalive 64;
@@ -140,87 +127,79 @@ server {
140 proxy_pass_header Authorization; 127 proxy_pass_header Authorization;
141 } 128 }
142} 129}
143``` 130#+end_src
144 131
145Finally, restart Nginx and you will be able to access your service via 132Finally, restart Nginx and you will be able to access your service via
146HTTP: 133HTTP:
147 134
148```sh 135#+begin_src sh
149sudo systemctl restart nginx.service 136sudo systemctl restart nginx.service
150``` 137#+end_src
151
152### HTTPS
153 138
139** HTTPS
154However, I don't want to access my RSS feeds via HTTP. I want it 140However, I don't want to access my RSS feeds via HTTP. I want it
155available only via HTTPS. In order to do this, I ran the 141available only via HTTPS. In order to do this, I ran the
156[certbot](https://certbot.eff.org/) program to generate SSL certificates 142[[https://certbot.eff.org/][certbot]] program to generate SSL
157for me: 143certificates for me:
158 144
159```sh 145#+begin_src sh
160sudo certbot --nginx 146sudo certbot --nginx
161``` 147#+end_src
162 148
163This process will automatically generate an SSL certificate for you and 149This process will automatically generate an SSL certificate for you and
164modify the Nginx configuration file to include a redirect from HTTP to 150modify the Nginx configuration file to include a redirect from HTTP to
165HTTPS. 151HTTPS.
166 152
167## Post-Installation Fixes 153* Post-Installation Fixes
168
169At this point, we have a functional FreshRSS website, available from 154At this point, we have a functional FreshRSS website, available from
170anywhere and secured with HTTPS. However, attempting to connect this 155anywhere and secured with HTTPS. However, attempting to connect this
171service to an RSS app resulted in many errors regarding unavailable URLs 156service to an RSS app resulted in many errors regarding unavailable URLs
172and incorrect credentials. 157and incorrect credentials.
173 158
174### API Set-Up 159** API Set-Up
175 160First, you need to open your user profile in FreshRSS (=Settings= >
176First, you need to open your user profile in FreshRSS 161=Profile=) and set an API password in the field at the bottom. This is
177(`Settings` > `Profile`) and set an API password 162the password you will need to provide to your RSS apps.
178in the field at the bottom. This is the password you will need to
179provide to your RSS apps.
180 163
181Once that is set and saved, click the link below the API password field 164Once that is set and saved, click the link below the API password field
182to open the API check tool. It should look something like 165to open the API check tool. It should look something like
183`https://localhost:8080/api/` or 166=https://localhost:8080/api/= or =https://rss.example.com/api/=.
184`https://rss.example.com/api/`.
185 167
186Within this page, you *should* see your correct external URL and 168Within this page, you /should/ see your correct external URL and "PASS"
187"PASS" at the bottom of each API type. This would mean everything is 169at the bottom of each API type. This would mean everything is set up
188set up correctly, and you can now move on and login to any RSS apps that 170correctly, and you can now move on and login to any RSS apps that
189support self-hosted options. 171support self-hosted options.
190 172
191In my case, the URL showed an internal URL and I had a warning that the 173In my case, the URL showed an internal URL and I had a warning that the
192`base_url` variable may be misconfigured. If this is the 174=base_url= variable may be misconfigured. If this is the case, see the
193case, see the next section for a fix. 175next section for a fix.
194
195### Base URL Fix
196 176
197In order to fix the `base_url` for the API, I opened up my 177** Base URL Fix
198docker container with the following command: 178In order to fix the =base_url= for the API, I opened up my docker
179container with the following command:
199 180
200```sh 181#+begin_src sh
201sudo docker exec -it freshrss bash 182sudo docker exec -it freshrss bash
202``` 183#+end_src
203 184
204Within this container, update the packages and install an editor: 185Within this container, update the packages and install an editor:
205 186
206```sh 187#+begin_src sh
207apt-get update 188apt-get update
208apt-get install nano 189apt-get install nano
209``` 190#+end_src
210 191
211Finally, open up `config.php` in the `data` 192Finally, open up =config.php= in the =data= directory:
212directory:
213 193
214```sh 194#+begin_src sh
215nano data/config.php 195nano data/config.php
216``` 196#+end_src
217 197
218Within `config.php`, you will need to update the 198Within =config.php=, you will need to update the =base_url= variable and
219`base_url` variable and update it to match your external URL. 199update it to match your external URL. In my case, I simply commented-out
220In my case, I simply commented-out the incorrect URL with 200the incorrect URL with =//= and added the correct one on a new line:
221`//` and added the correct one on a new line:
222 201
223```php 202#+begin_src php
224<?php 203<?php
225 return array ( 204 return array (
226 ... 205 ...
@@ -229,26 +208,25 @@ In my case, I simply commented-out the incorrect URL with
229 ... 208 ...
230 ) 209 )
231> 210>
232``` 211#+end_src
233 212
234You can now exit the file with `Ctrl + x`, press 213You can now exit the file with =Ctrl + x=, press =y= to save the file,
235`y` to save the file, and then click `Enter` to 214and then click =Enter= to keep the same file name.
236keep the same file name.
237 215
238Finally, just exit out of the docker container: 216Finally, just exit out of the docker container:
239 217
240```sh 218#+begin_src sh
241exit 219exit
242``` 220#+end_src
243 221
244Next, just restart the container: 222Next, just restart the container:
245 223
246```sh 224#+begin_src sh
247sudo docker restart freshrss 225sudo docker restart freshrss
248``` 226#+end_src
249 227
250Voilà! Your API check should now "PASS" and you should be able to use 228Voilà! Your API check should now "PASS" and you should be able to use
251one of the API URLs in your RSS apps. 229one of the API URLs in your RSS apps.
252 230
253In my case, I use [NetNewsWire](https://netnewswire.com) on my desktop 231In my case, I use [[https://netnewswire.com][NetNewsWire]] on my desktop
254and phone. 232and phone.
content/blog/2023-09-15-gitweb.md → blog/self-hosting-gitweb/index.org renamed +23 −28
@@ -1,15 +1,12 @@
1+++ 1#+title: Self-Hosting GitWeb via Nginx
2date = 2023-09-15 2#+date: 2023-09-15
3title = "GitWeb via Nginx" 3#+description: A guide to self-hosting GitWeb using the Nginx web server.
4description = "A guide to self-hosting GitWeb using the Nginx web server." 4#+filetags: :selfhosting:
5+++
6 5
7## Overview 6* Overview
8 7[[https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb][GitWeb]] is a
9[GitWeb](https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb) is a
10simple web-based visualizer for git repositories. By default, GitWeb 8simple web-based visualizer for git repositories. By default, GitWeb
11will only run with the `lighttpd` or `webrick` web 9will only run with the =lighttpd= or =webrick= web servers.
12servers.
13 10
14However, this guide will show you how to keep GitWeb running in the 11However, this guide will show you how to keep GitWeb running in the
15background and display information for all repositories in a chosen 12background and display information for all repositories in a chosen
@@ -17,25 +14,24 @@ directory.
17 14
18See below for the final result: 15See below for the final result:
19 16
20![Gitweb](https://img.cleberg.net/blog/20230915-gitweb/gitweb.png) 17#+caption: Gitweb
21 18[[https://img.cleberg.net/blog/20230915-gitweb/gitweb.png]]
22## Install Dependencies
23 19
20* Install Dependencies
24To start, you'll need install the following packages: 21To start, you'll need install the following packages:
25 22
26```sh 23#+begin_src sh
27sudo apt install git gitweb fcgiwrap nginx 24sudo apt install git gitweb fcgiwrap nginx
28``` 25#+end_src
29
30## Configure Nginx
31 26
27* Configure Nginx
32Once installed, create an Nginx configuration file. 28Once installed, create an Nginx configuration file.
33 29
34```sh 30#+begin_src sh
35sudo nano /etc/nginx/sites-available/git.example.com 31sudo nano /etc/nginx/sites-available/git.example.com
36``` 32#+end_src
37 33
38```conf 34#+begin_src conf
39server { 35server {
40 listen 80; 36 listen 80;
41 server_name example.com; 37 server_name example.com;
@@ -54,24 +50,23 @@ server {
54 index index.cgi; 50 index index.cgi;
55 } 51 }
56} 52}
57``` 53#+end_src
58 54
59To make the configuration active, you need to symlink it and then 55To make the configuration active, you need to symlink it and then
60restart Nginx. 56restart Nginx.
61 57
62```sh 58#+begin_src sh
63sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com 59sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/git.example.com
64sudo systemctl restart nginx.service 60sudo systemctl restart nginx.service
65``` 61#+end_src
66 62
67The GitWeb application should now be available via the URL you set in 63The GitWeb application should now be available via the URL you set in
68the Nginx configuration above. 64the Nginx configuration above.
69 65
70## Customize GitWeb 66* Customize GitWeb
71
72If you need to, you can customize many things about Gitweb by editing 67If you need to, you can customize many things about Gitweb by editing
73the [gitweb.conf](https://git-scm.com/docs/gitweb.conf) file. 68the [[https://git-scm.com/docs/gitweb.conf][gitweb.conf]] file.
74 69
75```sh 70#+begin_src sh
76sudo nano /etc/gitweb.conf 71sudo nano /etc/gitweb.conf
77``` 72#+end_src
content/blog/2022-11-07-matrix-synapse.md → blog/self-hosting-matrix/index.org renamed +69 −81
@@ -1,106 +1,98 @@
1+++ 1#+title: Self-Hosting Matrix Synapse on Alpine Linux
2date = 2022-11-07 2#+date: 2022-11-07
3title = "Self-Hosting Matrix Synapse on Alpine Linux" 3#+description: A guide to self-hosting the Matrix Synapse application on your own server.
4description = "A guide to self-hosting the Matrix Synapse application on your own server." 4#+filetags: :selfhosting:
5+++
6
7## Synpase
8 5
6* Synpase
9If you're reading this, you likely know that 7If you're reading this, you likely know that
10[Synapse](https://github.com/matrix-org/synapse/) is a popular 8[[https://github.com/matrix-org/synapse/][Synapse]] is a popular
11[Matrix](https://matrix.org/) home server software that allows users to 9[[https://matrix.org/][Matrix]] home server software that allows users
12run their own Matrix home server. 10to run their own Matrix home server.
13 11
14This post is a short guide describing how I was able to get Synapse 12This post is a short guide describing how I was able to get Synapse
15working in a minimally-usable state on Alpine Linux. 13working in a minimally-usable state on Alpine Linux.
16 14
17## Installation Process 15* Installation Process
18 16** Dependencies
19### Dependencies
20
21First, since there is no Alpine-specific package for Synapse, we need to 17First, since there is no Alpine-specific package for Synapse, we need to
22ensure that Alpine has the required dependencies for the Python-based 18ensure that Alpine has the required dependencies for the Python-based
23installation method. 19installation method.
24 20
25```sh 21#+begin_src sh
26doas apk -U update 22doas apk -U update
27doas apk add python3 py3-virtualenv 23doas apk add python3 py3-virtualenv
28``` 24#+end_src
29 25
30Next, we need to set up a Python virtual environment for Synapse: 26Next, we need to set up a Python virtual environment for Synapse:
31 27
32```sh 28#+begin_src sh
33mkdir -p ~/synapse && cd ~/synapse 29mkdir -p ~/synapse && cd ~/synapse
34virtualenv -p python3 ~/synapse/env 30virtualenv -p python3 ~/synapse/env
35source ~/synapse/env/bin/activate 31source ~/synapse/env/bin/activate
36pip install --upgrade pip 32pip install --upgrade pip
37pip install --upgrade setuptools 33pip install --upgrade setuptools
38pip install matrix-synapse 34pip install matrix-synapse
39``` 35#+end_src
40
41### Running Synapse
42 36
37** Running Synapse
43Once installed, running Synapse is easy. Simply execute the following 38Once installed, running Synapse is easy. Simply execute the following
44command, replacing `example.com` with the domain name that 39command, replacing =example.com= with the domain name that will be used
45will be used with this home server. This will generate the configuration 40with this home server. This will generate the configuration files needed
46files needed to run the server. 41to run the server.
47 42
48```sh 43#+begin_src sh
49python -m synapse.app.homeserver \ 44python -m synapse.app.homeserver \
50 --server-name example.com \ 45 --server-name example.com \
51 --config-path homeserver.yaml \ 46 --config-path homeserver.yaml \
52 --generate-config \ 47 --generate-config \
53 --report-stats=no 48 --report-stats=no
54``` 49#+end_src
55 50
56Once the configuration is generated, we can start up the Synapse server: 51Once the configuration is generated, we can start up the Synapse server:
57 52
58```sh 53#+begin_src sh
59synctl start 54synctl start
60``` 55#+end_src
61 56
62### Configuring Synapse 57** Configuring Synapse
58To make any change to Synapse, we need to edit the =YAML= configuration
59file:
63 60
64To make any change to Synapse, we need to edit the `YAML` 61#+begin_src sh
65configuration file:
66
67```sh
68nano ~/synapse/homeserver.yaml 62nano ~/synapse/homeserver.yaml
69``` 63#+end_src
70 64
71For now, we just need to ensure the `server_name` is 65For now, we just need to ensure the =server_name= is accurate. However,
72accurate. However, there are a lot of other configuration options found 66there are a lot of other configuration options found in the
73in the [Configuring 67[[https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html][Configuring
74Synapse](https://matrix-org.github.io/synapse/develop/usage/configuration/config_documentation.html) 68Synapse]] documentation that can be enabled/disabled at any point.
75documentation that can be enabled/disabled at any point.
76 69
77```yaml 70#+begin_src yaml
78server_name: "example.com" 71server_name: "example.com"
79``` 72#+end_src
80 73
81Make sure to restart Synapse when you make changes to the configuration: 74Make sure to restart Synapse when you make changes to the configuration:
82 75
83```sh 76#+begin_src sh
84synctl restart 77synctl restart
85``` 78#+end_src
86
87### Nginx Reverse-Proxy
88 79
80** Nginx Reverse-Proxy
89To ensure that Synapse is reachable from the public, we need to connect 81To ensure that Synapse is reachable from the public, we need to connect
90our domain to the Synapse server. In my case, I use a Nginx 82our domain to the Synapse server. In my case, I use a Nginx
91reverse-proxy for this purpose. 83reverse-proxy for this purpose.
92 84
93To use Nginx, we need to create a reverse-proxy configuration file: 85To use Nginx, we need to create a reverse-proxy configuration file:
94 86
95```sh 87#+begin_src sh
96doas nano /etc/nginx/http.d/example.com.conf 88doas nano /etc/nginx/http.d/example.com.conf
97``` 89#+end_src
98 90
99If you already have TLS certificates for this domain 91If you already have TLS certificates for this domain (=example.com=),
100(`example.com`), you can simply use the SSL configuration and 92you can simply use the SSL configuration and point toward your TLS
101point toward your TLS certificates. 93certificates.
102 94
103```conf 95#+begin_src conf
104server { 96server {
105 listen 443 ssl http2; 97 listen 443 ssl http2;
106 listen [::]:443 ssl http2; 98 listen [::]:443 ssl http2;
@@ -139,15 +131,15 @@ server {
139 listen 80; 131 listen 80;
140 return 404; 132 return 404;
141} 133}
142``` 134#+end_src
143 135
144If you need to generate TLS certificates (I recommend 136If you need to generate TLS certificates (I recommend
145[Certbot](https://certbot.eff.org/)), you'll need a more minimal Nginx 137[[https://certbot.eff.org/][Certbot]]), you'll need a more minimal Nginx
146conf file before you can use the TLS-enabled example above. Instead, use 138conf file before you can use the TLS-enabled example above. Instead, use
147this configuration file during the Certbot certificate generation 139this configuration file during the Certbot certificate generation
148process: 140process:
149 141
150```conf 142#+begin_src conf
151server { 143server {
152 server_name example.com; 144 server_name example.com;
153 location / { 145 location / {
@@ -155,64 +147,60 @@ server {
155 } 147 }
156 listen 80; 148 listen 80;
157} 149}
158``` 150#+end_src
159 151
160Once you're done editing the Nginx conf file, restart Nginx: 152Once you're done editing the Nginx conf file, restart Nginx:
161 153
162```sh 154#+begin_src sh
163doas rc-service nginx restart 155doas rc-service nginx restart
164``` 156#+end_src
165 157
166If you still need to generate TLS certificates, run `certbot` 158If you still need to generate TLS certificates, run =certbot= now and
167now and obtain the certificates. Certbot will ask if you want to use a 159obtain the certificates. Certbot will ask if you want to use a webroot
168webroot or spin up a temporary web server. I **highly** recommend using 160or spin up a temporary web server. I *highly** recommend using the
169the temporary web server due to the many issues with using a webroot. 161temporary web server due to the many issues with using a webroot.
170 162
171You will need to stop Nginx in order to user the temporary web server 163You will need to stop Nginx in order to user the temporary web server
172option with Certbot: 164option with Certbot:
173 165
174```sh 166#+begin_src sh
175# Stop Nginx so certbot can spin up a temp webserver for cert generation 167# Stop Nginx so certbot can spin up a temp webserver for cert generation
176doas rc-service nginx stop 168doas rc-service nginx stop
177doas certbot certonly -v 169doas certbot certonly -v
178doas rc-service nginx start 170doas rc-service nginx start
179``` 171#+end_src
180 172
181### Open Firewall & Router Ports 173** Open Firewall & Router Ports
182 174If you use a firewall on the server, open the =8448= port for discovery
183If you use a firewall on the server, open the `8448` port for 175and federation, as well as the normal web server ports if you're using a
184discovery and federation, as well as the normal web server ports if 176reverse proxy. If you want additional services, such as voice calls, you
185you're using a reverse proxy. If you want additional services, such as 177will need to read the Synapse documentation to see which ports need to
186voice calls, you will need to read the Synapse documentation to see 178be opened for those features.
187which ports need to be opened for those features.
188 179
189Here's an example of the Universal Firewall (UFW) software: 180Here's an example of the Universal Firewall (UFW) software:
190 181
191```sh 182#+begin_src sh
192# Matrix port 183# Matrix port
193doas ufw allow 8448 184doas ufw allow 8448
194# Standard web server ports 185# Standard web server ports
195doas ufw allow "Nginx Full" 186doas ufw allow "Nginx Full"
196``` 187#+end_src
197
198Remember to forward any Synapse ports, such as `8448`,
199`80`, and `443`, in your Router from the internet
200to your server's IP address.
201 188
202### Adding Matrix Users 189Remember to forward any Synapse ports, such as =8448=, =80=, and =443=,
190in your Router from the internet to your server's IP address.
203 191
192** Adding Matrix Users
204Finally, if you didn't enable public registration in the 193Finally, if you didn't enable public registration in the
205`homeserver.yaml` file, you can manually create users via the 194=homeserver.yaml= file, you can manually create users via the
206command-line: 195command-line:
207 196
208```sh 197#+begin_src sh
209cd ~/synapse 198cd ~/synapse
210register_new_matrix_user -c homeserver.yaml 199register_new_matrix_user -c homeserver.yaml
211``` 200#+end_src
212 201
213Remember that the format for federated Matrix usernames is 202Remember that the format for federated Matrix usernames is
214`@username:example.com` when logging in to client 203=@username:example.com= when logging in to client applications.
215applications.
216 204
217Once Synapse is running, and you have a username, you are ready to log 205Once Synapse is running, and you have a username, you are ready to log
218in to a Matrix client and start sending messages, joining rooms, and 206in to a Matrix client and start sending messages, joining rooms, and
blog/self-hosting-otter-wiki/index.org added +135
@@ -0,0 +1,135 @@
1#+title: Self-Hosting An Otter Wiki
2#+date: <2024-02-21>
3#+description: Learn how to easily self-host An Otter Wiki.
4#+filetags: :selfhosting:
5
6* An Otter Wiki
7[[https://otterwiki.com/][An Otter Wiki]] is an easy to use wiki
8software that takes almost no effort to set up and maintain.
9
10* Installation
11To install An Otter Wiki, I'm going to use Docker Compose to create the
12container and then use Nginx as a reverse proxy to allow external access
13via a subdomain I own.
14
15** Docker Compose
16Start by creating a directory for the container's files.
17
18#+begin_src sh
19mkdir ~/otterwiki
20#+end_src
21
22Next, create the =docker-compose.yml= file to define the container's
23parameters.
24
25#+begin_src sh
26nano ~/otterwiki/docker-compose.yml
27#+end_src
28
29Within the file, paste the following content. You can read the project's
30documentation if you want to further override or customize the
31container.
32
33#+begin_src conf
34version: '3'
35services:
36 otterwiki:
37 image: redimp/otterwiki:2
38 restart: unless-stopped
39 # Internal port must be assigned to port 80
40 # External port can be customized
41 ports:
42 - 8337:80
43 volumes:
44 - ./app-data:/app-data
45#+end_src
46
47Once the file is saved and closed, you can launch the container.
48
49#+begin_src sh
50cd ~/otterwiki
51sudo docker-compose up -d
52#+end_src
53
54The container is now available at =localhost:8337=. Next, we will use
55Nginx to serve this app externally.
56
57** Nginx
58To access the app externally, let's set up a reverse proxy. I'll start
59by creating the Nginx configuration file for my wiki.
60
61#+begin_src sh
62sudo nano /etc/nginx/sites-available/wiki
63#+end_src
64
65Within the file, I have saved the following content. This assumes you
66already have a TLS/SSL cert to use with this subdomain. If not, simply
67remove the =ssl_*= variables, remove the =80= server block, and change
68the =443= server block to =80= to serve the app without SSL.
69
70#+begin_src conf
71server {
72 if ($host ~ ^[^.]+\.example\.com$) {
73 return 301 https://$host$request_uri;
74 }
75
76 listen [::]:80;
77 listen 80;
78 server_name wiki.example.com;
79 return 404;
80}
81
82server {
83 listen [::]:443 ssl http2;
84 listen 443 ssl http2;
85 server_name wiki.example.com;
86 access_log /var/log/nginx/wiki.access.log;
87 error_log /var/log/nginx/wiki.error.log;
88
89 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
90 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
91 include /etc/letsencrypt/options-ssl-nginx.conf;
92 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
93
94 location / {
95 set $upstream_wiki http://127.0.0.1:8337;
96 proxy_pass $upstream_wiki;
97
98 proxy_set_header Host $http_host;
99 proxy_set_header X-Real-IP $remote_addr;
100 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
101 proxy_set_header X-Forwarded-Host $http_host;
102 }
103}
104#+end_src
105
106Save and close the configuration file. On Nginx, we need to symlink the
107file to enable it.
108
109#+begin_src sh
110sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/wiki
111#+end_src
112
113Once enabled, restart the Nginx server to start serving the app
114externally.
115
116#+begin_src sh
117sudo systemctl restart nginx.service
118#+end_src
119
120* Screenshots
121In the screenshot below, you can see the initial launch screen for An
122Otter Wiki. Start by creating your admin account and configure the app
123as necessary.
124
125#+caption: An Otter Wiki - Initial Screen
126[[https://img.cleberg.net/blog/20240221-otterwiki/otterwiki.png]]
127
128You can also see the default editing screen for creating and editing
129pages. It's as easy as typing Markdown and hitting the save button.
130
131#+caption: An Otter Wiki - Editing Screen
132[[https://img.cleberg.net/blog/20240221-otterwiki/otterwiki-edit.png]]
133
134Overall, I like An Otter Wiki and will be looking forward to seeing how
135much use I get out of it going forward.
content/blog/2023-06-30-self-hosting-voyager-lemmy-client.md → blog/self-hosting-voyager/index.org renamed +41 −48
@@ -1,55 +1,50 @@
1+++ 1#+title: Self-Hosting Voyager - A Lemmy Web Client
2date = 2023-06-30 2#+date: 2023-06-30
3title = "Self-Hosting Voyager - A Lemmy Web Client" 3#+description: A guide to self-hosting the Voyager application on your own server.
4description = "A guide to self-hosting the Voyager application on your own server." 4#+filetags: :selfhosting:
5+++
6 5
7## Installation Guide 6* Installation Guide
8 7[[https://github.com/aeharding/voyager][Voyager]] is a mobile-first
9[Voyager](https://github.com/aeharding/voyager) is a mobile-first Lemmy 8Lemmy web client, based on iOS design standards. It follows very closely
10web client, based on iOS design standards. It follows very closely to 9to Apollo's design.
11Apollo's design.
12 10
13This post is a guide showing how I was able to build and launch my own 11This post is a guide showing how I was able to build and launch my own
14instance of Voyager via Docker Compose. 12instance of Voyager via Docker Compose.
15 13
16### Clone the Repository 14** Clone the Repository
17
18Start by cloning the repository and entering it: 15Start by cloning the repository and entering it:
19 16
20```sh 17#+begin_src sh
21git clone https://github.com/aeharding/voyager 18git clone https://github.com/aeharding/voyager
22cd voyager 19cd voyager
23``` 20#+end_src
24
25### Build the Image
26 21
22** Build the Image
27With this repository, you can build the image yourself without any 23With this repository, you can build the image yourself without any
28further configuration. When complete, it'll give you the image ID for 24further configuration. When complete, it'll give you the image ID for
29you to run. 25you to run.
30 26
31```sh 27#+begin_src sh
32sudo docker build . 28sudo docker build .
33# Successfully built 5f00723cb5be 29# Successfully built 5f00723cb5be
34``` 30#+end_src
35 31
36With the image ID above, you can run the container and pass the 32With the image ID above, you can run the container and pass the
37requested port `5314` through or use a custom port, if you 33requested port =5314= through or use a custom port, if you wish.
38wish.
39 34
40You can also set the `CUSTOM_LEMMY_SERVERS` environment 35You can also set the =CUSTOM_LEMMY_SERVERS= environment variable if you
41variable if you want to add to the default suggested login servers. This 36want to add to the default suggested login servers. This must be set
42must be set with a comma separated list of suggested servers. The first 37with a comma separated list of suggested servers. The first instance in
43instance in the list will be the default view for logged-out users. 38the list will be the default view for logged-out users.
44 39
45I will be using a `docker-compose.yml` file to run this 40I will be using a =docker-compose.yml= file to run this container,
46container, instead of a `docker run` command. 41instead of a =docker run= command.
47 42
48```sh 43#+begin_src sh
49nano docker-compose.yml 44nano docker-compose.yml
50``` 45#+end_src
51 46
52```conf 47#+begin_src conf
53version: "2" 48version: "2"
54services: 49services:
55 voyager: 50 voyager:
@@ -59,30 +54,29 @@ services:
59 - "<custom_port>:5314" 54 - "<custom_port>:5314"
60 environment: 55 environment:
61 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org 56 - CUSTOM_LEMMY_SERVERS=lemmy.dbzer0.com,lemmy.world,lemmy.ml,beehaw.org
62``` 57#+end_src
63 58
64```sh 59#+begin_src sh
65sudo docker-compose up -d 60sudo docker-compose up -d
66``` 61#+end_src
67 62
68The web app will now be available at the following address: 63The web app will now be available at the following address:
69`<machine_ip>:<custom_port>`. If you are running it on your 64=<machine_ip>:<custom_port>=. If you are running it on your local
70local device, try `localhost:<custom_port>`. 65device, try =localhost:<custom_port>=.
71
72### Reverse Proxy
73 66
74If you want to visit this app via an external URL or domain name, 67** Reverse Proxy
75you'll need to set up a reverse proxy. The example below uses Nginx as 68If you want to visit this app via an external URL or domain name, you'll
76a reverse proxy. 69need to set up a reverse proxy. The example below uses Nginx as a
70reverse proxy.
77 71
78Simply create the configuration file, paste the contents below, save the 72Simply create the configuration file, paste the contents below, save the
79file, symlink the file, and restart Nginx. 73file, symlink the file, and restart Nginx.
80 74
81```sh 75#+begin_src sh
82sudo nano /etc/nginx/sites-available/voyager 76sudo nano /etc/nginx/sites-available/voyager
83``` 77#+end_src
84 78
85```conf 79#+begin_src conf
86server { 80server {
87 if ($host ~ ^[^.]+\.example\.com$) { 81 if ($host ~ ^[^.]+\.example\.com$) {
88 return 301 https://$host$request_uri; 82 return 301 https://$host$request_uri;
@@ -112,15 +106,14 @@ server {
112 include /etc/letsencrypt/options-ssl-nginx.conf; 106 include /etc/letsencrypt/options-ssl-nginx.conf;
113 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 107 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
114} 108}
115``` 109#+end_src
116 110
117```sh 111#+begin_src sh
118sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager 112sudo ln sudo ln -s /etc/nginx/sites-available/voyager /etc/nginx/sites-enabled/voyager
119sudo systemctl restart nginx.service 113sudo systemctl restart nginx.service
120``` 114#+end_src
121 115
122The site will now be available at the `server_name` you 116The site will now be available at the =server_name= you specified above!
123specified above!
124 117
125You can visit my instance at 118You can visit my instance at
126[voyager.cleberg.net](https://voyager.cleberg.net) for an example. 119[[https://voyager.cleberg.net][voyager.cleberg.net]] for an example.
content/blog/2023-01-28-self-hosting-wger-workout-manager.md → blog/self-hosting-wger/index.org renamed +59 −69
@@ -1,106 +1,97 @@
1+++ 1#+title: Self-Hosting Wger Workout Manager
2date = 2023-01-28 2#+date: 2023-01-28
3title = "Self-Hosting Wger Workout Manager" 3#+description: A guide to self-hosting the Wger application on your own server.
4description = "A guide to self-hosting the Wger application on your own server." 4#+filetags: :selfhosting:
5+++
6 5
7## Wger: The Self-Hosted Workout Manager 6* Wger: The Self-Hosted Workout Manager
8 7[[https://wger.de][Wger Workout Manager]] is a fitness tracking tool for
9[Wger Workout Manager](https://wger.de) is a fitness tracking tool for
10those who enjoy self-hosting their data. You can also register an 8those who enjoy self-hosting their data. You can also register an
11account on their main website if you'd prefer to try without 9account on their main website if you'd prefer to try without
12self-hosting. 10self-hosting.
13 11
14### Features 12** Features
15
16I didn't see a full listing of features anywhere, so I compiled this 13I didn't see a full listing of features anywhere, so I compiled this
17list of my own after installing wger: 14list of my own after installing wger:
18 15
191. Dashboard 161. Dashboard
20 17
21 - Dashboard view of Workout Schedule, Nutrition Plan, Weight 18 - Dashboard view of Workout Schedule, Nutrition Plan, Weight Graph, &
22 Graph, & last 5 Weight Logs 19 last 5 Weight Logs
23 20
242. Training 212. Training
25 22
26 - Workout Log 23 - Workout Log
27 - Workout Schedule 24 - Workout Schedule
28 - Calendar (shows weight logs and Bad/Neutral/Good days) 25 - Calendar (shows weight logs and Bad/Neutral/Good days)
29 - Gallery (shows images you upload) 26 - Gallery (shows images you upload)
30 - Workout templates 27 - Workout templates
31 - Public templates 28 - Public templates
32 - Exercises 29 - Exercises
33 30
343. Nutrition 313. Nutrition
35 32
36 - Nutrition plans 33 - Nutrition plans
37 - BMI calculator 34 - BMI calculator
38 - Daily calories calculator 35 - Daily calories calculator
39 - Ingredient overview 36 - Ingredient overview
40 37
414. Body Weight 384. Body Weight
42 39
43 - Weight overview 40 - Weight overview
44
45### Documentation
46 41
42** Documentation
47In order to self-host wger, I opted to use the Docker version of the 43In order to self-host wger, I opted to use the Docker version of the
48application. You can read the README within the 44application. You can read the README within the
49[wger-project/docker](https://github.com/wger-project/docker) project on 45[[https://github.com/wger-project/docker][wger-project/docker]] project
50GitHub for information and installation instructions. 46on GitHub for information and installation instructions.
51
52### Installation
53 47
48** Installation
54To start the installation, I created a folder for wger and started 49To start the installation, I created a folder for wger and started
55creating the three necessary files: 50creating the three necessary files:
56 51
57```sh 52#+begin_src sh
58mkdir ~/wger && mkdir ~/wger/config 53mkdir ~/wger && mkdir ~/wger/config
59touch ~/wger/docker-compose.yml && \ 54touch ~/wger/docker-compose.yml && \
60touch ~/wger/config/prod.env && \ 55touch ~/wger/config/prod.env && \
61touch ~/wger/config/nginx.conf 56touch ~/wger/config/nginx.conf
62``` 57#+end_src
63 58
64Once you have the folders and files created, you will need to copy the 59Once you have the folders and files created, you will need to copy the
65contents of the `docker-compose.yml`, `prod.env`, 60contents of the =docker-compose.yml=, =prod.env=, and =nginx.conf= from
66and `nginx.conf` from the GitHub link above. 61the GitHub link above.
67 62
68A few notes to explain the changes I made to the default files: 63A few notes to explain the changes I made to the default files:
69 64
70- I updated the `ALLOW_REGISTRAION` variable in 65- I updated the =ALLOW_REGISTRAION= variable in =prod.env= to =False=
71 `prod.env` to `False` after I created an 66 after I created an account via my LAN connection, *before** I connected
72 account via my LAN connection, **before** I connected this app to a 67 this app to a publicly-available domain.
73 publicly-available domain. 68- I uncommented and updated =CSRF_TRUSTED_ORIGINS= to be equal to the
74- I uncommented and updated `CSRF_TRUSTED_ORIGINS` to be 69 public version of this app: =https://wger.example.com=.
75 equal to the public version of this app: 70- I updated the port within =docker-compose.yml=, within the =nginx=
76 `https://wger.example.com`. 71 block. The port I updated this to will be reflected in my nginx
77- I updated the port within `docker-compose.yml`, within 72 configuration file on the server (NOT the wger nginx.conf file).
78 the `nginx` block. The port I updated this to will be
79 reflected in my nginx configuration file on the server (NOT the wger
80 nginx.conf file).
81
82### Deploy
83 73
74** Deploy
84Once all files are created and modified to your needs, simply start the 75Once all files are created and modified to your needs, simply start the
85container. 76container.
86 77
87```sh 78#+begin_src sh
88docker-compose up -d 79docker-compose up -d
89``` 80#+end_src
90 81
91You can now visit the website on your LAN by going to 82You can now visit the website on your LAN by going to
92`localhost:YOUR_PORT` or by the server's IP, if you're not 83=localhost:YOUR_PORT= or by the server's IP, if you're not on the same
93on the same machine that is running the container. 84machine that is running the container.
94 85
95If you wish to connect this app to a public domain name, you'll need to 86If you wish to connect this app to a public domain name, you'll need to
96point an `A` DNS record from the domain to your server's 87point an =A= DNS record from the domain to your server's public IP.
97public IP. You'll then need to create a configuration file for 88You'll then need to create a configuration file for whichever web server
98whichever web server or reverse proxy you're using. 89or reverse proxy you're using.
99 90
100Wger's README suggests the following reverse proxy configuration for 91Wger's README suggests the following reverse proxy configuration for
101Nginx: 92Nginx:
102 93
103```conf 94#+begin_src conf
104upstream wger { 95upstream wger {
105 # This port should match the port in the `nginx` block of docker-compose.yml 96 # This port should match the port in the `nginx` block of docker-compose.yml
106 # If the container is running on this same machine, replace this with 97 # If the container is running on this same machine, replace this with
@@ -127,16 +118,15 @@ server {
127 ssl_certificate /path/to/https/certificate.crt; 118 ssl_certificate /path/to/https/certificate.crt;
128 ssl_certificate_key /path/to/https/certificate.key; 119 ssl_certificate_key /path/to/https/certificate.key;
129} 120}
130``` 121#+end_src
131
132## Thoughts on Wger
133 122
134I'm still playing around with the app itself, but it seems to be a 123* Thoughts on Wger
135solid all-around workout manager, weight log, and food log. 124I'm still playing around with the app itself, but it seems to be a solid
125all-around workout manager, weight log, and food log.
136 126
137I like that the weight log graph is fluid and updates quickly. You can 127I like that the weight log graph is fluid and updates quickly. You can
138also import or export data in CSV format if you'd like to move your 128also import or export data in CSV format if you'd like to move your data
139data elsewhere. 129elsewhere.
140 130
141The workout manager is slightly odd, as it requires you to enter sets 131The workout manager is slightly odd, as it requires you to enter sets
142and reps for each exercise when you enter it into the plan. Then, when 132and reps for each exercise when you enter it into the plan. Then, when
@@ -146,8 +136,8 @@ actually performed, in terms of reps and weight.
146I haven't tried the food log yet and I likely will not, at least for a 136I haven't tried the food log yet and I likely will not, at least for a
147while. I have no need for a food log or calorie tracker at the moment. 137while. I have no need for a food log or calorie tracker at the moment.
148 138
149### Screenshot Example 139** Screenshot Example
150
151You can see an example of a dashboard with placeholder data here: 140You can see an example of a dashboard with placeholder data here:
152 141
153![wger dashboard](https://img.cleberg.net/blog/20230128-wger/wger.png) 142#+caption: wger dashboard
143[[https://img.cleberg.net/blog/20230128-wger/wger.png]]
content/blog/2022-09-17-serenity-os.md → blog/serenity-os/index.org renamed +52 −53
@@ -1,12 +1,10 @@
1+++ 1#+title: Serenity OS: Testing Out a Unique System
2date = 2022-09-17 2#+date: 2022-09-17
3title = "Serenity OS: Testing Out a Unique System" 3#+description: A quick look at the Serenity operating system.
4description = "A quick look at the Serenity operating system." 4#+filetags: :dev:
5+++
6 5
7## Overview 6* Overview
8 7[[https://serenityos.org][SerenityOS]] is a unique operating system (OS)
9[SerenityOS](https://serenityos.org) is a unique operating system (OS)
10that I have seen pop up in my news feed a few times over the last few 8that I have seen pop up in my news feed a few times over the last few
11years, but I have never had time to test it out until now. 9years, but I have never had time to test it out until now.
12 10
@@ -16,42 +14,42 @@ to my youth.
16 14
17Per their website: 15Per their website:
18 16
19> A graphical Unix-like operating system for desktop computers! 17#+begin_quote
20> 18A graphical Unix-like operating system for desktop computers!
21> SerenityOS is a love letter to '90s user interfaces with a custom 19
22> Unix-like core. It flatters with sincerity by stealing beautiful ideas 20SerenityOS is a love letter to '90s user interfaces with a custom
23> from various other systems. 21Unix-like core. It flatters with sincerity by stealing beautiful ideas
24> 22from various other systems.
25> Roughly speaking, the goal is a marriage between the aesthetic of 23
26> late-1990s productivity software and the power-user accessibility of 24Roughly speaking, the goal is a marriage between the aesthetic of
27> late-2000s *nix. 25late-1990s productivity software and the power-user accessibility of
28> 26late-2000s *nix.
29> This is a system by us, for us, based on the things we like.
30 27
31## Building 28This is a system by us, for us, based on the things we like.
32 29
33Your first question may be "Where's the iso?" and the answer is... 30#+end_quote
34there are none. SerenityOS does not provide pre-built images for
35testing. You must build the images yourself. This seems intentionally to
36limit participation to only those who are truly interested enough to
37learn how to build the OS.
38 31
39### Clone 32* Building
33Your first question may be "Where's the iso?" and the answer is... there
34are none. SerenityOS does not provide pre-built images for testing. You
35must build the images yourself. This seems intentionally to limit
36participation to only those who are truly interested enough to learn how
37to build the OS.
40 38
39** Clone
41In order to get started, you'll need to clone the source repository: 40In order to get started, you'll need to clone the source repository:
42 41
43```sh 42#+begin_src sh
44git clone https://github.com/SerenityOS/serenity && cd serenity 43git clone https://github.com/SerenityOS/serenity && cd serenity
45``` 44#+end_src
46 45
47### Build 46** Build
48 47Note that I followed the
49Note that I followed the [Build 48[[https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md][Build
50Instructions](https://github.com/SerenityOS/serenity/blob/master/Documentation/BuildInstructions.md) 49Instructions]] in the SerenityOS repository as of commit
51in the SerenityOS repository as of commit 50=660d2b53b1206e868d5470eee80b5e62d7e30da7=. Things may have changed
52`660d2b53b1206e868d5470eee80b5e62d7e30da7`. Things may have 51since my installation, and you should double-check the instructions
53changed since my installation, and you should double-check the 52first.
54instructions first.
55 53
56Regardless, I want to repeat my steps here to illustrate any errors or 54Regardless, I want to repeat my steps here to illustrate any errors or
57differing commands I needed to run in order to build and run SerenityOS. 55differing commands I needed to run in order to build and run SerenityOS.
@@ -59,58 +57,59 @@ differing commands I needed to run in order to build and run SerenityOS.
59Since I am running Fedora, I needed to install these packages in order 57Since I am running Fedora, I needed to install these packages in order
60to build the OS images: 58to build the OS images:
61 59
62```sh 60#+begin_src sh
63sudo dnf install texinfo binutils-devel curl cmake mpfr-devel libmpc-devel gmp-devel e2fsprogs ninja-build patch ccache rsync @"C Development Tools and Libraries" @Virtualization 61sudo dnf install texinfo binutils-devel curl cmake mpfr-devel libmpc-devel gmp-devel e2fsprogs ninja-build patch ccache rsync @"C Development Tools and Libraries" @Virtualization
64``` 62#+end_src
65 63
66Next, make sure you're inside the `serenity` directory 64Next, make sure you're inside the =serenity= directory created earlier
67created earlier during the git cloning process and process to build the 65during the git cloning process and process to build the toolchain:
68toolchain:
69 66
70```sh 67#+begin_src sh
71Meta/serenity.sh rebuild-toolchain 68Meta/serenity.sh rebuild-toolchain
72``` 69#+end_src
73 70
74Once the toolchain is built, you can Build and run the OS! 71Once the toolchain is built, you can Build and run the OS!
75 72
76```sh 73#+begin_src sh
77Meta/serenity.sh run 74Meta/serenity.sh run
78``` 75#+end_src
79 76
80After this process is completed, the image should run automatically and 77After this process is completed, the image should run automatically and
81launch. 78launch.
82 79
83## Issues 80* Issues
84
85I played around in SerenityOS for an hour or two in order to see what I 81I played around in SerenityOS for an hour or two in order to see what I
86could do and had a lot of fun with it. The only issue I ran into was a 82could do and had a lot of fun with it. The only issue I ran into was a
87lack of working internet. I didn't try very hard, but I could tell that 83lack of working internet. I didn't try very hard, but I could tell that
88the main network link wasn't connecting to my Fedora host properly. 84the main network link wasn't connecting to my Fedora host properly.
89 85
90## Screenshots 86* Screenshots
91
92The initial launch of the image displays the SerenityOS desktop, with a 87The initial launch of the image displays the SerenityOS desktop, with a
93simple terminal already launched: 88simple terminal already launched:
94 89
95![SerenityOS Terminal](https://img.cleberg.net/blog/20220917-serenityos/initial_launch.png) 90#+caption: SerenityOS Terminal
91[[https://img.cleberg.net/blog/20220917-serenityos/initial_launch.png]]
96 92
97Here you can see the Fire application (literally just shows fire 93Here you can see the Fire application (literally just shows fire
98burning), a browser with the local Serenity Browser page loaded, and a 94burning), a browser with the local Serenity Browser page loaded, and a
99text editor. 95text editor.
100 96
101![SerenityOS Fire](https://img.cleberg.net/blog/20220917-serenityos/basic_apps.png) 97#+caption: SerenityOS Fire
98[[https://img.cleberg.net/blog/20220917-serenityos/basic_apps.png]]
102 99
103I also poked around the system utilities and found most tools you'd 100I also poked around the system utilities and found most tools you'd
104expect to find within a standard desktop. 101expect to find within a standard desktop.
105 102
106![SerenityOS System Monitor](https://img.cleberg.net/blog/20220917-serenityos/system_monitor.png) 103#+caption: SerenityOS System Monitor
104[[https://img.cleberg.net/blog/20220917-serenityos/system_monitor.png]]
107 105
108Lastly, I noted that the default desktop contains numerous pre-defined 106Lastly, I noted that the default desktop contains numerous pre-defined
109themes to choose from. This is a small piece, but it's actually 107themes to choose from. This is a small piece, but it's actually
110wonderful to see desktop developers consider theming directly out of the 108wonderful to see desktop developers consider theming directly out of the
111box rather than using an addon-based mentality. 109box rather than using an addon-based mentality.
112 110
113![SerenityOS Themes](https://img.cleberg.net/blog/20220917-serenityos/themes.png) 111#+caption: SerenityOS Themes
112[[https://img.cleberg.net/blog/20220917-serenityos/themes.png]]
114 113
115I didn't take a screenshot of the other pre-installed games, but I did 114I didn't take a screenshot of the other pre-installed games, but I did
116spend nearly 30 minutes playing Solitaire before remembering that I was 115spend nearly 30 minutes playing Solitaire before remembering that I was
content/blog/2022-11-27-server-build.md → blog/server-build/index.org renamed +45 −51
@@ -1,11 +1,9 @@
1+++ 1#+title: Building a Custom Rack-Mounted Server
2date = 2022-11-27 2#+date: 2022-11-27
3title = "Building a Custom Rack-Mounted Server" 3#+description: A retrospective on building my own rack-mounted server.
4description = "A retrospective on building my own rack-mounted server." 4#+filetags: :hardware:
5+++
6
7## The Dilemma
8 5
6* The Dilemma
9For years, I have been using desktops and a Raspberry Pi as the backbone 7For years, I have been using desktops and a Raspberry Pi as the backbone
10of my homelab. I have always wanted to move toward a single dedicated 8of my homelab. I have always wanted to move toward a single dedicated
11server that could handle all of my tasks, but was often put off by the 9server that could handle all of my tasks, but was often put off by the
@@ -21,32 +19,31 @@ After returning the R720XD, I decided that I wanted to build my own
21server with modern, consumer-grade PC components. This time, I am very 19server with modern, consumer-grade PC components. This time, I am very
22happy with the results of my server. 20happy with the results of my server.
23 21
24## Components 22* Components
25
26I'll start by listing all the components I used for this server build: 23I'll start by listing all the components I used for this server build:
27 24
28- **Case**: [Rosewill RSV-R4100U 4U Server Chassis Rackmount 25- *Case*:
29 Case](https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825) 26 [[https://www.rosewill.com/rosewill-rsv-r4100u-black/p/9SIA072GJ92825][Rosewill
30- **Motherboard**: [NZXT B550](https://nzxt.com/product/n7-b550) 27 RSV-R4100U 4U Server Chassis Rackmount Case]]
31- **CPU**: AMD Ryzen 7 5700G with Radeon Graphics 28- *Motherboard*: [[https://nzxt.com/product/n7-b550][NZXT B550]]
32- **GPU**: N/A - I specifically chose one of the few AMD CPUs that 29- *CPU*: AMD Ryzen 7 5700G with Radeon Graphics
33 support onboard graphics. 30- *GPU*: N/A - I specifically chose one of the few AMD CPUs that support
34- **RAM**: 64GB RAM (2x32GB) *Max of 128GB RAM on this motherboard* 31 onboard graphics.
35- **Boot Drive**: Western Digital 500GB M.2 NVME SSD 32- *RAM*: 64GB RAM (2x32GB) /Max of 128GB RAM on this motherboard/
36- **HDD Bay**: 33- *Boot Drive*: Western Digital 500GB M.2 NVME SSD
37 - 10TB WD White *(shucked, moved from previous server)* 34- *HDD Bay*:
38 - 8TB WD White *(shucked, moved from previous server)* 35 - 10TB WD White /(shucked, moved from previous server)/
39 - 2 x 8TB WD Red Plus *(Black Friday lined up perfectly with this 36 - 8TB WD White /(shucked, moved from previous server)/
40 build, so I grabbed two of these)* 37 - 2 x 8TB WD Red Plus /(Black Friday lined up perfectly with this
41- **PSU**: Corsair RM850 PSU 38 build, so I grabbed two of these)/
42- **Extras**: 39- *PSU*: Corsair RM850 PSU
43 - Corsair TM3Q Thermal Paste 40- *Extras*:
44 - Noctua 120mm fan *(replacement for front case fan)* 41 - Corsair TM3Q Thermal Paste
45 - 2 x Noctua 80mm fans *(replacement for rear case fans)* 42 - Noctua 120mm fan /(replacement for front case fan)/
46 - CableMatters 6Gbps SATA Cables 43 - 2 x Noctua 80mm fans /(replacement for rear case fans)/
47 44 - CableMatters 6Gbps SATA Cables
48## Building the Server 45
49 46* Building the Server
50This took quite a while for me to build (in my opinion of time), 47This took quite a while for me to build (in my opinion of time),
51totaling around 3 hours from start to finish. The case has some peculiar 48totaling around 3 hours from start to finish. The case has some peculiar
52construction, so you have to completely remove the ODD & HDD cages to 49construction, so you have to completely remove the ODD & HDD cages to
@@ -55,8 +52,8 @@ install the motherboard and other components first.
55Now, I've never built a computer of any kind before, so I was quite 52Now, I've never built a computer of any kind before, so I was quite
56nervous. Personally, the only challenging part was getting the CPU 53nervous. Personally, the only challenging part was getting the CPU
57cooler to screw into the motherboard without sliding the thermal paste 54cooler to screw into the motherboard without sliding the thermal paste
58around too much underneath. I'm still not entirely sure if I did a 55around too much underneath. I'm still not entirely sure if I did a great
59great job of it, but nothing's broken yet. 56job of it, but nothing's broken yet.
60 57
61The main components were all fine and simple. However, installing the 58The main components were all fine and simple. However, installing the
62hard drives is slightly tedious as I need to power off the server and 59hard drives is slightly tedious as I need to power off the server and
@@ -68,15 +65,13 @@ used to in other machines.
68Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay 65Seeing that the cases with hot-swap bays were 3-4x the price, I'm okay
69dealing with the tedium of removing the cage to install new drives. 66dealing with the tedium of removing the cage to install new drives.
70 67
71## Software 68* Software
72
73I'm not going to dive into the software as I have done so in other 69I'm not going to dive into the software as I have done so in other
74recent posts. However, I wanted to note that I am using Alpine Linux on 70recent posts. However, I wanted to note that I am using Alpine Linux on
75this server and hosting most services inside Docker. No virtual machines 71this server and hosting most services inside Docker. No virtual machines
76(VMs) and very few bare-metal services. 72(VMs) and very few bare-metal services.
77 73
78## The Results 74* The Results
79
80How did my build turn out? Well, after migrating my other servers and 75How did my build turn out? Well, after migrating my other servers and
81their services over, I found that my server is blazing fast. The 76their services over, I found that my server is blazing fast. The
82heaviest of my applications, Plex, is handled with ease. Even 4k 77heaviest of my applications, Plex, is handled with ease. Even 4k
@@ -86,8 +81,7 @@ I am very happy with the results and will likely continue to improve on
86this server as the years go by rather than buying another used server 81this server as the years go by rather than buying another used server
87online. 82online.
88 83
89### Mistakes I Made 84** Mistakes I Made
90
91This post wouldn't be complete unless I wrote about the mistakes I made 85This post wouldn't be complete unless I wrote about the mistakes I made
92while building. The only real mistake I made beyond a "whoops I dropped 86while building. The only real mistake I made beyond a "whoops I dropped
93a screw" related to airflow and fan direction. 87a screw" related to airflow and fan direction.
@@ -96,19 +90,19 @@ While installing the two new hard drives that showed up on 2022-11-30
96and getting ready to install the case in my rack, I noticed that the 90and getting ready to install the case in my rack, I noticed that the
97hard drive temperatures were quite high. 91hard drive temperatures were quite high.
98 92
99I used the `smartctl` command for each of my drives 93I used the =smartctl= command for each of my drives (=/dev/sda= through
100(`/dev/sda` through `/dev/sdd`): 94=/dev/sdd=):
101 95
102```sh 96#+begin_src sh
103doas smartctl -a /dev/sda | grep Temperature_Celsius 97doas smartctl -a /dev/sda | grep Temperature_Celsius
104``` 98#+end_src
105 99
106The results were unusual - all four drives were idling at ~44-46 100The results were unusual - all four drives were idling at ~44-46 degrees
107degrees Celsius. The only drive that was cooler was my 10TB drive, which 101Celsius. The only drive that was cooler was my 10TB drive, which was at
108was at 38 degrees Celsius. I noted that this 10TB drive was also closest 10238 degrees Celsius. I noted that this 10TB drive was also closest to the
109to the case fan. 103case fan.
110 104
111```sh 105#+begin_src sh
112ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE 106ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
113# /dev/sda 107# /dev/sda
114194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46) 108194 Temperature_Celsius 0x0002 147 147 000 Old_age Always - 44 (Min/Max 22/46)
@@ -118,7 +112,7 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_
118194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61) 112194 Temperature_Celsius 0x0002 144 144 000 Old_age Always - 45 (Min/Max 19/61)
119# /dev/sdd 113# /dev/sdd
120194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56) 114194 Temperature_Celsius 0x0002 171 171 000 Old_age Always - 38 (Min/Max 14/56)
121``` 115#+end_src
122 116
123After looking to see if I could fit more fans into the case, I noticed 117After looking to see if I could fit more fans into the case, I noticed
124that the 120mm fan used for intake from the front of the case was 118that the 120mm fan used for intake from the front of the case was
@@ -130,7 +124,7 @@ dropped immediately! They are now idling at ~31-33 degrees Celsius. A
130single fan spinning the wrong way caused my drives to idle 10-15 degrees 124single fan spinning the wrong way caused my drives to idle 10-15 degrees
131higher than they should have. 125higher than they should have.
132 126
133```sh 127#+begin_src sh
134ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE 128ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
135# /dev/sda 129# /dev/sda
136194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56) 130194 Temperature_Celsius 0x0002 209 209 000 Old_age Always - 31 (Min/Max 14/56)
@@ -140,7 +134,7 @@ ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_
140194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48) 134194 Temperature_Celsius 0x0002 203 203 000 Old_age Always - 32 (Min/Max 21/48)
141# /dev/sdd 135# /dev/sdd
142194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46) 136194 Temperature_Celsius 0x0002 196 196 000 Old_age Always - 33 (Min/Max 22/46)
143``` 137#+end_src
144 138
145This was a silly error to make, but I'm glad I found it today before I 139This was a silly error to make, but I'm glad I found it today before I
146screwed the case into the rack and made things a lot more tedious to 140screwed the case into the rack and made things a lot more tedious to
blog/server-hardening/index.org added +334
@@ -0,0 +1,334 @@
1#+title: Hardening a Public-Facing Home Server
2#+date: 2022-03-24
3#+description: Learn some basic techniques to harden a home server and network.
4#+filetags: :security:
5
6* Post Updates
7
8#+begin_quote
9After reviewing this post today (2022-10-04), I noticed quite a few gaps
10in my write-up and wanted to add a few things, even though this blog is
11really just a retrospective and knowledge dump for myself. I left things
12intact and simply crossed them out (+like this+) for posterity.
13#+end_quote
14
15* Planning Data Flows & Security
16** My Personal Data Flow
17#+begin_src txt
18 ┌───────┐ ┌─────────────────┐
19 ┌──► VLAN1 ├───► Private Devices │
20 │ └───────┘ └─────────────────┘
21┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
22│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
23└──────────┘ └────────┘ └──────────┘ └────────┘ │
24 │ ┌───────┐ ┌───────────────┐
25 └──► VLAN2 ├───► Public Server │
26 └───────┘ └───────────────┘
27#+end_src
28
29** Thought Process
30To serve content from your home server and harden your security posture,
31you have to think about the transport of data from =server= to =client=.
32
33Let's start with the actual server itself. Think about the following:
34
35- Do I have a firewall enabled? Do I need to update this to allow new
36 ports or IPs?
37- Do I have an IPS/IDS that may prevent outside traffic?
38- Do I have any other security software installed?
39- Are the services hosted inside Docker containers, behind a reverse
40 proxy, or virtualized? If so, are they configured to allow outside
41 traffic?
42
43Once the data leaves the server, where does it go? In my case, it goes
44to a managed switch. In this case, I asked the following:
45
46- What configurations is the switch using?
47- Am I using VLANs?
48 - Yes, I am using 802.1Q VLANs.
49- Are the VLANs configured properly?
50 - Yes, as shown in the Switch section below, I have a
51 separate VLAN to allow outside traffic to and from the server alone.
52 No other devices, except for a service port, and in that VLAN.
53
54At this point, the data has been processed through the switch. Where
55does it go next? In my case, it's pretty simple: it goes to the
56router/modem device.
57
58- Does my ISP block any ports that I need?
59 - This is an important step that a lot of people run into when
60 self-hosting at home. Use an online port-checker tool for your IP or
61 call your ISP if you think ports are blocked.
62- Is there a router firewall?
63 - Yes, I checked that it's configured to allow the ports I need to run
64 my services publicly. Common web servers and reverse proxies require
65 ports 80 and 443, but other services like media servers or games can
66 require unique ports, so be sure to check the documentation for your
67 service(s).
68- Are there any other settings affecting inbound/outbound traffic?
69 - Schedules or access blocks
70 - Static Routing
71 - QoS
72 - Port Forwarding
73 - DMZ Hosting
74 - Remote Management (this can sometimes mess with services that also
75 require the use of ports 80 and 443)
76
77Once the data leaves my router, it goes to the upstream ISP and can be
78accessed publicly.
79
80*** Server
81+The services I run on my server are installed straight into the OS,
82without any use of Docker or VMs, so I don't need any extra application
83configuration to make them accessible to the outside world.+
84
85As of 2022-10-04, the paragraph above is no longer true as I now run a
86reverse proxy with Nginx and host many services inside Docker. However,
87it doesn't change anything regarding this post as I still just need to
88open ports 80 & 443 and create the necessary website configuration
89files.
90
91When creating new services - either installed directly on bare metal or
92within something like Docker - I ensure that I read through the
93documentation thoroughly to understand a few key things: - What network
94activities should this app perform (if any)? Using which ports and
95protocols? - Does this app require any commands/services to be run as
96=root=? - Does this app log errors, authentication failures/successes,
97or anything else that would be useful for an investigation?
98
99For extra security, I use limit all incoming connections to SSH
100connections through my server firewall (=ufw=) and disable common SSH
101settings. After all of that, I use =fail2ban= as a preventative measure
102against brute-force login attempts.
103
104As another piece of security, you can randomize your SSH port to ensure
105that random scanners or attackers can't easily try to force their way
106into your network. For example, you can edit the port rules in your
107server to block all connection requests to port =22= but forward all
108remote connections from port =12345= to your server's port =22=. Then
109you just need to SSH to your network via your randomized port.
110
111** =ufw=
112To see how to configure =ufw=, see my other post:
113[[../secure-your-network-with-the-uncomplicated-firewall/][Secure
114Your Network with the Uncomplicated Firewall]].
115
116The general notion with an on-device firewall is that you want to deny
117all incoming connections by default and then selectively open certain
118ports for services or users that you know need access.
119
120If you know that you will only be logging into this server from a
121certain set or list of IPs, you can always set the firewall to only
122allow connections to port 22 from those IPs.
123
124For a quick start to only allow SSH connections to the server, use this:
125
126#+begin_src sh
127sudo ufw default deny incoming
128sudo ufw default allow outgoing
129sudo ufw allow 22
130sudo ufw enable
131#+end_src
132
133#+caption: ufw
134[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png]]
135
136** =ssh=
1371. Using SSH Keys
138
139 First, make sure you have an SSH keypair generated on the device(s)
140 that you'll be using to log in to the server. If you don't have an
141 SSH key, run this command:
142
143 #+begin_src sh
144 ssh-keygen
145 #+end_src
146
147 Now that we have an SSH key, copy it to the server with the following
148 command, which will ask for the user's password before accepting the
149 key:
150
151 #+begin_src sh
152 ssh-copy-id my_user@my_server
153 #+end_src
154
155 If you have multiple keys, you'll need to specify which to use. After
156 it's complete, =ssh= back into the server as that user and make sure
157 it doesn't ask for a password.
158
1592. Disable Password & Root Authentication
160
161 Now that we can access the server without a password, we will disable
162 password authentication and disable anyone from using =ssh= to login
163 as =root=.
164
165 To do this, open the =sshd_config= file:
166
167 #+begin_src sh
168 sudo nano /etc/ssh/sshd_config
169 #+end_src
170
171 You'll need to update the parameters to the values below. If one of
172 these rules is commented-out or doesn't exist, create the rule at the
173 bottom of the file.
174
175 #+begin_src config
176 PermitRootLogin no
177 PasswordAuthentication no
178 PubkeyAuthentication yes
179 #+end_src
180
181 Finally, restart the =ssh= service:
182
183 #+begin_src sh
184 sudo systemctl restart sshd.service
185 #+end_src
186
187 To test that everything's working so far, open ANOTHER terminal and
188 try logging in as =root= over SSH. It is very important that you keep
189 your current SSH session open and test with an additional session, or
190 you will lock yourself out at some point and will need to use a
191 recovery method (e.g., hooking monitor up to home server) to get
192 yourself back in.
193
1943. Enable MFA for =ssh=
195
196 This part is optional, but I highly recommend it. So far, we've
197 ensured that no one can log into our user on the server without using
198 our secret key, and we've ensured that no one can log in remotely as
199 =root=. Next, you can enable MFA authentication for =ssh=
200 connections.
201
202 This process involves editing a couple files and installing an MFA
203 package, so I will not include all the details in this post. To see
204 how to configure MFA for =ssh=, see my other post:
205 [[../enable-totp-mfa-for-ssh/][Enabling MFA for SSH]].
206
207 #+caption: SSH MFA
208 [[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png]]
209
210** =fail2ban=
211I haven't written a post on how I use =fail2ban=, but it's quite simple.
212I use the default =sshd= jail, but you can always create new jails for
213respective applications or ports. For example, if you use Nginx as your
214web server, you can use the =nginx-http-auth= jail.
215
216In order to get it up and running, use the following commands:
217
218#+begin_src sh
219sudo apt install fail2ban
220sudo fail2ban-client start sshd
221sudo fail2ban-client status sshd
222#+end_src
223
224This should be used as a last-resort defense and shouldn't be a
225replacement for the security measures mentioned above.
226
227#+caption: fail2ban
228[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png]]
229
230* Switch
231Between the router and any local devices is my managed switch, which is
232used to create VLANs. The example below shows how I would isolate the
233VLANs if I were starting to host a single service at home.
234
235** 802.1Q VLAN Configuration
236In this configuration, port 8 is the public server that needs to be
237accessed from the outside. Port 23 is my 'dedicated service port' for
238this server. In order to SSH to this server, I need to plug my laptop
239into port 23 or else I cannot SSH. Otherwise, I'd need to hook up a
240monitor and keyboard directly to the server to manage it.
241
242| VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports |
243|---------+-----------+--------------+--------------+----------------|
244| 1 | Default | 1-24 | | 1-24 |
245| 2 | Server | 1,8,23 | | 1,8,23 |
246
247** 802.1Q VLAN PVID Setting
248Once the VLAN is created, I simply add the =VLAN ID= of =2= as the
249=PVID= for any related ports (in this case, see that ports =8= and =23=
250have a PVID of =2=).
251
252| Port | PVID |
253|------+------|
254| 1 | 1 |
255| 2 | 1 |
256| 3 | 1 |
257| 4 | 1 |
258| 5 | 1 |
259| 6 | 1 |
260| 7 | 1 |
261| 8 | 2 |
262| 9 | 1 |
263| 10 | 1 |
264| 11 | 1 |
265| 12 | 1 |
266| 13 | 1 |
267| 14 | 1 |
268| 15 | 1 |
269| 16 | 1 |
270| 17 | 1 |
271| 18 | 1 |
272| 19 | 1 |
273| 20 | 1 |
274| 21 | 1 |
275| 22 | 1 |
276| 23 | 2 |
277| 24 | 1 |
278
279* Router
280On my router, the configuration was as easy as opening the firewall
281settings and unblocking the ports I needed for my services (e.g.,
282HTTP/S, Plex, SSH, MySQL, etc.).
283
284+Since I'm relying on an ISP-provided modem/router combo for now (not by
285choice), I do not use any other advanced settings on my router that
286would inhibit any valid traffic to these services.+
287
288The paragraph above regarding the ISP-owned router is no longer accurate
289as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within
290this router, I enabled port forwarding/firewall rules, segregate the
291network based on the device, and enable traffic restrictions (e.g.,
292silently drop traffic from certain countries and threat categories).
293
294If you have the option with your ISP, I recommend using a personal
295router with software that you are familiar with so that you can explore
296all the options available to you.
297
298* Physical Security
299One large piece of self-hosting that people generally don't discuss
300online is physical security. However, physical security is very
301important for everyone who hosts a server like this. Exactly /how/
302important it is depends on the server use/purpose.
303
304If you self-host customer applications that hold protected data (HIPAA,
305GDPR, COPPA, etc.), then physical security is extremely important and
306cannot be ignored. If you simply host a blog and some hobby sites, then
307it's a relatively minor consideration, but one you still need to think
308about.
309
310** Location
311The first consideration is quite simple: location. - Is the server
312within a property you own or housed on someone else's property? - Is it
313nearby (in your house, in your work office, in your neighbor's garage,
314in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are
315there climate considerations, such as humidity, fires, tornadoes,
316monsoons? - Do you have emergency equipment nearby in case of emergency?
317
318** Hardware Ownership
319Secondly, consider the hardware itself: - Do you own the server in its
320entirety? - Are any other users able to access the server, even if your
321data/space is segregated? - If you're utilizing a third party, do they
322have any documentation to show responsibility? This could be a SOC 1/2/3
323report, ISO compliance report, internal security/safety documentation.
324
325** Physical Controls
326Regardless of who owns the hardware, ensure that there are adequate
327safeguards in place, if necessary. These usually don't apply to small
328home servers and are usually covered already if you're utilizing a third
329party.
330
331These can include: - Server bezel locks - Server room locks - physical,
332digital, or biometric authentication - Security cameras - Raised
333floors/lowered ceilings with proper guards/gates in-place within the
334floors or ceilings - Security personnel - Log sheets and/or guest badges
content/blog/2020-03-25-session-messenger.md → blog/session-manager/index.org renamed +62 −70
@@ -1,32 +1,31 @@
1+++ 1#+title: Session Private Messenger
2date = 2020-03-25 2#+date: 2020-03-25
3title = "Session Private Messenger" 3#+description: Exploring the Session Private Messenger application.
4description = "Exploring the Session Private Messenger application." 4#+filetags: :privacy:
5+++
6
7# Privacy Warning
8 5
6* Privacy Warning
9The company behind Session (Loki Foundation) is from Australia. If you 7The company behind Session (Loki Foundation) is from Australia. If you
10didn't know, Australia has introduced 8didn't know, Australia has introduced
11[legislation](https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf) 9[[https://parlinfo.aph.gov.au/parlInfo/download/legislation/bills/r6195_aspassed/toc_pdf/18204b01.pdf][legislation]]
12mandating companies comply with government requests to build backdoor 10mandating companies comply with government requests to build backdoor
13access into applications. For more information, read my article on [AES 11access into applications. For more information, read my article on
14Encryption](./2020-01-25-aes-encryption.html). 12[[./2020-01-25-aes-encryption.html][AES Encryption]].
15 13
16# About Session 14* About Session
17 15[[https://getsession.org][Session]] is a private, cross-platform
18[Session](https://getsession.org) is a private, cross-platform messaging 16messaging app from the [[https://loki.foundation][Loki Foundation]]. As
19app from the [Loki Foundation](https://loki.foundation). As someone who 17someone who has spent years looking for quality alternatives to major
20has spent years looking for quality alternatives to major messaging 18messaging apps, I was excited when I first heard about Session. Reading
21apps, I was excited when I first heard about Session. Reading through 19through [[https://arxiv.org/pdf/2002.04609.pdf][Session's white paper]],
22[Session's white paper](https://arxiv.org/pdf/2002.04609.pdf), you can 20you can learn the technologies behind the Session app. Part of the
23learn the technologies behind the Session app. Part of the security of 21security of Session comes from the Signal protocol, which was forked as
24Session comes from the Signal protocol, which was forked as the origin 22the origin of Session.
25of Session. 23
26 24#+begin_quote
27> Session is an end-to-end encrypted messenger that removes sensitive 25Session is an end-to-end encrypted messenger that removes sensitive
28> metadata collection, and is designed for people who want privacy and 26metadata collection, and is designed for people who want privacy and
29> freedom from any forms of surveillance. 27freedom from any forms of surveillance.
28#+end_quote
30 29
31In general, this app promises security through end-to-end encryption, 30In general, this app promises security through end-to-end encryption,
32decentralized onion routing, and private identities. The biggest change 31decentralized onion routing, and private identities. The biggest change
@@ -39,90 +38,83 @@ ID.
39Since Session's website and white paper describe the details of 38Since Session's website and white paper describe the details of
40Session's security, I'm going to focus on using the app in this post. 39Session's security, I'm going to focus on using the app in this post.
41 40
42# Features 41* Features
43
44Since most people are looking for an alternative to a popular chat app, 42Since most people are looking for an alternative to a popular chat app,
45I am going to list out the features that Session has so that you are 43I am going to list out the features that Session has so that you are
46able to determine if the app would suit your needs: 44able to determine if the app would suit your needs:
47 45
48- Multiple device linking (via QR code or ID) 46- Multiple device linking (via QR code or ID)
49- App locking via device screen lock, password, or fingerprint 47- App locking via device screen lock, password, or fingerprint
50- Screenshot blocking 48- Screenshot blocking
51- Incognito keyboard 49- Incognito keyboard
52- Read receipts and typing indicators 50- Read receipts and typing indicators
53- Mobile notification customization 51- Mobile notification customization
54- Old message deletion and conversation limit 52- Old message deletion and conversation limit
55- Backups 53- Backups
56- Recovery phrase 54- Recovery phrase
57- Account deletion, including ID, messages, sessions, and contacts 55- Account deletion, including ID, messages, sessions, and contacts
58 56
59# Downloads 57* Downloads
60
61I have tested this app on Ubuntu 19.10, Android 10, macOS Monterey, and 58I have tested this app on Ubuntu 19.10, Android 10, macOS Monterey, and
62iOS 15. All apps have worked well without many issues. 59iOS 15. All apps have worked well without many issues.
63 60
64Below is a brief overview of the Session app on Linux. To get this app, 61Below is a brief overview of the Session app on Linux. To get this app,
65you'll need to go to the [Downloads](https://getsession.org/download/) 62you'll need to go to the [[https://getsession.org/download/][Downloads]]
66page and click to link to the operating system you're using. 63page and click to link to the operating system you're using.
67 64
68For Linux, it will download an AppImage that you'll need to enable with 65For Linux, it will download an AppImage that you'll need to enable with
69the following command: 66the following command:
70 67
71```sh 68#+begin_src sh
72sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage 69sudo chmod u+x session-messenger-desktop-linux-x86_64-1.0.5.AppImage
73``` 70#+end_src
74
75![Session Download
76Options](https://img.cleberg.net/blog/20200325-session-private-messenger/session_downloads.png)
77 71
78# Creating an Account 72#+caption: Session Download Options
73[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_downloads.png]]
79 74
80Once you've installed the app, simply run the app and create your 75* Creating an Account
81unique Session ID. It will look something like this: 76Once you've installed the app, simply run the app and create your unique
82`05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608`. 77Session ID. It will look something like this:
78=05af1835afdd63c947b47705867501d6373f486aa1ae05b1f2f3fcd24570eba608=.
83 79
84You'll need to set a display name and, optionally, a password. If you 80You'll need to set a display name and, optionally, a password. If you
85set a password, you will need to enter it every time you open the app. 81set a password, you will need to enter it every time you open the app.
86 82
87![Session Login 83#+caption: Session Login (Linux)
88(Linux)](https://img.cleberg.net/blog/20200325-session-private-messenger/session_linux_login.png) 84[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_linux_login.png]]
89
90![Session Login
91(macOS)](https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_login.png)
92 85
93![Password 86#+caption: Session Login (macOS)
94Authentication](https://img.cleberg.net/blog/20200325-session-private-messenger/session_password_authentication.png) 87[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_login.png]]
95 88
96# Start Messaging 89#+caption: Password Authentication
90[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_password_authentication.png]]
97 91
92* Start Messaging
98Once you've created your account and set up your profile details, the 93Once you've created your account and set up your profile details, the
99next step is to start messaging other people. To do so, you'll need to 94next step is to start messaging other people. To do so, you'll need to
100share your Session ID with other people. From this point, it's fairly 95share your Session ID with other people. From this point, it's fairly
101straightforward and acts like any other messaging app, so I won't dive 96straightforward and acts like any other messaging app, so I won't dive
102into much detail here. 97into much detail here.
103 98
104## macOS 99** macOS
105 100#+caption: macOS Conversations
106![macOS 101[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_conversations.png]]
107Conversations](https://img.cleberg.net/blog/20200325-session-private-messenger/session_macos_conversations.png)
108 102
109One key feature to note is that the desktop application now provides a 103One key feature to note is that the desktop application now provides a
110helpful pop-up box explaining the process that Session uses to hide your 104helpful pop-up box explaining the process that Session uses to hide your
111IP address: 105IP address:
112 106
113![IP Address Help 107#+caption: IP Address Help Box
114Box](https://img.cleberg.net/blog/20200325-session-private-messenger/session_ip.png) 108[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_ip.png]]
115
116## iOS
117 109
110** iOS
118The mobile app is quite simple and effective, giving you all the 111The mobile app is quite simple and effective, giving you all the
119standard mobile messaging options you'd expect. 112standard mobile messaging options you'd expect.
120 113
121![iOS 114#+caption: iOS App
122App](https://img.cleberg.net/blog/20200325-session-private-messenger/session_ios.png) 115[[https://img.cleberg.net/blog/20200325-session-private-messenger/session_ios.png]]
123
124# Potential Issues
125 116
117* Potential Issues
126I've discovered one annoying issue that would prevent from using this 118I've discovered one annoying issue that would prevent from using this
127app regularly. On a mobile device, there have been issues with receiving 119app regularly. On a mobile device, there have been issues with receiving
128messages on time. Even with battery optimization disabled and no network 120messages on time. Even with battery optimization disabled and no network
content/blog/2021-01-01-seum.md → blog/seum/index.org renamed +43 −43
@@ -1,27 +1,25 @@
1+++ 1#+title: SEUM: Speedrunners from Hell
2date = 2021-01-01 2#+date: 2021-01-01
3title = "SEUM: Speedrunners from Hell" 3#+description: A video game review for SEUM: Speedrunners from Hell.
4description = "A video game review for SEUM: Speedrunners from Hell." 4#+filetags: :gaming:
5+++ 5
6 6* Game Description
7## Game Description {#game-description id="809DA4E6-4E0D-4179-B8FF-6C9E767EC74D"} 7[[https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/][SEUM:
8 8Speedrunners from Hell]] is an incredibly fast-paced mashup of a puzzle
9[SEUM: Speedrunners from 9game and a free-running game. Created by [[https://pinestudio.co][Pine
10Hell](https://store.steampowered.com/app/457210/SEUM_Speedrunners_from_Hell/) 10Studio]] in early 2016 and first released as a
11is an incredibly fast-paced mashup of a puzzle game and a free-running 11[[https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868][demo
12game. Created by [Pine Studio](https://pinestudio.co) in early 2016 and 12on GameJolt]], this game was successfully green-lit on Steam and has
13first released as a [demo on 13amassed a cult following on multiple consoles.
14GameJolt](https://gamejolt.com/games/seum-speedrunners-from-hell-demo/154868),
15this game was successfully green-lit on Steam and has amassed a cult
16following on multiple consoles.
17 14
18Here's the game description straight from the developers: 15Here's the game description straight from the developers:
19 16
20> Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners 17#+begin_quote
21> from Hell is truly hardcore and focuses on speed and fast reaction. 18Like a bastard child of Quake 3 and Super Meat Boy, SEUM: Speedrunners
22 19from Hell is truly hardcore and focuses on speed and fast reaction.
23## Story {#story id="BF401145-763D-4399-922B-7D73322B5B40"} 20#+end_quote
24 21
22* Story
25SEUM does a great job setting the stage when you open the game for the 23SEUM does a great job setting the stage when you open the game for the
26first time, playing an animated video in the form of comic book images. 24first time, playing an animated video in the form of comic book images.
27You see Marty, the protagonist, sitting around drinking his beer as 25You see Marty, the protagonist, sitting around drinking his beer as
@@ -33,10 +31,10 @@ use all the powers in the game.
33Check out the screenshot below for one of the first panels of the 31Check out the screenshot below for one of the first panels of the
34storyline: 32storyline:
35 33
36![SEUM Story](https://img.cleberg.net/blog/20210101-seum/seum_story.png) 34#+caption: SEUM Story
37 35[[https://img.cleberg.net/blog/20210101-seum/seum_story.png]]
38## Gameplay {#gameplay id="A55CB1AF-E818-4CA7-BF69-E8141369C269"}
39 36
37* Gameplay
40To accomplish each level, you will need to get to the final blue portal 38To accomplish each level, you will need to get to the final blue portal
41under a certain time limit. You can beat a level by getting to the 39under a certain time limit. You can beat a level by getting to the
42portal before the time meter runs out or "Dominate" a level by beating 40portal before the time meter runs out or "Dominate" a level by beating
@@ -49,44 +47,46 @@ to go through any existing orange portals, light all yellow beacons,
49avoid things like fireballs and blades, or use any satanic power orbs 47avoid things like fireballs and blades, or use any satanic power orbs
50lying around. These special abilities include: 48lying around. These special abilities include:
51 49
52- Gravity 50- Gravity
53- Teleport 51- Teleport
54- Rewind 52- Rewind
55- Spawn platform 53- Spawn platform
56- Roar (DLC) 54- Roar (DLC)
57- Rocket (DLC) 55- Rocket (DLC)
58- Shadow world (DLC) 56- Shadow world (DLC)
59 57
60For the main storyline, there are nine floors to beat. Each floor 58For the main storyline, there are nine floors to beat. Each floor
61contains nine regular levels, one boss level, and one bonus level; 59contains nine regular levels, one boss level, and one bonus level;
62although you don't technically need to beat all levels to advance to 60although you don't technically need to beat all levels to advance to the
63the next floor. 61next floor.
64
65![SEUM Floor
66Menu](https://img.cleberg.net/blog/20210101-seum/seum_floor.png)
67 62
68## In-Game Screenshots {#in-game-screenshots id="C779F008-4C8A-4BA4-AA31-60A1BF5A3EE3"} 63#+caption: SEUM Floor Menu
64[[https://img.cleberg.net/blog/20210101-seum/seum_floor.png]]
69 65
66* In-Game Screenshots
70The main menu gives you plenty of options for tuning your system, 67The main menu gives you plenty of options for tuning your system,
71playing main levels, playing the DLC, or exploring the other game modes. 68playing main levels, playing the DLC, or exploring the other game modes.
72 69
73![SEUM Main 70#+caption: SEUM Main Menu
74Menu](https://img.cleberg.net/blog/20210101-seum/seum_menu.png) 71[[https://img.cleberg.net/blog/20210101-seum/seum_menu.png]]
75 72
76Once you enter a level, you can look around and strategize before 73Once you enter a level, you can look around and strategize before
77starting. Clicking any button will start the menu, and you'll have to 74starting. Clicking any button will start the menu, and you'll have to
78restart if you die. One of the great things about SEUM is that it has 75restart if you die. One of the great things about SEUM is that it has
79great keyboard shortcuts. You can quickly smash `R` to 76great keyboard shortcuts. You can quickly smash =R= to restart the level
80restart the level or `M` to return to the level menu. 77or =M= to return to the level menu.
81 78
82![SEUM Level](https://img.cleberg.net/blog/20210101-seum/seum_level.png) 79#+caption: SEUM Level
80[[https://img.cleberg.net/blog/20210101-seum/seum_level.png]]
83 81
84Once you're able to win a level, you'll see the high scores and can 82Once you're able to win a level, you'll see the high scores and can
85watch replays of the best scores. 83watch replays of the best scores.
86 84
87![SEUM Win](https://img.cleberg.net/blog/20210101-seum/seum_win.png) 85#+caption: SEUM Win
86[[https://img.cleberg.net/blog/20210101-seum/seum_win.png]]
88 87
89Each regular level contains a beer in a disguised location that may take 88Each regular level contains a beer in a disguised location that may take
90some out-of-the-box thinking. 89some out-of-the-box thinking.
91 90
92![SEUM Beer](https://img.cleberg.net/blog/20210101-seum/seum_beer.png) 91#+caption: SEUM Beer
92[[https://img.cleberg.net/blog/20210101-seum/seum_beer.png]]
content/blog/2022-03-26-ssh-mfa.md → blog/ssh-mfa/index.org renamed +61 −70
@@ -1,83 +1,78 @@
1+++ 1#+title: Enable TOTP MFA for SSH
2date = 2022-03-26 2#+date: 2022-03-26
3title = "Enable TOTP MFA for SSH" 3#+description: Learn how to enable timed one-time passcodes for SSH.
4description = "Learn how to enable timed one-time passcodes for SSH." 4#+filetags: :security:
5+++
6
7## Why Do I Need MFA for SSH?
8 5
6* Why Do I Need MFA for SSH?
9If you are a sysadmin of a server anywhere (that includes at home!), you 7If you are a sysadmin of a server anywhere (that includes at home!), you
10may want an added layer of protection against intruders. This is not a 8may want an added layer of protection against intruders. This is not a
11replacement for other security measures, such as: 9replacement for other security measures, such as:
12 10
13- Disable root SSH 11- Disable root SSH
14- Disable SSH password authentication 12- Disable SSH password authentication
15- Allow only certain users to login via SSH 13- Allow only certain users to login via SSH
16- Allow SSH only from certain IPs 14- Allow SSH only from certain IPs
17 15
18However, MFA can be added as an additional security measure to ensure 16However, MFA can be added as an additional security measure to ensure
19that your server is protected. This is especially important if you need 17that your server is protected. This is especially important if you need
20to allow password authentication for SSH. 18to allow password authentication for SSH.
21 19
22For more guidance on server security measures, see my other post: 20For more guidance on server security measures, see my other post:
23[Hardening a Public-Facing Home 21[[../hardening-a-public-facing-home-server/][Hardening a
24Server](/blog/hardening-a-public-facing-home-server/). 22Public-Facing Home Server]].
25
26## Install MFA PAM Module
27 23
24* Install MFA PAM Module
28PAM, which stands for Pluggable Authentication Module, is an 25PAM, which stands for Pluggable Authentication Module, is an
29authentication infrastructure used on Linux systems to authenticate a 26authentication infrastructure used on Linux systems to authenticate a
30user. In order to use this technology, let's install the 27user. In order to use this technology, let's install the
31`libpam-google-authenticator` package: 28=libpam-google-authenticator= package:
32 29
33```sh 30#+begin_src sh
34sudo apt-get update 31sudo apt-get update
35``` 32#+end_src
36 33
37```sh 34#+begin_src sh
38sudo apt-get install libpam-google-authenticator 35sudo apt-get install libpam-google-authenticator
39``` 36#+end_src
40
41## Initialize the PAM Module
42
43### Interactive Method
44 37
38* Initialize the PAM Module
39** Interactive Method
45Once the package is installed, initialize it and following the 40Once the package is installed, initialize it and following the
46interactive prompts to generate your OTP or TOTP: 41interactive prompts to generate your OTP or TOTP:
47 42
48```sh 43#+begin_src sh
49google-authenticator 44google-authenticator
50``` 45#+end_src
51 46
52If you are not sure how to answer, read the prompts carefully and think 47If you are not sure how to answer, read the prompts carefully and think
53about having to how each situation would affect your normal login 48about having to how each situation would affect your normal login
54attempts. If you are still not sure, use my default responses below. 49attempts. If you are still not sure, use my default responses below.
55 50
56```txt 51#+begin_src txt
57OUTPUT 52OUTPUT
58 53
59Do you want authentication tokens to be time-based (y/n) y 54Do you want authentication tokens to be time-based (y/n) y
60``` 55#+end_src
61 56
62At this point, use an authenticator app somewhere one of your devices to 57At this point, use an authenticator app somewhere one of your devices to
63scan the QR code. Any future login attempts after our upcoming 58scan the QR code. Any future login attempts after our upcoming
64configuration changes will require that TOTP. 59configuration changes will require that TOTP.
65 60
66```txt 61#+begin_src txt
67OUTPUT 62OUTPUT
68 63
69Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y 64Do you want me to update your "/home/user/.google_authenticator" file? (y/n) y
70``` 65#+end_src
71 66
72```txt 67#+begin_src txt
73OUTPUT 68OUTPUT
74 69
75Do you want to disallow multiple uses of the same authentication 70Do you want to disallow multiple uses of the same authentication
76token? This restricts you to one login about every 30s, but it increases 71token? This restricts you to one login about every 30s, but it increases
77your chances to notice or even prevent man-in-the-middle attacks (y/n) y 72your chances to notice or even prevent man-in-the-middle attacks (y/n) y
78``` 73#+end_src
79 74
80```txt 75#+begin_src txt
81OUTPUT 76OUTPUT
82 77
83By default, a new token is generated every 30 seconds by the mobile app. 78By default, a new token is generated every 30 seconds by the mobile app.
@@ -90,30 +85,29 @@ code, the next code) to 17 permitted codes (the 8 previous codes, the current
90code, and the 8 next codes). This will permit for a time skew of up to 4 minutes 85code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
91between client and server. 86between client and server.
92Do you want to do so? (y/n) n 87Do you want to do so? (y/n) n
93``` 88#+end_src
94 89
95```txt 90#+begin_src txt
96OUTPUT 91OUTPUT
97 92
98If the computer that you are logging into isn't hardened against brute-force 93If the computer that you are logging into isn't hardened against brute-force
99login attempts, you can enable rate-limiting for the authentication module. 94login attempts, you can enable rate-limiting for the authentication module.
100By default, this limits attackers to no more than 3 login attempts every 30s. 95By default, this limits attackers to no more than 3 login attempts every 30s.
101Do you want to enable rate-limiting? (y/n) y 96Do you want to enable rate-limiting? (y/n) y
102``` 97#+end_src
103
104### Non-Interactive Method
105 98
99** Non-Interactive Method
106If you need to do this quickly, know your responses to the prompts, or 100If you need to do this quickly, know your responses to the prompts, or
107are setting this up for numerous users, the non-interactive method can 101are setting this up for numerous users, the non-interactive method can
108be much faster: 102be much faster:
109 103
110```sh 104#+begin_src sh
111google-authenticator -t -d -f -r 3 -R 30 -w 3 105google-authenticator -t -d -f -r 3 -R 30 -w 3
112``` 106#+end_src
113 107
114The options referenced above are as follows: 108The options referenced above are as follows:
115 109
116```txt 110#+begin_src txt
117google-authenticator [<options>] 111google-authenticator [<options>]
118 -h, --help Print this message 112 -h, --help Print this message
119 -c, --counter-based Set up counter-based (HOTP) verification 113 -c, --counter-based Set up counter-based (HOTP) verification
@@ -133,66 +127,63 @@ google-authenticator [<options>]
133 -w, --window-size=W Set window of concurrently valid codes 127 -w, --window-size=W Set window of concurrently valid codes
134 -W, --minimal-window Disable window of concurrently valid codes 128 -W, --minimal-window Disable window of concurrently valid codes
135 -e, --emergency-codes=N Number of emergency codes to generate 129 -e, --emergency-codes=N Number of emergency codes to generate
136``` 130#+end_src
137 131
138This fully configures the authenticator, saves it to a file, and then 132This fully configures the authenticator, saves it to a file, and then
139outputs the secret key, QR code, and recovery codes. (If you add the 133outputs the secret key, QR code, and recovery codes. (If you add the
140flag `-q`, then there won't be any output). If you use this 134flag =-q=, then there won't be any output). If you use this command in
141command in an automated fashion, make sure your script captures the 135an automated fashion, make sure your script captures the secret key
142secret key and/or recovery codes and makes them available to the user. 136and/or recovery codes and makes them available to the user.
143
144## PAM Configuration Settings
145 137
138* PAM Configuration Settings
146Once you've enabled the T/OTP and have it saved to an MFA app on your 139Once you've enabled the T/OTP and have it saved to an MFA app on your
147phone or other device, open the PAM `sshd` file: 140phone or other device, open the PAM =sshd= file:
148 141
149```sh 142#+begin_src sh
150sudo nano /etc/pam.d/sshd 143sudo nano /etc/pam.d/sshd
151``` 144#+end_src
152 145
153You need to do two things in this file. First, add the following lines 146You need to do two things in this file. First, add the following lines
154to the bottom of the file: 147to the bottom of the file:
155 148
156```config 149#+begin_src config
157auth required pam_google_authenticator.so nullok 150auth required pam_google_authenticator.so nullok
158auth required pam_permit.so 151auth required pam_permit.so
159``` 152#+end_src
160 153
161Second, comment-out the following line near the top of the file. 154Second, comment-out the following line near the top of the file.
162 155
163If you leave this line uncommented, every SSH login attempt will ask for 156If you leave this line uncommented, every SSH login attempt will ask for
164the following three authentication factors: 157the following three authentication factors:
165 158
1661. Publickey 1591. Publickey
1672. Password 1602. Password
1683. T/OTP code 1613. T/OTP code
169 162
170```config 163#+begin_src config
171#@include common-auth 164#@include common-auth
172``` 165#+end_src
173
174## SSH Configuration Settings
175 166
176Finally, edit the `sshd_config` file again: 167* SSH Configuration Settings
168Finally, edit the =sshd_config= file again:
177 169
178```sh 170#+begin_src sh
179sudo nano /etc/ssh/sshd_config 171sudo nano /etc/ssh/sshd_config
180``` 172#+end_src
181 173
182You'll need to change `ChallengeResponseAuthentication` to 174You'll need to change =ChallengeResponseAuthentication= to yes and add
183yes and add the `AuthenticationMethods` line to the bottom of 175the =AuthenticationMethods= line to the bottom of the file.
184the file.
185 176
186```config 177#+begin_src config
187ChallengeResponseAuthentication yes 178ChallengeResponseAuthentication yes
188AuthenticationMethods publickey,password publickey,keyboard-interactive 179AuthenticationMethods publickey,password publickey,keyboard-interactive
189``` 180#+end_src
190 181
191Finally, restart the `ssh` service: 182Finally, restart the =ssh= service:
192 183
193```sh 184#+begin_src sh
194sudo systemctl restart sshd.service 185sudo systemctl restart sshd.service
195``` 186#+end_src
196 187
197The next time you log in, you should be greeted with a verification code 188The next time you log in, you should be greeted with a verification code
198request! 189request!
blog/st/index.org added +87
@@ -0,0 +1,87 @@
1#+title: Simple Terminal
2#+date: 2022-12-17
3#+description: Instructions for building simple terminal on Fedora.
4#+filetags: :linux:
5
6* st
7[[https://st.suckless.org][st]] standards for Simple Terminal, a simple
8terminal implementation for X made by the
9[[https://suckless.org][suckless]] team.
10
11This post walks through the dependencies needed and process to build and
12install =st= on Fedora Workstation.
13
14** Obtain Files
15To start, obtain the source files for =st= via =git clone=.
16
17#+begin_src sh
18mkdir ~/suckless && cd ~/suckless
19git clone https://git.suckless.org/st && cd st
20#+end_src
21
22** Dependencies
23Once you have the files and are in the =st= directory, ensure the
24following packages are installed.
25
26#+begin_src sh
27sudo dnf update && sudo dnf upgrade
28sudo dnf install gcc patch libX11-devel libXft-devel
29#+end_src
30
31** Building
32Before building, ensure that you read the README file.
33
34#+begin_src sh
35cat README
36#+end_src
37
38Once you've read the instructions, open the =config.mk= file and ensure
39it matches your setup. If you're not sure, leave the default options
40within the file.
41
42Finally, you can build =st= with the following command. Ensure you run
43as root (e.g., =sudo=) or else you may not end up with a usable
44application file.
45
46#+begin_src sh
47sudo make clean install
48#+end_src
49
50** Customization (Patches)
51Note that customizing =st= requires you to modify the source files or to
52download one of the [[https://st.suckless.org/patches/][available
53patches]] for suckless.org.
54
55If you've already installed =st= and want to customize or install a
56patch, start by uninstalling the current program.
57
58#+begin_src sh
59cd ~/suckless/st
60sudo make uninstall
61#+end_src
62
63Next, grab the =<path>.diff= file from the page of the patch you chose.
64For example, I will be using the
65[[https://st.suckless.org/patches/defaultfontsize/][defaultfontsize]]
66patch in the below example.
67
68#+begin_src sh
69wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
70#+end_src
71
72Once the file is downloaded inside the =st= folder, apply the patch and
73re-install the program. You may need to install the =patch= command if
74you don't have it installed already (you should have installed it
75above).
76
77#+begin_src sh
78patch -i st-defaultfontsize-20210225-4ef0cbd.diff
79sudo make clean install
80#+end_src
81
82Once installed, you can use the default font size patch to launch =st=
83with any font size you wish:
84
85#+begin_src sh
86st -z 16
87#+end_src
blog/steam-on-ntfs/index.org added +93
@@ -0,0 +1,93 @@
1#+title: Linux Gaming Tweak: Steam on NTFS Drives
2#+date: 2020-01-26
3#+description: Learn how to fix Steam NTFS issues on Linux.
4#+filetags: :gaming:linux:
5
6* Auto-Mount Steam Drives
7#+caption: Steam Screenshot
8[[https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png]]
9
10If you want to see how to install Steam on Linux, see my other post:
11[[../linux-software/][Linux Software]].
12
13Are you having trouble launching games, even though they've installed
14correctly? This may happen if you're storing your games on an
15NTFS-formatted drive. This shouldn't be an issue if you're storing your
16games on the same drive that Steam is on, but some gamers prefer to put
17Steam on their main drive and game files on another SSD or HDD.
18
19To fix this problem, you'll need to try a few things. First, you'll need
20to install the =ntfs-3g= package, which is meant for better
21interoperability with Linux.
22
23#+begin_src sh
24sudo apt install ntfs-3g
25#+end_src
26
27Next, you should set up the =/etc/fstab= file to automatically mount
28your drives on boot. To automatically mount your drives when the
29computer boots up, you'll have to create the folders you want to mount
30your drive to first. I store mine in the =/mnt= folder using names that
31I'll recognize, but you can create your folders wherever you want.
32
33#+begin_src sh
34mkdir /path/to/folder
35#+end_src
36
37For example:
38
39#+begin_src sh
40mkdir /mnt/steam_library
41#+end_src
42
43To automatically mount drives upon system boot, you will need to collect
44a few items. The UUID is the identification number connected to
45whichever drive you're using to store Steam games.
46
47Drives are usually labeled similar to =/dev/nvme0n1p1= or =/dev/sda1=,
48so you'll need to find the line in the output of the command below that
49correlates to your drive and copy the UUID over to the =/etc/fstab=
50file.
51
52#+begin_src sh
53sudo blkid | grep UUID=
54#+end_src
55
56Next, you'll need your =uid= and =gid=. To find these, run the following
57command:
58
59#+begin_src sh
60id -u && id -g
61#+end_src
62
63Now that you have collected the necessary information, open the
64=/etc/fstab= file:
65
66#+begin_src sh
67sudo nano /etc/fstab
68#+end_src
69
70Each drive you want to mount on boot should have its own line in the
71=/etc/fstab= file that looks similar to this:
72
73#+begin_src config
74UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
75#+end_src
76
77Now all you need to do is unmount your drive and re-mount it. You can
78unmount the drive by doing this (be sure to use the correct drive name
79here):
80
81#+begin_src sh
82sudo umount /dev/sdxX
83#+end_src
84
85You can re-mount all your drives by executing the following:
86
87#+begin_src sh
88sudo mount -a
89#+end_src
90
91If you don't know what your drive name is, or you're nervous about
92unmounting and re-mounting, simply reboot your computer, and it will be
93done for you automatically.
content/blog/2022-10-20-syncthing.md → blog/syncthing/index.org renamed +82 −91
@@ -1,70 +1,66 @@
1+++ 1#+title: Syncthing: A Minimal Self-Hosted Cloud Storage Solution
2date = 2022-10-04 2#+date: 2022-10-04
3title = "Syncthing: A Minimal Self-Hosted Cloud Storage Solution" 3#+description: An overview of how you can use Syncthing as a minimal alternative to cloud storage.
4description = "" 4#+filetags: :selfhosting:
5+++
6
7## An Overview of Syncthing
8 5
6* An Overview of Syncthing
9If you've been looking around the self-hosted cloud storage space for a 7If you've been looking around the self-hosted cloud storage space for a
10while, you've undoubtedly run into someone suggesting 8while, you've undoubtedly run into someone suggesting
11[Syncthing](https://syncthing.net) as an option. However, it is an 9[[https://syncthing.net][Syncthing]] as an option. However, it is an
12unusual alternative for those users out there who are used to having a 10unusual alternative for those users out there who are used to having a
13centralized cloud server that serves as the "controller" of the data 11centralized cloud server that serves as the "controller" of the data and
14and interacts with clients on devices to fetch files. 12interacts with clients on devices to fetch files.
15 13
16This post is a walkthrough of the Syncthing software, how I set up my 14This post is a walkthrough of the Syncthing software, how I set up my
17personal storage, and some pros and cons of using the software. 15personal storage, and some pros and cons of using the software.
18 16
19## Installing Syncthing 17* Installing Syncthing
20
21To install Syncthing, visit the 18To install Syncthing, visit the
22[Downloads](https://syncthing.net/downloads/) page or install via your 19[[https://syncthing.net/downloads/][Downloads]] page or install via your
23device's package manager. 20device's package manager.
24 21
25### Server & Desktop 22** Server & Desktop
26
27You can install Syncthing on servers and desktops via the Downloads page 23You can install Syncthing on servers and desktops via the Downloads page
28linked above or via the command-line. 24linked above or via the command-line.
29 25
30For Debian-based distros: 26For Debian-based distros:
31 27
32```sh 28#+begin_src sh
33sudo apt install syncthing 29sudo apt install syncthing
34``` 30#+end_src
35 31
36For Fedora-based distros: 32For Fedora-based distros:
37 33
38```sh 34#+begin_src sh
39sudo dnf install syncthing 35sudo dnf install syncthing
40``` 36#+end_src
41
42### Mobile
43 37
38** Mobile
44Syncthing for Android is available on 39Syncthing for Android is available on
45[F-Droid](https://f-droid.org/packages/com.nutomic.syncthingandroid/) 40[[https://f-droid.org/packages/com.nutomic.syncthingandroid/][F-Droid]]
46and [Google 41and
47Play](https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid). 42[[https://play.google.com/store/apps/details?id=com.nutomic.syncthingandroid][Google
48Syncthing does not have an official iOS client, but there is a 43Play]]. Syncthing does not have an official iOS client, but there is a
49third-party client called [Möbius 44third-party client called
50Sync](https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216). 45[[https://apps.apple.com/us/app/m%C3%B6bius-sync/id1539203216][Möbius
51 46Sync]].
52## How Does Syncthing Work? 47
53 48* How Does Syncthing Work?
54To start, I wanted to include the main marketing blurb from their 49To start, I wanted to include the main marketing blurb from their
55website: 50website:
56 51
57> Syncthing is a continuous file synchronization program. It 52#+begin_quote
58> synchronizes files between two or more computers in real time, safely 53Syncthing is a continuous file synchronization program. It synchronizes
59> protected from prying eyes. Your data is your data alone, and you 54files between two or more computers in real time, safely protected from
60> deserve to choose where it is stored, whether it is shared with some 55prying eyes. Your data is your data alone, and you deserve to choose
61> third party, and how it's transmitted over the internet. 56where it is stored, whether it is shared with some third party, and how
57it's transmitted over the internet.
58#+end_quote
62 59
63Let's break this apart and add in some other details to help explain 60Let's break this apart and add in some other details to help explain
64what exactly Syncthing does in order to sync files between devices. 61what exactly Syncthing does in order to sync files between devices.
65 62
66### Local Syncthing Server(s) 63** Local Syncthing Server(s)
67
68Syncthing syncs files between multiple devices by creating a local 64Syncthing syncs files between multiple devices by creating a local
69server on each device. These local servers handle a few different 65server on each device. These local servers handle a few different
70things, such as watching files and directories for changes, hosting an 66things, such as watching files and directories for changes, hosting an
@@ -72,40 +68,37 @@ administrative GUI website, and authenticating with connected devices.
72 68
73You can also start, stop, and restart the Syncthing server via the 69You can also start, stop, and restart the Syncthing server via the
74command-line or web dashboard. If you're running Syncthing on a device 70command-line or web dashboard. If you're running Syncthing on a device
75with `systemd`, you can use the following commands: 71with =systemd=, you can use the following commands:
76 72
77```sh 73#+begin_src sh
78sudo systemctl start syncthing@username.service 74sudo systemctl start syncthing@username.service
79sudo systemctl restart syncthing@username.service 75sudo systemctl restart syncthing@username.service
80sudo systemctl stop syncthing@username.service 76sudo systemctl stop syncthing@username.service
81``` 77#+end_src
82
83### Syncthing Dashboard
84 78
79** Syncthing Dashboard
85This biggest part of Syncthing is the admin GUI website that runs on 80This biggest part of Syncthing is the admin GUI website that runs on
86each device (note that mobile devices will use the Syncthing app rather 81each device (note that mobile devices will use the Syncthing app rather
87than the web GUI). The admin GUI is available through the web browser on 82than the web GUI). The admin GUI is available through the web browser on
88the local device that is running Syncthing - simply go to 83the local device that is running Syncthing - simply go to
89`http://localhost:8384` or 84=http://localhost:8384= or =http://127.0.0.1:8384=. This web page is the
90`http://127.0.0.1:8384`. This web page is the place where you 85place where you will change settings, add/modify synced files, and
91will change settings, add/modify synced files, and add/modify connected 86add/modify connected devices.
92devices.
93 87
94Here's an example web GUI dashboard: 88Here's an example web GUI dashboard:
95 89
96![Syncthing 90#+caption: Syncthing Dashboard
97Dashboard](https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png) 91[[https://img.cleberg.net/blog/20221020-syncthing/syncthing_gui.png]]
98
99### Remote Devices
100 92
93** Remote Devices
101A cloud storage solution wouldn't be very useful if you aren't able to 94A cloud storage solution wouldn't be very useful if you aren't able to
102share data among various devices. Syncthing does this by sharing Device 95share data among various devices. Syncthing does this by sharing Device
103IDs to connect servers, and then by manually sharing Folders with 96IDs to connect servers, and then by manually sharing Folders with
104devices that have been connected. 97devices that have been connected.
105 98
106For instance, if you have a laptop running Syncthing and then install 99For instance, if you have a laptop running Syncthing and then install
107the Syncthing mobile app on a phone, you could scan the laptop's QR 100the Syncthing mobile app on a phone, you could scan the laptop's QR code
108code for Device ID and then accept the authentication on the laptop's 101for Device ID and then accept the authentication on the laptop's
109dashboard. Next, you can use either device to select a folder for 102dashboard. Next, you can use either device to select a folder for
110sharing and dictating which device should send, receive, or both. 103sharing and dictating which device should send, receive, or both.
111 104
@@ -114,8 +107,7 @@ which can add devices from the introducer to the device list, for
114mutually shared folders. You can also configure Auto Accept, 107mutually shared folders. You can also configure Auto Accept,
115compression, rate limits, and more settings per device. 108compression, rate limits, and more settings per device.
116 109
117## My Personal Cloud Storage Set-up 110* My Personal Cloud Storage Set-up
118
119Personally, I use a model similar to a traditional cloud storage 111Personally, I use a model similar to a traditional cloud storage
120service. I have a "centralized" server running 24/7 that acts as an 112service. I have a "centralized" server running 24/7 that acts as an
121Introducer for my Syncthing network. I think of this as my main storage 113Introducer for my Syncthing network. I think of this as my main storage
@@ -129,50 +121,49 @@ network, both running intermittently as they are not powered-on 24/7.
129The initial set-up of the software was easy enough, but data transfer 121The initial set-up of the software was easy enough, but data transfer
130rates were incredibly slow for me due to the Wi-Fi. Instead, I plugged 122rates were incredibly slow for me due to the Wi-Fi. Instead, I plugged
131my laptop into the ethernet network that my server is on and manually 123my laptop into the ethernet network that my server is on and manually
132copied my folders over to the server with `scp`. Once 124copied my folders over to the server with =scp=. Once complete,
133complete, Syncthing validated that all files were there and not missing, 125Syncthing validated that all files were there and not missing, and it
134and it did not need to transfer any data through the WAN. 126did not need to transfer any data through the WAN.
135 127
136As slow as the transfer was going, this probably saved me a few days of 128As slow as the transfer was going, this probably saved me a few days of
137waiting for my ~100GB sync. 129waiting for my ~100GB sync.
138 130
139## Pros & Cons 131* Pros & Cons
140 132I've put together a short list of pros and cons for Syncthing. I thought
141I've put together a short list of pros and cons for Syncthing. I 133about my experiences with Nextcloud, WebDAV, proprietary services
142thought about my experiences with Nextcloud, WebDAV, proprietary 134(Google Drive, iCloud, etc.), and privacy-focused cloud solutions
143services (Google Drive, iCloud, etc.), and privacy-focused cloud 135(pCloud, Tresorit, etc.).
144solutions (pCloud, Tresorit, etc.). 136
145 137*Pros:*
146**Pros:** 138
147 139- I've faced no data loss at all through my two-month trial run.
148- I've faced no data loss at all through my two-month trial run. 140- No third-parties store your data on their servers.
149- No third-parties store your data on their servers. 141- You have full control over your data and can take your data and leave
150- You have full control over your data and can take your data and 142 at any time.
151 leave at any time. 143- It's possible to encrypt client-side easily with software like
152- It's possible to encrypt client-side easily with software like 144 Cryptomator.
153 Cryptomator. 145- No proprietary clients or mounted volumes, just plain files and
154- No proprietary clients or mounted volumes, just plain files and 146 folders.
155 folders. 147
156 148*Cons:*
157**Cons:** 149
158 150- The learning curve is steeper than traditional cloud services and is
159- The learning curve is steeper than traditional cloud services and is 151 focused on a technical audience.
160 focused on a technical audience. 152- If a device needs to modify files in a Folder, the devices will need
161- If a device needs to modify files in a Folder, the devices will need 153 to sync ALL files from the folder, which may be large. To avoid size
162 to sync ALL files from the folder, which may be large. To avoid size 154 restraints, split large folders into smaller folders for syncing.
163 restraints, split large folders into smaller folders for syncing. 155- Syncing can be slow due to the clients/servers initially connecting or
164- Syncing can be slow due to the clients/servers initially connecting 156 re-connecting after sleeping.
165 or re-connecting after sleeping. 157- Multiple personal devices are required and require the user to own or
166- Multiple personal devices are required and require the user to own 158 rent them as no third-party servers are involved in the storage of
167 or rent them as no third-party servers are involved in the storage 159 data.
168 of data. 160
169 161Overall, I've had a great experience with Syncthing so far. I've had no
170Overall, I've had a great experience with Syncthing so far. I've had 162data loss, syncing has been quick and easy when changes are made to
171no data loss, syncing has been quick and easy when changes are made to
172files, device connections are reliable, and I love the freedom of 163files, device connections are reliable, and I love the freedom of
173controlling the clients and servers as I choose. 164controlling the clients and servers as I choose.
174 165
175Not to mention that I appreciate that I - or someone else - could pull 166Not to mention that I appreciate that I - or someone else - could pull
176the Syncthing [source code](https://github.com/syncthing) and continue 167the Syncthing [[https://github.com/syncthing][source code]] and continue
177development/support if the Syncthing Foundation decides to stop 168development/support if the Syncthing Foundation decides to stop
178developing the software or sells the business. 169developing the software or sells the business.
blog/tableau-dashboard/index.org added +156
@@ -0,0 +1,156 @@
1#+title: Data Visualization: Mapping Omaha Crime Data with Tableau
2#+date: 2024-01-27
3#+description: Learn how to utilize Tableau Public to build a dashboard to map crimes across Omaha.
4#+filetags: :data:
5
6In this project, I am going to show you how to use Tableau Public for
7free to create simple dashboards.
8
9I will be creating simple visuals from an Omaha crime data set and
10combining them to create the dashboard below. You can view this
11dashboard interactively online here:
12[[https://public.tableau.com/app/profile/c.c7042/viz/OmahaCrimeData2015-2023/OmahaCrimeData2015-2023#1][Omaha
13Crime Data (2015 - 2023)]].
14
15#+caption: Tableau Dashboard
16[[https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png]]
17
18* Gather the Data
19You can download incident data from the Omaha Police Department on their
20[[https://police.cityofomaha.org/crime-information/incident-data-download][Incident
21Data Download]] page. They currently have files for the years 2015
22through 2023.
23
24Each file will be downloaded as a CSV file, approximately 3 MB - 8 MB.
25
26* Clean and Transform the Data
27I have used Python to combine the files into a single CSV file, as well
28as adding a custom =datetime= column. You could do this step in any
29software you prefer, but I prefer Python as its free, easy to use, and
30has a plethora of support resources online.
31
32Start by opening a terminal, navigating to your Downloads directory, and
33creating a python script.
34
35#+begin_src sh
36cd ~/Downloads
37nano data_processing.py
38#+end_src
39
40Within the Python script, paste the following:
41
42#+begin_src python
43# Import modules
44import pandas as pd
45import glob
46import os
47
48# Import the data
49path = r"~/Downloads/*.csv"
50files = glob.glob(path)
51
52list = []
53
54for file in files:
55 df_tmp = pd.read_csv(file)
56 li.append(df_tmp)
57
58df = pd.concat(list, axis=0, ignore_index=True)
59
60# Create a combined datetime column
61df["datetime"] = pd.to_datetime(
62 df["date"] + " " + df["time"],
63 format="%m/%d/%Y %H:%M:%S"
64)
65df.head()
66
67# Export the combined data
68df.to_csv(r"~/Downloads/combined_incidents.csv")
69#+end_src
70
71Once pasted, save and close the file. You can execute the file like so:
72
73#+begin_src sh
74python3 data_processing.py
75#+end_src
76
77After this, you should have a combined data file that contains all
78incidents between 2015 and 2023. Mine was approximately 55 MB.
79
80* Tableau Public
81[[https://public.tableau.com/][Tableau Public]] is a free-to-use web
82application that allows you to create visualizations by uploading data
83sources. Note that there's no way to keep the data and visualizations
84private, so don't upload anything private.
85
86After creating an account, you can click the =Create= > =Web Authoring=
87link to create your first visualization.
88
89#+caption: Create Button
90[[https://img.cleberg.net/blog/20240127-tableau-dashboard/create_button.png]]
91
92** Upload the Data
93Once you've opened your first project, Tableau will ask you to connect
94to your data. For this project, click the =Upload from computer= button
95and select the CSV file previously combined in the step above.
96
97#+caption: Connect Data
98[[https://img.cleberg.net/blog/20240127-tableau-dashboard/connect_data.png]]
99
100Once connected, you can refresh the preview of the data with the
101=Refresh Data Source= button in the toolbar.
102
103#+caption: Data Preview
104[[https://img.cleberg.net/blog/20240127-tableau-dashboard/data_preview.png]]
105
106If you need to edit any of the data types, column names, etc., you can
107do so now. Once complete, generate an extract so that you can start
108creating visualizations.
109
110** Create Visualizations
111To start, create a worksheet in the toolbar at the bottom of the screen.
112
113Within this screen, select a column from the =Data= side bar on the left
114and drag it into the =Columns= or =Rows= area of the canvas.
115
116See below for the map visualization. You can recreate this by adding the
117following fields:
118
119- =Columns=: Lon
120- =Rows=: Lat
121- =Marks=:
122 - Description
123 - Datetime
124- =Filters=: Datetime
125
126#+caption: Worksheet Editing
127[[https://img.cleberg.net/blog/20240127-tableau-dashboard/worksheet_edit.png]]
128
129You can repeat this process for each visualization you want to create.
130Explore your options by dragging data fields to different areas and by
131opening the field options to explore what operations can be performed on
132different data types (e.g., average, count, etc.).
133
134** Create Dashboard
135To create a dashboard, click the button on the toolbar at the bottom of
136the screen. Within the dashboard, drag each sheet from the left side bar
137onto the dashboard canvas.
138
139#+caption: Dashboard Editing
140[[https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard_edit.png]]
141
142** Formatting
143You can explore a ton of different formatting options throughout the
144worksheets and dashboard. Specifically for maps, you can alter the map
145layers, background, and visible features through the =Map= menu in the
146top file menu of the editing screen.
147
148In the finished dashboard below, I opted for a dark mode with a map that
149showed county lines and city names.
150
151There's a ton of other options available to be used in a dashboard like
152this, but this project shows a quick preview of what you can do in
153Tableau Public.
154
155#+caption: Tableau Dashboard
156[[https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png]]
content/blog/2022-06-16-terminal-lifestyle.md → blog/terminal-lifestyle/index.org renamed +84 −88
@@ -1,25 +1,23 @@
1+++ 1#+title: A Terminal Lifestyle
2date = 2022-06-16 2#+date: 2022-06-16
3title = "A Terminal Lifestyle" 3#+description: Explaining how I live my digital life mostly in the console/terminal.
4description = "Explaining how I live my digital life mostly in the console/terminal." 4#+filetags: :linux:
5+++
6
7## Text-Based Simplicity
8 5
6* Text-Based Simplicity
9I've detailed my views on web-based minimalism and related topics in 7I've detailed my views on web-based minimalism and related topics in
10other posts throughout the years; e.g., JavaScript/CSS bloat slowing 8other posts throughout the years; e.g., JavaScript/CSS bloat slowing
11down websites that are essentially a text document. However, I have 9down websites that are essentially a text document. However, I have
12never really expanded beyond talking about the web and describing how I 10never really expanded beyond talking about the web and describing how I
13focus on minimizing distractions in other digital environments. 11focus on minimizing distractions in other digital environments.
14 12
15This post is going to set the baseline for how I *try* to live my 13This post is going to set the baseline for how I /try/ to live my
16digital life. It does not necessarily get into my physical life, which 14digital life. It does not necessarily get into my physical life, which
17is often harder to control and contain all the noise in our modern 15is often harder to control and contain all the noise in our modern
18world. 16world.
19 17
20While there are new things to do every day in our digital world, I find 18While there are new things to do every day in our digital world, I find
21that keeping a core set of values and interests can ground you and keep 19that keeping a core set of values and interests can ground you and keep
22you mindful of *why* you are participating in the digital world. For 20you mindful of /why/ you are participating in the digital world. For
23example, if - at your core - you have no interest in what strangers 21example, if - at your core - you have no interest in what strangers
24think about random topics, it would be unwise to start participating in 22think about random topics, it would be unwise to start participating in
25social media. However, I am someone who has been dragged in by effective 23social media. However, I am someone who has been dragged in by effective
@@ -30,12 +28,11 @@ I won't dive much further into explaining the philosophy of all this,
30but I will link a few helpful articles that may pique your interest if 28but I will link a few helpful articles that may pique your interest if
31you're in search of more meaningful experiences: 29you're in search of more meaningful experiences:
32 30
33- [Mindfulness](https://en.wikipedia.org/wiki/Mindfulness) 31- [[https://en.wikipedia.org/wiki/Mindfulness][Mindfulness]]
34- [Minimalism](https://en.wikipedia.org/wiki/Minimalism) 32- [[https://en.wikipedia.org/wiki/Minimalism][Minimalism]]
35- [Stoicism](https://en.wikipedia.org/wiki/Stoicism) 33- [[https://en.wikipedia.org/wiki/Stoicism][Stoicism]]
36
37## Living Life in the Terminal
38 34
35* Living Life in the Terminal
39My personal approach to reducing digital distractions and increasing my 36My personal approach to reducing digital distractions and increasing my
40focus on the task at hand is to use a terminal for as much as I possibly 37focus on the task at hand is to use a terminal for as much as I possibly
41can. 38can.
@@ -43,12 +40,12 @@ can.
43Most days, this means that I have a few tabs open constantly in my 40Most days, this means that I have a few tabs open constantly in my
44terminal: 41terminal:
45 42
461. A web browser 431. A web browser
472. A chat client 442. A chat client
483. An email client 453. An email client
494. An RSS feed reader 464. An RSS feed reader
505. A local shell for navigating my computer's files 475. A local shell for navigating my computer's files
516. A remote shell for managing servers and other machines 486. A remote shell for managing servers and other machines
52 49
53Beyond this, I rarely open other tabs or GUI applications, unless 50Beyond this, I rarely open other tabs or GUI applications, unless
54absolutely necessary. If you look, you may be surprised what can be 51absolutely necessary. If you look, you may be surprised what can be
@@ -57,56 +54,55 @@ accomplished in the terminal.
57For example, I have moved my music and entertainment downloads to the 54For example, I have moved my music and entertainment downloads to the
58terminal, along with my device VPN connections. I am exploring options 55terminal, along with my device VPN connections. I am exploring options
59for moving my RSS subscriptions to something like 56for moving my RSS subscriptions to something like
60[Newsboat](https://newsboat.org/), so that I can read my daily articles 57[[https://newsboat.org/][Newsboat]], so that I can read my daily
61without all the fuss. 58articles without all the fuss.
62 59
63Now that we have some examples out of the way, let's dive into the 60Now that we have some examples out of the way, let's dive into the
64specifics. 61specifics.
65 62
66### Browsing the Web 63** Browsing the Web
67 64I'm going to start off with a hard topic for those who prefer to live in
68I'm going to start off with a hard topic for those who prefer to live 65the terminal: web browsing. This task is made hard mostly by websites
69in the terminal: web browsing. This task is made hard mostly by websites
70and web apps that require JavaScript to run. The other difficult part is 66and web apps that require JavaScript to run. The other difficult part is
71that if you're using a text-based browser, that means images won't 67that if you're using a text-based browser, that means images won't load
72load (hopefully that's obvious). 68(hopefully that's obvious).
73 69
74I am using [Lynx](https://lynx.invisible-island.net), a text-based 70I am using [[https://lynx.invisible-island.net][Lynx]], a text-based
75browser that runs quickly and easily in the terminal. Lynx allows me to 71browser that runs quickly and easily in the terminal. Lynx allows me to
76browser most websites by simply typing `g` and then typing in 72browser most websites by simply typing =g= and then typing in the URL I
77the URL I want. 73want.
78 74
79![Lynx](https://img.cleberg.net/blog/20220616-terminal-lifestyle/lynx.png) 75#+caption: Lynx
76[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/lynx.png]]
80 77
81If you need a search engine while in Lynx, I recommend [DuckDuckGo 78If you need a search engine while in Lynx, I recommend
82(Lite)](https://lite.duckduckgo.com/lite/), which allows you to search 79[[https://lite.duckduckgo.com/lite/][DuckDuckGo (Lite)]], which allows
83the web using their text-only interface. 80you to search the web using their text-only interface.
84 81
85![DuckDuckGo 82#+caption: DuckDuckGo !Lite
86!Lite](https://img.cleberg.net/blog/20220616-terminal-lifestyle/ddg.png) 83[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/ddg.png]]
87 84
88Eventually, you will run into websites that don't work (or are just too 85Eventually, you will run into websites that don't work (or are just too
89ugly and messy) in a text-only mode, and you'll be forced to switch 86ugly and messy) in a text-only mode, and you'll be forced to switch over
90over to a GUI browser to look at that site. Personally, I don't mind 87to a GUI browser to look at that site. Personally, I don't mind this as
91this as it doesn't happen as often as I thought it would. 88it doesn't happen as often as I thought it would.
92 89
93The only time I need to do this is when I want to browse an 90The only time I need to do this is when I want to browse an
94image/video-focused webpage or if I need to log in to a site, and it 91image/video-focused webpage or if I need to log in to a site, and it
95doesn't support a text-only login page. For example, I am able to 92doesn't support a text-only login page. For example, I am able to easily
96easily log in to [Sourcehut](https://sr.ht) in lynx. 93log in to [[https://sr.ht][Sourcehut]] in lynx.
97
98### Chatting with Friends
99 94
95** Chatting with Friends
100After web browsing activities, my main form of terminal communication is 96After web browsing activities, my main form of terminal communication is
101Matrix. I use the [gomuks](https://docs.mau.fi/gomuks/) client 97Matrix. I use the [[https://docs.mau.fi/gomuks/][gomuks]] client
102currently. 98currently.
103 99
104This was incredibly easy to install on macOS (but I will need to see if 100This was incredibly easy to install on macOS (but I will need to see if
105it'll be just as easy on Linux when my new laptop arrives): 101it'll be just as easy on Linux when my new laptop arrives):
106 102
107```sh 103#+begin_src sh
108brew install gomuks 104brew install gomuks
109``` 105#+end_src
110 106
111Once you launch gomuks, it will sync and require your username and 107Once you launch gomuks, it will sync and require your username and
112password to login. After doing so, the only problem I ran into was 108password to login. After doing so, the only problem I ran into was
@@ -114,102 +110,102 @@ verifying my gomuks client so that I could participate in rooms with
114E2EE. 110E2EE.
115 111
116Finally, I was able to verify the session by opening the Element desktop 112Finally, I was able to verify the session by opening the Element desktop
117app (I assume you can do this in the browser and mobile app too, but 113app (I assume you can do this in the browser and mobile app too, but I'm
118I'm not sure) and manually verifying myself with this process: 114not sure) and manually verifying myself with this process:
119 115
1201. Open the Element desktop app 1161. Open the Element desktop app
1212. Open a room I was a member of 1172. Open a room I was a member of
1223. Open the `Room Info` pane 1183. Open the =Room Info= pane
1234. Open the `People` menu and search for myself 1194. Open the =People= menu and search for myself
1245. Click on my profile name 1205. Click on my profile name
1256. Click on the session link under the `Security` section 1216. Click on the session link under the =Security= section and follow the
126 and follow the prompts to manually verify the session 122 prompts to manually verify the session
127 123
128Overall, I like gomuks and am able to enjoy all the features I was using 124Overall, I like gomuks and am able to enjoy all the features I was using
129in Element. The only hiccup I have occurred is manually downloading 125in Element. The only hiccup I have occurred is manually downloading
130images to view them, which can be annoying. 126images to view them, which can be annoying.
131 127
132![gomuks](https://img.cleberg.net/blog/20220616-terminal-lifestyle/gomuks.png) 128#+caption: gomuks
133 129[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/gomuks.png]]
134### Email
135 130
131** Email
136Moving email to the terminal has been the hardest of the tasks for me. 132Moving email to the terminal has been the hardest of the tasks for me.
137Unlike web browsing, where I can simply decide to not look at a website 133Unlike web browsing, where I can simply decide to not look at a website
138that does not work in the terminal, I cannot simply ignore emails sent 134that does not work in the terminal, I cannot simply ignore emails sent
139to me. 135to me.
140 136
141Personally, I am experimenting with [neomutt](https://neomutt.org/) as a 137Personally, I am experimenting with [[https://neomutt.org/][neomutt]] as
142potential email client. 138a potential email client.
143 139
144However, this requires a **TON** of configuration and tweaking to get 140However, this requires a *TON** of configuration and tweaking to get
145right. Even when I was able to set up neomutt, configure my email 141right. Even when I was able to set up neomutt, configure my email
146account, and customize a few personal preferences, a lot of emails still 142account, and customize a few personal preferences, a lot of emails still
147do not display correctly (mostly due to HTML and images). 143do not display correctly (mostly due to HTML and images).
148 144
149I won't get into the details of configuring `neomutt`; I 145I won't get into the details of configuring =neomutt=; I mostly followed
150mostly followed this blog post: [Email in the Terminal: Configuring 146this blog post:
151Neomutt](https://gideonwolfe.com/posts/workflow/neomutt/intro/). 147[[https://gideonwolfe.com/posts/workflow/neomutt/intro/][Email in the
148Terminal: Configuring Neomutt]].
152 149
153Finally, I have yet to figure out how to connect my GPG keys to 150Finally, I have yet to figure out how to connect my GPG keys to
154`neomutt`, but that's a problem for another day. 151=neomutt=, but that's a problem for another day.
155
156### RSS Feed Reader
157 152
158I have just started using [Newsboat](https://newsboat.org/) to read 153** RSS Feed Reader
154I have just started using [[https://newsboat.org/][Newsboat]] to read
159articles in my terminal and have found quick success with it. 155articles in my terminal and have found quick success with it.
160 156
161I'll show you a quick screenshot first: 157I'll show you a quick screenshot first:
162 158
163![Newsboat](https://img.cleberg.net/blog/20220616-terminal-lifestyle/newsboat.png) 159#+caption: Newsboat
160[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/newsboat.png]]
164 161
165The configuration was super easy for this app; I simply installed the 162The configuration was super easy for this app; I simply installed the
166app, created a file for URLs, and imported my OPML subscriptions that I 163app, created a file for URLs, and imported my OPML subscriptions that I
167had exported out of my old feed reader: 164had exported out of my old feed reader:
168 165
169```sh 166#+begin_src sh
170brew install newsboat 167brew install newsboat
171``` 168#+end_src
172 169
173```sh 170#+begin_src sh
174touch ~/.newsboat/urls 171touch ~/.newsboat/urls
175``` 172#+end_src
176 173
177```sh 174#+begin_src sh
178newsboat -i=my_subscriptions.opml 175newsboat -i=my_subscriptions.opml
179``` 176#+end_src
180
181### Writing & Programming
182 177
178** Writing & Programming
183Unfortunately, the weak link in my terminal-based environment right now 179Unfortunately, the weak link in my terminal-based environment right now
184is my grasp of the possibilities of editing files within a shell. 180is my grasp of the possibilities of editing files within a shell.
185 181
186I am used to the easy extensions found in VSCodium and Kate, so I am 182I am used to the easy extensions found in VSCodium and Kate, so I am
187slowly learning how to mold the default editing tools to my needs. 183slowly learning how to mold the default editing tools to my needs.
188Currently, this means I am using `nano` with the following 184Currently, this means I am using =nano= with the following
189configuration: 185configuration:
190 186
191```config 187#+begin_src config
192set breaklonglines 188set breaklonglines
193set autoindent 189set autoindent
194set linenumbers 190set linenumbers
195set tabstospaces 191set tabstospaces
196set tabsize 2 192set tabsize 2
197set fill 80 193set fill 80
198``` 194#+end_src
199 195
200This configuration allows nano to automatically hard-wrap lines at 80 196This configuration allows nano to automatically hard-wrap lines at 80
201characters, autoindent the wrapped lines (if the previous line was 197characters, autoindent the wrapped lines (if the previous line was
202indented), use 2 spaces per tab, and display line numbers within each 198indented), use 2 spaces per tab, and display line numbers within each
203file I open. 199file I open.
204 200
205I am currently looking to see if `vim` or `emacs` 201I am currently looking to see if =vim= or =emacs= would be more useful
206would be more useful for my current needs, but I'm not in any rush, so 202for my current needs, but I'm not in any rush, so I don't expect to find
207I don't expect to find an answer anytime soon. 203an answer anytime soon.
208 204
209With my current life demands, I am not programming at the moment and 205With my current life demands, I am not programming at the moment and
210have not explored the best terminal set-up for programming. However, I 206have not explored the best terminal set-up for programming. However, I
211have seen many peers find success configuring `vim` and 207have seen many peers find success configuring =vim= and =emacs=, so
212`emacs`, so that's where I will start when I pick my 208that's where I will start when I pick my projects back up.
213projects back up.
214 209
215![nano](https://img.cleberg.net/blog/20220616-terminal-lifestyle/nano.png) 210#+caption: nano
211[[https://img.cleberg.net/blog/20220616-terminal-lifestyle/nano.png]]
blog/the-ansoff-matrix/index.org added +140
@@ -0,0 +1,140 @@
1#+title: The Ansoff Matrix
2#+date: 2019-12-03
3#+description: Learn about the Ansoff Matrix, a strategic management tool.
4#+filetags: :business:
5
6* Overview
7As the world of business evolves, managers must approach business
8planning and strategy with a contemporary mindset. According to Dess,
9McNamara, Eisner, and Lee, managers must be willing to adapt to the
10modern business environment by going beyond "'incremental management',
11whereby they view their job as making a series of small, minor changes
12to improve the efficiency of the firm's operations"[fn:1].
13
14One reason that strategic management is crucial is because most
15businesses that fail in the United States each year fail due to a lack
16of strategic focus or direction[fn:2]. The rate of failure for
17businesses with poor strategies shows that strategic planning and
18management are crucial to a business's strength and longevity, injecting
19the critical factors of growth and direction into a company's business
20plan.
21
22One of the most significant strategic planning and management frameworks
23that companies can use is the
24[[https://en.wikipedia.org/wiki/Ansoff_matrix][Ansoff Matrix]]. While
25this framework has unique purposes and use-cases, it can effectively
26help an organization grow and compete. Specifically, the Ansoff matrix
27is one of the most effective frameworks for companies who want to focus
28on increasing sales revenue or profitability[fn:3].
29
30This framework uses a two-by-two figure to show the four strategic
31options for companies to use in this framework: market penetration,
32market development, product development, and diversification (see
33*Figure 1*). The x-axis of the matrix focuses on the firm's markets and
34also determines if the firm is looking to enter new markets or innovate
35in its current markets. The y-axis of the matrix focuses on the firm's
36products and determines if the firm wants to pursue strategies around
37their existing products or explore new products.
38
39#+caption: The Ansoff Matrix by JaisonAbeySabu, Own work, CC BY-SA 3.0
40[[https://img.cleberg.net/blog/20191203-the-ansoff-matrix/ansoff_matrix-min.png]]
41
42* Strategic Options
43** Market Penetration
44The most straightforward strategy in the Ansoff matrix is to focus on
45existing products in existing markets, also known as market
46penetration[fn:4]. Companies such as Coca-Cola have used market
47penetration successfully by investing a lot of money to get further
48value out of their current markets. Coca-Cola does this by introducing
49new features such as Christmas-themed bottles, personal names on the
50bottles, and other marketing schemes.
51
52** Market Development
53Market development extends existing products into new markets in an
54attempt to increase the number of buyers. One interesting way that
55Coca-Cola used this strategy comes from the stigma that Diet Coke is a
56woman's drink[fn:5]. Coca-Cola introduced Coca-Cola Zero, which
57contained the same nutritional content as Diet Coke, but was packaged in
58a dark black can to appear more "manly"[fn:6].
59
60** Product Development
61Product development uses existing markets to introduce new products so
62that the firm can better meet customer needs[fn:7]. The extreme end of
63diversification is home to companies such as Johnson & Johnson, a
64healthcare company that has developed a business portfolio of more than
6560,000 different products[fn:8]. Johnson & Johnson's dedication to
66continuous diversification has led them to a balance sheet rating of
67"AAA", industry recognition for diversification, and increases in their
68investor dividends for 57 consecutive years[fn:9].
69
70** Related Diversification
71Diversification, the final strategy of the Ansoff Matrix, is more
72difficult than the others since it involves exploring both new markets
73and new products. Related diversification is a diversification strategy
74that closely relates to the firm's core business. Coca-Cola's best
75example of related diversification is its acquisition of Glaceau and
76Vitamin Water, which expanded their drinking lines of business[fn:10].
77
78** Unrelated Diversification
79Unrelated diversification is a diversification strategy that does not
80really relate to the firm's core business but still diversifies their
81business portfolio. A good example of this would be a coffee company who
82has decided to enter the market for bicycle sales. The main purpose of
83this strategy is to an extremely diverse company that will not go
84bankrupt if one market goes through difficult times. However, this
85requires a lot of independent skills and heavy investments since the
86company most likely cannot easily transfer knowledge between the markets
87they compete in.
88
89* Requirements for Success
90To use the Ansoff Matrix framework, managers need to formulate corporate
91goals and objectives. Without goals and direction, management frameworks
92do not present much practical utility. Further, the Ansoff Matrix
93requires the managers involved to make tactical decisions and create a
94path for the company to take toward their goals. Lastly, both the Ansoff
95Matrix needs to consider both internal and external perspectives
96throughout the strategy formulation process.
97
98One interesting probability is that companies will be using multiple
99strategic planning and management frameworks at the same time. While
100this may sound like it could crowd the management process, there are
101numerous reasons to do so. For example, the Ansoff Matrix and the
102Balanced Scorecard are relatively popular, and they cover entirely
103different parts of a company's strategy. Using the results from the
104Balanced Scorecard could inform a company of the potential product and
105market demands, such as from customer or supplier survey results, to
106help the company determine which Ansoff Matrix strategy to pursue.
107However, a combined approach at this level would require mature
108frameworks and focused managers who are able to strategize at a high
109level.
110
111Lastly, it should be noted that the author of the Ansoff matrix, Igor
112Ansoff, often used the term
113[[https://en.wikipedia.org/wiki/Analysis_paralysis][paralysis by
114analysis]] to explain the mistake of companies who overuse analysis and
115spend too much time planning. Companies need to understand the utility
116of a strategic management framework while ensuring that the company is
117poised to execute as efficiently as they have planned.
118
119* Footnotes
120
121[fn:1] Dess, G. G., McNamara, G., Eisner, A. B., Lee, S. H. (2019). Strategic management: Text & cases, ninth edition. New York, NY: McGraw-Hill Education.
122
123[fn:2] Juneja, P. (n.d.). Benefits of strategic management. Management Study Guide. Retrieved from [[https://www.managementstudyguide.com/strategic-management-benefits.htm]].
124
125[fn:3] Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing Concepts. London: Palgrave.
126
127[fn:4] Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing Concepts. London: Palgrave.
128
129[fn:5] Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda. Retrieved from [[https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/]].
130
131[fn:6] Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda. Retrieved from [[https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/]].
132
133[fn:7] Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda. Retrieved from [[https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/]].
134
135[fn:8] Lemke, T. (2019). The most diversified companies in the stock market. The balance. Retrieved from [[https://www.thebalance.com/the-most-diversified-companies-in-the-stock-market-4169730]].
136
137[fn:9] Johnson & Johnson. (2018). 2018 Investor Fact Sheet. [PDF file]. Retrieved from [[http://www.investor.jnj.com/_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000][http://www.investor.jnj.com/_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000]].
138
139[fn:10] Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda. Retrieved from [[https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/]].
140
blog/tuesday/index.org added +37
@@ -0,0 +1,37 @@
1#+title: Tuesday
2#+date: 2022-02-22
3#+description: 2-22-22 || 22-2-22
4#+filetags: :personal:
5
6* Tuesday, Twosday
7I'm taking a break from my usual technology-related posts and writing
8about something a little more enjoyable today.
9
10Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is
11Twosday.
12
13Let's look at everything that fell in place today:
14
151. Written in the =m-dd-yy= or =dd-m-yy= formats, today is 2-22-22 or
16 22-2-22, which is a neat little palindrome in either format. (The
17 last ubiquitous six-digit palindrome was 1-11-11.)
182. Today is Tuesday, which is why everyone is using the nickname Twosday
19 to call out these similarities.
203. Falling on Tuesday means today is the 2nd day of the week (for most
21 cultures. For the US, it's the 3rd day of the week since we start on
22 Sunday).
234. The only culture I could find with a connection to a =2= is that some
24 Slavic languages derived their version of Tuesday from the Old Church
25 Slavonic word =въторъ=, meaning "the second."
265. Written in the classic monospaced, digital font (think of digital
27 clocks from the 80s/90s), there is nice symmetry to the numbers
28 ([[https://img.cleberg.net/blog/20220222-tuesday/digital_font.png][view
29 the image here]]!).
306. This one isn't naturally-occurring, but it seems people around the
31 world are celebrating the day. For example, a group is putting
32 together
33 [[[[https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597]]][a
34 wedding of 222 couples at the California State Capitol in
35 Sacramento]], concluding at exactly 2:22 PM. These couples will
36 record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale
37 for $222.22.
blog/ubuntu-emergency-mode/index.org added +69
@@ -0,0 +1,69 @@
1#+title: Stuck in Ubuntu's Emergency Mode? Try Fixing the Fstab File
2#+date: 2024-02-13
3#+description: Learn how to fix a missing disk issue that can lock you in Ubuntu's emergency mode.
4#+filetags: :linux:
5
6* The Problem
7I recently [[../zfs/][migrated my hard drives to a ZFS pool]] and
8found myself stuck in Ubuntu's emergency mode after the first reboot I
9performed after creating the ZFS pool.
10
11My server was stuck in the boot process and showed the following error
12on the screen:
13
14#+begin_src txt
15You are in emergency mode.
16After logging in, type "journalctl -xb" to view system logs,
17"systemctl reboot" to reboot, "systemctl default"
18or ^D to try again to boot into default mode".
19#+end_src
20
21After rebooting the server and watching the logs scroll on a monitor, I
22noticed the root cause was related to a very long search for certain
23drives. I kept seeing errors like this:
24
25#+begin_src txt
26[ TIME ] Timed out waiting of device dev-disk-by/[disk-uuid]
27#+end_src
28
29I realized that I had not removed the =/etc/fstab= references that asked
30Ubuntu to mount two disks on boot, but I had recently changed those
31disks to be part of my ZFS pool instead. Therefore, Ubuntu was trying to
32identify and mount a disk that was not available.
33
34Now that we have an idea of the issue, let's move to solution.
35
36* The Solution
37In order to fix the issue, I waited until I was allowed to type the root
38user's password, and then I executed the following command:
39
40#+begin_src sh
41nano /etc/fstab
42#+end_src
43
44Within the =fstab= file, I needed to comment/remove the following lines
45at the bottom of the file. You can comment-out a line by prepending a
46=#= symbol at the beginning of the line. You can also delete the line
47entirely.
48
49#+begin_src conf
50# What it looked like when running into the issue:
51UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
52UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
53
54# What I changed it to, in order to fix the issue:
55# UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
56# UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
57#+end_src
58
59Once removing the lines above from the =/etc/fstab= file, save and exit
60the file by hitting the =Ctrl= + =x= key combo.
61
62You can now hit =Ctrl= + =D= to continue, or reboot:
63
64#+begin_src sh
65systemctl reboot
66#+end_src
67
68Once rebooted, I was able to watch the machine boot properly and launch
69to the TTY login screen without errors!
content/blog/2021-01-07-ufw.md → blog/ufw/index.org renamed +76 −86
@@ -1,11 +1,9 @@
1+++ 1#+title: Secure Your Network with the Uncomplicated Firewall (ufw)
2date = 2021-01-07 2#+date: 2021-01-07
3title = "Secure Your Network with the Uncomplicated Firewall" 3#+description: A simple guide to the UFW.
4description = "A simple guide to the UFW." 4#+filetags: :sysadmin:
5+++
6
7## Uncomplicated Firewall
8 5
6* Uncomplicated Firewall
9Uncomplicated Firewall, also known as ufw, is a convenient and 7Uncomplicated Firewall, also known as ufw, is a convenient and
10beginner-friendly way to enforce OS-level firewall rules. For those who 8beginner-friendly way to enforce OS-level firewall rules. For those who
11are hosting servers or any device that is accessible to the world (i.e., 9are hosting servers or any device that is accessible to the world (i.e.,
@@ -15,147 +13,140 @@ implemented and active.
15Ufw is available by default in all Ubuntu installations after 8.04 LTS. 13Ufw is available by default in all Ubuntu installations after 8.04 LTS.
16For other distributions, you can look to install ufw or check if there 14For other distributions, you can look to install ufw or check if there
17are alternative firewalls installed already. There are usually 15are alternative firewalls installed already. There are usually
18alternatives available, such as Fedora's `firewall` and the 16alternatives available, such as Fedora's =firewall= and the package
19package available on most distributions: `iptables`. Ufw is 17available on most distributions: =iptables=. Ufw is considered a
20considered a beginner-friendly front-end to iptables. 18beginner-friendly front-end to iptables.
21 19
22[Gufw](https://gufw.org) is available as a graphical user interface 20[[https://gufw.org][Gufw]] is available as a graphical user interface
23(GUI) application for users who are uncomfortable setting up a firewall 21(GUI) application for users who are uncomfortable setting up a firewall
24through a terminal. 22through a terminal.
25 23
26![Gufw Screenshot](https://img.cleberg.net/blog/20210107-secure-your-network-with-the-uncomplicated-firewall/gufw.png) 24#+caption: Gufw Screenshot
27 25[[https://img.cleberg.net/blog/20210107-secure-your-network-with-the-uncomplicated-firewall/gufw.png]]
28## Getting Help
29 26
27* Getting Help
30If you need help figuring out commands, remember that you can run the 28If you need help figuring out commands, remember that you can run the
31`--help` flag to get a list of options. 29=--help= flag to get a list of options.
32 30
33```sh 31#+begin_src sh
34sudo ufw --help 32sudo ufw --help
35``` 33#+end_src
36
37## Set Default State
38 34
35* Set Default State
39The proper way to run a firewall is to set a strict default state and 36The proper way to run a firewall is to set a strict default state and
40slowly open up ports that you want to allow. This helps prevent anything 37slowly open up ports that you want to allow. This helps prevent anything
41malicious from slipping through the cracks. The following command 38malicious from slipping through the cracks. The following command
42prevents all incoming traffic (other than the rules we specify later), 39prevents all incoming traffic (other than the rules we specify later),
43but you can also set this for outgoing connections, if necessary. 40but you can also set this for outgoing connections, if necessary.
44 41
45```sh 42#+begin_src sh
46sudo ufw default deny incoming 43sudo ufw default deny incoming
47``` 44#+end_src
48 45
49You should also allow outgoing traffic if you want to allow the device 46You should also allow outgoing traffic if you want to allow the device
50to communicate back to you or other parties. For example, media servers 47to communicate back to you or other parties. For example, media servers
51like Plex need to be able to send out data related to streaming the 48like Plex need to be able to send out data related to streaming the
52media. 49media.
53 50
54```sh 51#+begin_src sh
55sudo ufw default allow outgoing 52sudo ufw default allow outgoing
56``` 53#+end_src
57 54
58## Adding Port Rules 55* Adding Port Rules
56Now that we've disabled all incoming traffic by default, we need to open
57up some ports (or else no traffic would be able to come in). If you need
58to be able to =ssh= into the machine, you'll need to open up port 22.
59 59
60Now that we've disabled all incoming traffic by default, we need to 60#+begin_src sh
61open up some ports (or else no traffic would be able to come in). If you
62need to be able to `ssh` into the machine, you'll need to
63open up port 22.
64
65```sh
66sudo ufw allow 22 61sudo ufw allow 22
67``` 62#+end_src
68 63
69You can also issue more restrictive rules. The following rule will allow 64You can also issue more restrictive rules. The following rule will allow
70`ssh` connections only from machines on the local subnet. 65=ssh= connections only from machines on the local subnet.
71 66
72```sh 67#+begin_src sh
73sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22 68sudo ufw allow proto tcp from 192.168.0.0/24 to any port 22
74``` 69#+end_src
75 70
76If you need to set a rule that isn't tcp, just append your connection 71If you need to set a rule that isn't tcp, just append your connection
77type to the end of the rule. 72type to the end of the rule.
78 73
79```sh 74#+begin_src sh
80sudo ufw allow 1900/udp 75sudo ufw allow 1900/udp
81``` 76#+end_src
82
83## Enable ufw
84 77
78* Enable ufw
85Now that the firewall is configured and ready to go, you can enable the 79Now that the firewall is configured and ready to go, you can enable the
86firewall. 80firewall.
87 81
88```sh 82#+begin_src sh
89sudo ufw enable 83sudo ufw enable
90``` 84#+end_src
91 85
92A restart may be required for the firewall to begin operating. 86A restart may be required for the firewall to begin operating.
93 87
94```sh 88#+begin_src sh
95sudo reboot now 89sudo reboot now
96``` 90#+end_src
97
98## Checking Status
99 91
92* Checking Status
100Now that the firewall is enabled, let's check and see what the rules 93Now that the firewall is enabled, let's check and see what the rules
101look like. 94look like.
102 95
103```sh 96#+begin_src sh
104sudo ufw status numbered 97sudo ufw status numbered
105``` 98#+end_src
106 99
107```txt 100#+begin_src txt
108Status: active 101Status: active
109 102
110 To Action From 103 To Action From
111 -- ------ ---- 104 -- ------ ----
112[ 1] 22 ALLOW IN Anywhere 105[ 1] 22 ALLOW IN Anywhere
113[ 2] 22 (v6) ALLOW IN Anywhere (v6) 106[ 2] 22 (v6) ALLOW IN Anywhere (v6)
114``` 107#+end_src
115
116## Deleting Rules
117 108
109* Deleting Rules
118If you need to delete a rule, you need to know the number associated 110If you need to delete a rule, you need to know the number associated
119with that rule. Let's delete the first rule in the table above. You'll 111with that rule. Let's delete the first rule in the table above. You'll
120be asked to confirm the deletion as part of this process. 112be asked to confirm the deletion as part of this process.
121 113
122```sh 114#+begin_src sh
123sudo ufw delete 1 115sudo ufw delete 1
124``` 116#+end_src
125
126## Managing App Rules
127 117
118* Managing App Rules
128Luckily, there's a convenient way for installed applications to create 119Luckily, there's a convenient way for installed applications to create
129files that ufw can easily implement so that you don't have to search 120files that ufw can easily implement so that you don't have to search and
130and find which ports your application requires. To see if your device 121find which ports your application requires. To see if your device has
131has any applications with pre-installed ufw rules, execute the following 122any applications with pre-installed ufw rules, execute the following
132command: 123command:
133 124
134```sh 125#+begin_src sh
135sudo ufw app list 126sudo ufw app list
136``` 127#+end_src
137 128
138The results should look something like this: 129The results should look something like this:
139 130
140```txt 131#+begin_src txt
141Available applications: 132Available applications:
142 OpenSSH 133 OpenSSH
143 Samba 134 Samba
144 plexmediaserver 135 plexmediaserver
145 plexmediaserver-all 136 plexmediaserver-all
146 plexmediaserver-dlna 137 plexmediaserver-dlna
147``` 138#+end_src
148 139
149If you want to get more information on a specific app rule, use the 140If you want to get more information on a specific app rule, use the
150`info` command. 141=info= command.
151 142
152```sh 143#+begin_src sh
153sudo ufw app info plexmediaserver-dlna 144sudo ufw app info plexmediaserver-dlna
154``` 145#+end_src
155 146
156You'll get a blurb of info back like this: 147You'll get a blurb of info back like this:
157 148
158```txt 149#+begin_src txt
159Profile: plexmediaserver-dlna 150Profile: plexmediaserver-dlna
160Title: Plex Media Server (DLNA) 151Title: Plex Media Server (DLNA)
161Description: The Plex Media Server (additional DLNA capability only) 152Description: The Plex Media Server (additional DLNA capability only)
@@ -163,29 +154,28 @@ Description: The Plex Media Server (additional DLNA capability only)
163Ports: 154Ports:
164 1900/udp 155 1900/udp
165 32469/tcp 156 32469/tcp
166``` 157#+end_src
167 158
168You can add or delete app rules the same way that you'd add or delete 159You can add or delete app rules the same way that you'd add or delete
169specific port rules. 160specific port rules.
170 161
171```sh 162#+begin_src sh
172sudo ufw allow plexmediaserver-dlna 163sudo ufw allow plexmediaserver-dlna
173``` 164#+end_src
174 165
175```sh 166#+begin_src sh
176sudo ufw delete RULE|NUM 167sudo ufw delete RULE|NUM
177``` 168#+end_src
178
179## Creating App Rules
180 169
170* Creating App Rules
181If you'd like to create you own app rule, you'll need to create a file 171If you'd like to create you own app rule, you'll need to create a file
182in the `/etc/ufw/applications.d` directory. Within the file 172in the =/etc/ufw/applications.d= directory. Within the file you create,
183you create, you need to make sure the content is properly formatted. 173you need to make sure the content is properly formatted.
184 174
185For example, here are the contents my `plexmediaserver` file, 175For example, here are the contents my =plexmediaserver= file, which
186which creates three distinct app rules for ufw: 176creates three distinct app rules for ufw:
187 177
188```config 178#+begin_src config
189[plexmediaserver] 179[plexmediaserver]
190title=Plex Media Server (Standard) 180title=Plex Media Server (Standard)
191description=The Plex Media Server 181description=The Plex Media Server
@@ -200,24 +190,24 @@ ports=1900/udp|32469/tcp
200title=Plex Media Server (Standard + DLNA) 190title=Plex Media Server (Standard + DLNA)
201description=The Plex Media Server (with additional DLNA capability) 191description=The Plex Media Server (with additional DLNA capability)
202ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp 192ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp
203``` 193#+end_src
204 194
205So, if I wanted to create a custom app rule called "mycustomrule," 195So, if I wanted to create a custom app rule called "mycustomrule," I'd
206I'd create a file and add my content like this: 196create a file and add my content like this:
207 197
208```sh 198#+begin_src sh
209sudo nano /etc/ufw/applications.d/mycustomrule 199sudo nano /etc/ufw/applications.d/mycustomrule
210``` 200#+end_src
211 201
212```config 202#+begin_src config
213[mycustomrule] 203[mycustomrule]
214title=My Custom Rule 204title=My Custom Rule
215description=This is a temporary ufw app rule. 205description=This is a temporary ufw app rule.
216ports=88/tcp|9100/udp 206ports=88/tcp|9100/udp
217``` 207#+end_src
218 208
219Then, I would just enable this rule in ufw. 209Then, I would just enable this rule in ufw.
220 210
221```sh 211#+begin_src sh
222sudo ufw allow mycustomrule 212sudo ufw allow mycustomrule
223``` 213#+end_src
blog/unifi-ip-blocklist/index.org added +82
@@ -0,0 +1,82 @@
1#+title: Block IP Addresses and Subnets with Unifi Network Firewall
2#+date: 2023-06-18
3#+description: Learn how to use the Unifi Network Firewall to block IP addresses and subnets.
4#+filetags: :network:
5
6* Identifying Abusive IPs
7If you're like me and use Unifi network equipment at the edge of the
8network you manage, you may know that Unifi is only somewhat decent at
9identifying and blocking IPs that represent abusive or threat actors.
10
11While Unifi has a
12[[https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management][threat
13management]] tool inside their Network application, it can be lacking in
14functionality and identification. For example, I have my UDM Pro set to
15identify and block almost all categories of threats available within the
16Unifi settings. However, I regularly identify abusive actors on my web
17server via the server logs.
18
19In addition, I have identified IP addresses and subnets directly within
20Unifi's logs that the UDM did not block for whatever reason.
21
22This guide is meant to be another step in the process to manually block
23abusive IP addresses or subnets that you have identified but are not
24being automatically blocked yet.
25
26* Create an IP Group Profile
27To start, login to the Unifi machine's web GUI and navigate to the
28Network app > Settings > Profiles.
29
30Within this page, choose the =IP Groups= tab and click =Create New=.
31
32#+caption: Network Profiles
33[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/unifi_profiles.png]]
34
35Each IP Group profile can be used as one of three options:
36
371. Port Group
382. IPv4 Address/Subnet
393. IPv6 Address/Subnet
40
41In this example, I'm creating an IPv4 Address/Subnet group and adding a
42few different IP addresses and a subnet. Once you've added all IP
43addresses and subnets, click the =Apply= button that should appear at
44the bottom.
45
46#+caption: Network Profile IPs
47[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/abusive_ips.png]]
48
49At this point, the IPv4 Address/Subnet has been created but not yet
50used.
51
52* Drop IP Group Profile via the Unifi Firewall
53To instruct the Unifi machine to block the profile we just created, we
54need to navigate to the Network app > Settings > Firewall & Security.
55
56Within this screen, find the Firewall Rules table and click
57=Create Entry=. This entry should contain the following settings:
58
59- Type: =Internet In=
60- Description: =<Your Custom Rule>=
61- Rule Applied: =Before Predefined Rules=
62- Action: =Drop=
63- Source Type: =Port/IP Group=
64- IPv4 Address Group: =<Name of the Group Profile You Created Above>=
65
66Customize the remaining configurations to your liking, and then save and
67enable the firewall rule.
68
69#+caption: Firewall Rule
70[[https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/firewall_drop_rule.png]]
71
72Once enabled, the Unifi machine will be able to drop all incoming
73connections from the defined IP addresses and subnets within the created
74profile.
75
76#+begin_quote
77As a personal aside to this topic, I'm looking for a convenient way to
78update the firewall rules or profiles remotely (within the LAN) from the
79web server to accelerate this process. If you have an idea on how to
80automatically update Unifi IP groups or firewall rules, let me know!
81
82#+end_quote
content/blog/2023-12-03-unifi-nextdns.md → blog/unifi-nextdns/index.org renamed +58 −68
@@ -1,72 +1,64 @@
1+++ 1#+title: How to Install NextDNS on the Unifi Dream Machine
2date = 2023-12-03 2#+date: 2023-12-03
3title = "How to Install NextDNS on the Unifi Dream Machine" 3#+description: A guide to properly install the NextDNS client on the UDM Pro.
4description = "A guide to properly install the NextDNS client on the UDM Pro." 4#+filetags: :network:
5+++
6
7# Overview
8 5
6* Overview
9I recently installed NextDNS on my Unifi Dream Machine router using the 7I recently installed NextDNS on my Unifi Dream Machine router using the
10[UnifiOS](https://github.com/nextdns/nextdns/wiki/UnifiOS) wiki page on 8[[https://github.com/nextdns/nextdns/wiki/UnifiOS][UnifiOS]] wiki page
11NextDNS's GitHub repository. 9on NextDNS's GitHub repository.
12 10
13As a result of this, I wanted to write down the process in case the wiki 11As a result of this, I wanted to write down the process in case the wiki
14or installer ever gets lost. 12or installer ever gets lost.
15 13
16# Wiki 14* Wiki
17
18The following is copied from the wiki page linked above, with one 15The following is copied from the wiki page linked above, with one
19difference in the `ssh` command. 16difference in the =ssh= command.
20 17
21Install instructions for Unifi Dream Machine (UDM) standard and pro 18Install instructions for Unifi Dream Machine (UDM) standard and pro
22routers. 19routers.
23 20
24## Install 21** Install
25
26Enable SSH: 22Enable SSH:
27 23
28- Go to your unifi admin interface and select your device (not the 24- Go to your unifi admin interface and select your device (not the
29 controller settings, but the Dream Machine settings) 25 controller settings, but the Dream Machine settings)
30- Click on "Settings" at the bottom of the page 26- Click on "Settings" at the bottom of the page
31- Go to the "Advanced" section on the left pan 27- Go to the "Advanced" section on the left pan
32- Enable SSH 28- Enable SSH
33- Set a SSH password 29- Set a SSH password
34 30
35Connect to your router using `ssh root@xxx.xxx.xxx.xxx` with 31Connect to your router using =ssh root@xxx.xxx.xxx.xxx= with the
36the password you configured. 32password you configured.
37 33
38Run the following command and follow the instructions: 34Run the following command and follow the instructions:
39 35
40```sh 36#+begin_src sh
41sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"' 37sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"'
42``` 38#+end_src
43 39
44Note: Queries from the UDM itself won't be routed to NextDNS nor 40Note: Queries from the UDM itself won't be routed to NextDNS nor
45encrypted due to current system limitation. All traffic from other 41encrypted due to current system limitation. All traffic from other
46devices on then network will. 42devices on then network will.
47 43
48## Upgrade 44** Upgrade
49
50To upgrade to the last version, simply re-run the installer above. If a 45To upgrade to the last version, simply re-run the installer above. If a
51new version is available, the upgrade action will added to the list of 46new version is available, the upgrade action will added to the list of
52possible actions. 47possible actions.
53 48
54## Uninstall 49** Uninstall
55
56To uninstall, re-run the installer above and select "Remove" in the 50To uninstall, re-run the installer above and select "Remove" in the
57menu. 51menu.
58 52
59## Troubleshooting 53** Troubleshooting
60
61If the installation fail, please the installer in debug mode and contact 54If the installation fail, please the installer in debug mode and contact
62us at team@nextdns.io with the transcript of the installation: 55us at team@nextdns.io with the transcript of the installation:
63 56
64```sh 57#+begin_src sh
65sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"' 58sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"'
66``` 59#+end_src
67
68### Content Filtering Conflict
69 60
61*** Content Filtering Conflict
70NextDNS CLI and the UDM Content Filtering or the Ad Blocking features 62NextDNS CLI and the UDM Content Filtering or the Ad Blocking features
71are incompatible. If you want to use NextDNS CLI, please make sure they 63are incompatible. If you want to use NextDNS CLI, please make sure they
72are disabled. 64are disabled.
@@ -77,28 +69,26 @@ network, set the Content Filtering feature to None
77To disable Ad Blocking, go to Settings > Application Firewall. In the 69To disable Ad Blocking, go to Settings > Application Firewall. In the
78General tab, uncheck the Ad Blocking checkbox. 70General tab, uncheck the Ad Blocking checkbox.
79 71
80### APT Error 72*** APT Error
81
82If you get an apt error as follow: 73If you get an apt error as follow:
83 74
84```sh 75#+begin_src sh
85E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80] 76E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80]
86``` 77#+end_src
87 78
88You may try to following: 79You may try to following:
89 80
90```sh 81#+begin_src sh
91sed -i -e 's/deb.debian.org/archive.debian.org/g' \ 82sed -i -e 's/deb.debian.org/archive.debian.org/g' \
92 -e 's|security.debian.org|archive.debian.org/|g' \ 83 -e 's|security.debian.org|archive.debian.org/|g' \
93 -e '/stretch-updates/d' /etc/apt/sources.list 84 -e '/stretch-updates/d' /etc/apt/sources.list
94``` 85#+end_src
95
96# install.sh
97 86
98Here are the contents of the `install.sh` file used above, as 87** install.sh
99of 2023-12-03: 88Here are the contents of the =install.sh= file used above, as of
892023-12-03:
100 90
101```sh 91#+begin_src sh
102#!/bin/sh 92#!/bin/sh
103 93
104main() { 94main() {
@@ -323,13 +313,13 @@ install_bin() {
323 fi 313 fi
324 log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path" 314 log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path"
325 case "$INSTALL_RELEASE" in 315 case "$INSTALL_RELEASE" in
326 */*) 316 ,*/*)
327 # Snapshot 317 # Snapshot
328 branch=${INSTALL_RELEASE%/*} 318 branch=${INSTALL_RELEASE%/*}
329 hash=${INSTALL_RELEASE#*/} 319 hash=${INSTALL_RELEASE#*/}
330 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz" 320 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz"
331 ;; 321 ;;
332 *) 322 ,*)
333 url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz" 323 url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz"
334 ;; 324 ;;
335 esac 325 esac
@@ -631,12 +621,12 @@ install_type() {
631 echo "$FORCE_INSTALL_TYPE"; return 0 621 echo "$FORCE_INSTALL_TYPE"; return 0
632 fi 622 fi
633 case "$INSTALL_RELEASE" in 623 case "$INSTALL_RELEASE" in
634 */*) 624 ,*/*)
635 case $OS in 625 case $OS in
636 ubios) 626 ubios)
637 echo "ubios_snapshot"; return 0 627 echo "ubios_snapshot"; return 0
638 ;; 628 ;;
639 *) 629 ,*)
640 # Snapshot mode always use binary install 630 # Snapshot mode always use binary install
641 echo "bin"; return 0 631 echo "bin"; return 0
642 ;; 632 ;;
@@ -664,7 +654,7 @@ install_type() {
664 . /etc/os-release 654 . /etc/os-release
665 major=$(echo "$VERSION_ID" | cut -d. -f1) 655 major=$(echo "$VERSION_ID" | cut -d. -f1)
666 case $major in 656 case $major in
667 *[!0-9]*) 657 ,*[!0-9]*)
668 if [ "$VERSION_ID" = "19.07.0-rc1" ]; then 658 if [ "$VERSION_ID" = "19.07.0-rc1" ]; then
669 # No opkg support before 19.07.0-rc2 659 # No opkg support before 19.07.0-rc2
670 echo "bin" 660 echo "bin"
@@ -673,7 +663,7 @@ install_type() {
673 echo "openwrt" 663 echo "openwrt"
674 fi 664 fi
675 ;; 665 ;;
676 *) 666 ,*)
677 if [ "$major" -lt 19 ]; then 667 if [ "$major" -lt 19 ]; then
678 # No opkg support before 19.07.0-rc2 668 # No opkg support before 19.07.0-rc2
679 echo "bin" 669 echo "bin"
@@ -719,7 +709,7 @@ install_type() {
719 # TODO: pkg for xbps 709 # TODO: pkg for xbps
720 echo "bin" 710 echo "bin"
721 ;; 711 ;;
722 *) 712 ,*)
723 log_error "Unsupported installation for $(detect_os)" 713 log_error "Unsupported installation for $(detect_os)"
724 return 1 714 return 1
725 ;; 715 ;;
@@ -871,7 +861,7 @@ ask_bool() {
871 false) 861 false)
872 msg="$msg [y|N]: " 862 msg="$msg [y|N]: "
873 ;; 863 ;;
874 *) 864 ,*)
875 msg="$msg (y/n): " 865 msg="$msg (y/n): "
876 esac 866 esac
877 while true; do 867 while true; do
@@ -889,7 +879,7 @@ ask_bool() {
889 echo "false" 879 echo "false"
890 return 0 880 return 0
891 ;; 881 ;;
892 *) 882 ,*)
893 echo "Invalid input, use yes or no" 883 echo "Invalid input, use yes or no"
894 ;; 884 ;;
895 esac 885 esac
@@ -901,12 +891,12 @@ detect_endiannes() {
901 # Some firmwares do not contain hexdump, for those, try to detect endianness 891 # Some firmwares do not contain hexdump, for those, try to detect endianness
902 # differently. 892 # differently.
903 case $(cat /proc/cpuinfo) in 893 case $(cat /proc/cpuinfo) in
904 *BCM5300*) 894 ,*BCM5300*)
905 # RT-AC66U does not support Merlin version over 380.70 which 895 # RT-AC66U does not support Merlin version over 380.70 which
906 # lacks hexdump command. 896 # lacks hexdump command.
907 echo "le" 897 echo "le"
908 ;; 898 ;;
909 *) 899 ,*)
910 log_error "Cannot determine endianness" 900 log_error "Cannot determine endianness"
911 return 1 901 return 1
912 ;; 902 ;;
@@ -937,10 +927,10 @@ detect_goarch() {
937 arm) 927 arm)
938 # FreeBSD does not include arm version 928 # FreeBSD does not include arm version
939 case "$(sysctl -b hw.model 2>/dev/null)" in 929 case "$(sysctl -b hw.model 2>/dev/null)" in
940 *A9*) 930 ,*A9*)
941 echo "armv7" 931 echo "armv7"
942 ;; 932 ;;
943 *) 933 ,*)
944 # Unknown version, fallback to the lowest 934 # Unknown version, fallback to the lowest
945 echo "armv5" 935 echo "armv5"
946 ;; 936 ;;
@@ -964,7 +954,7 @@ detect_goarch() {
964 # "out of memory allocating heap arena metadata". 954 # "out of memory allocating heap arena metadata".
965 echo "armv7" 955 echo "armv7"
966 ;; 956 ;;
967 *) 957 ,*)
968 echo "arm64" 958 echo "arm64"
969 ;; 959 ;;
970 esac 960 esac
@@ -976,7 +966,7 @@ detect_goarch() {
976 # TODO: detect hardfloat 966 # TODO: detect hardfloat
977 echo "$(uname -m)$(detect_endiannes)_softfloat" 967 echo "$(uname -m)$(detect_endiannes)_softfloat"
978 ;; 968 ;;
979 *) 969 ,*)
980 log_error "Unsupported GOARCH: $(uname -m)" 970 log_error "Unsupported GOARCH: $(uname -m)"
981 return 1 971 return 1
982 ;; 972 ;;
@@ -1003,7 +993,7 @@ detect_goos() {
1003 OpenBSD) 993 OpenBSD)
1004 echo "openbsd" 994 echo "openbsd"
1005 ;; 995 ;;
1006 *) 996 ,*)
1007 log_error "Unsupported GOOS: $(uname -s)" 997 log_error "Unsupported GOOS: $(uname -s)"
1008 return 1 998 return 1
1009 esac 999 esac
@@ -1080,7 +1070,7 @@ detect_os() {
1080 OpenBSD) 1070 OpenBSD)
1081 echo "openbsd"; return 0 1071 echo "openbsd"; return 0
1082 ;; 1072 ;;
1083 *) 1073 ,*)
1084 esac 1074 esac
1085 log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)" 1075 log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)"
1086 return 1 1076 return 1
@@ -1099,7 +1089,7 @@ guess_host_type() {
1099 darwin|steamos) 1089 darwin|steamos)
1100 echo "workstation" 1090 echo "workstation"
1101 ;; 1091 ;;
1102 *) 1092 ,*)
1103 echo "unsure" 1093 echo "unsure"
1104 ;; 1094 ;;
1105 esac 1095 esac
@@ -1158,7 +1148,7 @@ bin_location() {
1158 steamos) 1148 steamos)
1159 echo "$HOME/.local/bin/nextdns" 1149 echo "$HOME/.local/bin/nextdns"
1160 ;; 1150 ;;
1161 *) 1151 ,*)
1162 log_error "Unknown bin location for $OS" 1152 log_error "Unknown bin location for $OS"
1163 ;; 1153 ;;
1164 esac 1154 esac
@@ -1166,12 +1156,12 @@ bin_location() {
1166 1156
1167is_version_current() { 1157is_version_current() {
1168 case "$INSTALL_RELEASE" in 1158 case "$INSTALL_RELEASE" in
1169 */*) 1159 ,*/*)
1170 # Snapshot 1160 # Snapshot
1171 hash=${INSTALL_RELEASE#*/} 1161 hash=${INSTALL_RELEASE#*/}
1172 test "0.0.0-$hash" = "$CURRENT_RELEASE" 1162 test "0.0.0-$hash" = "$CURRENT_RELEASE"
1173 ;; 1163 ;;
1174 *) 1164 ,*)
1175 test "$INSTALL_RELEASE" = "$CURRENT_RELEASE" 1165 test "$INSTALL_RELEASE" = "$CURRENT_RELEASE"
1176 ;; 1166 ;;
1177 esac 1167 esac
@@ -1196,7 +1186,7 @@ get_release() {
1196 curl) cmd="curl -A curl -s" ;; 1186 curl) cmd="curl -A curl -s" ;;
1197 wget) cmd="wget -qO- -U curl" ;; 1187 wget) cmd="wget -qO- -U curl" ;;
1198 openssl) cmd="openssl_get" ;; 1188 openssl) cmd="openssl_get" ;;
1199 *) 1189 ,*)
1200 log_error "Cannot retrieve latest version" 1190 log_error "Cannot retrieve latest version"
1201 return 1191 return
1202 ;; 1192 ;;
@@ -1248,4 +1238,4 @@ openssl_get() {
1248 1238
1249umask 0022 1239umask 0022
1250main 1240main
1251``` 1241#+end_src
content/blog/2019-01-07-useful-css.md → blog/useful-css/index.org renamed +64 −74
@@ -1,12 +1,10 @@
1+++ 1#+title: Useful CSS Snippets
2date = 2019-01-07 2#+date: 2019-01-07
3title = "Useful CSS Snippets" 3#+description: Explore some useful CSS snippets.
4description = "Explore some useful CSS snippets." 4#+filetags: :dev:
5+++
6 5
7# Introduction to CSS 6* Introduction to CSS
8 7[[https://en.wikipedia.org/wiki/CSS][CSS]], the language used to markup
9[CSS](https://en.wikipedia.org/wiki/CSS), the language used to markup
10HTML code and make it "pretty", is one of the most effective ways to 8HTML code and make it "pretty", is one of the most effective ways to
11increase the attractiveness of a website. It can also lead to increased 9increase the attractiveness of a website. It can also lead to increased
12user engagement, retention, and satisfaction. In fact, there are whole 10user engagement, retention, and satisfaction. In fact, there are whole
@@ -15,12 +13,11 @@ known as UI design and UX design.
15 13
16Some web developers are used to the common CSS properties, such as 14Some web developers are used to the common CSS properties, such as
17element sizing, fonts, colors, etc., but are not as well versed in 15element sizing, fonts, colors, etc., but are not as well versed in
18less-used properties and values such as `flexbox`, 16less-used properties and values such as =flexbox=, =clip-path=, and
19`clip-path`, and `transform`. This article will 17=transform=. This article will provide some insight into the less-used
20provide some insight into the less-used and unique CSS properties. 18and unique CSS properties.
21
22# CSS Variables
23 19
20* CSS Variables
24The first topic today is CSS variables. Variables are not often used by 21The first topic today is CSS variables. Variables are not often used by
25smaller developers. CSS variables allow you to give your website a 22smaller developers. CSS variables allow you to give your website a
26well-defined structure, where you can easily reuse CSS properties 23well-defined structure, where you can easily reuse CSS properties
@@ -29,15 +26,14 @@ throughout the project.
29You can use variables to define things, such as color palettes. Then, 26You can use variables to define things, such as color palettes. Then,
30you can use these colors for backgrounds anywhere else in the HTML. This 27you can use these colors for backgrounds anywhere else in the HTML. This
31could be extended, where extra variables could be defined for 28could be extended, where extra variables could be defined for
32`primary-text`, `quoted-text`, etc. Variables can 29=primary-text=, =quoted-text=, etc. Variables can also be used to define
33also be used to define spacing (e.g. `32px` or 30spacing (e.g. =32px= or =2rem=), which can then be applied to margins,
34`2rem`), which can then be applied to margins, padding, font 31padding, font sizes, and more.
35sizes, and more.
36 32
37For example, here are some variables defined at the root of the website, 33For example, here are some variables defined at the root of the website,
38which allows for any subsequent CSS rules to use those variables: 34which allows for any subsequent CSS rules to use those variables:
39 35
40```css 36#+begin_src css
41:root { 37:root {
42 --primary-color: black; 38 --primary-color: black;
43 --secondary-color: white; 39 --secondary-color: white;
@@ -47,39 +43,38 @@ body {
47 background-color: var(--primary-color); 43 background-color: var(--primary-color);
48 color: var(--secondary-color); 44 color: var(--secondary-color);
49} 45}
50``` 46#+end_src
51
52# CSS Box Shadows
53 47
48* CSS Box Shadows
54Box shadows were once my mortal enemy. No matter how hard I tried, I 49Box shadows were once my mortal enemy. No matter how hard I tried, I
55just couldn't get them to work how I wanted. Because of this, my 50just couldn't get them to work how I wanted. Because of this, my
56favorite discovery has been CSSMatic's [box shadow 51favorite discovery has been CSSMatic's
57generator](https://www.cssmatic.com/box-shadow). It provides an 52[[https://www.cssmatic.com/box-shadow][box shadow generator]]. It
58excellent tool to generate box shadows using their simple sliders. 53provides an excellent tool to generate box shadows using their simple
59Surprisingly, this is the reason I learned how box shadows work! You can 54sliders. Surprisingly, this is the reason I learned how box shadows
60use the sliders and watch how the CSS code changes in the image that is 55work! You can use the sliders and watch how the CSS code changes in the
61displayed. Through this, you should understand that the basic structure 56image that is displayed. Through this, you should understand that the
62for box shadows is: 57basic structure for box shadows is:
63 58
64```css 59#+begin_src css
65box-shadow: inset horizontal vertical blur spread color; 60box-shadow: inset horizontal vertical blur spread color;
66``` 61#+end_src
67 62
68Now, let's look at some basic examples! You can copy and paste the 63Now, let's look at some basic examples! You can copy and paste the
69following code into a site like CodePen or your own HTML files. Feel 64following code into a site like CodePen or your own HTML files. Feel
70free to play around with the code, experiment, and learn. 65free to play around with the code, experiment, and learn.
71 66
72****Box Shadow #1**** 67*Box Shadow #1*
73 68
74```html 69#+begin_src html
75<div class="shadow-examples"> 70<div class="shadow-examples">
76 <div class="box effect1"> 71 <div class="box effect1">
77 <h3>Effect 1</h3> 72 <h3>Effect 1</h3>
78 </div> 73 </div>
79</div> 74</div>
80``` 75#+end_src
81 76
82```css 77#+begin_src css
83.box h3 { 78.box h3 {
84 text-align: center; 79 text-align: center;
85 position: relative; 80 position: relative;
@@ -94,19 +89,19 @@ free to play around with the code, experiment, and learn.
94.effect1 { 89.effect1 {
95 box-shadow: 0 10px 6px -6px #777; 90 box-shadow: 0 10px 6px -6px #777;
96} 91}
97``` 92#+end_src
98 93
99****Box Shadow #2**** 94*Box Shadow #2*
100 95
101```html 96#+begin_src html
102<div class="shadow-examples"> 97<div class="shadow-examples">
103 <div class="box effect2"> 98 <div class="box effect2">
104 <h3>Effect 2</h3> 99 <h3>Effect 2</h3>
105 </div> 100 </div>
106</div> 101</div>
107``` 102#+end_src
108 103
109```css 104#+begin_src css
110.box h3 { 105.box h3 {
111 text-align: center; 106 text-align: center;
112 position: relative; 107 position: relative;
@@ -121,28 +116,27 @@ free to play around with the code, experiment, and learn.
121.effect2 { 116.effect2 {
122 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75); 117 box-shadow: 10px 10px 5px -5px rgba(0, 0, 0, 0.75);
123} 118}
124``` 119#+end_src
125 120
126Try these box shadows out on your own and see how changing each shadow 121Try these box shadows out on your own and see how changing each shadow
127value works. 122value works.
128 123
129# CSS Flexbox 124* CSS Flexbox
130
131Now, let's move on to the best part of this article: flexbox. The 125Now, let's move on to the best part of this article: flexbox. The
132flexbox is by far my favorite new toy. I originally stumbled across this 126flexbox is by far my favorite new toy. I originally stumbled across this
133solution after looking for more efficient ways of centering content 127solution after looking for more efficient ways of centering content
134horizontally AND vertically. I had used a few hack-ish methods before, 128horizontally AND vertically. I had used a few hack-ish methods before,
135but flexbox throws those out the window. The best part of it all is that 129but flexbox throws those out the window. The best part of it all is that
136flexbox is *dead simple*. 130flexbox is /dead simple/.
137 131
138Flexbox pertains to the parent div of any element. You want the parent 132Flexbox pertains to the parent div of any element. You want the parent
139to be the flexbox in which items are arranged to use the flex methods. 133to be the flexbox in which items are arranged to use the flex methods.
140It's easier to see this in action that explained, so let's see an 134It's easier to see this in action that explained, so let's see an
141example. 135example.
142 136
143****Flexbox**** 137*Flexbox*
144 138
145```html 139#+begin_src html
146<div class="flex-examples"> 140<div class="flex-examples">
147 <div class="sm-box"> 141 <div class="sm-box">
148 <h3>1</h3> 142 <h3>1</h3>
@@ -151,9 +145,9 @@ example.
151 <h3>2</h3> 145 <h3>2</h3>
152 </div> 146 </div>
153</div> 147</div>
154``` 148#+end_src
155 149
156```css 150#+begin_src css
157.flex-examples { 151.flex-examples {
158 display: flex; 152 display: flex;
159 flex-wrap: wrap; 153 flex-wrap: wrap;
@@ -171,29 +165,25 @@ example.
171 background: #fff; 165 background: #fff;
172 margin: 40px 10px; 166 margin: 40px 10px;
173} 167}
174``` 168#+end_src
175 169
176You may notice that we no longer need to use the `top` 170You may notice that we no longer need to use the =top= property for the
177property for the `h3` elements in our code. This is because 171=h3= elements in our code. This is because we set the display box to be
178we set the display box to be a flex container for the small boxes, AND 172a flex container for the small boxes, AND we made the small boxes flex
179we made the small boxes flex containers for their elements (the h3 173containers for their elements (the h3 tags). Flex boxes can be nested
180tags). Flex boxes can be nested like this to center content that is 174like this to center content that is inside centered content.
181inside centered content. 175
182 176For the example above, we designated the =justify-content= property to
183For the example above, we designated the `justify-content` 177be =flex-start= so that the boxes stack from the left side of the
184property to be `flex-start` so that the boxes stack from the 178screen. This property can be changed to =center= to make the boxes
185left side of the screen. This property can be changed to 179appear in the center of the screen.
186`center` to make the boxes appear in the center of the 180
187screen. 181For an interactive example,
188 182[[https://codepen.io/LandonSchropp/pen/KpzzGo][check out this CodePen]]
189For an interactive example, [check out this 183from [[https://codepen.io/LandonSchropp/][LandonScropp]]. Resize the
190CodePen](https://codepen.io/LandonSchropp/pen/KpzzGo) from 184window with dice to see how they collapse and re-align.
191[LandonScropp](https://codepen.io/LandonSchropp/). Resize the window 185
192with dice to see how they collapse and re-align. 186* Even More CSS
193 187For more inspiration, you can visit [[https://www.codepen.io][CodePen]],
194# Even More CSS 188[[https://dribbble.com][Dribbble]], or [[https://uimovement.com][UI
195 189Movement]] to browse the collections of many amazing web designers.
196For more inspiration, you can visit [CodePen](https://www.codepen.io),
197[Dribbble](https://dribbble.com), or [UI
198Movement](https://uimovement.com) to browse the collections of many
199amazing web designers.
content/blog/2021-03-28-vaporwave-vs-outrun.md → blog/vaporwave-vs-outrun/index.org renamed +51 −57
@@ -1,11 +1,9 @@
1+++ 1#+title: Vaporwave vs Outrun
2date = 2021-03-28 2#+date: 2021-03-28
3title = "Vaporwave vs Outrun" 3#+description: Learn the differences between the vaporwave and Outrun aesthetics.
4description = "Learn the differences between the vaporwave and Outrun aesthetics." 4#+filetags: :personal:
5+++
6
7## Overview
8 5
6* Overview
9When it comes to an aesthetic that originated primarily online, there 7When it comes to an aesthetic that originated primarily online, there
10tends to be a lot of confusion around what falls into each specific 8tends to be a lot of confusion around what falls into each specific
11genre. This post discusses Vaporwave and Outrun, which have become 9genre. This post discusses Vaporwave and Outrun, which have become
@@ -13,58 +11,58 @@ almost synonymous in the minds of those online who aren't hardcore into
13these genres of media. More specifically, Outrun is largely an unknown 11these genres of media. More specifically, Outrun is largely an unknown
14term while all aesthetics in these two genres are usually attributed to 12term while all aesthetics in these two genres are usually attributed to
15Vaporwave. For example, take a look at the image search results for 13Vaporwave. For example, take a look at the image search results for
16Vaporwave: the results include **a lot** of Outrun-themed images. 14Vaporwave: the results include *a lot** of Outrun-themed images. You'll
17You'll find a similar trend almost everywhere. 15find a similar trend almost everywhere.
18
19![Vaporwave Search Results](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png)
20 16
21## Vaporwave 17#+caption: Vaporwave Search Results
18[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/vaporwave-search-results.png]]
22 19
23![Macintosh Plus Album Cover](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png) 20* Vaporwave
21#+caption: Macintosh Plus Album Cover
22[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/macintosh-plus.png]]
24 23
25Okay, so what is Vaporwave? I'm going to just copy-and-paste some 24Okay, so what is Vaporwave? I'm going to just copy-and-paste some
26general info from the Wikipedia article on 25general info from the Wikipedia article on
27[Vaporwave](https://en.wikipedia.org/wiki/Vaporwave), so that I'm not 26[[https://en.wikipedia.org/wiki/Vaporwave][Vaporwave]], so that I'm not
28repeating everything you can already search for online: 27repeating everything you can already search for online:
29 28
30> Vaporwave is a microgenre of electronic music, a visual art style, and 29#+begin_quote
31> an Internet meme that emerged in the early 2010s. It is defined partly 30Vaporwave is a microgenre of electronic music, a visual art style, and
32> by its slowed-down, chopped and screwed samples of smooth jazz, 31an Internet meme that emerged in the early 2010s. It is defined partly
33> elevator, R&B, and lounge music from the 1980s and 1990s. The 32by its slowed-down, chopped and screwed samples of smooth jazz,
34> surrounding subculture is sometimes associated with an ambiguous or 33elevator, R&B, and lounge music from the 1980s and 1990s. The
35> satirical take on consumer capitalism and pop culture, and tends to be 34surrounding subculture is sometimes associated with an ambiguous or
36> characterized by a nostalgic or surrealist engagement with the popular 35satirical take on consumer capitalism and pop culture, and tends to be
37> entertainment, technology and advertising of previous decades. 36characterized by a nostalgic or surrealist engagement with the popular
38> Visually, it incorporates early Internet imagery, late 1990s web 37entertainment, technology and advertising of previous decades. Visually,
39> design, glitch art, anime, 3D-rendered objects, and cyberpunk tropes 38it incorporates early Internet imagery, late 1990s web design, glitch
40> in its cover artwork and music videos. 39art, anime, 3D-rendered objects, and cyberpunk tropes in its cover
40artwork and music videos.
41#+end_quote
41 42
42This is an excellent summary, and it helps address my point here: there 43This is an excellent summary, and it helps address my point here: there
43are specific aspects that make Vaporwave unique: 44are specific aspects that make Vaporwave unique:
44 45
45### Time Frame 46** Time Frame
46
47The time frame for references, logos, etc. focuses mostly on the 1990s 47The time frame for references, logos, etc. focuses mostly on the 1990s
48in Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens, 48in Vaporwave. You'll see old school Pepsi logos, Microsoft 95 screens,
49tropical plants, classic marble sculptures, and many references from 49tropical plants, classic marble sculptures, and many references from
50Japan's influence in the 90s. 50Japan's influence in the 90s.
51 51
52### Art 52** Art
53
54The color scheme is generally a soft, light palette that uses pastel 53The color scheme is generally a soft, light palette that uses pastel
55colors. The backdrop will often be in a light-pink or teal blue. 54colors. The backdrop will often be in a light-pink or teal blue.
56 55
57### Music 56** Music
58
59The musical genre of Vaporwave incorporates soft tunes, lounge music, 57The musical genre of Vaporwave incorporates soft tunes, lounge music,
60and sound effects that will make the listener reminisce of the 90s. The 58and sound effects that will make the listener reminisce of the 90s. The
61sounds of the genre are generally slower-paced and calming. The major 59sounds of the genre are generally slower-paced and calming. The major
62breakthrough artist for Vaporwave was [Macintosh 60breakthrough artist for Vaporwave was
63Plus](https://en.wikipedia.org/wiki/Vektroid), who released the album 61[[https://en.wikipedia.org/wiki/Vektroid][Macintosh Plus]], who released
64[Floral 62the album
65Shoppe](https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete) 63[[https://archive.org/details/MACINTOSHPLUS-FLORALSHOPPE_complete][Floral
66in 2011. Another more recent example is the artist 64Shoppe]] in 2011. Another more recent example is the artist
67[sadbert](https://sadbert.bandcamp.com/), whose latest album 65[[https://sadbert.bandcamp.com/][sadbert]], whose latest album
68incorporates the upbeat tones of the 1999 Dilbert TV series. 66incorporates the upbeat tones of the 1999 Dilbert TV series.
69 67
70Notice that Vaporwave doesn't include things like racing cars, 68Notice that Vaporwave doesn't include things like racing cars,
@@ -74,23 +72,21 @@ was becoming common in households, a reality that we have already
74experienced. Focus on the most aesthetically-pleasing parts of that past 72experienced. Focus on the most aesthetically-pleasing parts of that past
75is a large part of Vaporwave. 73is a large part of Vaporwave.
76 74
77## Outrun 75* Outrun
78 76#+caption: Outrun
79![Outrun](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png) 77[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/outrun.png]]
80 78
81Now, let's get to Outrun. This one is a little trickier since the genre 79Now, let's get to Outrun. This one is a little trickier since the genre
82has largely been lumped under the Vaporwave title for so long. However, 80has largely been lumped under the Vaporwave title for so long. However,
83it stems from the [Synthwave](https://en.wikipedia.org/wiki/Synthwave) 81it stems from the [[https://en.wikipedia.org/wiki/Synthwave][Synthwave]]
84music genre and is likely named after the 1986 racer game, [Out 82music genre and is likely named after the 1986 racer game,
85Run](https://en.wikipedia.org/wiki/Out_Run). 83[[https://en.wikipedia.org/wiki/Out_Run][Out Run]].
86
87### Time Frame
88 84
85** Time Frame
89Outrun can be thought of as a retro-futuristic aesthetic born from the 86Outrun can be thought of as a retro-futuristic aesthetic born from the
901980s. 871980s.
91 88
92### Art 89** Art
93
94The color scheme uses a very dark color palette with the main colors 90The color scheme uses a very dark color palette with the main colors
95being deep oranges, blues, and purples. Red edges are common around 91being deep oranges, blues, and purples. Red edges are common around
96objects in Outrun art. The background of the Outrun aesthetic is almost 92objects in Outrun art. The background of the Outrun aesthetic is almost
@@ -101,8 +97,7 @@ Classic sports cars, chrome robots, computer generated graphics and
101fonts, and the occasional use of rain or palm trees can be found in 97fonts, and the occasional use of rain or palm trees can be found in
102Outrun art. 98Outrun art.
103 99
104### Music 100** Music
105
106This aesthetic has a more aggressive and fast-paced style of music, 101This aesthetic has a more aggressive and fast-paced style of music,
107which tends to match the subject of the art in this aesthetic. 102which tends to match the subject of the art in this aesthetic.
108 103
@@ -110,20 +105,19 @@ Outrun enthusiasts love what people in the 1980s thought the future
110would look like. Take a look at a common video game discussed in Outrun 105would look like. Take a look at a common video game discussed in Outrun
111circles, Far Cry 3: Blood Dragon: 106circles, Far Cry 3: Blood Dragon:
112 107
113![Fry Cry 3: Blood 108#+caption: Fry Cry 3: Blood Dragon
114Dragon](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png) 109[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/far-cry.png]]
115 110
116Another example that doesn't force the color scheme as hard as some 111Another example that doesn't force the color scheme as hard as some
117online art does is Kung Fury: 112online art does is Kung Fury:
118 113
119![Kung 114#+caption: Kung Fury
120Fury](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png) 115[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury.png]]
121
122![Kung Fury Hacker
123Scene](https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png)
124 116
125## Conclusion 117#+caption: Kung Fury Hacker Scene
118[[https://img.cleberg.net/blog/20210328-vaporwave-vs-outrun/kung-fury-hacker.png]]
126 119
120* Conclusion
127While Vaporwave and Outrun share similarities, they are two distinct 121While Vaporwave and Outrun share similarities, they are two distinct
128aesthetics with many important distinctions. Someone who enjoys one may 122aesthetics with many important distinctions. Someone who enjoys one may
129not necessarily enjoy the other, so it's important to make sure we 123not necessarily enjoy the other, so it's important to make sure we
content/blog/2020-07-20-video-game-sales.md → blog/video-game-sales/index.org renamed +62 −68
@@ -1,36 +1,33 @@
1+++ 1#+title: Data Exploration: Video Game Sales
2date = 2020-07-20 2#+date: 2020-07-20
3title = "Data Exploration: Video Game Sales" 3#+description: Exploring and visualizing data with Python.
4description = "Exploring and visualizing data with Python." 4#+filetags: :data:
5+++
6
7# Background Information
8 5
6* Background Information
9This dataset (obtained from 7This dataset (obtained from
10[Kaggle](https://www.kaggle.com/gregorut/videogamesales/data)) contains 8[[https://www.kaggle.com/gregorut/videogamesales/data][Kaggle]])
11a list of video games with sales greater than 100,000 copies. It was 9contains a list of video games with sales greater than 100,000 copies.
12generated by a scrape of vgchartz.com. 10It was generated by a scrape of vgchartz.com.
13 11
14Fields include: 12Fields include:
15 13
16- Rank: Ranking of overall sales 14- Rank: Ranking of overall sales
17- Name: The game name 15- Name: The game name
18- Platform: Platform of the game release (i.e. PC,PS4, etc.) 16- Platform: Platform of the game release (i.e. PC,PS4, etc.)
19- Year: Year of the game's release 17- Year: Year of the game's release
20- Genre: Genre of the game 18- Genre: Genre of the game
21- Publisher: Publisher of the game 19- Publisher: Publisher of the game
22- NA~Sales~: Sales in North America (in millions) 20- NA_{Sales}: Sales in North America (in millions)
23- EU~Sales~: Sales in Europe (in millions) 21- EU_{Sales}: Sales in Europe (in millions)
24- JP~Sales~: Sales in Japan (in millions) 22- JP_{Sales}: Sales in Japan (in millions)
25- Other~Sales~: Sales in the rest of the world (in millions) 23- Other_{Sales}: Sales in the rest of the world (in millions)
26- Global~Sales~: Total worldwide sales. 24- Global_{Sales}: Total worldwide sales.
27 25
28There are 16,598 records. 2 records were dropped due to incomplete 26There are 16,598 records. 2 records were dropped due to incomplete
29information. 27information.
30 28
31# Import the Data 29* Import the Data
32 30#+begin_src python
33```python
34# Import the Python libraries we will be using 31# Import the Python libraries we will be using
35import pandas as pd 32import pandas as pd
36import numpy as np 33import numpy as np
@@ -41,55 +38,54 @@ import matplotlib.pyplot as plt
41file = r'video_game_sales.csv' 38file = r'video_game_sales.csv'
42df = pd.read_csv(file) 39df = pd.read_csv(file)
43df 40df
44``` 41#+end_src
45
46![Dataframe
47Results](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/01_dataframe-min.png)
48 42
49# Explore the Data 43#+caption: Dataframe Results
44[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/01_dataframe-min.png]]
50 45
51```python 46* Explore the Data
47#+begin_src python
52# With the description function, we can see the basic stats. For example, we can also see that the 'Year' column has some incomplete values. 48# With the description function, we can see the basic stats. For example, we can also see that the 'Year' column has some incomplete values.
53df.describe() 49df.describe()
54``` 50#+end_src
55 51
56![df.describe()](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/02_describe-min.png) 52#+caption: df.describe()
53[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/02_describe-min.png]]
57 54
58```python 55#+begin_src python
59# This function shows the rows and columns of NaN values. For example, df[179,3] = nan 56# This function shows the rows and columns of NaN values. For example, df[179,3] = nan
60np.where(pd.isnull(df)) 57np.where(pd.isnull(df))
61 58
62(array([179, ..., 16553], dtype=int64), 59(array([179, ..., 16553], dtype=int64),
63 array([3, ..., 5], dtype=int64)) 60 array([3, ..., 5], dtype=int64))
64``` 61#+end_src
65 62
66# Visualize the Data 63* Visualize the Data
67 64#+begin_src python
68```python
69# This function plots the global sales by platform 65# This function plots the global sales by platform
70sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90) 66sns.catplot(x='Platform', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=90)
71``` 67#+end_src
72 68
73![Plot of Global Sales by 69#+caption: Plot of Global Sales by Platform
74Platform](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/03_plot-min.png) 70[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/03_plot-min.png]]
75 71
76```python 72#+begin_src python
77# This function plots the global sales by genre 73# This function plots the global sales by genre
78sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45) 74sns.catplot(x='Genre', y='Global_Sales', data=df, jitter=False).set_xticklabels(rotation=45)
79``` 75#+end_src
80 76
81![Plot of Global Sales by 77#+caption: Plot of Global Sales by Genre
82Genre](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/04_plot-min.png) 78[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/04_plot-min.png]]
83 79
84```python 80#+begin_src python
85# This function plots the global sales by year 81# This function plots the global sales by year
86sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45) 82sns.lmplot(x='Year', y='Global_Sales', data=df).set_xticklabels(rotation=45)
87``` 83#+end_src
88 84
89![Plot of Global Sales by 85#+caption: Plot of Global Sales by Year
90Year](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/05_plot-min.png) 86[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/05_plot-min.png]]
91 87
92```python 88#+begin_src python
93# This function plots four different lines to show sales from different regions. 89# This function plots four different lines to show sales from different regions.
94# The global sales plot line is commented-out, but can be included for comparison 90# The global sales plot line is commented-out, but can be included for comparison
95df2 = df.groupby('Year').sum() 91df2 = df.groupby('Year').sum()
@@ -113,14 +109,13 @@ ax.plot(years, d, label='Other_Sales')
113 109
114ax.legend() 110ax.legend()
115plt.show() 111plt.show()
116``` 112#+end_src
117
118![Plot of Regional Sales by
119Year](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/06_plot-min.png)
120 113
121# Investigate Outliers 114#+caption: Plot of Regional Sales by Year
115[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/06_plot-min.png]]
122 116
123```python 117** Investigate Outliers
118#+begin_src python
124# Find the game with the highest sales in North America 119# Find the game with the highest sales in North America
125df.loc[df['NA_Sales'].idxmax()] 120df.loc[df['NA_Sales'].idxmax()]
126 121
@@ -140,30 +135,29 @@ Name: 0, dtype: object
140# Explore statistics in the year 2006 (highest selling year) 135# Explore statistics in the year 2006 (highest selling year)
141df3 = df[(df['Year'] == 2006)] 136df3 = df[(df['Year'] == 2006)]
142df3.describe() 137df3.describe()
143``` 138#+end_src
144 139
145![Descriptive Statistics of 2006 140#+caption: Descriptive Statistics of 2006 Sales
146Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/07_2006_stats-min.png) 141[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/07_2006_stats-min.png]]
147 142
148```python 143#+begin_src python
149# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports 144# Plot the results of the previous dataframe (games from 2006) - we can see the year's results were largely carried by Wii Sports
150sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45) 145sns.catplot(x="Genre", y="Global_Sales", data=df3, jitter=False).set_xticklabels(rotation=45)
151``` 146#+end_src
152 147
153![Plot of 2006 148#+caption: Plot of 2006 Sales
154Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/08_plot-min.png) 149[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/08_plot-min.png]]
155 150
156```python 151#+begin_src python
157# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe 152# We can see 4 outliers in the graph above, so let's get the top 5 games from that dataframe
158# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS) 153# The results below show that Nintendo had all top 5 games (3 on the Wii and 2 on the DS)
159df3.sort_values(by=['Global_Sales'], ascending=False).head(5) 154df3.sort_values(by=['Global_Sales'], ascending=False).head(5)
160``` 155#+end_src
161
162![Outliers of 2006
163Sales](https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/09_outliers-min.png)
164 156
165# Discussion 157#+caption: Outliers of 2006 Sales
158[[https://img.cleberg.net/blog/20200720-data-exploration-video-game-sales/09_outliers-min.png]]
166 159
160* Discussion
167The purpose of exploring datasets is to ask questions, answer questions, 161The purpose of exploring datasets is to ask questions, answer questions,
168and discover intelligence that can be used to inform decision-making. 162and discover intelligence that can be used to inform decision-making.
169So, what have we found in this dataset? 163So, what have we found in this dataset?
content/blog/2020-09-01-visual-recognition.md → blog/visual-recognition/index.org renamed +50 −57
@@ -1,66 +1,62 @@
1+++ 1#+title: IBM Watson Visual Recognition
2date = 2020-09-01 2#+date: 2020-09-01
3title = "IBM Watson Visual Recognition" 3#+description: Exploring and visualizing data with Python.
4description = "Exploring and visualizing data with Python." 4#+filetags: :dev:
5+++
6 5
7# What is IBM Watson? 6* What is IBM Watson?
8 7If you've never heard of [[https://www.ibm.com/watson][Watson]], this
9If you've never heard of [Watson](https://www.ibm.com/watson), this
10service is a suite of enterprise-ready AI services, applications, and 8service is a suite of enterprise-ready AI services, applications, and
11tooling provided by IBM. Watson contains quite a few useful tools for 9tooling provided by IBM. Watson contains quite a few useful tools for
12data scientists and students, including the subject of this post today: 10data scientists and students, including the subject of this post today:
13visual recognition. 11visual recognition.
14 12
15If you'd like to view the official documentation for the Visual 13If you'd like to view the official documentation for the Visual
16Recognition API, visit the [API 14Recognition API, visit the
17Docs](https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python). 15[[https://cloud.ibm.com/apidocs/visual-recognition/visual-recognition-v3?code=python][API
18 16Docs]].
19# Prerequisites
20 17
18* Prerequisites
21To be able to use Watson Visual Recognition, you'll need the following: 19To be able to use Watson Visual Recognition, you'll need the following:
22 20
231. Create a free account on [IBM Watson 211. Create a free account on
24 Studio](https://www.ibm.com/cloud/watson-studio). 22 [[https://www.ibm.com/cloud/watson-studio][IBM Watson Studio]].
252. Add the [Watson Visual 232. Add the [[https://www.ibm.com/cloud/watson-visual-recognition][Watson
26 Recognition](https://www.ibm.com/cloud/watson-visual-recognition) 24 Visual Recognition]] service to your IBM Watson account.
27 service to your IBM Watson account. 253. Get your API key and URL. To do this, first go to the
283. Get your API key and URL. To do this, first go to the [profile 26 [[https://dataplatform.cloud.ibm.com/home2?context=cpdaas][profile
29 dashboard](https://dataplatform.cloud.ibm.com/home2?context=cpdaas) 27 dashboard]] for your IBM account and click on the Watson Visual
30 for your IBM account and click on the Watson Visual Recognition 28 Recognition service you created. This will be listed in the section
31 service you created. This will be listed in the section titled 29 titled *Your services*. Then click the *Credentials** tab and open the
32 **Your services**. Then click the **Credentials** tab and open the 30 *Auto-generated credentials** dropdown. Copy your API key and URL so
33 **Auto-generated credentials** dropdown. Copy your API key and URL 31 that you can use them in the Python script later.
34 so that you can use them in the Python script later. 324. *[Optional]** While not required, you can also create the Jupyter
354. **[Optional]** While not required, you can also create the Jupyter 33 Notebook for this project right inside
36 Notebook for this project right inside [Watson 34 [[https://www.ibm.com/cloud/watson-studio][Watson Studio]]. Watson
37 Studio](https://www.ibm.com/cloud/watson-studio). Watson Studio will 35 Studio will save your notebooks inside an organized project and allow
38 save your notebooks inside an organized project and allow you to use 36 you to use their other integrated products, such as storage
39 their other integrated products, such as storage containers, AI 37 containers, AI models, documentation, external sharing, etc.
40 models, documentation, external sharing, etc. 38
41 39* Calling the IBM Watson Visual Recognition API
42# Calling the IBM Watson Visual Recognition API
43
44Okay, now let's get started. 40Okay, now let's get started.
45 41
46To begin, we need to install the proper Python package for IBM Watson. 42To begin, we need to install the proper Python package for IBM Watson.
47 43
48```sh 44#+begin_src sh
49pip install --upgrade --user "ibm-watson>=4.5.0" 45pip install --upgrade --user "ibm-watson>=4.5.0"
50``` 46#+end_src
51 47
52Next, we need to specify the API key, version, and URL given to us when 48Next, we need to specify the API key, version, and URL given to us when
53we created the Watson Visual Recognition service. 49we created the Watson Visual Recognition service.
54 50
55```python 51#+begin_src python
56apikey = "<your-apikey>" 52apikey = "<your-apikey>"
57version = "2018-03-19" 53version = "2018-03-19"
58url = "<your-url>" 54url = "<your-url>"
59``` 55#+end_src
60 56
61Now, let's import the necessary libraries and authenticate our service. 57Now, let's import the necessary libraries and authenticate our service.
62 58
63```python 59#+begin_src python
64import json 60import json
65from ibm_watson import VisualRecognitionV3 61from ibm_watson import VisualRecognitionV3
66from ibm_cloud_sdk_core.authenticators import IAMAuthenticator 62from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
@@ -72,20 +68,19 @@ visual_recognition = VisualRecognitionV3(
72) 68)
73 69
74visual_recognition.set_service_url(url) 70visual_recognition.set_service_url(url)
75``` 71#+end_src
76 72
77**[Optional]** If you'd like to tell the API not to use any data to 73*[Optional]* If you'd like to tell the API not to use any data to
78improve their products, set the following header. 74improve their products, set the following header.
79 75
80```python 76#+begin_src python
81visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"}) 77visual_recognition.set_default_headers({'x-watson-learning-opt-out': "true"})
82``` 78#+end_src
83 79
84Now we have our API all set and ready to go. For this example, I'm 80Now we have our API all set and ready to go. For this example, I'm going
85going to include a `dict` of photos to load as we test out 81to include a =dict= of photos to load as we test out the API.
86the API.
87 82
88```python 83#+begin_src python
89data = [ 84data = [
90 { 85 {
91 "title": "Grizzly Bear", 86 "title": "Grizzly Bear",
@@ -112,7 +107,7 @@ data = [
112 "url": "https://example.com/photos/image6.jpg" 107 "url": "https://example.com/photos/image6.jpg"
113 } 108 }
114] 109]
115``` 110#+end_src
116 111
117Now that we've set up our libraries and have the photos ready, let's 112Now that we've set up our libraries and have the photos ready, let's
118create a loop to call the API for each image. The code below shows a 113create a loop to call the API for each image. The code below shows a
@@ -123,7 +118,7 @@ to the console with dotted lines separating each section.
123In the case of an API error, the codes and explanations are output to 118In the case of an API error, the codes and explanations are output to
124the console. 119the console.
125 120
126```python 121#+begin_src python
127from ibm_watson import ApiException 122from ibm_watson import ApiException
128 123
129for x in range(len(data)): 124for x in range(len(data)):
@@ -144,10 +139,9 @@ try:
144 print("-----------------------------------------------") 139 print("-----------------------------------------------")
145except ApiException as ex: 140except ApiException as ex:
146 print("Method failed with status code " + str(ex.code) + ": " + ex.message) 141 print("Method failed with status code " + str(ex.code) + ": " + ex.message)
147``` 142#+end_src
148
149# The Results
150 143
144* The Results
151Here we can see the full result set of our function above. If you view 145Here we can see the full result set of our function above. If you view
152each of the URLs that we sent to the API, you'll be able to see that it 146each of the URLs that we sent to the API, you'll be able to see that it
153was remarkably accurate. To be fair, these are clear high-resolution, 147was remarkably accurate. To be fair, these are clear high-resolution,
@@ -160,13 +154,13 @@ instead of attempting to write our own image recognition function. Each
160of the classifications returned was a fair description of the image. 154of the classifications returned was a fair description of the image.
161 155
162If you wanted to restrict the results to those that are at least 90% 156If you wanted to restrict the results to those that are at least 90%
163confident or greater, you would simply adjust the `threshold` 157confident or greater, you would simply adjust the =threshold= in the
164in the `visual_recognition.classify()` function. 158=visual_recognition.classify()= function.
165 159
166When your program runs, it should show the output below for each photo 160When your program runs, it should show the output below for each photo
167you provide. 161you provide.
168 162
169```txt 163#+begin_src txt
170---------------------------------------------------------------- 164----------------------------------------------------------------
171Image Title: Grizzly Bear 165Image Title: Grizzly Bear
172Image URL: https://example.com/photos/image1.jpg 166Image URL: https://example.com/photos/image1.jpg
@@ -179,10 +173,9 @@ animal ( 1 )
179Alaskan brown bear ( 0.759 ) 173Alaskan brown bear ( 0.759 )
180greenishness color ( 0.975 ) 174greenishness color ( 0.975 )
181---------------------------------------------------------------- 175----------------------------------------------------------------
182``` 176#+end_src
183
184# Discussion
185 177
178* Discussion
186Now, this was a very minimal implementation of the API. We simply 179Now, this was a very minimal implementation of the API. We simply
187supplied some images and looked to see how accurate the results were. 180supplied some images and looked to see how accurate the results were.
188However, you could implement this type of API into many machine learning 181However, you could implement this type of API into many machine learning
content/blog/2021-03-30-vps-web-server.md → blog/vps-web-server/index.org renamed +168 −186
@@ -1,35 +1,34 @@
1+++ 1#+title: How to Set Up a VPS Web Server
2date = 2021-03-30 2#+date: 2021-03-30
3title = "How to Set Up a VPS Web Server" 3#+description: A beginner's guide to setting up a virtual private server (VPS).
4description = "A beginner's guide to setting up a virtual private server (VPS)." 4#+filetags: :sysadmin:
5+++
6
7## Shared Hosting vs. VPS
8 5
6* Shared Hosting vs. VPS
9Choosing a place to host a website is one of the most confusing 7Choosing a place to host a website is one of the most confusing
10decisions for beginner web developers. Even for experienced web devs, 8decisions for beginner web developers. Even for experienced web devs,
11choosing between different forms of web hosting can be a daunting 9choosing between different forms of web hosting can be a daunting
12choice. 10choice.
13 11
14First, let's take a look at [shared web 12First, let's take a look at
15hosting](https://en.wikipedia.org/wiki/Shared_web_hosting_service). 13[[https://en.wikipedia.org/wiki/Shared_web_hosting_service][shared web
16Shared web hosting is a product where you are purchasing a small piece 14hosting]]. Shared web hosting is a product where you are purchasing a
17of a web server that is being shared between many websites. As a result, 15small piece of a web server that is being shared between many websites.
18the cost of shared hosting is extremely low. You won't have access to 16As a result, the cost of shared hosting is extremely low. You won't have
19the server itself, which means you can't install your own software on 17access to the server itself, which means you can't install your own
20the server, such as Docker. Usually, you are simply allowed to connect 18software on the server, such as Docker. Usually, you are simply allowed
21your domains to the server, set up domain security, and other small 19to connect your domains to the server, set up domain security, and other
22utilities. 20small utilities.
23 21
24In contrast, a [virtual private 22In contrast, a
25server](https://en.wikipedia.org/wiki/Virtual_private_server) (VPS) is a 23[[https://en.wikipedia.org/wiki/Virtual_private_server][virtual private
26virtual machine that replicates the environment of having a dedicated 24server]] (VPS) is a virtual machine that replicates the environment of
27server to yourself. You are able to control the virtual server's host 25having a dedicated server to yourself. You are able to control the
28names, base file system, package manager, etc. Another great upside of a 26virtual server's host names, base file system, package manager, etc.
29VPS is that since it's virtual, the company providing the VPS can 27Another great upside of a VPS is that since it's virtual, the company
30dynamically increase the disk size, RAM size, or number of CPUs at any 28providing the VPS can dynamically increase the disk size, RAM size, or
31time. However, the virtual server is still physically located on a 29number of CPUs at any time. However, the virtual server is still
32server that is shared between multiple virtual servers. 30physically located on a server that is shared between multiple virtual
31servers.
33 32
34The choice between shared hosting and VPS mostly depends on your skill 33The choice between shared hosting and VPS mostly depends on your skill
35level with system administration. If you're comforting working on a 34level with system administration. If you're comforting working on a
@@ -38,8 +37,7 @@ a VPS is usually a better option. However, shared hosting is a fantastic
38option for people who don't want to have to learn how to manage their 37option for people who don't want to have to learn how to manage their
39server. 38server.
40 39
41## My Situation 40* My Situation
42
43I had used shared hosting for approximately 5 years before trying my 41I had used shared hosting for approximately 5 years before trying my
44first VPS. I manage a homelab and have had success running a server and 42first VPS. I manage a homelab and have had success running a server and
45performing typical sysadmin duties, but I was still hesitant to get a 43performing typical sysadmin duties, but I was still hesitant to get a
@@ -48,49 +46,48 @@ networking part of a server - DNS and hostname configurations were not
48my friend. 46my friend.
49 47
50As a little bit of background, I originally used 48As a little bit of background, I originally used
51[Siteground](https://www.siteground.com) for my initially shared hosting 49[[https://www.siteground.com][Siteground]] for my initially shared
52and stayed on that platform for at least a year. However, the UI was 50hosting and stayed on that platform for at least a year. However, the UI
53clunky, and I didn't like how they handled certain technical aspects, 51was clunky, and I didn't like how they handled certain technical
54so I switched to [Namecheap](https://www.namecheap.com). Namecheap was 52aspects, so I switched to [[https://www.namecheap.com][Namecheap]].
55great because it is the service I primarily use for purchasing domain 53Namecheap was great because it is the service I primarily use for
56names, which made it incredibly easy to link them to my hosting service. 54purchasing domain names, which made it incredibly easy to link them to
57However, it was still mediocre shared hosting, and Namecheap is 55my hosting service. However, it was still mediocre shared hosting, and
58notorious for not letting you use [Let's 56Namecheap is notorious for not letting you use
59Encrypt](https://letsencrypt.org) to obtain free SSL/TLS certificates; 57[[https://letsencrypt.org][Let's Encrypt]] to obtain free SSL/TLS
60Namecheap wants to make you purchase certificates through their store. 58certificates; Namecheap wants to make you purchase certificates through
61 59their store.
62Finally, I settled down with [iWebFusion](https://www.iwebfusion.net) 60
61Finally, I settled down with [[https://www.iwebfusion.net][iWebFusion]]
63for about the last year of my shared hosting. This service was pretty 62for about the last year of my shared hosting. This service was pretty
64great, came with free SSL/TLS, and I never had any complaints. 63great, came with free SSL/TLS, and I never had any complaints.
65 64
66However, I finally grew tired of not being able to install software on 65However, I finally grew tired of not being able to install software on
67my own web server. I wanted to be able to try out things like 66my own web server. I wanted to be able to try out things like
68[Postmill](https://postmill.xyz) or [Matrix](https://matrix.org). This 67[[https://postmill.xyz][Postmill]] or [[https://matrix.org][Matrix]].
69is possible with a VPS, so I decided to grab a new domain name to try it 68This is possible with a VPS, so I decided to grab a new domain name to
70out. 69try it out.
71
72## Getting Started: Buying a VPS
73 70
71* Getting Started: Buying a VPS
74The first step to moving over to a VPS is (you guessed it): finding a 72The first step to moving over to a VPS is (you guessed it): finding a
75VPS provider. For my VPSs, I use [1984](https://1984hosting.com) and 73VPS provider. For my VPSs, I use [[https://1984hosting.com][1984]] and
76prefer their services much more than any alternative, due to their 74prefer their services much more than any alternative, due to their
77location (Iceland), their [privacy 75location (Iceland), their [[https://1984hosting.com/GDPR/][privacy
78policy](https://1984hosting.com/GDPR/), their respect for GDPR, and the 76policy]], their respect for GDPR, and the ability to remain anonymous if
79ability to remain anonymous if you pay in Bitcoin or Monero. 77you pay in Bitcoin or Monero.
80 78
81[Njalla](https://njal.la) is another good, privacy-oriented option for 79[[https://njal.la][Njalla]] is another good, privacy-oriented option for
82VPS services. 80VPS services.
83 81
84You'll have to decide what specifications you want on your VPS. For me, 82You'll have to decide what specifications you want on your VPS. For me,
85I only build and deploy low-resource HTML, PHP, and Python websites. 83I only build and deploy low-resource HTML, PHP, and Python websites.
86This means I can survive on the smallest VPS: 1 CPU, 1GB of RAM, and 84This means I can survive on the smallest VPS: 1 CPU, 1GB of RAM, and
8725GB SSD for \$5.00 per month. 8525GB SSD for $5.00 per month.
88 86
89As noted above, the great thing about a VPS is you can request your 87As noted above, the great thing about a VPS is you can request your
90provider to increase the resources at any time. 88provider to increase the resources at any time.
91 89
92## Configuring DNS Settings 90* Configuring DNS Settings
93
94Okay, so now let's get into some actual work that has to be done to get 91Okay, so now let's get into some actual work that has to be done to get
95content moved from a shared host to a VPS. At this point, I'm assuming 92content moved from a shared host to a VPS. At this point, I'm assuming
96you have a shared host with website content that you can still access, 93you have a shared host with website content that you can still access,
@@ -98,143 +95,135 @@ and you've purchased a new VPS and can SSH into that server.
98 95
99The first change is minor, but it should be done immediately in order to 96The first change is minor, but it should be done immediately in order to
100get things moving: DNS settings. Go to wherever your DNS settings are 97get things moving: DNS settings. Go to wherever your DNS settings are
101handled. If your shared host also managed your DNS settings, you'll 98handled. If your shared host also managed your DNS settings, you'll need
102need to first move that DNS over to your new VPS provider. For me, I 99to first move that DNS over to your new VPS provider. For me, I route my
103route my DNS through [Gandi](https://www.gandi.net). 100DNS through [[https://www.gandi.net][Gandi]].
104 101
105Once you know where your DNS settings are, go ahead and update the 102Once you know where your DNS settings are, go ahead and update the =A=
106`A` records to match the public IP address of your VPS. For 103records to match the public IP address of your VPS. For example:
107example:
108 104
109```txt 105#+begin_src txt
110A example.com xxx.xxx.xxx.xxx 106A example.com xxx.xxx.xxx.xxx
111A subdomain xxx.xxx.xxx.xxx 107A subdomain xxx.xxx.xxx.xxx
112CNAME www example.com. 108CNAME www example.com.
113``` 109#+end_src
114 110
115If you have any other records that require updates, such as MX or TXT 111If you have any other records that require updates, such as MX or TXT
116records for a mail server, be sure to update those accordingly. 112records for a mail server, be sure to update those accordingly.
117Personally, I don't host my own mail server. I route all mail on my 113Personally, I don't host my own mail server. I route all mail on my
118custom domains to [Migadu](https://www.migadu.com). Hosting your own 114custom domains to [[https://www.migadu.com][Migadu]]. Hosting your own
119email server can become complex quickly and is not for beginners. 115email server can become complex quickly and is not for beginners.
120 116
121DNS changes can take up to 48 hours to propagate, so be sure to give it 117DNS changes can take up to 48 hours to propagate, so be sure to give it
122some time before assuming you've made an error. 118some time before assuming you've made an error.
123 119
124## Server Updates and Packages 120* Server Updates and Packages
125
126Now that the DNS settings have been changed, let's set up our server 121Now that the DNS settings have been changed, let's set up our server
127while we wait for the DNS to propagate. First up is to ssh into your 122while we wait for the DNS to propagate. First up is to ssh into your
128server. If you've signed up with a service like DigitalOcean, you can 123server. If you've signed up with a service like DigitalOcean, you can
129add your SSH key to your account and to your VPS droplet so that you 124add your SSH key to your account and to your VPS droplet so that you
130don't need a password in order to SSH. 125don't need a password in order to SSH.
131 126
132```sh 127#+begin_src sh
133ssh root@xxx.xxx.xxx.xxx 128ssh root@xxx.xxx.xxx.xxx
134``` 129#+end_src
135 130
136The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache 131The VPS that is used in this blog post runs Ubuntu 20.04 with an Apache
137web server. If you're working on a different operating system (OS) or 132web server. If you're working on a different operating system (OS) or
138want a different web server, such as Nginx, you'll have to use 133want a different web server, such as Nginx, you'll have to use different
139different commands to set it up. 134commands to set it up.
140 135
141First, let's update and upgrade our server. 136First, let's update and upgrade our server.
142 137
143**NOTE:** Since we have logged in to the server as `root` for 138*NOTE:* Since we have logged in to the server as =root= for now, we
144now, we don't need to use the `sudo` modifier before our 139don't need to use the =sudo= modifier before our commands.
145commands.
146 140
147```sh 141#+begin_src sh
148apt update && apt upgrade -y 142apt update && apt upgrade -y
149``` 143#+end_src
150
151## Create A User Account
152 144
153While being able to use `root` can be beneficial at times, 145* Create A User Account
154you shouldn't use `root` unless you have to. 146While being able to use =root= can be beneficial at times, you shouldn't
147use =root= unless you have to.
155 148
156So let's set up a new user in our system. The `-m` option 149So let's set up a new user in our system. The =-m= option below tells
157below tells the OS to create a home directory for the new user. 150the OS to create a home directory for the new user.
158 151
159```sh 152#+begin_src sh
160adduser USERNAME 153adduser USERNAME
161``` 154#+end_src
162 155
163Now, create a password for that user. 156Now, create a password for that user.
164 157
165```sh 158#+begin_src sh
166passwd USERNAME 159passwd USERNAME
167``` 160#+end_src
168 161
169Finally, add the user to the sudoers file, so they can perform 162Finally, add the user to the sudoers file, so they can perform
170priveleged commands. 163priveleged commands.
171 164
172```sh 165#+begin_src sh
173usermod -a -G sudo USERNAME 166usermod -a -G sudo USERNAME
174``` 167#+end_src
175 168
176If you are using SSH keys and not passwords, you'll need to copy your 169If you are using SSH keys and not passwords, you'll need to copy your
177SSH key from your local machine to the VPS. If you haven't disabled 170SSH key from your local machine to the VPS. If you haven't disabled
178password-based SSH yet, the easiest way to do this is 171password-based SSH yet, the easiest way to do this is =ssh-copy-id= from
179`ssh-copy-id` from your local computer (not from the VPS): 172your local computer (not from the VPS):
180 173
181```sh 174#+begin_src sh
182ssh-copy-id testuser@xxx.xxx.xxx.xxx 175ssh-copy-id testuser@xxx.xxx.xxx.xxx
183``` 176#+end_src
184 177
185If you've disabled password-based SSH, you'll need to manually copy 178If you've disabled password-based SSH, you'll need to manually copy your
186your SSH key into the `~/.ssh/authorized_keys` file. 179SSH key into the =~/.ssh/authorized_keys= file.
187
188## Install Software
189 180
181* Install Software
190Our goal here is to host a web server, so the next step is to install 182Our goal here is to host a web server, so the next step is to install
191the Apache web server and any other packages we need. 183the Apache web server and any other packages we need.
192 184
193From this point on, I will be logged in as a user (not 185From this point on, I will be logged in as a user (not =root=) and will
194`root`) and will need to use the `sudo` modifier 186need to use the =sudo= modifier for most commands.
195for most commands.
196 187
197```sh 188#+begin_src sh
198sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y 189sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
199sudo apt install apache2 190sudo apt install apache2
200``` 191#+end_src
201 192
202If you need other language support, such as PHP, you'll need to install 193If you need other language support, such as PHP, you'll need to install
203that too. 194that too.
204 195
205```sh 196#+begin_src sh
206sudo apt install libapache2-mod-php php-dom 197sudo apt install libapache2-mod-php php-dom
207sudo a2enmod php 198sudo a2enmod php
208sudo systemctl restart apache2 199sudo systemctl restart apache2
209``` 200#+end_src
210
211## Website Files & Folders
212 201
202* Website Files & Folders
213Next up is to create the directories for the domain(s) we want to be 203Next up is to create the directories for the domain(s) we want to be
214hosted on this web server. 204hosted on this web server.
215 205
216```sh 206#+begin_src sh
217cd /var/www 207cd /var/www
218sudo mkdir example.com 208sudo mkdir example.com
219``` 209#+end_src
220 210
221We have a folder for `example.com` now, so let's add an 211We have a folder for =example.com= now, so let's add an =index.html=
222`index.html` file and put it within a specific 212file and put it within a specific =public_html= folder. You don't need
223`public_html` folder. You don't need this 213this =public_html= if you don't want it, but it helps with organizing
224`public_html` if you don't want it, but it helps with 214items related to =example.com= that you don't want to publish to the
225organizing items related to `example.com` that you don't 215internet.
226want to publish to the internet.
227 216
228```sh 217#+begin_src sh
229cd example.com 218cd example.com
230sudo mkdir public_html && cd public_html 219sudo mkdir public_html && cd public_html
231sudo nano index.html 220sudo nano index.html
232``` 221#+end_src
233 222
234You can put anything you want in this `index.html` file. If 223You can put anything you want in this =index.html= file. If you can't
235you can't think of anything, paste this in there: 224think of anything, paste this in there:
236 225
237```html 226#+begin_src html
238<!DOCTYPE html> 227<!DOCTYPE html>
239<html lang="en"> 228<html lang="en">
240 <head> 229 <head>
@@ -246,36 +235,34 @@ you can't think of anything, paste this in there:
246 <h1>Hello, world!</h1> 235 <h1>Hello, world!</h1>
247 </body> 236 </body>
248</html> 237</html>
249``` 238#+end_src
250 239
251If you want something to be served at 240If you want something to be served at =example.com/page01/file.txt=,
252`example.com/page01/file.txt`, you'll have to create the 241you'll have to create the =page01= directory under the =example.com=
253`page01` directory under the `example.com`
254directory. For example: 242directory. For example:
255 243
256```sh 244#+begin_src sh
257cd /var/www/example.com/public_html 245cd /var/www/example.com/public_html
258sudo mkdir page01 246sudo mkdir page01
259sudo nano file.txt 247sudo nano file.txt
260``` 248#+end_src
261
262## Apache Configuration
263 249
250* Apache Configuration
264Now, let's set up the files that will tell the server where to find the 251Now, let's set up the files that will tell the server where to find the
265files for `example.com`. We will copy the default 252files for =example.com=. We will copy the default configuration file and
266configuration file and create our own. 253create our own.
267 254
268```sh 255#+begin_src sh
269cd /etc/apache2/sites-available 256cd /etc/apache2/sites-available
270sudo cp 000-default.conf example.com.conf 257sudo cp 000-default.conf example.com.conf
271sudo nano example.com.conf 258sudo nano example.com.conf
272``` 259#+end_src
273 260
274This configuration file will have a few default lines, but you'll need 261This configuration file will have a few default lines, but you'll need
275to edit it to look similar to this (settings may change based on your 262to edit it to look similar to this (settings may change based on your
276personal needs): 263personal needs):
277 264
278```config 265#+begin_src config
279<VirtualHost *:80> 266<VirtualHost *:80>
280 ServerAdmin your-email@email-provider.com 267 ServerAdmin your-email@email-provider.com
281 ServerName example.com 268 ServerName example.com
@@ -284,85 +271,82 @@ personal needs):
284 ErrorLog ${APACHE_LOG_DIR}/error.log 271 ErrorLog ${APACHE_LOG_DIR}/error.log
285 CustomLog ${APACHE_LOG_DIR}/access.log combined 272 CustomLog ${APACHE_LOG_DIR}/access.log combined
286</VirtualHost> 273</VirtualHost>
287``` 274#+end_src
288 275
289Now, enable the configuration for your new site, disable the default 276Now, enable the configuration for your new site, disable the default
290configuration, and reload the web server. 277configuration, and reload the web server.
291 278
292```sh 279#+begin_src sh
293sudo a2ensite example.com.conf 280sudo a2ensite example.com.conf
294sudo a2dissite 000-default.conf 281sudo a2dissite 000-default.conf
295sudo systemctl reload apache2 282sudo systemctl reload apache2
296``` 283#+end_src
297 284
298You can always run a test to make sure no errors or warnings are found 285You can always run a test to make sure no errors or warnings are found
299in your configuration files. 286in your configuration files.
300 287
301```sh 288#+begin_src sh
302sudo apache2ctl configtest 289sudo apache2ctl configtest
303``` 290#+end_src
304 291
305Now, restart the web server entirely. After this, you should be able to 292Now, restart the web server entirely. After this, you should be able to
306browse to `http://example.com` and see the HTML content you 293browse to =http://example.com= and see the HTML content you provided
307provided earlier. Note that SSL/TLS has not been enabled yet, so you 294earlier. Note that SSL/TLS has not been enabled yet, so you won't be
308won't be able to use the secure version yet 295able to use the secure version yet (=https://example.com=).
309(`https://example.com`).
310 296
311```sh 297#+begin_src sh
312sudo systemctl restart apache2 298sudo systemctl restart apache2
313``` 299#+end_src
314 300
315You can repeat this for as many websites as you need. Just create the 301You can repeat this for as many websites as you need. Just create the
316domain folders in `/var/www/`, add the configuration file, 302domain folders in =/var/www/=, add the configuration file, enable the
317enable the configuration, and restart `apache2`. 303configuration, and restart =apache2=.
318
319## SSL/TLS Certificates: Serve Websites Over HTTPS
320 304
305* SSL/TLS Certificates: Serve Websites Over HTTPS
321In order to serve secure content, you'll need to obtain SSL/TLS 306In order to serve secure content, you'll need to obtain SSL/TLS
322certificates. Luckily, there's a free tool called 307certificates. Luckily, there's a free tool called
323[Certbot](https://certbot.eff.org) that helps us with the process. 308[[https://certbot.eff.org][Certbot]] that helps us with the process.
324 309
325The first step is to install `snapd` and `core` 310The first step is to install =snapd= and =core= for Ubuntu.
326for Ubuntu.
327 311
328```sh 312#+begin_src sh
329sudo apt install snapd 313sudo apt install snapd
330sudo snap install core 314sudo snap install core
331sudo snap refresh core 315sudo snap refresh core
332``` 316#+end_src
333 317
334Next, install the `certbot` snap package. 318Next, install the =certbot= snap package.
335 319
336```sh 320#+begin_src sh
337sudo snap install --classic certbot 321sudo snap install --classic certbot
338``` 322#+end_src
339 323
340Execute the following command to ensure that the `certbot` 324Execute the following command to ensure that the =certbot= command can
341command can be run. 325be run.
342 326
343```sh 327#+begin_src sh
344sudo ln -s /snap/bin/certbot /usr/bin/certbot 328sudo ln -s /snap/bin/certbot /usr/bin/certbot
345``` 329#+end_src
346 330
347Finally, you can run `certbot` one of two ways: 331Finally, you can run =certbot= one of two ways:
348 332
3491. run it and let it alter your Apache configuration files 3331. run it and let it alter your Apache configuration files automatically
350 automatically to enable HTTPS redirects. 334 to enable HTTPS redirects.
3512. run it and only allow it to create certificates. You'll need to 3352. run it and only allow it to create certificates. You'll need to
352 manually alter the config files to enable HTTPS redirects. 336 manually alter the config files to enable HTTPS redirects.
353 337
354Run certbot and allow automatic config changes: 338Run certbot and allow automatic config changes:
355 339
356```sh 340#+begin_src sh
357sudo certbot --apache 341sudo certbot --apache
358``` 342#+end_src
359 343
360Run certbot for certificates only and don't allow it to alter config 344Run certbot for certificates only and don't allow it to alter config
361files: 345files:
362 346
363```sh 347#+begin_src sh
364sudo certbot certonly --apache 348sudo certbot certonly --apache
365``` 349#+end_src
366 350
367The Certbot packages on your system come with a cron job or systemd 351The Certbot packages on your system come with a cron job or systemd
368timer that will renew your certificates automatically before they 352timer that will renew your certificates automatically before they
@@ -370,48 +354,46 @@ expire. You will not need to run Certbot again unless you change your
370configuration. You can test automatic renewal for your certificates by 354configuration. You can test automatic renewal for your certificates by
371running this command: 355running this command:
372 356
373```sh 357#+begin_src sh
374sudo certbot renew --dry-run 358sudo certbot renew --dry-run
375``` 359#+end_src
376
377Now, test your domains by going to `https://example.com`.
378 360
379## Firewall Security 361Now, test your domains by going to =https://example.com=.
380 362
363* Firewall Security
381To enable better security on your server, you'll need to enable a basic 364To enable better security on your server, you'll need to enable a basic
382firewall. For Ubuntu, we'll use [the uncomplicated 365firewall. For Ubuntu, we'll use
383firewall](https://cleberg.net/blog/secure-your-network-with-the-uncomplicated-firewall.html). 366[[../secure-your-network-with-the-uncomplicated-firewall/][the
367uncomplicated firewall]].
384 368
385Now, add the following rules to the firewall allow SSH, Apache, and 369Now, add the following rules to the firewall allow SSH, Apache, and
386HTTP(S) connections. If you need to, you can enable different ports for 370HTTP(S) connections. If you need to, you can enable different ports for
387specifics applications, SFTP, etc. 371specifics applications, SFTP, etc.
388 372
389```sh 373#+begin_src sh
390sudo ufw default deny incoming 374sudo ufw default deny incoming
391sudo ufw default allow outgoing 375sudo ufw default allow outgoing
392sudo ufw allow OpenSSH 376sudo ufw allow OpenSSH
393sudo ufw allow Apache 377sudo ufw allow Apache
394sudo ufw allow proto tcp from any to any port 80,443 378sudo ufw allow proto tcp from any to any port 80,443
395``` 379#+end_src
396 380
397Once you've added all the rules you need, enable the firewall. 381Once you've added all the rules you need, enable the firewall.
398 382
399```sh 383#+begin_src sh
400sudo ufw enable 384sudo ufw enable
401``` 385#+end_src
402
403## Troubleshooting
404 386
387* Troubleshooting
405If you run into any issues during your VPS set-up, be sure to walk back 388If you run into any issues during your VPS set-up, be sure to walk back
406through your actions and make sure you didn't miss any steps. 389through your actions and make sure you didn't miss any steps.
407 390
408Many websites have fantastic guides to setting up various web servers. 391Many websites have fantastic guides to setting up various web servers.
409This is one of the areas [where DigitalOcean 392This is one of the areas
410shines](https://www.digitalocean.%20com/community/tutorials). For 393[[https://www.digitalocean.%20com/community/tutorials][where
411simpler or more Linux-oriented questions, I suggest using [Linuxize] 394DigitalOcean shines]]. For simpler or more Linux-oriented questions, I
412(<https://linuxize.com>). 395suggest using [Linuxize] ([[https://linuxize.com]]).
413 396
414If you're getting certain errors (e.g. =500 Internal Server Error=) and 397If you're getting certain errors (e.g. =500 Internal Server Error=) and
415need to debug locally, you can view the `access.log` and 398need to debug locally, you can view the =access.log= and =error.log=
416`error.log` files in the `/var/log/apache/` 399files in the =/var/log/apache/= directory.
417directory.
content/blog/2020-12-27-website-redesign.md → blog/website-redesign/index.org renamed +34 −40
@@ -1,11 +1,9 @@
1+++ 1#+title: Redesigning My Website: The 5 KB Result
2date = 2020-12-27 2#+date: 2020-12-27
3title = "Redesigning My Website: The 5 KB Result" 3#+description: A retrospective on my recent website redesign.
4description = "A retrospective on my recent website redesign." 4#+filetags: :sysadmin:
5+++
6
7# A Brief History
8 5
6* A Brief History
9As a form of continuous learning and entertainment, I've been running a 7As a form of continuous learning and entertainment, I've been running a
10handful of websites since 2016 when I took my first programming courses 8handful of websites since 2016 when I took my first programming courses
11in college. I maintain one main website, the place I consider the 9in college. I maintain one main website, the place I consider the
@@ -14,35 +12,33 @@ subdirectories and subdomains.
14 12
15One of the parts I've enjoyed the most about web development is the 13One of the parts I've enjoyed the most about web development is the
16aspect of designing an identity for a web page and working to find 14aspect of designing an identity for a web page and working to find
17exciting ways to display the site's content. Inevitably, this means 15exciting ways to display the site's content. Inevitably, this means I've
18I've changed the designs for my websites more times than I could 16changed the designs for my websites more times than I could possibly
19possibly count. Since I don't really host anything on my main webpage 17count. Since I don't really host anything on my main webpage that's
20that's vital, it allows me the freedom to change things as inspiration 18vital, it allows me the freedom to change things as inspiration strikes.
21strikes.
22 19
23Historically, I've relied on core utilities for spacing, components, 20Historically, I've relied on core utilities for spacing, components, and
24and layouts from [Bootstrap](https://getbootstrap.com) and added custom 21layouts from [[https://getbootstrap.com][Bootstrap]] and added custom
25CSS for fonts, accents, colors, and other items. I also tend to create 22CSS for fonts, accents, colors, and other items. I also tend to create
26sites with no border radius on items, visible borders, and content that 23sites with no border radius on items, visible borders, and content that
27takes up the entire screen (using whitespace inside components instead 24takes up the entire screen (using whitespace inside components instead
28of whitespace around my components). 25of whitespace around my components).
29 26
30# The Redesign Process 27* The Redesign Process
31
32About a week ago, I found myself wishing for a new design yet again. The 28About a week ago, I found myself wishing for a new design yet again. The
33prior design was largely inspired by IBM's [Carbon Design 29prior design was largely inspired by IBM's
34System](https://www.carbondesignsystem.com) and relied on jQuery, 30[[https://www.carbondesignsystem.com][Carbon Design System]] and relied
35Bootstrap, along with some compressed 31on jQuery, Bootstrap, along with some compressed
36[.webp](https://en.wikipedia.org/wiki/WebP) images. 32[[https://en.wikipedia.org/wiki/WebP][.webp]] images.
37 33
38To anyone who knows my preferences toward web design - and even in my 34To anyone who knows my preferences toward web design - and even in my
39personal life - it should be no surprise that I immediately started 35personal life - it should be no surprise that I immediately started
40looking for inspiration on minimalism. While there are some decent 36looking for inspiration on minimalism. While there are some decent
41minimalistic designs on sites like 37minimalistic designs on sites like
42[Dribbble](https://dribbble.com/search/shots/popular/web-design?q=minimalism), 38[[https://dribbble.com/search/shots/popular/web-design?q=minimalism][Dribbble]],
43people seem to mostly discuss [brutalist web 39people seem to mostly discuss [[https://brutalist-web.design][brutalist
44design](https://brutalist-web.design) when you ask about minimalism. 40web design]] when you ask about minimalism. While brutalist web design
45While brutalist web design doesn't have to be minimal, it often is. 41doesn't have to be minimal, it often is.
46 42
47I suppose, in a way, I did create a brutalist website since my HTML is 43I suppose, in a way, I did create a brutalist website since my HTML is
48semantic and accessible, hyperlinks are colored and underlined, and all 44semantic and accessible, hyperlinks are colored and underlined, and all
@@ -64,28 +60,26 @@ also helped to move all categories to a single page, rather than have
64each category on its own page. 60each category on its own page.
65 61
66The final big piece to finish the 62The final big piece to finish the
67"[KonMari](https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method)"-like 63"[[https://en.wikipedia.org/wiki/Marie_Kondo#KonMari_method][KonMari]]"-like
68part of my process was to remove Bootstrap CSS in its entirety. However, 64part of my process was to remove Bootstrap CSS in its entirety. However,
69this meant pulling out a few very useful classes, such as `.img-fluid` 65this meant pulling out a few very useful classes, such as =.img-fluid=
70and the default font stacks to keep in my custom CSS. 66and the default font stacks to keep in my custom CSS.
71 67
72After removing all the unnecessary pieces, I was finally able to 68After removing all the unnecessary pieces, I was finally able to
73reorganize my content and add a very small amount of custom CSS to make 69reorganize my content and add a very small amount of custom CSS to make
74everything pretty. This took a brief amount of time, effectively just 70everything pretty. This took a brief amount of time, effectively just
75consisting of me converting `<div>` tags into things like 71consisting of me converting =<div>= tags into things like =<ul>= lists
76`<ul>` lists and choosing accent colors. 72and choosing accent colors.
77
78# The Results
79
80## Reflection
81 73
74* The Results
75** Reflection
82So, what did all of this reorganizing do to my webpages? Well, first, my 76So, what did all of this reorganizing do to my webpages? Well, first, my
83websites are now **ridiculously fast**. Since the prior designs were 77websites are now *ridiculously fast*. Since the prior designs were also
84also minimal and didn't have many images, they measured up in 78minimal and didn't have many images, they measured up in Firefox's
85Firefox's Network Monitor around 300 KB - 600KB. After making the 79Network Monitor around 300 KB - 600KB. After making the changes, my main
86changes, my main site is at 5 KB transferred (22 KB total), and my blog 80site is at 5 KB transferred (22 KB total), and my blog is at 6.5 KB
87is at 6.5 KB transferred (13 KB total). **That means the redesigned 81transferred (13 KB total). *That means the redesigned pages are less
88pages are less than 2% the size of the old designs.** 82than 2% the size of the old designs.*
89 83
90Google Lighthouse ranks the new webpage as 100 in performance, 84Google Lighthouse ranks the new webpage as 100 in performance,
91accessibility, and best practices, with SEO at 92 since they think tap 85accessibility, and best practices, with SEO at 92 since they think tap
@@ -98,5 +92,5 @@ separate CSS file, and the CSS for my main website is simply embedded in
98the HTML file. 92the HTML file.
99 93
100Now that everything is complete, I can confidently say I'm happy with 94Now that everything is complete, I can confidently say I'm happy with
101the result and proud to look at the fastest set of websites I've 95the result and proud to look at the fastest set of websites I've created
102created so far. 96so far.
content/blog/2023-07-12-mullvad-wireguard-lan.md → blog/wireguard-lan/index.org renamed +54 −55
@@ -1,30 +1,28 @@
1+++ 1#+title: Enable LAN Access in Mullvad Wireguard Conf Files
2date = 2023-07-12 2#+date: 2023-07-12
3title = "Enable LAN Access in Mullvad Wireguard Conf Files" 3#+description: Learn how to enable LAN access manually in Mullvad configuration files.
4description = "Learn how to enable LAN access manually in Mullvad configuration files." 4#+filetags: :linux:
5+++ 5
6 6* Download Configuration Files from Mullvad
7## Download Configuration Files from Mullvad 7To begin, you'll need
8 8[[https://mullvad.net/account/wireguard-config][Wireguard configuration
9To begin, you'll need [Wireguard configuration files from 9files from Mullvad]]. You can choose any of the options as you download
10Mullvad](https://mullvad.net/account/wireguard-config). You can choose 10them. For example, I enabled the kill switch, selected all countries,
11any of the options as you download them. For example, I enabled the kill 11and selected a few content filters.
12switch, selected all countries, and selected a few content filters.
13 12
14Once downloaded, unzip the files and move them to the Wireguard folder 13Once downloaded, unzip the files and move them to the Wireguard folder
15on your system. 14on your system.
16 15
17```sh 16#+begin_src sh
18cd ~/Downloads 17cd ~/Downloads
19unzip mullvad_wireguard_linux_all_all.zip 18unzip mullvad_wireguard_linux_all_all.zip
20doas mv *.conf /etc/wireguard/ 19doas mv *.conf /etc/wireguard/
21``` 20#+end_src
22
23### Configuration File Layout
24 21
22** Configuration File Layout
25The default configuration files will look something like this: 23The default configuration files will look something like this:
26 24
27```conf 25#+begin_src conf
28[Interface] 26[Interface]
29# Device: <redacted> 27# Device: <redacted>
30PrivateKey = <redacted> 28PrivateKey = <redacted>
@@ -37,45 +35,46 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
37PublicKey = <redacted> 35PublicKey = <redacted>
38AllowedIPs = <redacted> 36AllowedIPs = <redacted>
39Endpoint = <redacted> 37Endpoint = <redacted>
40``` 38#+end_src
41 39
42> Note: If you didn't select the kill switch option, you won't see the 40#+begin_quote
43> `PostUp` and `PreDown` lines. In this case, 41Note: If you didn't select the kill switch option, you won't see the
44> you'll need to modify the script below to simply append those lines 42=PostUp= and =PreDown= lines. In this case, you'll need to modify the
45> to the `[Interface]` block. 43script below to simply append those lines to the =[Interface]= block.
46 44#+end_quote
47## Editing the Configuration Files
48 45
46* Editing the Configuration Files
49Once you have the files, you'll need to edit them and replace the 47Once you have the files, you'll need to edit them and replace the
50`PostUp` and `PreDown` lines to enable LAN access. 48=PostUp= and =PreDown= lines to enable LAN access.
51 49
52I recommend that you do this process as root, since you'll need to be 50I recommend that you do this process as root, since you'll need to be
53able to access files in `/etc/wireguard`, which are generally 51able to access files in =/etc/wireguard=, which are generally owned by
54owned by root. You can also try using `sudo` or 52root. You can also try using =sudo= or =doas=, but I didn't test that
55`doas`, but I didn't test that scenario so you may need to 53scenario so you may need to adjust, as necessary.
56adjust, as necessary.
57 54
58```sh 55#+begin_src sh
59su 56su
60``` 57#+end_src
61 58
62Create the Python file that we'll be using to update the Wireguard 59Create the Python file that we'll be using to update the Wireguard
63configuration files. 60configuration files.
64 61
65```sh 62#+begin_src sh
66nano replace.py 63nano replace.py
67``` 64#+end_src
68 65
69Within the Python file, copy and paste the logic below. This script will 66Within the Python file, copy and paste the logic below. This script will
70open a directory, loop through every configuration file within the 67open a directory, loop through every configuration file within the
71directory, and replace the `PostUp` and `PreDown` 68directory, and replace the =PostUp= and =PreDown= lines with the new
72lines with the new LAN-enabled iptables commands. 69LAN-enabled iptables commands.
70
71#+begin_quote
72Note: If your LAN is on a subnet other than =192.168.1.0/24=, you'll
73need to update the Python script below appropriately.
73 74
74> Note: If your LAN is on a subnet other than 75#+end_quote
75> `192.168.1.0/24`, you'll need to update the Python script
76> below appropriately.
77 76
78```python 77#+begin_src python
79import os 78import os
80import fileinput 79import fileinput
81 80
@@ -94,25 +93,25 @@ for file in os.listdir(dir):
94 print(line, end="") 93 print(line, end="")
95 94
96print("--- done ---") 95print("--- done ---")
97``` 96#+end_src
98 97
99Once you're done, save and close the file. You can now run the Python 98Once you're done, save and close the file. You can now run the Python
100script and watch as each file is updated. 99script and watch as each file is updated.
101 100
102```sh 101#+begin_src sh
103python3 replace.py 102python3 replace.py
104``` 103#+end_src
105 104
106To confirm it worked, you can `cat` one of the configuration 105To confirm it worked, you can =cat= one of the configuration files to
107files to inspect the new logic and connect to one to test it out. 106inspect the new logic and connect to one to test it out.
108 107
109```sh 108#+begin_src sh
110cat /etc/wireguard/us-chi-wg-001.conf 109cat /etc/wireguard/us-chi-wg-001.conf
111``` 110#+end_src
112 111
113The configuration files should now look like this: 112The configuration files should now look like this:
114 113
115```conf 114#+begin_src conf
116[Interface] 115[Interface]
117# Device: <redacted> 116# Device: <redacted>
118PrivateKey = <redacted> 117PrivateKey = <redacted>
@@ -125,20 +124,20 @@ PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m ad
125PublicKey = <redacted> 124PublicKey = <redacted>
126AllowedIPs = <redacted> 125AllowedIPs = <redacted>
127Endpoint = <redacted> 126Endpoint = <redacted>
128``` 127#+end_src
129 128
130If you connect to a Wireguard interface, such as 129If you connect to a Wireguard interface, such as =us-chi-wg-001=, you
131`us-chi-wg-001`, you can test your SSH functionality and see 130can test your SSH functionality and see that it works even while on the
132that it works even while on the VPN. 131VPN.
133 132
134```sh 133#+begin_src sh
135wg-quick up us-chi-wg-001 134wg-quick up us-chi-wg-001
136ssh user@lan-host 135ssh user@lan-host
137``` 136#+end_src
138 137
139To confirm your VPN connection, you can curl Mullvad's connection API: 138To confirm your VPN connection, you can curl Mullvad's connection API:
140 139
141```sh 140#+begin_src sh
142curl https://am.i.mullvad.net/connected 141curl https://am.i.mullvad.net/connected
143# You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted> 142# You are connected to Mullvad (server us-chi-wg-001). Your IP address is <redacted>
144``` 143#+end_src
content/blog/2024-02-06-zfs.md → blog/zfs/index.org renamed +134 −131
@@ -1,42 +1,43 @@
1+++ 1#+title: How to Create a ZFS Pool on Ubuntu Linux
2date = 2024-02-06 2#+date: 2024-02-06
3title = "How to Create a ZFS Pool on Ubuntu Linux" 3#+description: Learn how to create a simple ZFS pool on Ubuntu Linux.
4description = "Learn how to create a simple ZFS pool on Ubuntu Linux." 4#+filetags: :linux:
5+++
6 5
7This post details the process I used to create ZFS pools, datasets, and 6This post details the process I used to create ZFS pools, datasets, and
8snapshots on Ubuntu Server. 7snapshots on Ubuntu Server.
9 8
10I found the following pages very helpful while going through this process: 9I found the following pages very helpful while going through this
10process:
11 11
12- [Setup a ZFS storage pool](https://ubuntu.com/tutorials/setup-zfs-storage-pool) 12- [[https://ubuntu.com/tutorials/setup-zfs-storage-pool][Setup a ZFS
13- [Kernel/Reference/ZFS](https://wiki.ubuntu.com/Kernel/Reference/ZFS) 13 storage pool]]
14- [ZFS for Dummies](https://blog.victormendonca.com/2020/11/03/zfs-for-dummies/) 14- [[https://wiki.ubuntu.com/Kernel/Reference/ZFS][Kernel/Reference/ZFS]]
15 15- [[https://blog.victormendonca.com/2020/11/03/zfs-for-dummies/][ZFS for
16## Installation 16 Dummies]]
17 17
18* Installation
18To start, I installed the ZFS package with the following command: 19To start, I installed the ZFS package with the following command:
19 20
20```sh 21#+begin_src sh
21sudo apt install zfsutils-linux 22sudo apt install zfsutils-linux
22``` 23#+end_src
23 24
24Once installed, you can check the version to see if it installed correctly. 25Once installed, you can check the version to see if it installed
26correctly.
25 27
26```sh 28#+begin_src sh
27> zsf --version 29> zsf --version
28 30
29zfs-2.1.5-1ubuntu6~22.04.2 31zfs-2.1.5-1ubuntu6~22.04.2
30zfs-kmod-2.1.5-1ubuntu6~22.04.1 32zfs-kmod-2.1.5-1ubuntu6~22.04.1
31``` 33#+end_src
32
33## ZFS Configuration
34 34
35* ZFS Configuration
35Now that ZFS is installed, we can create and configure the pool. 36Now that ZFS is installed, we can create and configure the pool.
36 37
37You have various options for configuring ZFS pools that all come different pros 38You have various options for configuring ZFS pools that all come
38and cons. I suggest visiting the links at the top of this post or searching 39different pros and cons. I suggest visiting the links at the top of this
39online for the best configuration for your use-case. 40post or searching online for the best configuration for your use-case.
40 41
41- Striped VDEVs (Raid0) 42- Striped VDEVs (Raid0)
42- Mirrored VDEVs (Raid1) 43- Mirrored VDEVs (Raid1)
@@ -46,22 +47,21 @@ online for the best configuration for your use-case.
46- RAIDz3 47- RAIDz3
47- Nested RAIDz (Raid50, Raid60) 48- Nested RAIDz (Raid50, Raid60)
48 49
49I will be using Raid10 in this guide. However, the majority of the steps are the 50I will be using Raid10 in this guide. However, the majority of the steps
50same regardless of your chosen pool configuration. 51are the same regardless of your chosen pool configuration.
51
52### Creating the Pool
53 52
54To start, let's list the disks available to use. You can use `fdisk` command to 53** Creating the Pool
55see all available disks. 54To start, let's list the disks available to use. You can use =fdisk=
55command to see all available disks.
56 56
57```sh 57#+begin_src sh
58sudo fdisk -l 58sudo fdisk -l
59``` 59#+end_src
60 60
61Or, if you currently have them mounted, you can use the `df` command to view 61Or, if you currently have them mounted, you can use the =df= command to
62your disks. 62view your disks.
63 63
64```sh 64#+begin_src sh
65> sudo df -h 65> sudo df -h
66 66
67Filesystem Size Used Avail Use% Mounted on 67Filesystem Size Used Avail Use% Mounted on
@@ -72,116 +72,118 @@ Filesystem Size Used Avail Use% Mounted on
72/dev/sdd1 9.1T 8.7T 435G 96% /mnt/white-01 72/dev/sdd1 9.1T 8.7T 435G 96% /mnt/white-01
73/dev/sde1 7.3T 28K 6.9T 1% /mnt/red-03 73/dev/sde1 7.3T 28K 6.9T 1% /mnt/red-03
74/dev/sdf1 7.3T 28K 6.9T 1% /mnt/red-04 74/dev/sdf1 7.3T 28K 6.9T 1% /mnt/red-04
75``` 75#+end_src
76 76
77If you're going to use mounted disks, make sure to umount them before creating 77If you're going to use mounted disks, make sure to umount them before
78the pool. 78creating the pool.
79 79
80```sh 80#+begin_src sh
81sudo umount /dev/sda1 81sudo umount /dev/sda1
82sudo umount /dev/sdb1 82sudo umount /dev/sdb1
83``` 83#+end_src
84 84
85Now that I've identified the disks I want to use and have them unmounted, let's 85Now that I've identified the disks I want to use and have them
86create the pool. For this example, I will call it `tank`. 86unmounted, let's create the pool. For this example, I will call it
87=tank=.
87 88
88```sh 89#+begin_src sh
89sudo zpool create -f -m /mnt/pool tank mirror /dev/sda /dev/sdb 90sudo zpool create -f -m /mnt/pool tank mirror /dev/sda /dev/sdb
90``` 91#+end_src
91 92
92See below for the results of the new ZFS pool named `tank`, with a vdev 93See below for the results of the new ZFS pool named =tank=, with a vdev
93automatically named `mirror-0`. 94automatically named =mirror-0=.
94 95
95```sh 96#+begin_src sh
96> zfs list 97> zfs list
97 98
98NAME USED AVAIL REFER MOUNTPOINT 99NAME USED AVAIL REFER MOUNTPOINT
99tank 396K 7.14T 96K /tank 100tank 396K 7.14T 96K /tank
100``` 101#+end_src
101 102
102```sh 103#+begin_src sh
103> zpool status 104> zpool status
104 105
105 pool: tank 106 pool: tank
106 state: ONLINE 107 state: ONLINE
107config: 108config:
108 109
109 NAME STATE READ WRITE CKSUM 110 NAME STATE READ WRITE CKSUM
110 tank ONLINE 0 0 0 111 tank ONLINE 0 0 0
111 mirror-0 ONLINE 0 0 0 112 mirror-0 ONLINE 0 0 0
112 sda ONLINE 0 0 0 113 sda ONLINE 0 0 0
113 sdb ONLINE 0 0 0 114 sdb ONLINE 0 0 0
114 115
115errors: No known data errors 116errors: No known data errors
116``` 117#+end_src
117 118
118We can also look at the mounted filesystem to see where the pool is mounted and 119We can also look at the mounted filesystem to see where the pool is
119some quick stats. 120mounted and some quick stats.
120 121
121```sh 122#+begin_src sh
122> df -h 123> df -h
123 124
124Filesystem Size Used Avail Use% Mounted on 125Filesystem Size Used Avail Use% Mounted on
125... 126...
126tank 7.2T 128K 7.2T 1% /tank 127tank 7.2T 128K 7.2T 1% /tank
127``` 128#+end_src
128 129
129### Expanding the Pool 130** Expanding the Pool
131If you want to expand this pool, you will need to add a new VDEV to the
132pool. Since I am using 2 disks per VDEV, I will need to add a new 2-disk
133VDEV to the existing =tank= pool.
130 134
131If you want to expand this pool, you will need to add a new VDEV to the pool. 135#+begin_src sh
132Since I am using 2 disks per VDEV, I will need to add a new 2-disk VDEV to the
133existing `tank` pool.
134
135```sh
136sudo zpool add tank mirror /dev/sdX /dev/sdY 136sudo zpool add tank mirror /dev/sdX /dev/sdY
137``` 137#+end_src
138 138
139If you're adding disks of different sizes, you'll need to use the `-f` flag. 139If you're adding disks of different sizes, you'll need to use the =-f=
140Keep in mind that the max size will be limited to the smallest disk added. 140flag. Keep in mind that the max size will be limited to the smallest
141disk added.
141 142
142```sh 143#+begin_src sh
143sudo zpool add -f tank mirror /dev/sdX /dev/sdY 144sudo zpool add -f tank mirror /dev/sdX /dev/sdY
144``` 145#+end_src
145 146
146I added two 8TB hard drives and this process took around 10 seconds to complete. 147I added two 8TB hard drives and this process took around 10 seconds to
148complete.
147 149
148When viewing the pool again, you can see that the pool has now doubled in size. 150When viewing the pool again, you can see that the pool has now doubled
149We have 14.3 TB useable space and the same space used for mirroring. 151in size. We have 14.3 TB useable space and the same space used for
152mirroring.
150 153
151```sh 154#+begin_src sh
152> zfs list 155> zfs list
153 156
154NAME USED AVAIL REFER MOUNTPOINT 157NAME USED AVAIL REFER MOUNTPOINT
155tank 145G 14.3T 104K /tank 158tank 145G 14.3T 104K /tank
156tank/cloud 145G 14.3T 145G /tank/cloud 159tank/cloud 145G 14.3T 145G /tank/cloud
157tank/media 96K 14.3T 96K /tank/media 160tank/media 96K 14.3T 96K /tank/media
158``` 161#+end_src
159
160#### Converting Disks
161 162
162Some disks, such as NTFS-formatted drives, will need to be partitioned and 163*** Converting Disks
163formatted prior to being added to the pool. 164Some disks, such as NTFS-formatted drives, will need to be partitioned
165and formatted prior to being added to the pool.
164 166
165Start by identifying the disks you want to format and add to the pool. 167Start by identifying the disks you want to format and add to the pool.
166 168
167```sh 169#+begin_src sh
168sudo fdisk -l | grep /dev 170sudo fdisk -l | grep /dev
169``` 171#+end_src
170 172
171I am going to format my `/dev/sdc` and `/dev/sdd` disks with the `fdisk` 173I am going to format my =/dev/sdc= and =/dev/sdd= disks with the =fdisk=
172command. 174command.
173 175
174See below for instructions on how to use `fdisk`. Here's what I did to create 176See below for instructions on how to use =fdisk=. Here's what I did to
175basic Linux formatted disks: 177create basic Linux formatted disks:
176 178
177- `g` : Create GPT partition table 179- =g= : Create GPT partition table
178- `n` : Create a new partition, hit Enter for all default options 180- =n= : Create a new partition, hit Enter for all default options
179- `t` : Change partition type to `20` for `Linux filesystem` 181- =t= : Change partition type to =20= for =Linux filesystem=
180- `w` : Write the changes to disk and exit 182- =w= : Write the changes to disk and exit
181 183
182I repeated this process for both disks. 184I repeated this process for both disks.
183 185
184```sh 186#+begin_src sh
185> sudo fdisk /dev/sdc 187> sudo fdisk /dev/sdc
186 188
187Welcome to fdisk (util-linux 2.37.2). 189Welcome to fdisk (util-linux 2.37.2).
@@ -227,95 +229,96 @@ Help:
227 G create a new empty SGI (IRIX) partition table 229 G create a new empty SGI (IRIX) partition table
228 o create a new empty DOS partition table 230 o create a new empty DOS partition table
229 s create a new empty Sun partition table 231 s create a new empty Sun partition table
230``` 232#+end_src
231 233
232Once the drives are formatted, we can add these disks to the pool. 234Once the drives are formatted, we can add these disks to the pool.
233 235
234```sh 236#+begin_src sh
235sudo zpool add tank mirror /dev/sdc /dev/sdd 237sudo zpool add tank mirror /dev/sdc /dev/sdd
236``` 238#+end_src
237 239
238When we list the pool again, we can see that our size is now updated to 240When we list the pool again, we can see that our size is now updated to
239approximately 22TB. This represents my hard drives totalling 45.6TB when 241approximately 22TB. This represents my hard drives totalling 45.6TB when
240shown with `fdisk -l`, with a Raid10 configuration using 22TB for mirroring 242shown with =fdisk -l=, with a Raid10 configuration using 22TB for
241and 22TB of useable space. 243mirroring and 22TB of useable space.
242 244
243```sh 245#+begin_src sh
244> zfs list 246> zfs list
245 247
246NAME USED AVAIL REFER MOUNTPOINT 248NAME USED AVAIL REFER MOUNTPOINT
247tank 145G 21.7T 104K /tank 249tank 145G 21.7T 104K /tank
248tank/cloud 145G 21.7T 145G /tank/cloud 250tank/cloud 145G 21.7T 145G /tank/cloud
249tank/media 145GT 21.7T 96K /tank/media 251tank/media 145GT 21.7T 96K /tank/media
250``` 252#+end_src
251 253
252### Creating Datasets 254** Creating Datasets
255According to
256[[https://docs.oracle.com/cd/E18752_01/html/819-5461/ftyue.html][ZFS
257Terminology]], a =dataset= can refer to “clones, file systems,
258snapshots, and volumes.
253 259
254According to [ZFS 260For this guide, I will use the =dataset= term to refer to file systems
255Terminology](https://docs.oracle.com/cd/E18752_01/html/819-5461/ftyue.html), a 261created under a pool.
256`dataset` can refer to "clones, file systems, snapshots, and volumes.
257 262
258For this guide, I will use the `dataset` term to refer to file systems created 263Within my =tank= pool, I am going to create some datasets to help
259under a pool. 264organize my files. This will give me location to store data rather than
265simply dumping everything at the =/tank/= location.
260 266
261Within my `tank` pool, I am going to create some datasets to help organize my 267#+begin_src sh
262files. This will give me location to store data rather than simply dumping
263everything at the `/tank/` location.
264
265```sh
266sudo zfs create tank/cloud 268sudo zfs create tank/cloud
267sudo zfs create tank/media 269sudo zfs create tank/media
268``` 270#+end_src
269 271
270Once created, you can see these datasets in the output of your pool list: 272Once created, you can see these datasets in the output of your pool
273list:
271 274
272```sh 275#+begin_src sh
273> zfs list 276> zfs list
274NAME USED AVAIL REFER MOUNTPOINT 277NAME USED AVAIL REFER MOUNTPOINT
275tank 752K 7.14T 104K /tank 278tank 752K 7.14T 104K /tank
276tank/cloud 96K 7.14T 96K /tank/cloud 279tank/cloud 96K 7.14T 96K /tank/cloud
277tank/media 96K 7.14T 96K /tank/media 280tank/media 96K 7.14T 96K /tank/media
278``` 281#+end_src
279
280### Creating Snapshots
281 282
282Next, let's create our first snapshot. We can do this by calling the `snapshot` 283** Creating Snapshots
283command and give it an output name. I will be throwing the current date and time 284Next, let's create our first snapshot. We can do this by calling the
284into my example. 285=snapshot= command and give it an output name. I will be throwing the
286current date and time into my example.
285 287
286```sh 288#+begin_src sh
287sudo zfs snapshot tank@$(date '+%Y-%m-%d_%H-%M') 289sudo zfs snapshot tank@$(date '+%Y-%m-%d_%H-%M')
288``` 290#+end_src
289 291
290We can list the snapshots in our pool with the following command: 292We can list the snapshots in our pool with the following command:
291 293
292```sh 294#+begin_src sh
293> zfs list -t snapshot 295> zfs list -t snapshot
294NAME USED AVAIL REFER MOUNTPOINT 296NAME USED AVAIL REFER MOUNTPOINT
295tank@2024-02-06_19-41 0B - 104K - 297tank@2024-02-06_19-41 0B - 104K -
296``` 298#+end_src
297
298### Destroy Snapshots
299 299
300** Destroy Snapshots
300You can always destroy snapshots that are no longer needed: 301You can always destroy snapshots that are no longer needed:
301 302
302```sh 303#+begin_src sh
303sudo zfs destroy tank@2024-02-06_19-41 304sudo zfs destroy tank@2024-02-06_19-41
304``` 305#+end_src
305 306
306Once deleted, they will no longer appear in the list: 307Once deleted, they will no longer appear in the list:
307 308
308```sh 309#+begin_src sh
309> zfs list -t snapshot 310> zfs list -t snapshot
310no datasets available 311no datasets available
311``` 312#+end_src
312
313## My Thoughts on ZFS So Far
314 313
315- I sacrificed 25TB to be able to mirror my data, but I feel more comfortable 314* My Thoughts on ZFS So Far
316 with the potential to save my data by quickly replacing a disk if I need to. 315- I sacrificed 25TB to be able to mirror my data, but I feel more
316 comfortable with the potential to save my data by quickly replacing a
317 disk if I need to.
317- The set-up was surprisingly easy and fast. 318- The set-up was surprisingly easy and fast.
318- Disk I/O is fast as well. I was worried that the data transfer speeds would be 319- Disk I/O is fast as well. I was worried that the data transfer speeds
319 slower due to the RAID configuration. 320 would be slower due to the RAID configuration.
320- Media streaming and transcoding has seen no noticeable drop in performance. 321- Media streaming and transcoding has seen no noticeable drop in
321- My only limitation really is the number of HDD bays in my server HDD cage. 322 performance.
323- My only limitation really is the number of HDD bays in my server HDD
324 cage.
content/blog/2020-12-29-zork.md → blog/zork/index.org renamed +45 −50
@@ -1,25 +1,23 @@
1+++ 1#+title: Zork: Let's Explore a Classic
2date = 2020-12-29 2#+date: 2020-12-29
3title = "Zork: Let's Explore a Classic" 3#+description: A video game review for Zork.
4description = "A video game review for Zork." 4#+filetags: :gaming:
5+++
6
7# Download (Free)
8 5
6* Download (Free)
9Before we get into the game itself, you should know that you can 7Before we get into the game itself, you should know that you can
10download Zork for free from Infocom's [download 8download Zork for free from Infocom's
11page](http://infocom-if.org/downloads/downloads.html). So feel free to 9[[http://infocom-if.org/downloads/downloads.html][download page]]. So
12boot it up and take a ride back to the 1980s with this masterpiece. 10feel free to boot it up and take a ride back to the 1980s with this
13 11masterpiece.
14# Game Description 12
15 13* Game Description
16Zork is an interactive, text-based computer game originally released in 14Zork is an interactive, text-based computer game originally released
171980. This series, split into three separate games, introduced a robust 15in 1980. This series, split into three separate games, introduced a
18and sophisticated text parser to gamers. People were largely used to the 16robust and sophisticated text parser to gamers. People were largely used
19simple commands used in the popular game [Colossal Cave 17to the simple commands used in the popular game
20Adventure](https://en.wikipedia.org/wiki/Colossal_Cave_Adventure), but 18[[https://en.wikipedia.org/wiki/Colossal_Cave_Adventure][Colossal Cave
21Zork allowed users to send more complex commands that included 19Adventure]], but Zork allowed users to send more complex commands that
22prepositions and conjunctions. 20included prepositions and conjunctions.
23 21
24Zork tracks your score as you explore the map, find tools, and collect 22Zork tracks your score as you explore the map, find tools, and collect
25trophy items (e.g., a jewel-encrusted egg). When you place your trophy 23trophy items (e.g., a jewel-encrusted egg). When you place your trophy
@@ -28,59 +26,57 @@ points. Collecting the Twenty Treasures of Zork and placing them within
28the trophy case wins the game. However, you must explore the map, solve 26the trophy case wins the game. However, you must explore the map, solve
29puzzles, and avoid being eaten by a grue to collect these treasures. 27puzzles, and avoid being eaten by a grue to collect these treasures.
30 28
31# The Map 29* The Map
32
33Since Zork is a vast and complex game, it helps to have a map as you 30Since Zork is a vast and complex game, it helps to have a map as you
34explore and collect your trophies. However, if you want to play the game 31explore and collect your trophies. However, if you want to play the game
35as it was truly intended, you should try to play it without using the 32as it was truly intended, you should try to play it without using the
36map. 33map.
37 34
38![Zork Map](https://img.cleberg.net/blog/20201229-zork/zork_map.png) 35#+caption: Zork Map
39 36[[https://img.cleberg.net/blog/20201229-zork/zork_map.png]]
40*[Map Source](https://www.filfre.net/2012/01/exploring-zork-part-1/)*
41 37
42# In-Game Screenshots 38/[[https://www.filfre.net/2012/01/exploring-zork-part-1/][Map Source]]/
43 39
40* In-Game Screenshots
44After playing the game (for the first time ever) for several weeks 41After playing the game (for the first time ever) for several weeks
45around 2014, I was finally able to beat the game with some online help 42around 2014, I was finally able to beat the game with some online help
46to find the last couple items. As I was writing this post, I installed 43to find the last couple items. As I was writing this post, I installed
47the game again to grab some screenshots to show off the true glory of 44the game again to grab some screenshots to show off the true glory of
48this game. As noted in [Jimmy Maher's 45this game. As noted in
49playthrough](https://www.filfre.net/2012/01/exploring-zork-part-1/), the 46[[https://www.filfre.net/2012/01/exploring-zork-part-1/][Jimmy Maher's
50original Zork games looked quite a bit different due to the older 47playthrough]], the original Zork games looked quite a bit different due
51hardware of computers like the Apple II and multiple bug fixes that 48to the older hardware of computers like the Apple II and multiple bug
52Infocom pushed out after the game's initial release. My play-through 49fixes that Infocom pushed out after the game's initial release. My
53uses the [Zork 50play-through uses the
54Anthology](https://store.steampowered.com/app/570580/Zork_Anthology/) 51[[https://store.steampowered.com/app/570580/Zork_Anthology/][Zork
55version, which utilizes DOSBox on Windows. 52Anthology]] version, which utilizes DOSBox on Windows.
56 53
57The first screenshot here shows the introductory information, which 54The first screenshot here shows the introductory information, which
58doesn't include instructions of any kind for the player. If you 55doesn't include instructions of any kind for the player. If you haven't
59haven't played text adventures before, try to use simple commands like 56played text adventures before, try to use simple commands like "go
60"go west," "look around," or "hit troll with elvish sword." 57west," "look around," or "hit troll with elvish sword."
61 58
62![Zork Screen, pt. 59#+caption: Zork Screen, pt. 1
631](https://img.cleberg.net/blog/20201229-zork/zork_01.png) 60[[https://img.cleberg.net/blog/20201229-zork/zork_01.png]]
64 61
65In this second screenshot, we see the player has entered the house and 62In this second screenshot, we see the player has entered the house and
66found the trophy case in the living room. The lantern and sword in this 63found the trophy case in the living room. The lantern and sword in this
67room allow the player to explore dark areas and attack enemies. If you 64room allow the player to explore dark areas and attack enemies. If you
68don't use the lantern, you won't be able to see anything in dark 65don't use the lantern, you won't be able to see anything in dark areas,
69areas, and you may be eaten by a grue. 66and you may be eaten by a grue.
70 67
71![Zork Screen, pt. 68#+caption: Zork Screen, pt. 2
722](https://img.cleberg.net/blog/20201229-zork/zork_02.png) 69[[https://img.cleberg.net/blog/20201229-zork/zork_02.png]]
73 70
74Finally, we see that the player has found the first treasure: a 71Finally, we see that the player has found the first treasure: a
75jewel-encrusted egg. These treasures can be taken back to the house and 72jewel-encrusted egg. These treasures can be taken back to the house and
76placed in the trophy case or carried until you feel like you want to put 73placed in the trophy case or carried until you feel like you want to put
77things away. 74things away.
78 75
79![Zork Screen, pt 76#+caption: Zork Screen, pt 3.
803.](https://img.cleberg.net/blog/20201229-zork/zork_03.png) 77[[https://img.cleberg.net/blog/20201229-zork/zork_03.png]]
81
82# Conclusion
83 78
79* Conclusion
84It's been quite a few years since I first played Zork, but I clearly 80It's been quite a few years since I first played Zork, but I clearly
85remember the late nights and bloodshot eyes that helped me find all the 81remember the late nights and bloodshot eyes that helped me find all the
86treasures. This game is well worth the time and effort, even though the 82treasures. This game is well worth the time and effort, even though the
@@ -90,6 +86,5 @@ and skills learned in early video games like Zork can actually help you,
90even when playing newer games. 86even when playing newer games.
91 87
92If you do decide to play Zork, you can download Zork I, II, and III from 88If you do decide to play Zork, you can download Zork I, II, and III from
93Infocom's [download 89Infocom's [[http://infocom-if.org/downloads/downloads.html][download
94page](http://infocom-if.org/downloads/downloads.html) for free or search 90page]] for free or search the internet for an online version.
95the internet for an online version.
build.sh deleted −6
@@ -1,6 +0,0 @@
1#!/bin/sh
2
3cd ~/Source/cleberg.net/
4rm -rf public/
5zola build --force
6scp -r public/* ubuntu:/var/www/cleberg.net/
config.toml deleted −21
@@ -1,21 +0,0 @@
1# Site settings
2base_url = "https://cleberg.net"
3title = "cleberg.net"
4description = "The personal website of Christian Cleberg."
5author = "Christian Cleberg"
6
7# RSS settings
8generate_feed = true
9feed_filename = "atom.xml"
10
11[markdown]
12highlight_code = true
13highlight_theme = "css"
14highlight_themes_css = [
15 { theme = "ayu-dark", filename = "syntax-theme-dark.css" },
16 { theme = "inspired-github", filename = "syntax-theme-light.css" },
17]
18external_links_target_blank = true
19lazy_async_image = true
20
21[extra]
content/blog/2018-12-08-aes-encryption.md deleted −124
@@ -1,124 +0,0 @@
1+++
2date = 2018-12-08
3title = "AES Encryption"
4description = "An exploration of the AES encryption standard."
5+++
6
7# Basic AES
8
9If you're not familiar with encryption techniques,
10[AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) is the
11**Advanced Encryption Standard**. This specification was established by
12the National Institute of Standards and Technology, sub-selected from
13the Rijndael family of ciphers (128, 192, and 256 bits) in 2001.
14Furthering its popularity and status, the US government chose AES as
15their default encryption method for top-secret data, removing the
16previous standard which had been in place since 1977.
17
18AES has proven to be an extremely safe encryption method, with 7-round
19and 8-round attacks making no material improvements since the release of
20this encryption standard almost two decades ago.
21
22> Though many papers have been published on the cryptanalysis of AES,
23> the fastest single-key attacks on round-reduced AES variants [20,
24> 33] so far are only slightly more powerful than those proposed 10
25> years ago [23,24].
26>
27> - [Bogdonav, et
28> al.](http://research.microsoft.com/en-us/projects/cryptanalysis/aesbc.pdf)
29
30# How Secure is AES?
31
32In theory, AES-256 is non-crackable due to the massive number of
33combinations that can be produced. However, AES-128 is no longer
34recommended as a viable implementation to protect important data.
35
36A semi-short [comic
37strip](http://www.moserware.com/2009/09/stick-figure-guide-to-advanced.html)
38from Moserware quickly explains AES for the public to understand.
39Basically AES encrypts the data by obscuring the relationship between
40the data and the encrypted data. Additionally, this method spreads the
41message out. Lastly, the key produced by AES is the secret to decrypting
42it. Someone may know the method of AES, but without the key, they are
43powerless.
44
45To obscure and spread the data out, AES creates a
46substitution-permutation network. Wikipedia has a wonderful [example of
47an SP
48network](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/SubstitutionPermutationNetwork2.png/468px-SubstitutionPermutationNetwork2.png)
49available. This network sends the data through a set of S boxes (using
50the unique key) to substitute the bits with another block of bits. Then,
51a P box will permutate, or rearrange, the bits. This is done over and
52over, with the key being derived from the last round. For AES, the key
53size specifies the number of transformation rounds: 10, 12, and 14
54rounds for 128-bit, 192-bit, and 256-bit keys, respectively.
55
56# The Process
57
581. \*KeyExpansion=: Using [Rijndael's key
59 schedule](https://en.m.wikipedia.org/wiki/Advanced_Encryption_Standard),
60 the keys are dynamically generated.
612. **AddRoundKey**: Each byte of the data is combined with this key
62 using bitwise xor.
633. **SubBytes**: This is followed by the substitution of each byte of
64 data.
654. **ShiftRows**: Then, the final three rows are shifted a certain
66 number of steps, dictated by the cipher.
675. **MixColumns**: After the rows have been shifted, the columns are
68 mixed and combined.
69
70This process does not necessarily stop after one full round. Steps 2
71through 5 will repeat for the number of rounds specified by the key.
72However, the final round excludes the MixColumns step. As you can see,
73this is a fairly complex process. One must have a solid understanding of
74general mathematic principles to fully understand how the sequence works
75(and to even attempt to find a weakness).
76
77According to research done by Bogdanov et al., it would take billions of
78years to brute force a 126-bit key with current hardware. Additionally,
79this brute force attack would require storing 2^88^ bits of data!
80However, there are a few different attacks that have been used to show
81vulnerabilities with the use of this technology. Side-channel attacks
82use inadvertent leaks of data from the hardware or software, which can
83allow attackers to obtain the key or run programs on a user's hardware.
84
85Please note that this is not something you should run out and try to
86implement in your `Hello, World!` app after only a few hours of
87research. While AES (basically all encryption methods) is extremely
88efficient in what it does, it takes a lot of time and patience to
89understand. If you're looking for something which currently implements
90AES, check out the [Legion of the Bouncy
91Castle](https://www.bouncycastle.org/documentation.html) for Java
92implementations of cryptographic algorithms.
93
94# Why Does Encryption Matter?
95
96There are limitless reasons to enable encryption at-rest or in-transit
97for various aspects of your digital life. You can research specific
98examples, such as [Australia passes new law to thwart strong
99encryption](https://arstechnica.com/tech-policy/2018/12/australia-passes-new-law-to-thwart-strong-encryption/).
100However, I will simply list a few basic reasons to always enable
101encryption, where feasible:
102
1031. Privacy is a human right and is recognized as a national right in
104 some countries (e.g., [US Fourth
105 Amendment](https://www.law.cornell.edu/wex/fourth_amendment)).
1062. "Why not?" Encryption rarely affects performance or speed, so
107 there's usually not a reason to avoid it in the first place.
1083. Your digital identity and activity (texts, emails, phone calls,
109 online accounts, etc.) are extremely valuable and can result in
110 terrible consequences, such as identity theft, if leaked to other
111 parties. Encrypting this data prevents such leaks from ruining
112 lives.
1134. Wiping or factory-resetting does not actually wipe all data from the
114 storage device. There are methods to read data from the physical
115 disks/boards inside devices.
1165. Corporations, governments, and other nefarious groups/individuals
117 are actively looking for ways to collect personal information about
118 anyone they can. If someone's data is unencrypted, that person may
119 become a target due to the ease of data collection.
120
121****Read More:****
122
123- [Federal Information Processing Standards Publication
124 197](http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf)
content/blog/2019-12-03-the-ansoff-matrix.md deleted −205
@@ -1,205 +0,0 @@
1+++
2date = 2019-12-03
3title = "The Ansoff Matrix"
4description = "Learn about the Ansoff Matrix, a strategic management tool."
5+++
6
7# Overview
8
9As the world of business evolves, managers must approach business
10planning and strategy with a contemporary mindset. According to Dess,
11McNamara, Eisner, and Lee, managers must be willing to adapt to the
12modern business environment by going beyond "'incremental
13management', whereby they view their job as making a series of small,
14minor changes to improve the efficiency of the firm's operations"[^1].
15
16One reason that strategic management is crucial is because most
17businesses that fail in the United States each year fail due to a lack
18of strategic focus or direction[^2]. The rate of failure for businesses
19with poor strategies shows that strategic planning and management are
20crucial to a business's strength and longevity, injecting the critical
21factors of growth and direction into a company's business plan.
22
23One of the most significant strategic planning and management frameworks
24that companies can use is the [Ansoff
25Matrix](https://en.wikipedia.org/wiki/Ansoff_matrix). While this
26framework has unique purposes and use-cases, it can effectively help an
27organization grow and compete. Specifically, the Ansoff matrix is one of
28the most effective frameworks for companies who want to focus on
29increasing sales revenue or profitability[^3].
30
31This framework uses a two-by-two figure to show the four strategic
32options for companies to use in this framework: market penetration,
33market development, product development, and diversification (see
34**Figure 1**). The x-axis of the matrix focuses on the firm's markets
35and also determines if the firm is looking to enter new markets or
36innovate in its current markets. The y-axis of the matrix focuses on the
37firm's products and determines if the firm wants to pursue strategies
38around their existing products or explore new products.
39
40![The Ansoff Matrix by JaisonAbeySabu, Own work, CC BY-SA
413.0](https://img.cleberg.net/blog/20191203-the-ansoff-matrix/ansoff_matrix-min.png)
42
43# Strategic Options
44
45## Market Penetration
46
47The most straightforward strategy in the Ansoff matrix is to focus on
48existing products in existing markets, also known as market
49penetration[^4]. Companies such as Coca-Cola have used market
50penetration successfully by investing a lot of money to get further
51value out of their current markets. Coca-Cola does this by introducing
52new features such as Christmas-themed bottles, personal names on the
53bottles, and other marketing schemes.
54
55## Market Development
56
57Market development extends existing products into new markets in an
58attempt to increase the number of buyers. One interesting way that
59Coca-Cola used this strategy comes from the stigma that Diet Coke is a
60woman's drink[^5]. Coca-Cola introduced Coca-Cola Zero, which contained
61the same nutritional content as Diet Coke, but was packaged in a dark
62black can to appear more "manly"[^6].
63
64## Product Development
65
66Product development uses existing markets to introduce new products so
67that the firm can better meet customer needs[^7]. The extreme end of
68diversification is home to companies such as Johnson & Johnson, a
69healthcare company that has developed a business portfolio of more than
7060,000 different products[^8]. Johnson & Johnson's dedication to
71continuous diversification has led them to a balance sheet rating of
72"AAA", industry recognition for diversification, and increases in
73their investor dividends for 57 consecutive years[^9].
74
75## Related Diversification
76
77Diversification, the final strategy of the Ansoff Matrix, is more
78difficult than the others since it involves exploring both new markets
79and new products. Related diversification is a diversification strategy
80that closely relates to the firm's core business. Coca-Cola's best
81example of related diversification is its acquisition of Glaceau and
82Vitamin Water, which expanded their drinking lines of business[^10].
83
84## Unrelated Diversification
85
86Unrelated diversification is a diversification strategy that does not
87really relate to the firm's core business but still diversifies their
88business portfolio. A good example of this would be a coffee company who
89has decided to enter the market for bicycle sales. The main purpose of
90this strategy is to an extremely diverse company that will not go
91bankrupt if one market goes through difficult times. However, this
92requires a lot of independent skills and heavy investments since the
93company most likely cannot easily transfer knowledge between the markets
94they compete in.
95
96# Requirements for Success
97
98To use the Ansoff Matrix framework, managers need to formulate corporate
99goals and objectives. Without goals and direction, management frameworks
100do not present much practical utility. Further, the Ansoff Matrix
101requires the managers involved to make tactical decisions and create a
102path for the company to take toward their goals. Lastly, both the Ansoff
103Matrix needs to consider both internal and external perspectives
104throughout the strategy formulation process.
105
106One interesting probability is that companies will be using multiple
107strategic planning and management frameworks at the same time. While
108this may sound like it could crowd the management process, there are
109numerous reasons to do so. For example, the Ansoff Matrix and the
110Balanced Scorecard are relatively popular, and they cover entirely
111different parts of a company's strategy. Using the results from the
112Balanced Scorecard could inform a company of the potential product and
113market demands, such as from customer or supplier survey results, to
114help the company determine which Ansoff Matrix strategy to pursue.
115However, a combined approach at this level would require mature
116frameworks and focused managers who are able to strategize at a high
117level.
118
119Lastly, it should be noted that the author of the Ansoff matrix, Igor
120Ansoff, often used the term [paralysis by
121analysis](https://en.wikipedia.org/wiki/Analysis_paralysis) to explain
122the mistake of companies who overuse analysis and spend too much time
123planning. Companies need to understand the utility of a strategic
124management framework while ensuring that the company is poised to
125execute as efficiently as they have planned.
126
127# Footnotes
128
129[^1]:
130 ```example
131 Dess, G. G., McNamara, G., Eisner, A. B., Lee, S. H. (2019). Strategic
132 ```
133
134 management: Text & cases, ninth edition. New York, NY: McGraw-Hill
135 Education.
136
137[^2]:
138 ```example
139 Juneja, P. (n.d.). Benefits of strategic management. Management Study
140 ```
141
142 Guide. Retrieved from
143 <https://www.managementstudyguide.com/strategic-management-benefits.htm>.
144
145[^3]:
146 ```example
147 Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing
148 ```
149
150 Concepts. London: Palgrave.
151
152[^4]:
153 ```example
154 Meldrum M., McDonald M. (1995) The Ansoff matrix. In: Key Marketing
155 ```
156
157 Concepts. London: Palgrave.
158
159[^5]:
160 ```example
161 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
162 ```
163
164 Retrieved from
165 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
166
167[^6]:
168 ```example
169 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
170 ```
171
172 Retrieved from
173 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
174
175[^7]:
176 ```example
177 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
178 ```
179
180 Retrieved from
181 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
182
183[^8]:
184 ```example
185 Lemke, T. (2019). The most diversified companies in the stock market. The
186 ```
187
188 balance. Retrieved from
189 <https://www.thebalance.com/the-most-diversified-companies-in-the-stock-market-4169730>.
190
191[^9]:
192 ```example
193 Johnson & Johnson. (2018). 2018 Investor Fact Sheet. [PDF file]. Retrieved
194 ```
195
196 from
197 [http://www.investor.jnj.com/_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000](http://www.investor.jnj.com/\_document/2018-investor-fact-sheet-4-19'id=0000016a-5681-d475-a17f-d78db54a0000).
198
199[^10]:
200 ```example
201 Oakley, T. (2015). Coca-Cola: The Ansoff matrix. The Marketing Agenda.
202 ```
203
204 Retrieved from
205 <https://themarketingagenda.com/2015/03/28/coca-cola-ansoff-matrix/>.
content/blog/2019-12-16-password-security.md deleted −126
@@ -1,126 +0,0 @@
1+++
2date = 2019-12-16
3title = "Password Security"
4description = "Password security basics."
5+++
6
7# Users
8
9## Why Does It Matter?
10
11Information security, including passwords and identities, has become one
12of the most important digital highlights of the last decade. With
13[billions of people affected by data breaches each
14year](https://www.usatoday.com/story/money/2018/12/28/data-breaches-2018-billions-hit-growing-number-cyberattacks/2413411002/),
15there's a greater need to introduce strong information security
16systems. If you think you've been part of a breach, or you want to
17check and see, you can use [Have I Been
18Pwned](https://haveibeenpwned.com/) to see if your email has been
19involved in any public breaches. Remember that there's a possibility
20that a company experienced a breach and did not report it to anyone.
21
22## How Do I Protect Myself?
23
24The first place to start with any personal security check-up is to
25gather a list of all the different websites, apps, or programs that
26require you to have login credentials. Optionally, once you know where
27your information is being stored, you can sort the list from the
28most-important items such as banks or government logins to less
29important items such as your favorite meme site. You will want to ensure
30that your critical logins are secure before getting to the others.
31
32Once you think you have a good idea of all your different authentication
33methods, I recommend using a password manager such as
34[Bitwarden](https://bitwarden.com/). Using a password manager allows you
35to automatically save your logins, create randomized passwords, and
36transfer passwords across devices. However, you'll need to memorize
37your "vault password" that allows you to open the password manager.
38It's important to make this something hard to guess since it would
39allow anyone who has it to access every password you've stored in
40there.
41
42Personally, I recommend using a
43[passphrase](https://en.wikipedia.org/wiki/Passphrase) instead of a
44[password](https://en.wikipedia.org/wiki/Password) for your vault
45password. Instead of using a string of characters (whether random or
46simple), use a phrase and add in symbols and a number. For example, your
47vault password could be `Racing-Alphabet-Gourd-Parrot3`. Swap
48the symbols out for whichever symbol you want, move the number around,
49and fine-tune the passphrase until you are confident that you can
50remember it whenever necessary.
51
52Once you've stored your passwords, make sure you continually check up
53on your account and make sure you aren't following bad password
54practices. Krebs on Security has a great [blog post on password
55recommendations](https://krebsonsecurity.com/password-dos-and-donts/).
56Any time that a data breach happens, make sure you check to see if you
57were included, and if you need to reset any account passwords.
58
59# Developers
60
61## What Are the Basic Requirements?
62
63When developing any password-protected application, there are a few
64basic rules that anyone should follow even if they do not follow any
65official guidelines such as NIST. The foremost practice is to require
66users to use passwords that are at least 8 characters and cannot easily
67be guessed. This sounds extremely simple, but it requires quite a few
68different strategies. First, the application should check the potential
69passwords against a dictionary of insecure passwords such
70`password`, `1234abc`, or
71`application_name`.
72
73Next, the application should offer guidance on the strength of passwords
74being entered during enrollment. Further, NIST officially recommends
75**not** implementing any composition rules that make passwords hard to
76remember (e.g. passwords with letters, numbers, and special characters)
77and instead encouraging the use of long pass phrases which can include
78spaces. It should be noted that to be able to keep spaces within
79passwords, all unicode characters should be supported, and passwords
80should not be truncated.
81
82## What Does NIST Recommend?
83
84The National Institute of Standards and Technology
85([NIST](https://www.nist.gov)) in the US Department of Commerce
86regularly publishes information around information security and digital
87identity guidelines. Recently, NIST published [Special Publication
88800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html): Digital
89Identity Guidelines and Authentication and Lifecycle Management.
90
91> A Memorized Secret authenticator - commonly referred to as a password
92> or, if numeric, a PIN - is a secret value intended to be chosen and
93> memorized by the user. Memorized secrets need to be of sufficient
94> complexity and secrecy that it would be impractical for an attacker to
95> guess or otherwise discover the correct secret value. A memorized
96> secret is something you know.
97>
98> - NIST Special Publication 800-63B
99
100NIST offers a lot of guidance on passwords, but I'm going to highlight
101just a few of the important factors:
102
103- Require passwords to be a minimum of 8 characters (6 characters if
104 randomly generated and be generated using an approved random bit
105 generator).
106- Compare potential passwords against a list that contains values
107 known to be commonly-used, expected, or compromised.
108- Offer guidance on password strength, such as a strength meter.
109- Implement a rate-limiting mechanism to limit the number of failed
110 authentication attempts for each user account.
111- Do not require composition rules for passwords and do not require
112 passwords to be changed periodically (unless compromised).
113- Allow pasting of user identification and passwords to facilitate the
114 use of password managers.
115- Allow users to view the password as it is being entered.
116- Use secure forms of communication and storage, including salting and
117 hashing passwords using a one-way key derivation function.
118
119NIST offers further guidance on other devices that require specific
120security policies, querying for passwords, and more. All the information
121discussed so far comes from [NIST
122SP800-63b](https://pages.nist.gov/800-63-3/sp800-63b.html) but NIST
123offers a lot of information on digital identities, enrollment, identity
124proofing, authentication, lifecycle management, federation, and
125assertions in the total [NIST SP800-63 Digital Identity
126Guidelines](https://pages.nist.gov/800-63-3/).
content/blog/2020-01-26-steam-on-ntfs.md deleted −95
@@ -1,95 +0,0 @@
1+++
2date = 2020-01-26
3title = "Linux Gaming Tweak: Steam on NTFS Drives"
4description = "Learn how to fix Steam NTFS issues on Linux."
5+++
6
7# Auto-Mount Steam Drives
8
9![Steam Screenshot](https://img.cleberg.net/blog/20200125-the-best-linux-software/steam.png)
10
11If you want to see how to install Steam on Linux, see my other post:
12[Linux Software](/blog/linux-software/).
13
14Are you having trouble launching games, even though they've installed
15correctly? This may happen if you're storing your games on an
16NTFS-formatted drive. This shouldn't be an issue if you're storing
17your games on the same drive that Steam is on, but some gamers prefer to
18put Steam on their main drive and game files on another SSD or HDD.
19
20To fix this problem, you'll need to try a few things. First, you'll
21need to install the `ntfs-3g` package, which is meant for
22better interoperability with Linux.
23
24```sh
25sudo apt install ntfs-3g
26```
27
28Next, you should set up the `/etc/fstab` file to
29automatically mount your drives on boot. To automatically mount your
30drives when the computer boots up, you'll have to create the folders
31you want to mount your drive to first. I store mine in the
32`/mnt` folder using names that I'll recognize, but you can
33create your folders wherever you want.
34
35```sh
36mkdir /path/to/folder
37```
38
39For example:
40
41```sh
42mkdir /mnt/steam_library
43```
44
45To automatically mount drives upon system boot, you will need to collect
46a few items. The UUID is the identification number connected to
47whichever drive you're using to store Steam games.
48
49Drives are usually labeled similar to `/dev/nvme0n1p1` or
50`/dev/sda1`, so you'll need to find the line in the output
51of the command below that correlates to your drive and copy the UUID
52over to the `/etc/fstab` file.
53
54```sh
55sudo blkid | grep UUID=
56```
57
58Next, you'll need your `uid` and `gid`. To find
59these, run the following command:
60
61```sh
62id -u && id -g
63```
64
65Now that you have collected the necessary information, open the
66`/etc/fstab` file:
67
68```sh
69sudo nano /etc/fstab
70```
71
72Each drive you want to mount on boot should have its own line in the
73`/etc/fstab` file that looks similar to this:
74
75```config
76UUID=B64E53824E5339F7 /mnt/steam_library ntfs-3g uid=1000,gid=1000 0 0
77```
78
79Now all you need to do is unmount your drive and re-mount it. You can
80unmount the drive by doing this (be sure to use the correct drive name
81here):
82
83```sh
84sudo umount /dev/sdxX
85```
86
87You can re-mount all your drives by executing the following:
88
89```sh
90sudo mount -a
91```
92
93If you don't know what your drive name is, or you're nervous about
94unmounting and re-mounting, simply reboot your computer, and it will be
95done for you automatically.
content/blog/2020-05-19-customizing-ubuntu.md deleted −210
@@ -1,210 +0,0 @@
1+++
2date = 2020-05-19
3title = "Beginner's Guide: Customizing Ubuntu"
4description = "A beginner's guide to customizing the Ubuntu operating system."
5+++
6
7# More Information
8
9For inspiration on designing your *nix computer, check out the
10[r/unixporn](https://libredd.it/r/unixporn) subreddit!
11
12# Customizing Ubuntu
13
14New to Linux and want to add a personal touch to your machine? One of
15the best perks of Linux is that it is **extremely** customizable. You
16can change the styles of the windows, shell (status bars/docks), icons,
17fonts, terminals, and more.
18
19In this post, I'm going to go through customization on Ubuntu 20.04
20(GNOME) since most new users tend to choose Ubuntu-based distros. If
21you've found a way to install Arch with i3-gaps, I'm assuming you know
22how to find more advanced tutorials out there on customizations.
23
24## Required Tools
25
26![Gnome
27Tweaks](https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-tweaks-min.png)
28
29Ubuntu 20.04 ships with the default desktop environment
30[Gnome](https://www.gnome.org/), which includes the handy
31`gnome-tweaks` tool to quickly change designs. To install
32this, just open your terminal and enter the following command:
33
34```sh
35sudo apt install gnome-tweaks
36```
37
38After you've finished installing the tool, simply launch the Tweaks
39application, and you'll be able to access the various customization
40options available by default on Ubuntu. You might even like some of the
41pre-installed options.
42
43## GNOME Application Themes
44
45To change the themes applied to applications in GNOME, you will need to
46change the Applications dropdown in the Appearance section of Tweaks. To
47add more themes, you will have to find your preferred theme online and
48follow the steps below to have it show up in the Tweaks tool. While you
49may find themes anywhere, one of the most popular sites for GNOME themes
50is [gnome-look.org](https://www.gnome-look.org/). This website contains
51themes for applications, shells, icons, and cursors.
52
53Steps to import themes into Tweaks:
54
551. Download the theme.
562. These files are usually compressed (.zip, .tar.gz, .tar.xz), so you
57 will need to extract the contents. This is easiest when opening the
58 file explorer, right-clicking the compressed file, and choosing
59 "Extract here."
603. Move the theme folder to `/usr/share/themes/`. You can do
61 so with the following command:
62 `sudo mv theme-folder/ /usr/share/themes/`.
63 - Icons and cursors will be moved to the
64 `/usr/share/icons/` folder.
65 - Fonts will be moved to the `/usr/share/fonts/` folder
66 Alternatively, you can move them to the
67 `/usr/share/fonts/opentype/` or
68 `/usr/share/fonts/opentype/` folders, if you have a
69 specific font type.
704. Close tweaks if it is open. Re-open Tweaks and your new theme will
71 be available in the Applications dropdown in the Appearance section
72 of Tweaks.
73
74If the theme is not showing up after you've moved it into the themes
75folder, you may have uncompressed the folder into a sub-folder. You can
76check this by entering the theme folder and listing the contents:
77
78```sh
79cd /usr/share/themes/Mojave-Dark && ls -la
80```
81
82This is an example of what the contents of your theme folder should look
83like. If you just see another folder there, you should move that folder
84up into the `/usr/share/themes/` folder.
85
86```sh
87cinnamon COPYING gnome-shell gtk-2.0 gtk-3.0 index.theme metacity-1 plank xfwm4
88```
89
90## GNOME Shell Themes
91
92To change the appearance of the title bar, default dock, app menu, and
93other parts of the GNOME shell, you'll need to install the [user
94themes](https://extensions.gnome.org/extension/19/user-themes/)
95extension on [Gnome Extensions](https://extensions.gnome.org/). To be
96able to install extensions, you will first need to install the browser
97extension that the website instructs you to. See this screenshot for the
98blue box with a link to the extension.
99
100![Gnome
101Extensions](https://img.cleberg.net/blog/20200519-customizing-ubuntu/gnome-extensions-min.png)
102
103After the browser extension is installed, you will need to install the
104native host connector:
105
106```sh
107sudo apt install chrome-gnome-shell
108```
109
110Finally, you can go the [user
111themes](https://extensions.gnome.org/extension/19/user-themes/)
112extension page and click the installation button. This will enable the
113Shell option in Tweaks. Now you can move shell themes to the
114`/usr/share/themes` directory, using the same steps mentioned
115in the previous section, and enable the new theme in Tweaks.
116
117## Icons & Cursors
118
119Icons and cursors are installed exactly the same way, so I'm grouping
120these together in this post. Both of these items will need to follow the
121same process as installing themes, except you will want to move your
122font folders to the `/usr/share/icons/` directory instead.
123
124## Fonts
125
126Fonts are one of the overlooked parts of customization, but a good font
127can make the whole screen look different. For example, I have installed
128the [IBM Plex](https://github.com/IBM/plex/releases) fonts on my system.
129This follows the same process as installing themes, except you will want
130to move your font folders to the `/usr/share/fonts/`
131directory instead.
132
133## Terminal
134
135If you spend a lot of time typing commands, you know how important the
136style and functionality of the terminal is. After spending a lot of time
137using the default GNOME terminal with [unix
138shell](https://en.wikipedia.org/wiki/Bash_(Unix_shell)), I decided to
139try some different options. I ended up choosing
140[Terminator](https://terminator-gtk3.readthedocs.io/en/latest/) with
141[zsh](https://en.wikipedia.org/wiki/Z_shell).
142
143Terminator is great if you need to open multiple terminals at one time
144by simply right-clicking and splitting the screen into as many terminals
145as you want. While this project hasn't been updated in a while, [it is
146coming under new
147development](https://github.com/gnome-terminator/terminator/issues/1).
148However, this terminal is great and I haven't experienced any errors
149yet.
150
151For the shell choice, I decided to choose zsh after trying it out on a
152fresh Manjaro installation. Zsh is great if you like to change the
153themes of your terminal, include icons, or add plugins.
154
155The desktop uses the
156[zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions)
157to suggest past commands as you type. In addition, it suggests
158corrections if you misspell a command. Lastly, it uses the
159`af-magic` theme, which adds dashed lines between commands,
160moving the user@host tag to the right side of the terminal, and changes
161the colors. There are plenty of plugins and themes to choose from. Just
162figure out what you like and add it to your `~/.zshrc` file!
163
164### Steps to Replicate My Terminal
165
166To install zsh on Ubuntu, enter the following command into a terminal:
167
168```sh
169sudo apt install zsh
170```
171
172Then, enter the next command to activate zsh:
173
174```sh
175sudo chsh -s $(which zsh) $(whoami)
176```
177
178To install Terminator on Ubuntu:
179
180```sh
181sudo apt install terminator
182```
183
184To install Oh My Zsh on Ubuntu:
185
186```sh
187sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
188```
189
190To install zsh-autosuggestions via Oh My Zsh:
191
192```sh
193git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
194```
195
196Then, add the following plugin wording to your `~/.zshrc`
197file (the default config usually has the `git` plugin
198activated, so just add any other plugins to the parentheses separated by
199a space):
200
201```sh
202nano ~/.zshrc
203```
204
205```sh
206plugins=(git zsh-autosuggestions)
207```
208
209Finally, you need to log out of your computer and log back in so your
210user shell can refresh.
content/blog/2020-08-22-redirect-github-pages.md deleted −136
@@ -1,136 +0,0 @@
1+++
2date = 2020-08-22
3title = "Redirect GitHub Pages from Subdomain to the Top-Level Domain"
4description = "Learn how to redirect Github pages to the TLD."
5+++
6
7# Short answer
8
9## Step 1
10
11Add a new file CNAME to your GitHub Pages repository containing only one
12line: your top-level domain name. E.g.: `example.com`
13
14## Step 2
15
16[Optional] but highly recommended
17
182.1: Remove all other top-level records (prefixed with @) of type A from
19your DNS configuration.
20
212.2: Remove a CNAME record for the second-level domain www if it is
22present.
23
24## Step 3
25
26Add these 5 entries to the very top of your DNS configuration:
27
28```txt
29@ A 185.199.108.153
30@ A 185.199.109.153
31@ A 185.199.110.153
32@ A 185.199.111.153
33www CNAME your_github_username.github.io.
34```
35
36Replace `your_github_username` with your actual GitHub
37username.
38
39## Step 4
40
41Wait for your DNS changes to propagate. DNS changes aren't effective
42immediately. They can take up to a full day to propagate.
43
44# Long answer
45
46This issue has two sides. One is the DNS configuration itself. Another
47one is the way GitHub Pages will forward HTTP requests.
48
49We need to know a few things to understand what GitHub is trying to say
50in their documentation.
51
52## DNS Entry Types
53
54There are two types of DNS records which interest us: CNAME and A.
55
56`A` is also known as `Apex` or sometimes as
57`root entry`. It forwards requests to a specified fixed IP
58address. `CNAME` entry forwards requests to a specified URL
59(actual valid plain text URL, not an IP address).
60
61## DNS Load balancing
62
63GitHub has one central URL address which accepts all DNS requests for
64GitHub Pages: `http://username.github.io`. That URL is
65resolved to different IP addresses based on your geographical location.
66Website hosted on GitHub Pages is a simple collection of
67`HTML`, `CSS` and `JS` files. GitHub
68distributes these files to different servers across the globe. So that
69when your browser sends a request from Europe, it receives data from a
70server in Europe. The same is valid for the requests from Asia and the
71USA.
72
73## What GitHub is trying to say
74
75Since `A` records in DNS must contain IP addresses, and they
76must be either `185.199.108.153` or
77`185.199.109.153` or `185.199.110.153` or
78`185.199.111.153`, there is no way to forward requests to a
79server located somewhere in Europe or Asia. Your website hosted at
80GitHub Pages will be downloaded from a central GitHub Pages server.
81There is a minor risk that if GitHub Pages DNS servers
82(`x.x.x.153`) are down for some reason, all custom domains
83which use fixed GitHub Pages IP addresses will not be accessible (their
84DNS requests will not be resolvable).
85
86That is why GitHub strongly suggests to either use a second-level domain
87for your GitHub Pages (e.g. `blog.example.com`) or use a DNS
88service provider that supports a record type `ALIAS` that
89acts as `A` record but forwards request to a URL address
90(e.g. `username.github.io`) instead of a fixed IP address.
91
92## How GitHub Pages treats HTTP requests
93
94After a DNS request for `your_github_username.github.io` is
95resolved into an IP address, e.g. `185.199.108.153` your
96browser sends an HTTP request to that server with an HTTP header
97`Host`. Below are `curl` examples that load the
98same website (these examples might not work if you are behind a proxy
99server):
100
101```sh
102curl --header "Host: your_github_username.github.io" http://185.199.108.153/
103curl --header "Host: www.example.com" http://185.199.108.153/
104curl --header "Host: example.com" http://185.199.108.153/
105```
106
107This way GitHub Pages servers know which user website to serve.
108
109> GitHub Pages server will automatically redirect HTTP requests to the
110> top-level domain if your `CNAME` file contains
111> `example.com` but `www.example.com` is
112> requested.
113>
114> The same is valid if your `CNAME` file contains
115> `www.example.com` but the header `Host` in the
116> `HTTP` request contains `example.com`.
117
118## Why can't I add a `CNAME` record entry that accepts a top-level request (`@`) to my DNS configuration?
119
120Quote from the GitHub Pages documentation:
121
122> Warning: Do not create a CNAME record for your custom apex domain!
123> Doing so may cause issues with other services, such as email, on that
124> domain.
125
126# References:
127
1281. [Setting up a custom domain with GitHub
129 Pages](https://docs.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site)
1302. [My custom domain isn't
131 working](https://docs.github.com/en/github/working-with-github-pages/troubleshooting-custom-domains-and-github-pages)
1323. [Cannot access my GitHub Pages website by IP
133 Address](https://serverfault.com/questions/589370/cannot-access-my-github-pages-website-by-ip-address)
1344. [How do I set up GitHub Pages to redirect DNS requests from a
135 subdomain (e.g. www) to the top-level domain (TLD, Apex
136 record)?](https://stackoverflow.com/questions/23375422/how-do-i-set-up-github-pages-to-redirect-dns-requests-from-a-subdomain-e-g-www)
content/blog/2020-12-28-neon-drive.md deleted −94
@@ -1,94 +0,0 @@
1+++
2date = 2020-12-28
3title = "Neon Drive: A Nostalgic 80s Arcade Racing Game"
4description = "A video game review for Neon Drive."
5+++
6
7# Game Description
8
9[Neon Drive](https://store.steampowered.com/app/433910/Neon_Drive/)
10presents itself as a simple arcade-style game inspired by the arcade
11race games of the 1980s, yet it has managed to take up hours of my life
12without much effort. The game description, directly from the Steam page,
13is intriguing enough to entice anyone who's been looking for a good
14arcade racing game:
15
16> Neon Drive is a slick retro-futuristic arcade game that will make your
17> brain melt. You've been warned. From beautiful cityscapes and ocean
18> roads to exploding enemy spaceships, Neon Drive has it all.
19
20# Gameplay
21
22The game holds true to the
23[retro-futurism](https://en.wikipedia.org/wiki/Retrofuturism) style,
24including chrome female robots, pixelated arcade machines, and
25[outrun](https://teddit.net/r/outrun/) aesthetics.
26
27Each level of the game is shown as a separate arcade machine. Each
28arcade machine lets you play on Normal, Hard, Insane, Practice, and Free
29Run. To beat each arcade, you must reach the end of the level without
30crashing your car into the various obstacles on the course. Basic levels
31let you move left or right to avoid blocks in the road. Later levels put
32you through other tests, such as dodging traffic or blasting asteroids.
33
34The game uses synthwave music to keep you on track to make the correct
35moves by timing the beats of the songs to the correct moves on the
36screen. It reminds me of the early Guitar Hero games, as well as mobile
37apps like VOEZ - repetition and staying on-beat is the only way to win.
38
39# In-Game Screenshots
40
41Taking a look at the main menu, you can see that Neon Drive plays into
42every stereotype you can think of around retro-futuristic, synthwave
43arcades (in a good way).
44
45![Neon Drive
46Menu](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_menu.png)
47
48Once you get into the first level, we see that the choice of car fits
49right in with the stereotypical cars of the 80s, like the
50[DeLorean](https://en.wikipedia.org/wiki/DMC_DeLorean) or the [Ferrari
51F40](https://en.wikipedia.org/wiki/Ferrari_F40). Each new level comes
52with new color schemes and cars, so you should never get tired of the
53aesthetic.
54
55![Neon Drive
56Race](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_race.png)
57
58Personally, I love the orange and blue colors used in level 2:
59
60![Level
612](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_level_2.png)
62
63If you're the competitive type and getting 100% on all arcade machines
64isn't enough, there are leaderboards for the regular part of the game,
65and the endurance game mode.
66
67![Leaderboard](https://img.cleberg.net/blog/20201228-neon-drive/neon_drive_leaderboard.png)
68
69# Other Suggestions
70
71Neon Drive sits nicely within the well-founded cult genre of Outrun.
72Other games that I've enjoyed in this same spectrum are:
73
74- [Far Cry 3: Blood
75 Dragon](https://store.steampowered.com/app/233270/Far_Cry_3__Blood_Dragon/)
76- [Retrowave](https://store.steampowered.com/app/1239690/Retrowave/)
77- [Slipstream](https://store.steampowered.com/app/732810/Slipstream/)
78
79Although these games aren't necessarily in the same genre, they do have
80aspects that place them close enough to interest gamers that enjoyed
81Neon Drive:
82
83- [Black Ice](https://store.steampowered.com/app/311800/Black_Ice/)
84- [Cloudpunk](https://store.steampowered.com/app/746850/Cloudpunk/)
85- [Need for Speed:
86 Heat](https://store.steampowered.com/app/1222680/Need_for_Speed_Heat/)
87- [VirtuaVerse](https://store.steampowered.com/app/1019310/VirtuaVerse/)
88
89Of course, if all you really care about is the arcade aspect of these
90games, you can check out the [Atari
91Vault](https://store.steampowered.com/app/400020/Atari_Vault/) or any of
92the other classic games sold on Steam by companies like Namco, Atari.
93For something like Nintendo, you'd have to settle for buying used
94classic consoles or delve into the world of emulation.
content/blog/2021-10-09-apache-redirect.md deleted −46
@@ -1,46 +0,0 @@
1+++
2date = 2021-10-09
3title = "Apache Redirect HTML Files to a Directory"
4description = "A guide on redirecting HTML files to directory in Apache."
5+++
6
7## The Problem
8
9After recently switching static site generators (SSG), my blog URLs
10changed with no option to preserve the classic `.html`
11extension at the end of my blog post URLs.
12
13I really disliked using my old SSG ([Jekyll](https://jekyllrb.com)) and
14prefer my new tool ([Zola](https://www.getzola.org)) much more, so I was
15determined to figure out a way to get the proper redirect set up so that
16people who find my posts online aren't constantly met by 404 errors.
17
18## The Solution
19
20To solve this problem, I really needed to solve two pieces:
21
221. Redirect all blog post URL requests from
23 `/blog/some-post.html` to `/blog/some-post/`.
242. Ensure that no other `.html` files are redirected, such
25 as `index.html`.
26
27After *a lot* of tweaking and testing, I believe I have finally found
28the solution. The solution is shown below.
29
30```conf
31RewriteEngine On
32RewriteCond %{REQUEST_URI} !\index.html$ [NC]
33RewriteRule ^(.*).html$ https://example.com/$1 [R=301,L]
34```
35
36This piece of code in the Apache `.conf` or
37`.htaccess` file will do the following:
38
391. Turn on the RewriteEngine so that we can modify URLs.
402. Ignore any `index.html` files from the rule we are about
41 to specify.
423. Find any `.html` files within the website directory and
43 redirect it to exclude the file extension.
444. The final piece is adding the trailing slash (`/`) at the
45 end of the URL - you'll notice that I don't have an Apache rule
46 for that since Apache handles that automatically.
content/blog/2022-02-17-exiftool.md deleted −67
@@ -1,67 +0,0 @@
1+++
2date = 2022-02-17
3title = "Stripping Image Metadata with exiftool"
4description = "A simple guide to remove exif data with exiftool."
5+++
6
7## Why Strip Metadata?
8
9Okay, so you want to strip metadata from your photos. Perhaps you take
10pictures of very rare birds, and the location metadata is a gold mine
11for poachers, or perhaps you're just privacy-oriented like me and
12prefer to strip metadata from publicly-available images.
13
14There are various components of image metadata that you may want to
15delete before releasing a photo to the public. Here's an incomplete
16list of things I could easily see just by inspecting a photo on my
17laptop:
18
19- Location (Latitude & Longitude)
20- Dimensions
21- Device Make & Model
22- Color Space
23- Color Profile
24- Focal Length
25- Alpha Channel
26- Red Eye
27- Metering Mode
28- F Number
29
30Regardless of your reasoning, I'm going to explain how I used the
31`exiftool` package in Linux to automatically strip metadata
32from all images in a directory (+ subdirectories).
33
34## Installing `exiftool`
35
36First things first: we need to install the tool. I'm running Debian 11
37on my server (Ubuntu will work the same), so the command is as simple
38as:
39
40```sh
41sudo apt install exiftool
42```
43
44There are different tools that can accomplish the same thing across
45distributions, but I really only care to test out this one package.
46
47## Recursively Strip Data
48
49I actually use this tool extensively to strip any photos uploaded to the
50website that serves all the images for my blog
51(`img.cleberg.net`).
52
53The following command is incredibly useful and can be modified to
54include any image extensions that `exiftool` supports:
55
56```sh
57exiftool -r -all= -ext jpg -ext png /path/to/directory/
58```
59
60See below for the results of my most recent usage of
61`exiftool` after I uploaded the image for this blog post. You
62can see that the command will let you know how many directories were
63scanned, how many images were updated, and how many images were
64unchanged.
65
66![exiftool
67results](https://img.cleberg.net/blog/20220217-stripping-metadata-with-exiftool/exiftool.png)
content/blog/2022-02-20-nginx-caching.md deleted −72
@@ -1,72 +0,0 @@
1+++
2date = 2022-02-20
3title = "Caching Static Content with Nginx"
4description = "Learn how to enable the static content cache in Nginx."
5+++
6
7## Update Your Nginx Config to Cache Static Files
8
9If you run a website on Nginx that serves static content (i.e., content
10that is not dynamic and changing with interactions from the user), you
11would likely benefit from caching that content on the client-side. If
12you're used to Apache and looking for the Nginx equivalent, this post
13should help.
14
15Luckily, setting up the cache is as easy as identifying the file types
16you want to cache and determining the expiration length. To include more
17file types, simply use the bar separator (`|`) and type the
18new file extension you want to include.
19
20```config
21server {
22 ...
23
24 location ~* .(css|js|jpg|jpeg|gif|png|ico)$ {
25 expires 30d;
26 }
27
28 ...
29}
30```
31
32I have seen some people who prefer to set `expires` as
33`365d` or even `max`, but that is only for stable,
34infrequently changing websites. As my site often changes (i.e., I'm
35never content with my website), I need to know that my readers are
36seeing the new content without waiting too long.
37
38So, I went ahead and set the expiration date at `30d`, which
39is short enough to refresh for readers but long enough that
40clients/browsers won't be re-requesting the static files too often,
41hopefully resulting in faster loading times, as images should be the
42only thing slowing down my site.
43
44## Testing Results
45
46To test my changes to the Nginx configuration, I used the [HTTP Header
47Live](https://addons.mozilla.org/en-US/firefox/addon/http-header-live/)
48extension on my Gecko browser and used the sidebar to inspect the
49headers of a recent image from my blog.
50
51In the image below, you can see that the `Cache-Control`
52header is now present and set to 2592000, which is 30 days represented
53in seconds (30 days \_ 24 hours/day \_ 60 minutes/hour \* 60
54seconds/minute = 2,592,000 seconds).
55
56The `Expires` field is now showing 22 March 2022, which is 30
57days from the day of this post, 20 February 2022.
58
59![Image
60Headers](https://img.cleberg.net/blog/20220220-caching-static-content-with-nginx/image_headers.png)
61
62## Caveats
63
64Remember that this caching system is **client-side**, which means that
65content is only cached for as long as a client allows it. For example,
66my browser purges all caches, data, etc. upon exit, so this caching
67policy will only work as long as my browser remains open and running.
68
69If you need to test updates to your site, you'll need to clear the
70cache to see updates for any file extension you configured. This can
71often be done with the `Shift + F5` or `Ctrl + F5`
72key combinations in most browsers.
content/blog/2022-02-22-tuesday.md deleted −41
@@ -1,41 +0,0 @@
1+++
2date = 2022-02-22
3title = "Tuesday"
4description = "2-22-22 || 22-2-22"
5+++
6
7## Tuesday, Twosday
8
9I'm taking a break from my usual technology-related posts and writing
10about something a little more enjoyable today.
11
12Today is Tuesday, February 22nd, 2022. Today is 02-22-2022. Today is
13Twosday.
14
15Let's look at everything that fell in place today:
16
171. Written in the `m-dd-yy` or `dd-m-yy` formats,
18 today is 2-22-22 or 22-2-22, which is a neat little palindrome in
19 either format. (The last ubiquitous six-digit palindrome was
20 1-11-11.)
212. Today is Tuesday, which is why everyone is using the nickname
22 Twosday to call out these similarities.
233. Falling on Tuesday means today is the 2nd day of the week (for most
24 cultures. For the US, it's the 3rd day of the week since we start
25 on Sunday).
264. The only culture I could find with a connection to a `2`
27 is that some Slavic languages derived their version of Tuesday from
28 the Old Church Slavonic word `въторъ`, meaning "the
29 second."
305. Written in the classic monospaced, digital font (think of digital
31 clocks from the 80s/90s), there is nice symmetry to the numbers
32 ([view the image
33 here](https://img.cleberg.net/blog/20220222-tuesday/digital_font.png)!).
346. This one isn't naturally-occurring, but it seems people around the
35 world are celebrating the day. For example, a group is putting
36 together
37 [[<https://www.eventbrite.com/e/2-22-22-a-collective-wedding-ceremony-at-the-state-capitol-tickets-211434605597>][a
38 wedding of 222 couples at the California State Capitol in
39 Sacramento]], concluding at exactly 2:22 PM. These couples will
40 record their marriage dates as 2-22-22 2:22 PM. Tickets were on sale
41 for \$222.22.
content/blog/2022-03-03-financial-database.md deleted −271
@@ -1,271 +0,0 @@
1+++
2date = 2022-03-03
3title = "Maintaining a Personal Financial Database"
4description = "An example project showing to build and maintain a simple financial database."
5+++
6
7## Personal Financial Tracking
8
9For the last 6-ish years, I've tracked my finances in a spreadsheet.
10This is common practice in the business world, but any good dev will
11cringe at the thought of storing long-term data in a spreadsheet. A
12spreadsheet is not for long-term storage or as a source of data to pull
13data/reports.
14
15As I wanted to expand the functionality of my financial data (e.g.,
16adding more reports), I decided to migrate the data into a database. To
17run reports, I would query the database and use a language like Python
18or Javascript to process the data, perform calculations, and visualize
19the data.
20
21## SQLite
22
23When choosing the type of database I wanted to use for this project, I
24was split between three options:
25
261. MySQL: The database I have the most experience with and have used
27 for years.
282. PostgreSQL: A database I'm new to, but want to learn.
293. SQLite: A database that I've used for a couple projects and have
30 moderate experience.
31
32I ended up choosing SQLite since it can be maintained within a single
33`.sqlite` file, which allows me more flexibility for storage
34and backup. I keep this file in my cloud storage and pull it up whenever
35needed.
36
37### GUI Editing
38
39Since I didn't want to try and import 1000--1500 records into my new
40database via the command line, I opted to use [DB Browser for SQLite
41(DB4S)](https://sqlitebrowser.org/) as a GUI tool. This application is
42excellent, and I don't see myself going back to the CLI when working in
43this database.
44
45DB4S allows you to copy a range of cells from a spreadsheet and paste it
46straight into the SQL table. I used this process for all 36 accounts,
471290 account statements, and 126 pay statements. Overall, I'm guessing
48this took anywhere between 4--8 hours. In comparison, it probably took
49me 2-3 days to initially create the spreadsheet.
50
51![DB4S](https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/db4s.png)
52
53### Schema
54
55The schema for this database is actually extremely simple and involves
56only three tables (for now):
57
581. Accounts
592. Statements
603. Payroll
61
62**Accounts**
63
64The Accounts table contains summary information about an account, such
65as a car loan or a credit card. By viewing this table, you can find
66high-level data, such as interest rate, credit line, or owner.
67
68```sql
69CREATE TABLE "Accounts" (
70 "AccountID" INTEGER NOT NULL UNIQUE,
71 "AccountType" TEXT,
72 "AccountName" TEXT,
73 "InterestRate" NUMERIC,
74 "CreditLine" NUMERIC,
75 "State" TEXT,
76 "Owner" TEXT,
77 "Co-Owner" TEXT,
78 PRIMARY KEY("AccountID" AUTOINCREMENT)
79)
80```
81
82**Statements**
83
84The Statements table uses the same unique identifier as the Accounts
85table, meaning you can join the tables to find a monthly statement for
86any of the accounts listed in the Accounts table. Each statement has an
87account ID, statement date, and total balance.
88
89```sql
90CREATE TABLE "Statements" (
91 "StatementID" INTEGER NOT NULL UNIQUE,
92 "AccountID" INTEGER,
93 "StatementDate" INTEGER,
94 "Balance" NUMERIC,
95 PRIMARY KEY("StatementID" AUTOINCREMENT),
96 FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID")
97)
98```
99
100**Payroll**
101
102The Payroll table is a separate entity, unrelated to the Accounts or
103Statements tables. This table contains all information you would find on
104a pay statement from an employer. As you change employers or obtain new
105perks/benefits, just add new columns to adapt to the new data.
106
107```sql
108CREATE TABLE "Payroll" (
109 "PaycheckID" INTEGER NOT NULL UNIQUE,
110 "PayDate" TEXT,
111 "Payee" TEXT,
112 "Employer" TEXT,
113 "JobTitle" TEXT,
114 "IncomeRegular" NUMERIC,
115 "IncomePTO" NUMERIC,
116 "IncomeHoliday" NUMERIC,
117 "IncomeBonus" NUMERIC,
118 "IncomePTOPayout" NUMERIC,
119 "IncomeReimbursements" NUMERIC,
120 "FringeHSA" NUMERIC,
121 "FringeStudentLoan" NUMERIC,
122 "Fringe401k" NUMERIC,
123 "PreTaxMedical" NUMERIC,
124 "PreTaxDental" NUMERIC,
125 "PreTaxVision" NUMERIC,
126 "PreTaxLifeInsurance" NUMERIC,
127 "PreTax401k" NUMERIC,
128 "PreTaxParking" NUMERIC,
129 "PreTaxStudentLoan" NUMERIC,
130 "PreTaxOther" NUMERIC,
131 "TaxFederal" NUMERIC,
132 "TaxSocial" NUMERIC,
133 "TaxMedicare" NUMERIC,
134 "TaxState" NUMERIC,
135 PRIMARY KEY("PaycheckID" AUTOINCREMENT)
136)
137```
138
139### Python Reporting
140
141Once I created the database tables and imported all my data, the only
142step left was to create a process to report and visualize on various
143aspects of the data.
144
145In order to explore and create the reports I'm interested in, I
146utilized a two-part process involving Jupyter Notebooks and Python
147scripts.
148
1491. Step 1: Jupyter Notebooks
150
151 When I need to explore data, try different things, and re-run my
152 code cell-by-cell, I use Jupyter Notebooks. For example, I explored
153 the `Accounts` table until I found the following useful
154 information:
155
156 ```python
157 import sqlite3
158 import pandas as pd
159 import matplotlib
160
161 # Set up database filename and connect
162 db = "finances.sqlite"
163 connection = sqlite3.connect(db)
164 df = pd.read_sql_query("SELECT * FROM Accounts", connection)
165
166 # Set global matplotlib variables
167 %matplotlib inline
168 matplotlib.rcParams['text.color'] = 'white'
169 matplotlib.rcParams['axes.labelcolor'] = 'white'
170 matplotlib.rcParams['xtick.color'] = 'white'
171 matplotlib.rcParams['ytick.color'] = 'white'
172 matplotlib.rcParams['legend.labelcolor'] = 'black'
173
174 # Display graph
175 df.groupby(['AccountType']).sum().plot.pie(title='Credit Line by Account Type', y='CreditLine', figsize=(5,5), autopct='%1.1f%%')
176 ```
177
1782. Step 2: Python Scripts
179
180 Once I explored enough through the notebooks and had a list of
181 reports I wanted, I moved on to create a Python project with the
182 following structure:
183
184 ```txt
185 finance/
186 ├── notebooks/
187 │ │ ├── account_summary.ipynb
188 │ │ ├── account_details.ipynb
189 │ │ └── payroll.ipynb
190 ├── public/
191 │ │ ├── image-01.png
192 │ │ └── image-0X.png
193 ├── src/
194 │ └── finance.sqlite
195 ├── venv/
196 ├── _init.py
197 ├── database.py
198 ├── process.py
199 ├── requirements.txt
200 └── README.md
201 ```
202
203 This structure allows me to:
204
205 1. Compile all required python packages into
206 `requirements.txt` for easy installation if I move to
207 a new machine.
208 2. Activate a virtual environment in `venv/` so I don't
209 need to maintain a system-wide Python environment just for this
210 project.
211 3. Keep my `notebooks/` folder to continuously explore
212 the data as I see fit.
213 4. Maintain a local copy of the database in `src/` for
214 easy access.
215 5. Export reports, images, HTML files, etc. to
216 `public/`.
217
218 Now, onto the differences between the code in a Jupyter Notebook and
219 the actual Python files. To create the report in the Notebook
220 snippet above, I created the following function inside
221 `process.py`:
222
223 ```python
224 # Create summary pie chart
225 def summary_data(accounts: pandas.DataFrame) -> None:
226 accounts_01 = accounts[accounts["Owner"] == "Person01"]
227 accounts_02 = accounts[accounts["Owner"] == "Person02"]
228 for x in range(1, 4):
229 if x == 1:
230 df = accounts
231 account_string = "All Accounts"
232 elif x == 2:
233 df = accounts_01
234 account_string = "Person01's Accounts"
235 elif x == 3:
236 df = accounts_02
237 account_string = "Person02's Accounts"
238 print(f"Generating pie chart summary image for {account_string}...")
239 summary_chart = (
240 df.groupby(["AccountType"])
241 .sum()
242 .plot.pie(
243 title=f"Credit Line by Type for {account_string}",
244 y="CreditLine",
245 autopct="%1.1f%%",
246 )
247 )
248 summary_chart.figure.savefig(f"public/summary_chart_{x}.png", dpi=1200)
249 ```
250
251 The result? A high-quality pie chart that is read directly by the
252 `public/index.html` template I use.
253
254 ![Summary Pie
255 Chart](https://img.cleberg.net/blog/20220303-maintaining-a-personal-financial-database/summary_chart.png)
256
257 Other charts generated by this project include:
258
259 - Charts of account balances over time.
260 - Line chart of effective tax rate (taxes divided by taxable
261 income).
262 - Salary projections and error limits using past income and
263 inflation rates.
264 - Multi-line chart of gross income, taxable income, and net
265 income.
266
267 The best thing about this project? I can improve it at any given
268 time, shaping it into whatever helps me the most for that time. I
269 imagine that I will be introducing an asset tracking table soon to
270 track the depreciating value of cars, houses, etc. Who knows what's
271 next?
content/blog/2022-03-08-plex-migration.md deleted −253
@@ -1,253 +0,0 @@
1+++
2date = 2022-03-08
3title = "Migrating Plex to a New Server (& Nvidia Transcoding)"
4description = "A retrospective on migrating Plex between servers and enabling Nvidia GPU transcoding."
5+++
6
7## Migration Phases
8
9I recently decided to migrate my server from an old OptiPlex desktop
10machine to a custom-built tower with better hardware in every category.
11In order to do this, I would need to properly migrate a full Plex
12installation.
13
14The second part of this migration is that the new server uses an Nvidia
15GPU and does not have any integrated graphics, which requires extra work
16for installation, but provides much better hardware transcoding options
17for Plex.
18
19Therefore, I have broken this migration down into three phases:
20
211. [Configure the New Server](#phase-1-configure-the-new-server)
222. [Migrate Plex Data & Devices](#phase-2-migrate-plex-data-devices)
233. [Configure GPU Transcoding](#phase-3-configure-gpu-transcoding)
24
25------------------------------------------------------------------------
26
27## Phase 1: Configure the New Server
28
29### Choosing an OS
30
31In order to migrate Plex to my new server, I first needed to choose an
32appropriate operating system (OS) and install it on the machine. Given
33that I have encountered numerous issues installing other Linux
34distributions properly with Nvidia graphics, I chose [Ubuntu
35Server](https://ubuntu.com/download/server).
36
37The first step is to create a bootable USB with Ubuntu Server. This is
38easy with [Etcher](https://www.balena.io/etcher/), an app that runs on
39many different platforms. Just download the Ubuntu Server
40`.iso` image, launch Etcher, and install the
41`.iso` on the USB.
42
43Once the USB is created, insert it into my server, reboot, and click
44`Esc` (or any of the `F1-12` keys) until the BIOS
45menu appears. Finally, launch the USB boot drive.
46
47### Booting with Nvidia
48
49In order to install Ubuntu Server with an Nvidia Graphics card (and no
50integrated graphics on this device for some reason), you'll have to
51configure the boot menu to allow different graphics drivers to be
52loaded.
53
54When booting from the USB, the machine will launch the initial
55installation menu. From this menu, type `e` to view the
56default command options that come with the device - it's a good idea to
57take a photo of this screen, so you can enter these commands on the next
58screen (along with adding support for Nvidia).
59
60Finally, type `Ctrl + C` to enter the command line. From this
61command line, enter the commands found on the `e` screen.
62\*Remember to add `nomodeset` to the `linux ...`
63line so that your Nvidia device will display the installation screens
64properly!\*
65
66Here's an example of the commands I pulled from the `e`
67screen and entered on the command line.
68
69```sh
70setparams 'Install Ubuntu Server'
71setgfxpayload=keep
72linux /casper/vmlinuz quiet nomodeset ---
73initrd /casper/initrd
74boot
75```
76
77![Ubuntu Server Installation
7801](https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_01.png)
79
80![Ubuntu Server Installation
8102](https://img.cleberg.net/blog/20220308-plex-media-server-migration/ubuntu_server_installation_02.png)
82
83Once the machine is rebooted, enter the `e` screen again and
84add `nomodeset` to the `linux ...` line again and
85press `Ctrl + X` to save the boot options.
86
87The machine is now fully installed and can properly display on an
88external display using the Nvidia GPU.
89
90Always remember to update and upgrade on a new installation:
91
92```sh
93sudo apt update; sudo apt upgrade -y; sudo apt autoremove -y
94```
95
96------------------------------------------------------------------------
97
98## Phase 2: Migrate Plex Data & Devices
99
100This phase uses the great Plex article on migrations ([Move an
101Installation to Another
102System](https://support.plex.tv/articles/201370363-move-an-install-to-another-system/))
103and adds a bit more information to help with commands and context.
104
105### Terminology
106
107**Source:** The original server that is being replaced.\
108**Destination:** The new server.\
109**Client:** Any application that can be used to modify settings for both
110source/destination.
111
112### Step 01: [Client] Update Settings
113
114Open up a Plex app and *disable* the `Account` \>
115`Library` \>
116`Empty trash automatically after every scan` preference for
117the source server.
118
119### Step 02: [Destination] Install Plex
120
121Open up the [Plex Media Server download
122page](https://www.plex.tv/media-server-downloads/) and copy the link for
123the appropriate platform.
124
125Execute the following commands on the destination server to install
126Plex:
127
128```sh
129wget <url>
130sudo dpkg -i <filename>
131sudo systemctl stop plexmediaserver.service
132```
133
134### Step 03: [Source] Stop Plex & Migrate Data
135
136First, stop the Plex service so that no data is created or modified
137during the migration.
138
139```sh
140sudo systemctl stop plexmediaserver.service
141```
142
143Next, copy the data to the new server. To find where the Plex data
144directory is located, Plex has another excellent article available:
145[Where is the Plex Media Server data directory
146located?](https://support.plex.tv/articles/202915258-where-is-the-plex-media-server-data-directory-located/).
147
148There are many ways to copy the data to the new server and will largely
149depend on the size of the folder being copied. Personally, my data
150folder was \~23GB and I opted to simply use the `scp` command
151to copy the files over SSH.
152
153This process was throttled by the old server's slow HDD and ports and
154took approximately 90 minutes to complete. In comparison, moving the
155data from the new server's `home/user/` directory to the
156`/var/.../Plex Media Server` directory took 2-3 minutes.
157
158```sh
159scp -r "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server" your_user@xxx.xxx.xxx.xxx:"'/path/to/destination/'"
160```
161
162### Step 04: [Destination] Update File Permissions
163
164In case you move the data directory to a common area on the new server,
165it will have to be moved to the proper location before Plex can function
166properly:
167
168```sh
169mv "Plex Media Server" /var/lib/plexmediaserver/Library/Application Support/
170```
171
172To ensure permissions were retained properly, the server will need to
173show that all files and folders in the data directory are owned by
174`plex:plex` (or whichever user is running the Plex
175application).
176
177```sh
178sudo chown -R plex:plex "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server"
179```
180
181Finally, start the service and check the status.
182
183```sh
184sudo systemctl start plexmediaserver.service
185sudo systemctl status plexmediaserver.service
186```
187
188### Step 05: [Client] Update Libraries & Metadata
189
190The first step - now that the new server is up and running - is to sign
191out of the client and sign back in. Once this is done, update any
192library locations, if necessary. This was unnecessary in my case since I
193simply moved my storage drives from the source server to the destination
194server.
195
196Next, perform the following actions in the client:
197
1981. On the left sidebar, click `More` \> Three-Dot Menu \>
199 `Scan Library Files`
2002. *Enable* the `Account` \> `Library` \>
201 `Empty trash automatically after every scan` preference
202 for the source server.
2033. On the left sidebar, click `More` \> Three-Dot Menu \>
204 `Manage Server` \> `Empty Trash`
2054. On the left sidebar, click `More` \> Three-Dot Menu \>
206 `Manage Server` \> `Clean Bundles`
2075. On the left sidebar, click `More` \> Three-Dot Menu \>
208 `Manage Server` \> `Optimize Database`
209
210Finally, double-check the Remote Access settings to make sure no changes
211have caused issues with accessing the server from outside the network.
212
213In my case, I use a single port forwarding rule in my router and needed
214to update the Local LAN IP Address to the new server IP address.
215
216![Router port
217forwarding](https://img.cleberg.net/blog/20220308-plex-media-server-migration/port_forwarding.png)
218
219------------------------------------------------------------------------
220
221## Phase 3: Configure GPU Transcoding
222
223The final piece to the migration is enabling hardware transcoding so
224that Plex can fully utilize the new Nvidia GPU available in the server.
225The first step is to install Nvidia graphics drivers. This process may
226take a few minutes, but the commands are pretty simple:
227
228```sh
229sudo add-apt-repository ppa:graphics-drivers/ppa
230sudo apt update
231sudo apt-get install ubuntu-drivers-common
232sudo ubuntu-drivers autoinstall
233```
234
235Finally, reboot so that the changes are loaded:
236
237```sh
238sudo reboot now
239```
240
241To ensure that the Nvidia graphics drivers are working properly, run the
242following command to view the available GPUs, statistics, and processes:
243
244```sh
245sudo nvidia-smi
246```
247
248![nvidia-smi](https://img.cleberg.net/blog/20220308-plex-media-server-migration/nvidia_smi.png)
249
250Finally, enable hardware transcoding settings in the Plex application:
251
252![Plex transcoding
253settings](https://img.cleberg.net/blog/20220308-plex-media-server-migration/plex_transcoding.png)
content/blog/2022-03-24-server-hardening.md deleted −353
@@ -1,353 +0,0 @@
1+++
2date = 2022-03-24
3title = "Hardening a Public-Facing Home Server"
4description = "Learn some basic techniques to harden a home server and network."
5+++
6
7## Post Updates
8
9> After reviewing this post today (2022-10-04), I noticed quite a few
10> gaps in my write-up and wanted to add a few things, even though this
11> blog is really just a retrospective and knowledge dump for myself. I
12> left things intact and simply crossed them out (~~like this~~) for
13> posterity.
14
15## Planning Data Flows & Security
16
17### My Personal Data Flow
18
19```txt
20 ┌───────┐ ┌─────────────────┐
21 ┌──► VLAN1 ├───► Private Devices │
22 │ └───────┘ └─────────────────┘
23┌──────────┐ ┌────────┐ ┌──────────┐ ┌────────┐ │
24│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
25└──────────┘ └────────┘ └──────────┘ └────────┘ │
26 │ ┌───────┐ ┌───────────────┐
27 └──► VLAN2 ├───► Public Server │
28 └───────┘ └───────────────┘
29```
30
31### Thought Process
32
33To serve content from your home server and harden your security posture,
34you have to think about the transport of data from `server`
35to `client`.
36
37Let's start with the actual server itself. Think about the following:
38
39- Do I have a firewall enabled? Do I need to update this to allow new
40 ports or IPs?
41- Do I have an IPS/IDS that may prevent outside traffic?
42- Do I have any other security software installed?
43- Are the services hosted inside Docker containers, behind a reverse
44 proxy, or virtualized? If so, are they configured to allow outside
45 traffic?
46
47Once the data leaves the server, where does it go? In my case, it goes
48to a managed switch. In this case, I asked the following:
49
50- What configurations is the switch using?
51- Am I using VLANs?
52 - Yes, I am using 802.1Q VLANs.
53- Are the VLANs configured properly?
54 - Yes, as shown in the [Switch](#switch) section below, I have a
55 separate VLAN to allow outside traffic to and from the server
56 alone. No other devices, except for a service port, and in that
57 VLAN.
58
59At this point, the data has been processed through the switch. Where
60does it go next? In my case, it's pretty simple: it goes to the
61router/modem device.
62
63- Does my ISP block any ports that I need?
64 - This is an important step that a lot of people run into when
65 self-hosting at home. Use an online port-checker tool for your
66 IP or call your ISP if you think ports are blocked.
67- Is there a router firewall?
68 - Yes, I checked that it's configured to allow the ports I need
69 to run my services publicly. Common web servers and reverse
70 proxies require ports 80 and 443, but other services like media
71 servers or games can require unique ports, so be sure to check
72 the documentation for your service(s).
73- Are there any other settings affecting inbound/outbound traffic?
74 - Schedules or access blocks
75 - Static Routing
76 - QoS
77 - Port Forwarding
78 - DMZ Hosting
79 - Remote Management (this can sometimes mess with services that
80 also require the use of ports 80 and 443)
81
82Once the data leaves my router, it goes to the upstream ISP and can be
83accessed publicly.
84
85## Server
86
87+The services I run on my server are installed straight into the OS,
88without any use of Docker or VMs, so I don't need any extra application
89configuration to make them accessible to the outside world.+
90
91As of 2022-10-04, the paragraph above is no longer true as I now run a
92reverse proxy with Nginx and host many services inside Docker. However,
93it doesn't change anything regarding this post as I still just need to
94open ports 80 & 443 and create the necessary website configuration
95files.
96
97When creating new services - either installed directly on bare metal or
98within something like Docker - I ensure that I read through the
99documentation thoroughly to understand a few key things: - What network
100activities should this app perform (if any)? Using which ports and
101protocols? - Does this app require any commands/services to be run as
102`root`? - Does this app log errors, authentication
103failures/successes, or anything else that would be useful for an
104investigation?
105
106For extra security, I use limit all incoming connections to SSH
107connections through my server firewall (`ufw`) and disable
108common SSH settings. After all of that, I use `fail2ban` as a
109preventative measure against brute-force login attempts.
110
111As another piece of security, you can randomize your SSH port to ensure
112that random scanners or attackers can't easily try to force their way
113into your network. For example, you can edit the port rules in your
114server to block all connection requests to port `22` but
115forward all remote connections from port `12345` to your
116server's port `22`. Then you just need to SSH to your
117network via your randomized port.
118
119### `ufw`
120
121To see how to configure `ufw`, see my other post: [Secure
122Your Network with the Uncomplicated
123Firewall](/blog/secure-your-network-with-the-uncomplicated-firewall/).
124
125The general notion with an on-device firewall is that you want to deny
126all incoming connections by default and then selectively open certain
127ports for services or users that you know need access.
128
129If you know that you will only be logging into this server from a
130certain set or list of IPs, you can always set the firewall to only
131allow connections to port 22 from those IPs.
132
133For a quick start to only allow SSH connections to the server, use this:
134
135```sh
136sudo ufw default deny incoming
137sudo ufw default allow outgoing
138sudo ufw allow 22
139sudo ufw enable
140```
141
142![ufw](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png)
143
144### `ssh`
145
1461. Using SSH Keys
147
148 First, make sure you have an SSH keypair generated on the device(s)
149 that you'll be using to log in to the server. If you don't have an
150 SSH key, run this command:
151
152 ```sh
153 ssh-keygen
154 ```
155
156 Now that we have an SSH key, copy it to the server with the
157 following command, which will ask for the user's password before
158 accepting the key:
159
160 ```sh
161 ssh-copy-id my_user@my_server
162 ```
163
164 If you have multiple keys, you'll need to specify which to use.
165 After it's complete, `ssh` back into the server as that
166 user and make sure it doesn't ask for a password.
167
1682. Disable Password & Root Authentication
169
170 Now that we can access the server without a password, we will
171 disable password authentication and disable anyone from using
172 `ssh` to login as `root`.
173
174 To do this, open the `sshd_config` file:
175
176 ```sh
177 sudo nano /etc/ssh/sshd_config
178 ```
179
180 You'll need to update the parameters to the values below. If one of
181 these rules is commented-out or doesn't exist, create the rule at
182 the bottom of the file.
183
184 ```config
185 PermitRootLogin no
186 PasswordAuthentication no
187 PubkeyAuthentication yes
188 ```
189
190 Finally, restart the `ssh` service:
191
192 ```sh
193 sudo systemctl restart sshd.service
194 ```
195
196 To test that everything's working so far, open ANOTHER terminal and
197 try logging in as `root` over SSH. It is very important
198 that you keep your current SSH session open and test with an
199 additional session, or you will lock yourself out at some point and
200 will need to use a recovery method (e.g., hooking monitor up to home
201 server) to get yourself back in.
202
2033. Enable MFA for `ssh`
204
205 This part is optional, but I highly recommend it. So far, we've
206 ensured that no one can log into our user on the server without
207 using our secret key, and we've ensured that no one can log in
208 remotely as `root`. Next, you can enable MFA
209 authentication for `ssh` connections.
210
211 This process involves editing a couple files and installing an MFA
212 package, so I will not include all the details in this post. To see
213 how to configure MFA for `ssh`, see my other post:
214 [Enabling MFA for SSH](/blog/enable-totp-mfa-for-ssh/).
215
216 ![SSH
217 MFA](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png)
218
219### `fail2ban`
220
221I haven't written a post on how I use `fail2ban`, but it's
222quite simple. I use the default `sshd` jail, but you can
223always create new jails for respective applications or ports. For
224example, if you use Nginx as your web server, you can use the
225`nginx-http-auth` jail.
226
227In order to get it up and running, use the following commands:
228
229```sh
230sudo apt install fail2ban
231sudo fail2ban-client start sshd
232sudo fail2ban-client status sshd
233```
234
235This should be used as a last-resort defense and shouldn't be a
236replacement for the security measures mentioned above.
237
238![fail2ban](https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png)
239
240## Switch
241
242Between the router and any local devices is my managed switch, which is
243used to create VLANs. The example below shows how I would isolate the
244VLANs if I were starting to host a single service at home.
245
246### 802.1Q VLAN Configuration
247
248In this configuration, port 8 is the public server that needs to be
249accessed from the outside. Port 23 is my 'dedicated service port' for
250this server. In order to SSH to this server, I need to plug my laptop
251into port 23 or else I cannot SSH. Otherwise, I'd need to hook up a
252monitor and keyboard directly to the server to manage it.
253
254 VLAN ID VLAN Name Member Ports Tagged Ports Untagged Ports
255 --------- ----------- -------------- -------------- ----------------
256 1 Default 1-24 1-24
257 2 Server 1,8,23 1,8,23
258
259### 802.1Q VLAN PVID Setting
260
261Once the VLAN is created, I simply add the `VLAN ID` of
262`2` as the `PVID` for any related ports (in this
263case, see that ports `8` and `23` have a PVID of
264`2`).
265
266 Port PVID
267 ------ ------
268 1 1
269 2 1
270 3 1
271 4 1
272 5 1
273 6 1
274 7 1
275 8 2
276 9 1
277 10 1
278 11 1
279 12 1
280 13 1
281 14 1
282 15 1
283 16 1
284 17 1
285 18 1
286 19 1
287 20 1
288 21 1
289 22 1
290 23 2
291 24 1
292
293## Router
294
295On my router, the configuration was as easy as opening the firewall
296settings and unblocking the ports I needed for my services (e.g.,
297HTTP/S, Plex, SSH, MySQL, etc.).
298
299+Since I'm relying on an ISP-provided modem/router combo for now (not
300by choice), I do not use any other advanced settings on my router that
301would inhibit any valid traffic to these services.+
302
303The paragraph above regarding the ISP-owned router is no longer accurate
304as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within
305this router, I enabled port forwarding/firewall rules, segregate the
306network based on the device, and enable traffic restrictions (e.g.,
307silently drop traffic from certain countries and threat categories).
308
309If you have the option with your ISP, I recommend using a personal
310router with software that you are familiar with so that you can explore
311all the options available to you.
312
313## Physical Security
314
315One large piece of self-hosting that people generally don't discuss
316online is physical security. However, physical security is very
317important for everyone who hosts a server like this. Exactly *how*
318important it is depends on the server use/purpose.
319
320If you self-host customer applications that hold protected data (HIPAA,
321GDPR, COPPA, etc.), then physical security is extremely important and
322cannot be ignored. If you simply host a blog and some hobby sites, then
323it's a relatively minor consideration, but one you still need to think
324about.
325
326### Location
327
328The first consideration is quite simple: location. - Is the server
329within a property you own or housed on someone else's property? - Is it
330nearby (in your house, in your work office, in your neighbor's garage,
331in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are
332there climate considerations, such as humidity, fires, tornadoes,
333monsoons? - Do you have emergency equipment nearby in case of emergency?
334
335### Hardware Ownership
336
337Secondly, consider the hardware itself: - Do you own the server in its
338entirety? - Are any other users able to access the server, even if your
339data/space is segregated? - If you're utilizing a third party, do they
340have any documentation to show responsibility? This could be a SOC 1/2/3
341report, ISO compliance report, internal security/safety documentation.
342
343### Physical Controls
344
345Regardless of who owns the hardware, ensure that there are adequate
346safeguards in place, if necessary. These usually don't apply to small
347home servers and are usually covered already if you're utilizing a
348third party.
349
350These can include: - Server bezel locks - Server room locks - physical,
351digital, or biometric authentication - Security cameras - Raised
352floors/lowered ceilings with proper guards/gates in-place within the
353floors or ceilings - Security personnel - Log sheets and/or guest badges
content/blog/2022-06-24-fedora-i3.md deleted −158
@@ -1,158 +0,0 @@
1+++
2date = 2022-06-24
3title = "Rebooting My Love Affair with Linux"
4description = "A retrospective on moving from macOS to Linux."
5+++
6
7## Leaving macOS
8
9As I noted [in a recent post](/blog/foss-macos-apps), I have been
10planning on migrating from macOS back to a Linux-based OS. I am happy to
11say that I have finally completed my migration and am now stuck in the
12wonderful world of Linux again.
13
14My decision to leave macOS really came down to just a few important
15things:
16
17- Apple Security (Gatekeeper) restricting me from running any software
18 I want. Even if you disable Gatekeeper and allow software to bypass
19 the rest of the device installation security, you still have to
20 repeat that process every time the allowed software is updated.
21- macOS sends out nearly constant connections, pings, telemetry, etc.
22 to a myriad of mysterious Apple services. I'm not even going to
23 dive into how many macOS apps have constant telemetry on, as well.
24- Lastly, I just *really* missed the customization and freedom that
25 comes with Linux. Being able to switch to entirely new kernel, OS,
26 or desktop within minutes is a freedom I took for granted when I
27 switched to macOS.
28
29Now that I've covered macOS, I'm going to move on to more exciting
30topics: my personal choice of OS, DE, and various customizations I'm
31using.
32
33## Fedora
34
35After trying a ton of distros (I think I booted and tested around 20-25
36distros), I finally landed on [Fedora Linux](https://getfedora.org/). I
37have quite a bit of experience with Fedora and enjoy the
38`dnf` package manager. Fedora allows me to keep up-to-date
39with recent software (I'm looking at you, Debian), but still provides a
40level of stability you don't find in every distro.
41
42In a very close second place was Arch Linux, as well as its spin-off:
43Garuda Linux (Garuda w/ sway is *beautiful*). Arch is great for
44compatibility and the massive community it has, but I have just never
45had the time to properly sit down and learn the methodology behind their
46packaging systems.
47
48Basically, everything else I tested was unacceptable in at least one way
49or another. Void (`glibc`) was great, but doesn't support
50all the software I need. Slackware worked well as a tui, but I wasn't
51skilled enough to get a tiling window manager (WM) working on it.
52
53### i3
54
55One of the reasons I settled on Fedora is that it comes with an official
56i3 spin. Being able to use a tiling WM, such as i3 or sway, is one of
57the biggest things I wanted to do as soon as I adopted Linux again.
58
59I will probably set up a dotfile repository soon, so that I don't lose
60any of my configurations, but nothing big has been configured thus far.
61
62The two main things I have updated in i3wm are natural scrolling and
63binding my brightness keys to the `brightnessctl` program.
64
651. Natural Scrolling
66
67 You can enable natural scrolling by opening the following file:
68
69 ```sh
70 sudo nano /usr/share/X11/xorg.conf.d/40-libinput.conf
71 ```
72
73 Within the `40-libinput.conf` file, find the following
74 input sections and enable the natural scrolling option.
75
76 This is the `pointer` section:
77
78 ```conf
79 Section "InputClass"
80 Identifier "libinput pointer catchall"
81 MatchIsPointer "on"
82 MatchDevicePath "/dev/input/event*"
83 Driver "libinput"
84 Option "NaturalScrolling" "True"
85 EndSection
86 ```
87
88 This is the `touchpad` section:
89
90 ```conf
91 Section "InputClass"
92 Identifier "libinput touchpad catchall"
93 MatchIsTouchpad "on"
94 MatchDevicePath "/dev/input/event*"
95 Driver "libinput"
96 Option "NaturalScrolling" "True"
97 EndSection
98 ```
99
1002. Enabling Brightness Keys
101
102 Likewise, enabling brightness key functionality is as simple as
103 binding the keys to the `brightnessctl` program.
104
105 To do this, open up your i3 config file. Mine is located here:
106
107 ```sh
108 nano /home/<my-user>/.config/i3/config
109 ```
110
111 ```conf
112 # Use brightnessctl to adjust brightness.
113 bindsym XF86MonBrightnessDown exec --no-startup-id brightnessctl --min-val=2 -q set 3%-
114 bindsym XF86MonBrightnessUp exec --no-startup-id brightnessctl -q set 3%+
115 ```
116
1173. `polybar`
118
119 Instead of using the default `i3status` bar, I have opted
120 to use `polybar` instead (as you can also see in the
121 screenshot above).
122
123 My config for this menu bar is basically just the default settings
124 with modified colors and an added battery block to quickly show me
125 the machine's battery info.
126
1274. `alacritty`
128
129 Not much to say on this part yet, as I haven't configured it much,
130 but I installed `alacritty` as my default terminal, and I
131 am using `zsh` and the shell.
132
133## Software Choices
134
135Again, I'm not going to say much that I haven't said yet in other blog
136posts, so I'll just do a quick rundown of the apps I installed
137immediately after I set up the environment.
138
139Flatpak Apps:
140
141- Cryptomator
142- pCloud
143- Signal
144
145Fedora Packages:
146
147- gomuks
148- neomutt
149- neofetch
150- Firefox
151 - uBlock Origin
152 - Bitwarden
153 - Stylus
154 - Privacy Redirect
155
156Other:
157
158- exiftool
content/blog/2022-07-25-curseradio.md deleted −106
@@ -1,106 +0,0 @@
1+++
2date = 2022-07-25
3title = "CurseRadio: Listening to the Radio on the Comand Line"
4description = "Use Curse Radio to listen to radio on the command-line."
5+++
6
7## Overview
8
9While exploring some interesting Linux applications, I stumbled across
10[curseradio](https://github.com/chronitis/curseradio), a command-line
11radio player based on Python.
12
13This application is fantastic and incredibly easy to install, so I
14wanted to dedicate a post today to this app. Let's look at the features
15within the app and then walk through the installation process I took to
16get `curseradio` working.
17
18## Features
19
20![curseradio](https://img.cleberg.net/blog/20220725-curseradio/curseradio.png)
21
22The radio player itself is quite minimal. As you can see in the
23screenshot above, it contains a simple plaintext list of all available
24categories, which can be broken down further and further. In addition,
25radio shows are available for listening, alongside regular radio
26stations.
27
28For example, the `Sports` > `Pro Basketball` >
29`Shows` category contains a number of specific shows related
30to Professional Basketball.
31
32Aside from being able to play any of the listed stations/shows, you can
33make a channel your favorite by pressing `f`. It will now
34show up at the top of the radio player in the `Favourites`
35category.
36
37### Commands/Shortcuts
38
39 Key(s) Command
40 ------------ ---------------------------------
41 ↑, ↓ navigate
42 PgUp, PgDn navigate quickly
43 Home, End to top/bottom
44 Enter open/close folders, play stream
45 k stop playing stream
46 q quit
47 f toggle favourite
48
49## Installation
50
51### Dependencies
52
53Before installing `curseradio`, a handful of system and
54Python packages are required. To get started, install
55`python3`, `pip3`, and `mpv` on your
56system. In this example, I'm using Fedora Linux, which uses the
57`dnf` package manager. You may need to adjust this if you're
58using a different system.
59
60```sh
61sudo dnf install python3 pip3 mpv
62```
63
64Next, use `pip3` to install `requests`,
65`xdg`, and `lxml`:
66
67```sh
68pip3 install requests xdg lxml
69```
70
71### Repository Source Installation
72
73Once all the dependencies are installed, we can clone the source code
74and enter that directory:
75
76```sh
77git clone https://github.com/chronitis/curseradio && cd curseradio
78```
79
80Once you're within the `curseradio` directory, you can
81install the application with the provided `setup.py` script.
82
83```sh
84sudo python3 setup.py install
85```
86
87In my case, I ran into a few errors and needed to create the folders
88that curseradio wanted to use for its installation. If you don't get
89any errors, you can skip this and run the app.
90
91```sh
92sudo mkdir /usr/local/lib/python3.10/
93sudo mkdir /usr/local/lib/python3.10/site-packages/
94```
95
96```sh
97sudo chown -R $USER:$USER /usr/local/lib/python3.10/
98```
99
100## Run the Application
101
102Once fully installed without errors, you can run the application!
103
104```sh
105python3 /usr/local/bin/curseradio
106```
content/blog/2022-09-21-graphene-os.md deleted −167
@@ -1,167 +0,0 @@
1+++
2date = 2022-09-21
3title = "Installing Graphene OS on the Pixel 6 Pro"
4description = "A retrospective on the successful command-line installation of Graphene OS on a Pixel 6 Pro."
5+++
6
7## Introduction
8
9After using iOS for a couple of years, I finally took the plunge and
10purchased a Pixel 6 Pro in order to test and use [GrapheneOS]
11(<https://grapheneos.org>).
12
13The installation process was rather quick once you have the tools and
14files you need. Overall, it can be done in just a few minutes.
15
16## Gathering Tools & Files
17
18### Android Tools
19
20First, in order to interact with the device, we will need the [Android
21platform
22tools](https://developer.android.com/studio/releases/platform-tools.html).
23Find the Linux download and save the ZIP folder to your preferred
24location.
25
26Once we've downloaded the files, we will need to unzip them, enter the
27directory, and move the necessary executables to a central location,
28such as `/usr/bin/`. For this installation, we only need the
29`fastboot` and `adb` executables.
30
31```sh
32cd ~/Downloads
33```
34
35```sh
36unzip platform-tools_r33.0.3-linux.zip
37cd platform-tools
38sudo mv fastboot /usr/bin/
39sudo mv adb /usr/bin
40```
41
42### GrapheneOS Files
43
44Next, we need the [GrapheneOS files](https://grapheneos.org/releases)
45for our device and model. For example, the Pixel 6 Pro is codenamed
46`raven` on the release page.
47
48Once we have the links, let's download them to our working directory:
49
50```sh
51curl -O https://releases.grapheneos.org/factory.pub
52curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip
53curl -0 https://releases.grapheneos.org/raven-factory-2022091400.zip.sig
54```
55
561. Validate Integrity
57
58 In order to validate the integrity of the downloaded files, we will
59 need the `signify` package and Graphene's
60 `factory.pub` file.
61
62 ```sh
63 sudo dnf install signify
64 ```
65
66 ```sh
67 curl -O https://releases.grapheneos.org/factory.pub
68 ```
69
70 Then we can validate the files and ensure that no data was corrupted
71 or modified before it was saved to our device.
72
73 ```sh
74 signify -Cqp factory.pub -x raven-factory-2022091400.zip.sig && echo verified
75 ```
76
772. Unzip Files
78
79 Once the files are verified, we can unzip the Graphene image and
80 enter the directory:
81
82 ```sh
83 unzip raven-factory-2022091400.zip && cd raven-factory-2022091400
84 ```
85
86## Installation Process
87
88### Enable Developer Debugging & OEM Unlock
89
90Before we can actually flash anything to the phone, we will need to
91enable OEM Unlocking, as well as either USB Debugging or Wireless
92Debugging, depending on which method we will be using.
93
94To start, enable developer mode by going to `Settings` >
95`About` and tapping `Build Number` seven (7)
96times. You may need to enter your PIN to enable this mode.
97
98Once developer mode is enabled, go to `Settings` >
99`System` > `Devloper Options` and enable OEM
100Unlocking, as well as USB or Wireless Debugging. In my case, I chose USB
101Debugging and performed all actions via USB cable.
102
103Once these options are enabled, plug the phone into the computer and
104execute the following command:
105
106```sh
107adb devices
108```
109
110If an unauthorized error occurs, make sure the USB mode on the phone is
111changed from charging to something like "File Transfer" or "PTP."
112You can find the USB mode in the notification tray.
113
114### Reboot Device
115
116Once we have found the device via `adb`, we can either boot
117into the bootloader interface by holding the volume down button while
118the phone reboots or by executing the following command:
119
120```sh
121adb reboot bootloader
122```
123
124### Unlock the Bootloader
125
126The phone will reboot and load the bootloader screen upon startup. At
127this point, we are ready to start the actual flashing of GrapheneOS onto
128the device.
129
130**NOTE**: In my situation, I needed to use `sudo` with every
131`fastboot` command, but not with `adb` commands. I
132am not sure if this is standard or a Fedora quirk, but I'm documenting
133my commands verbatim in this post.
134
135First, we start by unlocking the bootloader so that we can load other
136ROMs:
137
138```sh
139sudo fastboot flashing unlock
140```
141
142### Flashing Factory Images
143
144Once the phone is unlocked, we can flash it with the
145`flash-all.sh` script found inside the
146`raven-factory-2022091400` folder we entered earlier:
147
148```sh
149sudo ./flash-all.sh
150```
151
152This process should take a few minutes and will print informational
153messages as things progress. Avoid doing anything on the phone while
154this process is operating.
155
156### Lock the Bootloader
157
158If everything was successful, the phone should reboot a few times and
159finally land back on the bootloader screen. At this point, we can
160re-lock the bootloader to enable full verified boot and protect the
161device from unwanted flashing or erasure of data.
162
163```sh
164sudo fastboot flashing lock
165```
166
167Once done, the device will be wiped and ready for a fresh set-up!
content/blog/2022-10-22-alpine-linux.md deleted −280
@@ -1,280 +0,0 @@
1+++
2date = 2022-10-22
3title = "Alpine Linux: My New Server OS"
4description = "A retrospective on installing and configuring Alpine Linux as my new server operating system."
5+++
6
7## Alpine Linux
8
9[Alpine Linux](https://alpinelinux.org) is a very small distro, built on
10musl libc and busybox. It uses ash as the default shell, OpenRC as the
11init system, and apk as the package manager. According to their website,
12an Alpine container "requires no more than 8 MB and a minimal
13installation to disk requires around 130 MB of storage." An actual bare
14metal machine is recommended to have 100 MB of RAM and 0-700 MB of
15storage space.
16
17Historically, I've used Ubuntu's minimal installation image as my
18server OS for the last five years. Ubuntu worked well and helped as my
19original server contained an nVidia GPU and no onboard graphics, so
20quite a few distros won't boot or install without a lot of tinkering.
21
22Alpine has given me a huge increase in performance across my Docker apps
23and Nginx websites. CPU load for the new server I'm using to test
24Alpine hovers around 0-5% on average with an Intel(R) Core(TM) i3-6100
25CPU @ 3.70GHz.
26
27The only services I haven't moved over to Alpine are Plex Media Server
28and Syncthing, which may increase CPU load quite a bit depending on how
29many streams are running.
30
31### Installation
32
33In terms of installation, Alpine has an incredibly useful
34[wiki](https://wiki.alpinelinux.org/wiki/Installation) that will guide a
35user throughout the installation and post-installation processes, as
36well as various other articles and guides.
37
38To install Alpine, find an appropriate [image to
39download](https://alpinelinux.org/downloads/) and flash it to a USB
40using software such as Rufus or Etcher. I opted to use the Standard
41image for my x86~64~ architecture.
42
43Once the USB is ready, plug it into the machine and reboot. Note that
44you may have to use a key such as `Esc` or `F1-12`
45to access the boot menu. The Alpine Linux terminal will load quickly and
46for a login.
47
48To log in to the installation image, use the `root` account;
49there is no password. Once logged-in, execute the setup command:
50
51```sh
52setup-alpine
53```
54
55The setup script will ask a series of questions to configure the system.
56Be sure to answer carefully or else you may have to re-configure the
57system after boot.
58
59- Keyboard Layout (Local keyboard language and usage mode, e.g., us
60 and variant of us-nodeadkeys.)
61- Hostname (The name for the computer.)
62- Network (For example, automatic IP address discovery with the
63 "DHCP" protocol.)
64- DNS Servers (Domain Name Servers to query. For privacy reasons, it
65 is NOT recommended to route every local request to servers like
66 Google's 8.8.8.8 .)
67- Timezone
68- Proxy (Proxy server to use for accessing the web. Use "none" for
69 direct connections to the internet.)
70- Mirror (From where to download packages. Choose the organization you
71 trust giving your usage patterns to.)
72- SSH (Secure SHell remote access server. "Openssh" is part of the
73 default install image. Use "none" to disable remote login, e.g. on
74 laptops.)
75- NTP (Network Time Protocol client used for keeping the system clock
76 in sync with a time-server. Package "chrony" is part of the
77 default install image.)
78- Disk Mode (Select between diskless (disk="none"), "data" or
79 "sys", as described above.)
80
81Once the setup script is finished, be sure to reboot the machine and
82remove the USB device.
83
84```sh
85reboot
86```
87
88### Post-Installation
89
90There are many things you can do once your Alpine Linux system is up and
91running, and it largely depends on what you'll use the machine for.
92I'm going to walk through my personal post-installation setup for my
93web server.
94
951. Upgrade the System
96
97 First, login as `root` in order to update and upgrade the
98 system:
99
100 ```sh
101 apk -U upgrade
102 ```
103
1042. Adding a User
105
106 I needed to add a user so that I don't need to log in as root. Note
107 that if you're used to using the `sudo` command, you
108 will now need to use the `doas` command on Alpine Linux.
109
110 ```sh
111 apk add doas
112 adduser <username>
113 adduser <username> wheel
114 ```
115
116 You can now log out and log back in using the newly-created user:
117
118 ```sh
119 exit
120 ```
121
1223. Enable Community Packages
123
124 In order to install more common packages that aren't found in the
125 `main` repository, you will need to enable the
126 `community` repository:
127
128 ```sh
129 doas nano /etc/apk/repositories
130 ```
131
132 Uncomment the community line for whichever version of Alpine you're
133 running:
134
135 ```sh
136 /media/usb/apks
137 http://dl-cdn.alpinelinux.org/alpine/v3.16/main
138 http://dl-cdn.alpinelinux.org/alpine/v3.16/community
139 #http://dl-cdn.alpinelinux.org/alpine/edge/main
140 #http://dl-cdn.alpinelinux.org/alpine/edge/community
141 #http://dl-cdn.alpinelinux.org/alpine/edge/testing
142 ```
143
1444. Install Required Packages
145
146 Now that the community packages are available, you can install any
147 packages you need. In my case, I installed the web server packages I
148 need for my services:
149
150 ```sh
151 doas apk add nano nginx docker docker-compose ufw
152 ```
153
1545. SSH
155
156 If you didn't install OpenSSH as part of the installation, you can
157 do so now:
158
159 ```sh
160 doas apk add openssh
161 ```
162
163 Next, either create a new key or copy your SSH key to the server
164 from your current machines:
165
166 ```sh
167 # Create a new key
168 ssh-keygen
169 ```
170
171 If you need to copy an existing SSH key from a current machine:
172
173 ```sh
174 # Copy key from existing machines
175 ssh-copy-id <username>@<ip_address>
176 ```
177
1786. Firewall
179
180 Lastly, I installed `ufw` above as my firewall. To set
181 up, default to deny incoming and allow outgoing connections. Then
182 selectively allow other ports or apps as needed.
183
184 ```sh
185 doas ufw default deny incoming
186 doas ufw default allow outgoing
187 doas ufw allow SSH
188 doas ufw allow "WWW Full"
189 doas ufw allow 9418 # Git server port
190 ```
191
1927. Change Hostname
193
194 If you don't like the hostname set during installation, you just
195 need to edit two files. First, edit the simple hostname file:
196
197 ```sh
198 doas nano /etc/hostname
199 ```
200
201 ```sh
202 <hostname>
203 ```
204
205 Next, edit the `hosts` file:
206
207 ```sh
208 doas nano /etc/hosts
209 ```
210
211 ```sh
212 127.0.0.1 <hostname>.local <hostname> localhost.local localhost
213 ::1 <hostname> <hostname>.local
214 ```
215
216## Nginx Web Server
217
218To set up my web server, I simply created the `www` user and
219created the necessary files.
220
221```sh
222doas adduser -D -g 'www' www
223mkdir /www
224doas mkdir /www
225doas chown -R www:www /var/lib/nginx/
226doas chown -R www:www /www
227```
228
229If you're running a simple webroot, you can alter the main
230`nginx.conf` file. Otherwise, you can drop configuration
231files in the following directory. You don't need to enable or symlink
232the configuration file like you do in other systems.
233
234```sh
235doas nano /etc/nginx/http.d/example_website.conf
236```
237
238Once the configuration is set and pointed at the `/www`
239directory to serve files, enable the Nginx service:
240
241```sh
242# Note that 'default' must be included or Nginx will not start on boot
243doas rc-update add nginx default
244```
245
246## Docker Containers
247
248Docker works exactly the same as other systems. Either execute a
249`docker run` command or create a
250`docker-compose.yml` file and do
251`docker-compose up -d`.
252
253## Git Server
254
255I went in-depth on how to self-host a git server in another post:
256[Self-Hosting a Personal Git Server](/blog/git-server/).
257
258However, there are a few differences with Alpine. First note that in
259order to change the `git` user's shell, you must do a few
260things a little different:
261
262```sh
263doas apk add libuser
264doas touch /etc/login.defs
265doas mkdir /etc/default
266doas touch /etc/default/useradd
267doas lchsh git
268```
269
270## Thoughts on Alpine
271
272So far, I love Alpine Linux. I have no complaints about anything at this
273point, but I'm not completely finished with the migration yet. Once
274I'm able to upgrade my hardware to a rack-mounted server, I will
275migrate Plex and Syncthing over to Alpine as well - possibly putting
276Plex into a container or VM.
277
278The performance is stellar, the `apk` package manager is
279seamless, and system administration tasks are effortless. My only regret
280is that I didn't install Alpine sooner.
content/blog/2022-10-30-linux-display-manager.md deleted −81
@@ -1,81 +0,0 @@
1+++
2date = 2022-10-30
3title = "How to Disable or Change the Display Manager on Void Linux"
4description = "Learn how to remove or modify the display manager on Void Linux."
5+++
6
7## Display Manager Services
8
9In order to change the [display
10manager](https://en.wikipedia.org/wiki/Display_manager) on Void Linux -
11or any other Linux distro - you need to identify the currently enabled
12display manager.
13
14### Disabling the Current Display Manager
15
16Void Linux only has one ISO available for download with a pre-built
17display manager at the time of this post: the XFCE ISO. If you've
18installed this version, the pre-assigned display manager is
19`lxdm`. If you installed another display manager, replace
20`lxdm` in the following command with the display manager you
21have installed.
22
23To disable `lxdm`, simply remove the service symlink:
24
25```sh
26sudo rm /var/service/lxdm
27```
28
29### Enabling a New Display Manager
30
31If you want to enable a new display manager, you can do so after
32`lxdm` is disabled. Make sure to replace
33`<new_display_manager>` with your new DM, such as
34`gdm`, `xdm`, etc.
35
36```sh
37sudo ln -s /etc/sv/<new_display_manager> /var/service
38```
39
40## Set Up `.xinitrc`
41
42Depending on your setup, you may need to create a few X files, such as
43`~/.xinitrc`. For my personal set-up, I created this file to
44launch the i3wm as my desktop.
45
46```sh
47nano ~/.xinitrc
48```
49
50```sh
51#!/bin/sh
52
53exec i3
54```
55
56If you run a desktop other than i3, simply replace `i3` with
57the shell command that launches that desktop.
58
59## Set Up Your Shell Profile
60
61Finally, in order to automatically launch an X session upon login, you
62will need to edit the `.bash_profile` (bash) or
63`.zprofile` (zsh) files for your shell:
64
65```sh
66nano ~/.zprofile
67```
68
69Add the following snippet to the end of the shell profile file. This
70will execute the `startx` command upon login.
71
72```sh
73if [ -z "${DISPLAY}" ] && [ "${XDG_VTNR}" -eq 1 ]; then
74 exec startx
75fi
76```
77
78Alternatively, you can ignore this step and simply choose to manually
79execute `startx` upon login. This can be useful if you have
80issues with your desktop or like to manually launch different desktops
81by choice.
content/blog/2022-12-01-nginx-compression.md deleted −79
@@ -1,79 +0,0 @@
1+++
2date = 2022-12-01
3title = "Enable GZIP Compression in Nginx"
4description = "Learn how to enable compression in Nginx."
5+++
6
7## Text Compression
8
9Text compression allows a web server to serve text-based resources
10faster than uncompressed data. This can speed up things like First
11Contentful Paint, Tie to Interactive, and Speed Index.
12
13## Enable Nginx Compression with gzip
14
15In order to enable text compression on Nginx, we need to enable it
16within the configuration file:
17
18```sh
19nano /etc/nginx/nginx.conf
20```
21
22Within the `http` block, find the section that shows
23something like the block below. This is the default gzip configuration I
24found in my `nginx.conf` file on Alpine Linux 3.17. Yours may
25look slightly different, just make sure that you're not creating any
26duplicate gzip options.
27
28```conf
29# Enable gzipping of responses.
30#gzip on;
31
32# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
33gzip_vary on;
34```
35
36Remove the default gzip lines and replace them with the following:
37
38```conf
39# Enable gzipping of responses.
40gzip on;
41gzip_vary on;
42gzip_min_length 10240;
43gzip_proxied expired no-cache no-store private auth;
44gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
45gzip_disable "MSIE [1-6]";
46```
47
48## Explanations of ngx~httpgzipmodule~ Options
49
50Each of the lines above enables a different aspect of the gzip response
51for Nginx. Here are the full explanations:
52
53- `gzip` -- Enables or disables gzipping of responses.
54- `gzip_vary` -- Enables or disables inserting the "Vary:
55 Accept-Encoding" response header field if the directives gzip,
56 gzip~static~, or gunzip are active.
57- `gzip_min_length` -- Sets the minimum length of a
58 response that will be gzipped. The length is determined only from
59 the "Content-Length" response header field.
60- `gzip_proxied` -- Enables or disables gzipping of
61 responses for proxied requests depending on the request and
62 response. The fact that the request is proxied is determined by the
63 presence of the "Via" request header field.
64- `gzip_types` -- Enables gzipping of responses for the
65 specified MIME types in addition to "text/html". The special value
66 "*" matches any MIME type (0.8.29). Responses with the
67 "text/html" type are always compressed.
68- `gzip_disable` -- Disables gzipping of responses for
69 requests with "User-Agent" header fields matching any of the
70 specified regular expressions.
71 - The special mask "msie6" (0.7.12) corresponds to the regular
72 expression "MSIE [4-6].", but works faster. Starting from
73 version 0.8.11, "MSIE 6.0; ... SV1" is excluded from this
74 mask.
75
76More information on these directives and their options can be found on
77the [Module
78ngx~httpgzipmodule~](https://nginx.org/en/docs/http/ngx_http_gzip_module.html)
79page in Nginx's documentation.
content/blog/2022-12-17-st.md deleted −94
@@ -1,94 +0,0 @@
1+++
2date = 2022-12-17
3title = "Simple Terminal"
4description = "Instructions for building simple terminal on Fedora."
5+++
6
7## st
8
9[st](https://st.suckless.org) standards for Simple Terminal, a simple
10terminal implementation for X made by the
11[suckless](https://suckless.org) team.
12
13This post walks through the dependencies needed and process to build and
14install `st` on Fedora Workstation.
15
16### Obtain Files
17
18To start, obtain the source files for `st` via
19`git clone`.
20
21```sh
22mkdir ~/suckless && cd ~/suckless
23git clone https://git.suckless.org/st && cd st
24```
25
26### Dependencies
27
28Once you have the files and are in the `st` directory, ensure
29the following packages are installed.
30
31```sh
32sudo dnf update && sudo dnf upgrade
33sudo dnf install gcc patch libX11-devel libXft-devel
34```
35
36### Building
37
38Before building, ensure that you read the README file.
39
40```sh
41cat README
42```
43
44Once you've read the instructions, open the `config.mk` file
45and ensure it matches your setup. If you're not sure, leave the default
46options within the file.
47
48Finally, you can build `st` with the following command.
49Ensure you run as root (e.g., `sudo`) or else you may not end
50up with a usable application file.
51
52```sh
53sudo make clean install
54```
55
56### Customization (Patches)
57
58Note that customizing `st` requires you to modify the source
59files or to download one of the [available
60patches](https://st.suckless.org/patches/) for suckless.org.
61
62If you've already installed `st` and want to customize or
63install a patch, start by uninstalling the current program.
64
65```sh
66cd ~/suckless/st
67sudo make uninstall
68```
69
70Next, grab the `<path>.diff` file from the page of the patch
71you chose. For example, I will be using the
72[defaultfontsize](https://st.suckless.org/patches/defaultfontsize/)
73patch in the below example.
74
75```sh
76wget https://st.suckless.org/patches/defaultfontsize/st-defaultfontsize-20210225-4ef0cbd.diff
77```
78
79Once the file is downloaded inside the `st` folder, apply the
80patch and re-install the program. You may need to install the
81`patch` command if you don't have it installed already (you
82should have installed it above).
83
84```sh
85patch -i st-defaultfontsize-20210225-4ef0cbd.diff
86sudo make clean install
87```
88
89Once installed, you can use the default font size patch to launch
90`st` with any font size you wish:
91
92```sh
93st -z 16
94```
content/blog/2023-02-02-exploring-hare.md deleted −171
@@ -1,171 +0,0 @@
1+++
2date = 2023-02-02
3title = "Exploring the Hare Programming Language"
4description = "A retrospective on my first time using the Hare Programming Language."
5+++
6
7## A Quick Note
8
9By no means am I a professional developer, so this post will be rather
10short. I won't be going into depth on the specification or anything
11that technical.
12
13Instead, I will simply be talking about how I (a relatively basic
14hobbyist programmer) have been playing with Hare and what intrigues me
15about the language.
16
17## Hare
18
19The [Hare](https://harelang.org) programming language is a
20straightforward language that should look familiar if you've ever
21programmed with C, Rust, or other languages that aim to build software
22at the system-level.
23
24The Hare homepage states the following:
25
26> Hare is a systems programming language designed to be simple, stable,
27> and robust. Hare uses a static type system, manual memory management,
28> and minimal runtime. It is well-suited to writing operating systems,
29> system tools, compilers, networking software, and other low-level,
30> high performance tasks.
31
32I have found this all to be true while playing with it for the first
33time today. In the next few sections, I'm going to walk through my
34installation and first program.
35
36### Installation
37
38I'm currently running Alpine Linux on my Thinkpad, so the installation
39was quite easy as there is a package for Hare in the `apk`
40repositories.
41
42```sh
43doas apk add hare hare-doc
44```
45
46However, I was able to install Hare from scratch on Fedora Linux a short
47while ago, which was also very easy to do. If you need further
48instructions and Hare doesn't have a package on your system, take a
49look at the [Hare Installation](https://harelang.org/installation/)
50page.
51
52### Creating a Test Project
53
54In order to play with the language, I created
55[hare-test](https://git.sr.ht/~cmc/hare-projects) and will be putting
56any of my Hare-related adventures in here.
57
58> **Update:** I also created a simple Hare program for creating a file
59> from user input:
60> [files.ha](https://git.sr.ht/~cmc/hare-projects/tree/main/item/files/files.ha)
61
62Luckily, Hare doesn't require any complex set-up tools or build
63environment. Once you have Hare installed, you simply need to create a
64file ending with `.ha` and you can run a Hare program.
65
66I created a file called `rgb.ha` in order to test out the
67random number generation and passing parameters between functions.
68
69```sh
70nano rgb.ha
71```
72
73Within this file, I was able to easily import a few of the [standard
74library modules](https://harelang.org/tutorials/stdlib/):
75`fmt`, `math::random`, and `datetime`.
76
77With these modules, I created two functions:
78
791. `main`: This function calls the `generate_rgb`
80 function and then prints out the returned values.
812. `generate_rgb`: This function uses the current Unix epoch
82 time to generate a pseudo-random value and uses this value to create
83 three more random values between 0 and 255. These three numbers
84 represent a color in RGB format.
85
86> **Note**: Some syntax coloring may look odd, as Zola currently
87> doesn't have a syntax highlighting theme for Hare. Instead, I'm
88> using the C theme, which may not be exactly accurate when coloring the
89> code below.
90
91```c
92use datetime;
93use fmt;
94use math::random;
95
96export fn main() void = {
97 const rgb = generate_rgb();
98 fmt::printfln("RGB: ({}, {}, {})", rgb[0], rgb[1], rgb[2])!;
99};
100
101fn generate_rgb() []u64 = {
102 // Use the current Unix epoch time as the seed value
103 let datetime = datetime::epochunix(&datetime::now());
104
105 // Generate initial pseudo-random value
106 // You must cast the datetime from int to u64
107 let x = random::init(datetime: u64);
108
109 // Generate RGB values between (0, 255) using pseudo-random init value
110 let r = random::u64n(&x, 255);
111 let g = random::u64n(&x, 255);
112 let b = random::u64n(&x, 255);
113
114 // Structure data as array and return
115 let rgb_array: [3]u64 = [r, g, b];
116 return rgb_array;
117};
118```
119
120### Running a Program
121
122Once you have a Hare file written and ready to run, you simply need to
123run it:
124
125```sh
126hare run file.ha
127```
128
129You can also compile the program into an executable:
130
131```sh
132hare build -o example file.ha
133./example
134```
135
136### Initial Thoughts
137
1381. Documentation Improvements Would Help
139
140 While I was able to piece everything together eventually, the
141 biggest downfall right now in Hare's documentation. For such a new
142 project, the documentation is in a great spot. However, bare
143 specifications don't help as much as a brief examples section
144 would.
145
146 For example, it took me a while to figure out what the
147 `u64n` function was looking for. I could tell that it
148 took two parameters and the second was my max value (255), but
149 couldn't figure out what the first value should be. Eventually, I
150 inspected the `random.ha` file in the [Hare source
151 code](https://git.sr.ht/~sircmpwn/hare/tree/master/item/math/random/random.ha)
152 and found the test suite that helped me discover that it needed an
153 `init()` value in the form of `&var`.
154
1552. More Basic Modules
156
157 This is another point that comes from Hare being new and awaiting
158 more contributions, but there are some basic functions that I would
159 personally enjoy seeing in Hare, such as one to convert decimal
160 (base 10) values to hexadecimal (base 16).
161
162 If I'm feeling comfortable with my math, I may work on the list of
163 functions I want and see if any can make it into the Hare source
164 code.
165
1663. Overall Thoughts
167
168 Overall, I actually really enjoy Hare. It's not as tedious to get a
169 project up and running as Rust, but it's also simpler and more
170 user-friendly than learning C. I am going to continue playing with
171 it and see if I can make anything of particular value.
content/blog/2023-06-18-unifi-ip-blocklist.md deleted −87
@@ -1,87 +0,0 @@
1+++
2date = 2023-06-18
3title = "Block IP Addresses and Subnets with Unifi Network Firewall"
4description = "Learn how to use the Unifi Network Firewall to block IP addresses and subnets."
5+++
6
7## Identifying Abusive IPs
8
9If you're like me and use Unifi network equipment at the edge of the
10network you manage, you may know that Unifi is only somewhat decent at
11identifying and blocking IPs that represent abusive or threat actors.
12
13While Unifi has a [threat
14management](https://help.ui.com/hc/en-us/articles/360006893234-UniFi-Gateway-Threat-Management)
15tool inside their Network application, it can be lacking in
16functionality and identification. For example, I have my UDM Pro set to
17identify and block almost all categories of threats available within the
18Unifi settings. However, I regularly identify abusive actors on my web
19server via the server logs.
20
21In addition, I have identified IP addresses and subnets directly within
22Unifi's logs that the UDM did not block for whatever reason.
23
24This guide is meant to be another step in the process to manually block
25abusive IP addresses or subnets that you have identified but are not
26being automatically blocked yet.
27
28## Create an IP Group Profile
29
30To start, login to the Unifi machine's web GUI and navigate to the
31Network app \> Settings \> Profiles.
32
33Within this page, choose the `IP Groups` tab and click
34`Create New`.
35
36![Network
37Profiles](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/unifi_profiles.png)
38
39Each IP Group profile can be used as one of three options:
40
411. Port Group
422. IPv4 Address/Subnet
433. IPv6 Address/Subnet
44
45In this example, I'm creating an IPv4 Address/Subnet group and adding a
46few different IP addresses and a subnet. Once you've added all IP
47addresses and subnets, click the `Apply` button that should
48appear at the bottom.
49
50![Network Profile
51IPs](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/abusive_ips.png)
52
53At this point, the IPv4 Address/Subnet has been created but not yet
54used.
55
56## Drop IP Group Profile via the Unifi Firewall
57
58To instruct the Unifi machine to block the profile we just created, we
59need to navigate to the Network app \> Settings \> Firewall & Security.
60
61Within this screen, find the Firewall Rules table and click
62`Create Entry`. This entry should contain the following
63settings:
64
65- Type: `Internet In`
66- Description: `<Your Custom Rule>`
67- Rule Applied: `Before Predefined Rules`
68- Action: `Drop`
69- Source Type: `Port/IP Group`
70- IPv4 Address Group:
71 `<Name of the Group Profile You Created Above>`
72
73Customize the remaining configurations to your liking, and then save and
74enable the firewall rule.
75
76![Firewall
77Rule](https://img.cleberg.net/blog/20230618-unifi-ip-blocklist/firewall_drop_rule.png)
78
79Once enabled, the Unifi machine will be able to drop all incoming
80connections from the defined IP addresses and subnets within the created
81profile.
82
83> As a personal aside to this topic, I'm looking for a convenient way
84> to update the firewall rules or profiles remotely (within the LAN)
85> from the web server to accelerate this process. If you have an idea on
86> how to automatically update Unifi IP groups or firewall rules, let me
87> know!
content/blog/2023-06-20-audit-review-template.md deleted −80
@@ -1,80 +0,0 @@
1+++
2date = 2023-06-20
3title = "Audit Testing Review Template"
4description = "A handy reference template for audit reviews."
5+++
6
7## Overview
8
9This post is a *very* brief overview on the basic process to review
10audit test results, focusing on work done as part of a financial
11statement audit (FSA) or service organization controls (SOC) report.
12
13While there are numerous different things to review and look for - all
14varying wildly depending on the report, client, and tester - this list
15serves as a solid base foundation for a reviewer.
16
17I have used this throughout my career as a starting point to my reviews,
18and it has worked wonders for creating a consistent and objective
19template to my reviews. The goal is to keep this base high-level enough
20to be used on a wide variety of engagements, while still ensuring that
21all key areas are covered.
22
23## Review Template
24
251. [ ] Check all documents for spelling and grammar.
262. [ ] Ensure all acronyms are fully explained upon first use.
273. [ ] For all people referenced, use their full names and job titles
28 upon first use.
294. [ ] All supporting documents must cross-reference to the lead sheet
30 and vice-versa.
315. [ ] Verify that the control has been adequately tested:
32 - [ ] **Test of Design**: Did the tester obtain information
33 regarding how the control should perform normally and abnormally
34 (e.g., emergency scenarios)?
35 - [ ] **Test of Operating Effectiveness**: Did the tester inquire,
36 observe, inspect, or re-perform sufficient evidence to support
37 their conclusion over the control? Inquiry alone is not
38 adequate!
396. [ ] For any information used in the control, whether by the control
40 operator or by the tester, did the tester appropriately document the
41 source (system or person), extraction method, parameters, and
42 completeness and accuracy (C&A)?
43 - [ ] For any reports, queries, etc. used in the extraction, did
44 the tester include a copy and notate C&A considerations?
457. [ ] Did the tester document the specific criteria that the control
46 is being tested against?
478. [ ] Did the tester notate in the supporting documents where each
48 criterion was satisfied?
499. [ ] If testing specific policies or procedures, are the documents
50 adequate?
51 - [ ] e.g., a test to validate that a review of policy XYZ occurs
52 periodically should also evaluate the sufficiency of the policy
53 itself, if meant to cover the risk that such a policy does not
54 exist and is not reviewed.
5510. [ ] Does the test cover the appropriate period under review?
56 - [ ] If the test is meant to cover only a portion of the audit
57 period, do other controls exist to mitigate the risks that exist
58 for the remainder of the period?
5911. [ ] For any computer-aided audit tools (CAATs) or other automation
60 techniques used in the test, is the use of such tools explained and
61 appropriately documented?
6212. [ ] If prior-period documentation exists, are there any missing
63 pieces of evidence that would further enhance the quality of the
64 test?
6513. [ ] Was any information discovered during the walkthrough or inquiry
66 phase that was not incorporated into the test?
6714. [ ] Are there new rules or expectations from your company's
68 internal guidance or your regulatory bodies that would affect the
69 audit approach for this control?
7015. [ ] Was an exception, finding, or deficiency identified as a result
71 of this test?
72 - [ ] Was the control deficient in design, operation, or both?
73 - [ ] What was the root cause of the finding?
74 - [ ] Does the finding indicate other findings or potential fraud?
75 - [ ] What's the severity and scope of the finding?
76 - [ ] Do other controls exist as a form of compensation against
77 the finding's severity, and do they mitigate the risk within
78 the control objective?
79 - [ ] Does the finding exist at the end of the period, or was it
80 resolved within the audit period?
content/blog/2023-06-28-backblaze-b2.md deleted −188
@@ -1,188 +0,0 @@
1+++
2date = 2023-06-28
3title = "Getting Started with Backblaze B2 Cloud Storage"
4description = "An introduction to the free ttier of Backblaze B2 Cloud Storage."
5+++
6
7## Overview
8
9Backblaze [B2 Cloud
10Storage](https://www.backblaze.com/b2/cloud-storage.html) is an
11inexpensive and reliable on-demand cloud storage and backup solution.
12
13The service starts at \$5/TB/month (\$0.005/GB/month) with a download
14rate of \$0.01/GB/month.
15
16However, there are free tiers:
17
18- The first 10 GB of storage is free.
19- The first 1 GB of data downloaded each day is free.
20- Class A transactions are free.
21- The first 2500 Class B transactions each day are free.
22- The first 2500 Class C transactions each day are free.
23
24You can see which API calls fall into categories A, B, or C here:
25[Pricing Organized by API
26Calls](https://www.backblaze.com/b2/b2-transactions-price.html).
27
28For someone like me, who wants an offsite backup of their server's
29`/home/` directory and various other server configs that fall
30under 10 GB total, Backblaze is a great solution from a financial
31perspective.
32
33## Create An Account
34
35To start with Backblaze, you'll need to [create a free
36account](https://www.backblaze.com/b2/sign-up.html) - no payment method
37is required to sign up.
38
39Once you have an account, you can test out the service with their web
40GUI, their mobile app, or their CLI tool. I'm going to use the CLI tool
41below to test a file upload and then sync an entire directory to my
42Backblaze bucket.
43
44## Create a Bucket
45
46Before you can start uploading, you need to create a bucket. If you're
47familiar with other object storage services, this will feel familiar. If
48not, it's pretty simple to create one.
49
50As their webpage says:
51
52> A bucket is a container that holds files that are uploaded into B2
53> Cloud Storage. The bucket name must be globally unique and must have a
54> minimum of 6 characters. A limit of 100 buckets may be created per
55> account. An unlimited number of files may be uploaded into a bucket.
56
57Once you click the `Create a Bucket` button on their webpage
58or mobile app, you need to provide the following:
59
60- Bucket Unique Name
61- Files in Bucket are: `Private` or `Public`
62- Default Encryption: `Disable` or `Enable`
63- Object Lock: `Disable` or `Enable`
64
65For my bucket, I created a private bucket with encryption enabled and
66object lock disabled.
67
68Once your bucket is created, you can test the upload/download feature on
69their web GUI or mobile app! At this point, you have a fully functional
70bucket and account.
71
72## Linux CLI Tool
73
74### Installation
75
76To install the `b2` CLI tool, you'll need to download it
77from the [CLI
78Tools](https://www.backblaze.com/docs/cloud-storage-command-line-tools)
79page. I recommend copying the URL from the link that says
80`Linux` and using wget to download it, as shown below.
81
82Once downloaded, make the file executable and move it to a location on
83your `$PATH`, so that you can execute that command from
84anywhere on the machine.
85
86```sh
87wget <b2_cli_url>
88chmod +x b2_linux
89mv b2_linux /usr/bin/b2
90```
91
92### Log In
93
94The first step after installation is to log in. To do this, execute the
95following command and provide your `<applicationKeyId>` and
96`<applicationKey>`.
97
98If you don't want to provide these values in the command itself, you
99can simply execute the base command and it will request them in an
100interactive prompt.
101
102```sh
103# if you want to provide the keys directly:
104b2 authorize-account [<applicationKeyId>] [<applicationKey>]
105
106# or, if you don't want your keys in your shell history:
107b2 authorize-account
108```
109
110### Upload a Test File
111
112In order to test the functionality of the CLI tool, I'll start by
113uploading a single test file to the bucket I created above. We can do
114this with the `upload_file` function.
115
116The command is issued as follows:
117
118```sh
119b2 upload_file <bucket_name> <local_file> <remote_file>
120```
121
122In my situation, I executed the following command with my username.
123
124```sh
125b2 upload_file my_unique_bucket /home/<user>/test.md test.md
126```
127
128To confirm that the file was uploaded successfully, list the files in
129your bucket:
130
131```sh
132b2 ls <bucket_name>
133```
134
135```txt
136test.md
137```
138
139### Sync a Directory
140
141If you have numerous files, you can use the `sync` function
142to perform functionality similar to `rsync`, where you can
143check what's in your bucket and sync anything that is new or modified.
144
145The command is issued as follows:
146
147```sh
148b2 sync <source file location> <B2 bucket destination>
149```
150
151In my case, I can sync my user's entire home directory to my bucket
152without specifying any of the files directly:
153
154```sh
155b2 sync /home/<user>/ "b2://<bucketName>/home/<user>"
156```
157
158## Caveats
159
160### Timing of Updates to the Web GUI
161
162When performing actions over a bucket, there is a slight delay in the
163web GUI when inspecting a bucket or its file. Note that simple actions
164such as uploading or deleting files may have a delay of a few minutes up
165to 24 hours. In my experience (\<10 GB and \~20,000 files), any actions
166took only a few minutes to update across clients.
167
168### Symlinks
169
170Note that symlinks are resolved by b2, so if you have a link from
171`/home/<user>/nas-storage` that symlinks out to a
172`/mnt/nas-storage` folder that has 10TB of data,
173`b2` will resolve that link and start uploading all 10TB of
174data linked within the folder.
175
176If you're not sure if you have any symlinks, a symlink will look like
177this (note the `->` symbol):
178
179```sh
180> ls -lha
181lrwxrwxrwx 1 root root 20 Jun 28 13:32 nas -> /mnt/nas-storage/
182```
183
184You can recursively find symlink in a path with the following command:
185
186```sh
187ls -lR /path/to/search | grep '^l'
188```
content/blog/2023-08-18-agile-auditing.md deleted −158
@@ -1,158 +0,0 @@
1+++
2date = 2023-08-18
3title = "Agile Auditing: An Introduction"
4description = "A quick introduction to using the Agile methodology in an audit."
5+++
6
7## What is Agile Auditing?
8
9[Agile](https://en.wikipedia.org/wiki/Agile_software_development), the
10collaborative philosophy behind many software development methods, has
11been picking up steam as a beneficial tool to use in the external and
12internal auditing world.
13
14This blog post will walk through commonly used terms within Agile,
15Scrum, and Kanban in order to translate these terms and roles into
16audit-specific terms.
17
18Whether your team is in charge of a financial statement audit, an
19attestation (SOC 1, SOC 2, etc.), or a unique internal audit, the terms
20used throughout this post should still apply.
21
22## Agile
23
24To start, I'll take a look at Agile.
25
26> The Agile methodology is a project management approach that involves
27> breaking the project into phases and emphasizes continuous
28> collaboration and improvement. Teams follow a cycle of planning,
29> executing, and evaluating.
30
31While this approach may seem familiar to what audit teams have
32historically done, an audit team must make distinct changes in their
33mentality and how they approach and manage a project.
34
35### Agile Values
36
37The Agile Manifesto, written in 2001 at a summit in Utah, contain a set
38of four main values that comprise the Agile approach:
39
401. Individuals and interactions over processes and tools.
412. Working software over comprehensive documentation.
423. Customer collaboration over contract negotiation.
434. Responding to change over following a plan.
44
45Beyond the four values, [twelve
46principles](https://agilemanifesto.org/principles.html) were also
47written as part of the summit.
48
49In order to relate these values to an audit or attestation engagement,
50we need to shift the focus from software development to the main goal of
51an engagement: completing sufficient audit testing to address to
52relevant risks over the processes and controls at hand.
53
54Audit Examples:
55
56- Engagement teams must value the team members, client contacts, and
57 their interactions over the historical processes and tools that have
58 been used.
59- Engagement teams must value a final report that contains sufficient
60 audit documentation over excessive documentation or scope creep.
61- Engagement teams must collaborate with the audit clients as much as
62 feasible to ensure that both sides are constantly updated with
63 current knowledge of the engagement's status and any potential
64 findings, rather than waiting for pre-set meetings or the end of the
65 engagement to communicate.
66- Engagement teams must be able to respond to change in an
67 engagement's schedule, scope, or environment to ensure that the
68 project is completed in a timely manner and that all relevant areas
69 are tested.
70 - In terms of an audit department's portfolio, they must be able
71 to respond to changes in their company's or client's
72 environment and be able to dynamically change their audit plan
73 accordingly.
74
75## Scrum
76
77The above section discusses the high-level details of the Agile
78philosophy and how an audit team can potentially mold that mindset into
79the audit world, but how does a team implement these ideas?
80
81There are many methods that use an Agile mindset, but I prefer
82[Scrum](https://en.wikipedia.org/wiki/Scrum_(software_development)).
83Scrum is a framework based on Agile that enables a team to work through
84a project through a series of roles, ceremonies, artifacts, and values.
85
86Let's dive into each of these individually.
87
88### Scrum Team
89
90A scrum project is only as good as the team running the project.
91Standard scrum teams are separated into three distinct areas:
92
931. **Product Owner (Client Contact)**: The client contact is the audit
94 equivalent of the product owner in Scrum. They are responsible for
95 partnering with the engagement or audit team to ensure progress is
96 being made, priorities are established, and clear guidance is given
97 when questions or findings arise within each sprint.
982. **Scrum Master (Engagement Lead)**: The engagement or audit team
99 lead is responsible for coaching the team and the client contact on
100 the scrum process, tracking team progress against plan, scheduling
101 necessary resources, and helping remove obstacles.
1023. **Scrum Developers (Engagement Members)**: The engagement or audit
103 team is the set of team members responsible for getting the work
104 done. These team members will work on each task, report progress,
105 resolve obstacles, and collaborate with other team members and the
106 client contact to ensure goals are being met.
107
108### Scrum Ceremonies
109
110Scrum ceremonies are events that are performed on a regular basis.
111
1121. **Sprint Planning**: The team works together to plan the upcoming
113 sprint goal and which user stories (tasks) will be added to the
114 sprint to achieve that goal.
1152. **Sprint**: The time period, typically at least one week and no more
116 than one month in length, where the team works on the stories and
117 anything in the backlog.
1183. **Daily Scrum**: A very short meeting held each day, typically 15
119 minutes, to quickly emphasize alignment on the sprint goal and plan
120 the next 24 hours. Each team member may share what they did the day
121 before, what they'll do today, and any obstacles to their work.
1224. **Sprint Review**: At the end of each sprint, the team will gather
123 and discuss the progress, obstacles, and backlog from the previous
124 sprint.
1255. **Sprint Retrospective**: More specific than the sprint review, the
126 retrospective is meant to discuss what worked and what did not work
127 during the sprint. This may be processes, tools, people, or even
128 things related to the Scrum ceremonies.
129
130One additional ceremony that may be applicable is organizing the
131backlog. This is typically the responsibility of the engagement leader
132and is meant to prioritize and clarify what needs to be done to complete
133items in the backlog.
134
135### Artifacts
136
137While artifacts are generally not customizable in the audit world (i.e.,
138each control test must include some kind of working paper with evidence
139supporting the test results), I wanted to include some quick notes on
140associating scrum artifact terms with an audit.
141
1421. **Product Backlog**: This is the overall backlog of unfinished audit
143 tasks from all prior sprints.
1442. **Sprint Backlog**: This is the backlog of unfinished audit tasks
145 from one individual sprint.
1463. **Increment**: This is the output of each sprint - generally this is
147 best thought of as any documentation prepared during the sprint,
148 such as risk assessments, control working papers, deficiency
149 analysis, etc.
150
151## Kanban
152
153Last but not least, Kanban is a methodology that relies on boards to
154categorize work into distinct, descriptive categories that allow an
155agile or scrum team to effectively plan the work of a sprint or project.
156
157See Atlassian's [Kanban](https://www.atlassian.com/agile/kanban) page
158for more information.
content/blog/2023-10-04-digital-minimalism.md deleted −106
@@ -1,106 +0,0 @@
1+++
2date = 2023-10-04
3title = "Digital Minimalism"
4description = "My personal retrospective on digital minimalism."
5+++
6
7I've written [a note about minimalism](/wiki/#digital-garden)
8before, but I wanted to dedicate some time to reflect on digital
9minimalism and how I've been able to minimize the impact of digital
10devices in my life.
11
12> These changes crept up on us and happened fast, before we had a chance
13> to step back and ask what we really wanted out of the rapid advances
14> of the past decade. We added new technologies to the periphery of our
15> experience for minor reasons, then woke one morning to discover that
16> they had colonized the core of our daily life. We didn't, in other
17> words, sign up for the digital world in which we're currently
18> entrenched; we seem to have stumbled backward into it.
19>
20> *(Digital Minimalism, 2019)*
21
22## The Principles of Digital Minimalism
23
24As noted in Cal Newport's book, *Digital Minimalism*, there are three
25main principles to digital minimalism that I tend to agree with:
26
271. Clutter is costly.
28 - Digital minimalists recognize that cluttering their time and
29 attention with too many devices, apps, and services creates an
30 overall negative cost that can swamp the small benefits that
31 each individual item provides in isolation.
322. Optimization is important.
33 - Digital minimalists believe that deciding a particular
34 technology supports something they value is only the first step.
35 To truly extract its full potential benefit, it's necessary to
36 think carefully about how they'll use the technology.
373. Intentionality is satisfying.
38 - Digital minimalists derive significant satisfaction from their
39 general commitment to being more intentional about how they
40 engage with new technologies. This source of satisfaction is
41 independent of the specific decisions they make and is one of
42 the biggest reasons that minimalism tends to be immensely
43 meaningful to its practitioners.
44
45## Taking Action
46
47In order to put the logic into practice, I've created a few new habits
48and continued performing old habits that are working well:
49
50### Using Devices With Intention
51
52- I already rarely use "social media", mostly limited to forums such
53 as Hacker News and Tildes, so I've just tweaked my behavior to stop
54 looking for content in those places when I'm bored.
55- Use devices with intention. Each time I pick up a digital device,
56 there should be an intention to use the device to improve my current
57 situation. No more endless scrolling or searching for something to
58 interest me.
59
60### Prevent Distractions
61
62- Disable (most) notifications on all devices. I spent 15-30 minutes
63 going through the notifications on my phone, watch, and computer to
64 ensure that only a select few apps have the ability to interrupt me:
65 Calendar, Messages, Phone, Reminders, & Signal.
66- Disable badges for any apps except the ones mentioned in the bullet
67 above.
68- Set-up focus profiles across devices so that I can enable different
69 modes, such as Personal when I only want to see notifications from
70 people I care about or Do Not Disturb, where absolutely nothing can
71 interrupt me.
72- Clean up my home screens. This one was quite easy as I already
73 maintain a minimalist set-up, but I went extreme by limiting my
74 phone to just eight apps on the home screen and four in the dock. If
75 I need another app, I'll have to search or use the app library.
76- Remove the work profile from my phone. This was a tough decision as
77 having my work profile on my device definitely makes my life easier
78 at times, but it also has quite a negative effect when I'm "always
79 online" and can see the notifications and team activity 24/7. I
80 believe creating a distinct barrier between my work and personal
81 devices will be beneficial in the end.
82
83### Creating Alternative Activities
84
85This is the most difficult piece, as most of my hobbies and interests
86lie in the digital world. However, I'm making a concerted effort to put
87devices down unless necessary and force myself to perform other
88activities in the physical world instead.
89
90I've started with a few basics that are always readily available to me:
91
92- Do a chore, such as organizing or cleaning.
93- Read a book, study a piece of art, etc.
94- Exercise or get outdoors.
95- Participate in a hobby, such as photography, birding, disc golf,
96 etc.
97- Let yourself be bored and wander into creativity.
98
99## Making Progress
100
101I'll be taking notes as I continue down this journey and hope to see
102positive trends. I've always been a minimalist in the physical world
103and it feels refreshing to filter out the clutter that has come to
104dominate my digital life over the years.
105
106I'm excited to see where this journey leads.
content/blog/2023-11-08-scli.md deleted −157
@@ -1,157 +0,0 @@
1+++
2date = 2023-11-08
3title = "Installing scli on Alpine Linux (musl)"
4description = "Learn how to properly configure scli for musl-based systems."
5+++
6
7[scli](https://github.com/isamert/scli) is a command-line tool that
8allows you to connect to your Signal messenger account. This program
9utilizes a two-pane display that shows you chats on the left and the
10focused conversation on the right.
11
12This guide will show you how to install `scli` and its
13dependencies on Alpine Linux, which requires some extra work due to
14musl.
15
16If you're using a non-musl system, you can likely following the
17`scli` README and download the packaged binaries for an
18easier installation process.
19
20## Dependencies
21
22In order to use `scli`, you need a few dependencies:
23
24- `openjdk17-jre` - Used as a dependency for the
25 `signal-cli` tool. Version may vary.
26- `signal-cli` - Used as the backbone of the
27 `scli` tool.
28- `findutils` - Replaces the standard Busybox version of
29 `xargs`.
30- `urwid` - A console user interface library for Python.
31- `urwid-readline` - For GNU emacs-like keybinds on the
32 input line.
33- `qrencode` - Displays a QR code in the terminal to link
34 the device using your phone. Not necessary if you're only linking
35 on desktop and can copy/paste the connection URL.
36
37Let's start by installing the packages available via Alpine's
38repositories. Be sure to install the latest version of
39`openjdk`. If you run into Java-related issues, uninstall
40`openjdk` and install an older version.
41
42```sh
43doas apk add openjdk17-jre findutils qrencode
44```
45
46Next, let's install `signal-cli`. Be sure to export the
47version of `signal-cli` that you want. I use version
48`0.12.4` below, but that may be outdated by the time you're
49reading this.
50
51```sh
52export VERSION="0.12.4"
53wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
54doas tar xf signal-cli-"${VERSION}".tar.gz -C /opt
55doas ln -sf /opt/signal-cli-${VERSION}"/bin/signal/cli /usr/local/bin
56```
57
58Finally, install the `urwid` packages using the Python
59packaging utility.
60
61```sh
62pip3 install urwid urwid-readline
63```
64
65## Installation
66
67Now that we have all of the dependencies we need, we can install
68`scli`. Start by simply cloning the repository.
69
70```sh
71git clone https://github.com/isamert/scli
72```
73
74When I cloned this repository on 2023-11-08, I found a bug in the logic
75that required a fix. You must edit the `scli` file and
76replace the one instance of `RLIMIT_OFILE` with
77`RLIMIT_NOFILE`.
78
79```sh
80cd scli
81nano scli
82```
83
84Once complete, you can move this program to anywhere on your
85`$PATH`. I chose the following directory.
86
87```sh
88doas mv scli /usr/local/bin/scli
89```
90
91## Initial Setup
92
93Now that everything is installed, we can login and configure the client.
94Start by generating a connection link.
95
96```sh
97signal-cli link -n "YOUR-DEVICE-NICKNAME" | tee >(xargs -L 1 qrencode -t utf8)
98```
99
100This will generate a connection link and related QR code for you to use
101to link the devices together. Once complete, **wait patiently** for the
102connection process to finish.
103
104Once it completes, it will exit and return you to the prompt. From here,
105you need to perform an initial `receive` command to start
106things off. The `USERNAME` variable should be your phone
107number, such as `+15551237890`.
108
109```sh
110signal-cli -u USERNAME receive
111```
112
113Also be sure to test the daemon to ensure it works properly. If no
114errors occur, it's working. If you run into errors because you're not
115running a DBUS session, see my notes below.
116
117```sh
118signal-cli -u USERNAME daemon
119```
120
121Once the initial reception is complete, you are ready to use
122`scli`.
123
124This process will differ depending on your desktop environment (DE). If
125you are running a DE, you likely have a DBUS session running already and
126can simply launch the program.
127
128However, if you're like me and running your computer straight on the
129TTY without a DE, you'll need to start a DBUS session for this program.
130
131```sh
132# If you're not running a DBUS session yet, you need to start one for scli
133dbus-run-session -- scli
134
135# OR - If you're already running a DBUS session, simply run scli
136scli
137```
138
139## Configuration
140
141Lastly, there are a number of configuration options that you can pass
142via the command or in the `~/.config/sclirc` file. See the
143Github README for more information on configuration options.
144
145```sh
146nano ~/.config/sclirc
147```
148
149```conf
150# ~/.config/sclirc
151
152wrap-at = 80
153enable-notifications = true
154```
155
156That's it! Following this guide, I have a functional `scli`
157program that successfully sends messages to my contacts and myself!
content/blog/2024-01-08-dont-say-hello.md deleted −17
@@ -1,17 +0,0 @@
1+++
2date = 2024-01-08
3title = "Don't Say Hello"
4description = "A short post describing my displeasure with cliffhanger conversations."
5+++
6
7I recently came back from a winter break and have started working again... only to immediately run into the dilemma of people sending me cliffhanger messages again.
8
9## No Hello
10
11A year or two ago, I discovered [no hello](https://nohello.net/en/) and have thought about it often since then. I've even sent it to a few people since then (who wouldn't take offense to it).
12
13I work in a fast-paced environment where efficiency is extremely valuable. Therefore, I have always held a deep displeasure for conversations where people start with "Hello" and then.
14
15I searched back through my work messages and found that I received ~50 messages from ~10 people last year from people that contained "hi", "hey", or "hello" and did not contain any indication of the purpose of the conversation. I also noticed that a few of the users were responsible for the large majority of the cliffhangers.
16
17There's no real point to this post, just a desparate request for people to please stop doing this.
content/blog/2024-01-09-macos-customization.md deleted −166
@@ -1,166 +0,0 @@
1+++
2date = 2024-01-09
3title = "Customizing macOS"
4description = "Learn how to customize macOS beyond the standard, built-in options provided by Apple."
5+++
6
7I have been using macOS more than Linux lately, so I wrote this post to describe
8some simple options to customize macOS beyond the normal built-in settings
9menu.
10
11While not all-encompassing, the options below should be a good start for anyone
12looking to dive down the rabbit hole.
13
14## Basics
15
16### Package Management
17
18To install a lot of software on macOS, you will need
19[Homebrew](https://brew.sh/). You can use their installation script to get
20started. Simply open the `Terminal` application and paste the following snippet:
21
22```sh
23/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
24```
25
26This will allow you to easily install and manage applications and other software
27easily through the `brew` command.
28
29### Terminal
30
31If you're serious about customizing your macOS system, I highly recommend
32installing a terminal emulator that you like and if you're not comfortable on
33the command line yet, start learning. A lot of customization options require you
34to edit hidden files, which is easiest in a terminal.
35
36There are options like iTerm2, Kitty, Alacritty, Hyper, Warp, or the built-in
37Terminal app.
38
39I use [iTerm2](https://iterm2.com/), which can be installed with Homebrew:
40
41```sh
42brew install iterm2
43```
44
45![iTerm2](https://img.cleberg.net/blog/20240109-macos-customization/iterm2.png "iTerm2")
46
47To install color schemes, such as the Dracula scheme shown in the screenshot
48above, you visit [iTerm Themes](https://iterm2colorschemes.com/) and follow
49their installation instructions to install any of the themes.
50
51## Desktop
52
53### Window Management
54
55[yabai](https://github.com/koekeishiya/yabai) is a tiling window manager for
56macOS. While other window managers exist, I found that most of them struggled to
57create logical layouts and to allow me to easily move windows around the screen.
58
59Some advanced settings for yabai are only available if partially disable System
60Integrity Protection (SIP). However, I chose not to do this and it hasn't
61affected my basic usage of yabai at all.
62
63Refer to the [yabai
64wiki](https://github.com/koekeishiya/yabai/wiki/Installing-yabai-(latest-release))
65for installation instructions. You will need to ensure that yabai is allowed to
66access the accessibility and screen recording APIs.
67
68You can see a basic three-pane layout atuomatically configured by yabai for me as I opened the windows below.
69
70![yabai window manager](https://img.cleberg.net/blog/20240109-macos-customization/yabai.png "yabai window manager")
71
72### Keyboard Shortcuts
73
74[skhd](https://github.com/koekeishiya/skhd) is a simple hotkey daemon that
75allows you to define hotkeys in a file for usage on your system.
76
77Installation is simple:
78
79```sh
80brew install koekeishiya/formulae/skhd
81skhd --start-service
82```
83
84After installation, be sure to allow `skhd` access to the accessibility API in
85the macOS privacy settings.
86
87You can configure your hotkeys in the `~/.config/skhd/skhdrc` file:
88
89```sh
90nano ~/.config/skhd/skhdrc
91```
92
93For example, I have hotkeys to open my browser and terminal:
94
95```conf
96# Terminal
97cmd - return : /Applications/iTerm.app/Contents/MacOS/iTerm2
98
99# Browser
100cmd + shift - return : /Applications/LibreWolf.app/Contents/MacOS/librewolf
101```
102
103### Widgets
104
105[uebersicht](https://github.com/felixhageloh/uebersicht/) is a handy
106desktop-based widget tool with a plethora of community-made widgets available in
107the [widgets gallery](https://tracesof.net/uebersicht-widgets/). You can also
108write your own widgets with this tool.
109
110To install, simply download the latest release from the [uebersicht
111website](https://tracesof.net/uebersicht/) and copy it to the Applications
112folder.
113
114See below for an example of the
115[Mond](https://tracesof.net/uebersicht-widgets/#Mond) widget in action.
116
117![uebersicht desktop widgets](https://img.cleberg.net/blog/20240109-macos-customization/uebersicht.png "uebersicht desktop widgets")
118
119### Status Bar
120
121[SketchyBar](https://github.com/FelixKratz/SketchyBar) is a customizable
122replacement for the macOS status or menu bar.
123
124You can browse a discussion where various users shared their
125[configurations](https://github.com/FelixKratz/SketchyBar/discussions/47?sort=top)
126for inspiration or to copy their dotfiles.
127
128See below for a quick (& slightly broken) copy of
129[zer0yu's](https://github.com/zer0yu/dotfiles) SketchyBar configuration.
130
131![SketchyBar](https://img.cleberg.net/blog/20240109-macos-customization/sketchybar.png "SketchyBar")
132
133### Dock
134
135The easiest way to customize the dock is to install
136[uBar](https://ubarapp.com/), which uses a Windows-like menu bar as the default
137style.
138
139However, the built-in macOS dock cannot be disabled and can only be set to
140"always hidden". This can be annoying as it will pop out any time your mouse
141cursor passes closely to the dock edge of the screen. Because of this, I simply
142use the built-in dock instead of customizing it with third-party software.
143
144Regardless, see below for the default installation style of uBar.
145
146![uBar](https://img.cleberg.net/blog/20240109-macos-customization/ubar.png "uBar")
147
148### Application Icons
149
150You can also customize the icon of any application in macOS, which will show up
151in Finder, the Dock, Launchpad, search results, etc. I recommend using
152[macOSicons](https://macosicons.com/) to download icons you want, and then apply
153them by following this process.
154
1551. Open the Finder application.
1562. Navigate to the `Applications` folder.
1573. Right-click an application of your choice, and select `Get Info`.
1584. Drag the image you downloaded on top of the application's icon at the top of
159 information window (you will see a green "plus" symbol when you're hovering
160 over it).
1615. Release the new icon on top of the old icon and it will update!
162
163You can see an example of me dragging a new `signal.icns` file onto my
164Signal.app information window to update it below:
165
166![replace macOS icons](https://img.cleberg.net/blog/20240109-macos-customization/replace_icon.png "replace macOS icons")
content/blog/2024-01-13-local-llm.md deleted −103
@@ -1,103 +0,0 @@
1+++
2date = 2024-01-13
3title = "Running Local LLMs on macOS and iOS"
4description = "Finding some useful applications for running local LLMs on macOS and iOS."
5+++
6
7## Requirements
8
9I've recently started playing with large language models (LLMs), mostly in the
10popular chatbot form, as part of my job and have decided to see if there's a
11consistent and reliable way to interact with these models on Apple devices
12without sacrificing privacy or requiring in-depth technical setup.
13
14My requirements for this test:
15
16- Open source platform
17- On-device model files
18- Minimal required configuration
19- Preferably pre-built, but a simple build process is acceptable
20
21I tested a handful of apps and have summarized my favorite (so far) for macOS
22and iOS below.
23
24> TL;DR - Here are the two that met my requirements and I have found the easiest
25> to install and use so far:
26
27- macOS: [Ollama](https://ollama.ai/)
28- iOS : [LLM Farm](https://llmfarm.site/)
29
30## macOS
31
32[Ollama](https://ollama.ai/) is a simple Go application for macOS and Linux that
33can run various LLMs locally.
34
35For macOS, you can download the pplication on the [Ollama download
36page](https://ollama.ai/download/mac) and install it by unzipping the
37`Ollama.app` file and moving it to the `Applications` folder.
38
39If you prefer the command line, you can run these commands after the download
40finished:
41
42```sh
43cd ~/Downloads && \
44unzip Ollama-darwin.zip && \
45mv ~/Downloads/Ollama.app /Applications/
46```
47
48After running the app, the app will ask you to open a terminal and run the
49default `llama2` model, which will open an interactive chat session in the
50terminal. You can startfully using the application at this point.
51
52![Ollama](https://img.cleberg.net/blog/20240113-local-llm/ollama.png "Ollama")
53
54If you don't want to use the default `llama2` model, you can download and run
55additional models found on the [Models](https://ollama.ai/library) page.
56
57To see the information for the currently-used model, you can run the `/show
58info` command in the chat.
59
60![Model Info](https://img.cleberg.net/blog/20240113-local-llm/ollama_info.png "Model Info")
61
62### Community Integrations
63
64I highly recommend browsing the [Community
65Integrations](https://github.com/jmorganca/ollama#community-integrations)
66section of the project to see how you would prefer to extend Ollama beyond a
67simple command-line interface. There are options for APIs, browser UIs, advanced
68terminal configurations, and more.
69
70![Ollama SwiftUI](https://img.cleberg.net/blog/20240113-local-llm/ollama-swiftui.png "Ollama SwifTUI")
71
72## iOS
73
74While there are a handful of decent macOS options, it was quite difficult to
75find an iOS app that offered an open source platform without an extensive
76configuration and building process. I found LLM Farm to be decent enough in
77quality to sit at the top of my list - however, it's definitely not user
78friendly enough for me to consider using it on a daily basis.
79
80[LLM Farm](https://llmfarm.site/) is available on TestFlight, so there's no
81manual build process required. However, you can view the [LLMFarm
82repository](https://github.com/guinmoon/LLMFarm) if you wish.
83
84The caveat is that you will have to manually download the model files from the
85links in the
86[models.md](https://github.com/guinmoon/LLMFarm/blob/main/models.md) file to
87your iPhone to use the app - there's currently no option in the app to reach out
88and grab the latest version of any supported model.
89
90Once you have a file downloaded, you simply create a new chat and select the
91downloaded model file and ensure the inference matches the requirement in the
92`models.md` file.
93
94See below for a test of the ORCA Mini v3 model:
95
96| Chat List | Chat |
97|--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
98| ![LLM Farm Chats](https://img.cleberg.net/blog/20240113-local-llm/llm_farm_chats.png "LLM Farm Chats") | ![LLM Farm](https://img.cleberg.net/blog/20240113-local-llm/llm_farm.png "LLM Farm")
99
100[Enchanted](https://github.com/AugustDev/enchanted) is also an iOS for private
101AI models, but it requires a public-facing Ollama API, which did not meet my
102"on device requirement." Nonetheless, it's an interesting looking app and I will
103likely set it up to test soon.
content/blog/2024-01-16-audit-dashboard.md deleted −134
@@ -1,134 +0,0 @@
1+++
2date = 2024-01-26
3title = "Building an Audit Status Dashboard"
4description = "Learn how to utilize Alteryx Designer and Power BI Desktop to build a simple status tracking dashboard for an audit or other engagement."
5+++
6
7Alteryx and Power BI are powerful tools that can help turn your old-school audit trackers into interactive tools that provide useful insights and potential action plans.
8
9With these tools, we are going to build the following dashboard:
10
11![Audit Dashboard, pg. 1](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png)
12![Audit Dashboard, pg. 2](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png)
13
14## Requirements
15
16This project assumes the following:
17
18- You have access to Alteryx Designer and Power BI Desktop.
19 - If you only have Power BI Desktop, you may need to perform some analysis in Power BI instead of Alteryx.
20- Your data is in a format that can be imported into Alteryx and/or Power BI.
21- You have a basic understanding of data types and visualization.
22
23## Alteryx: Data Preparation & Analysis
24
25### Import Data
26
27With Alteryx, importing data is easy with the use of the `Input Data` tool. Simply drag this tool onto the canvas from the `In/Out` tab in the Ribbon to create it as a node.
28
29You can choose the File Format manually or simply connect to your file/database and let Alteryx determine the format for you. For this example, we will be importing an Excel file and changing the `Start Data Import on Line` variable to `2`.
30
31![Alteryx Excel Import](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_import.png)
32
33### Transform Data
34
35Next, let's replace null data and remove whitespace to clean up our data. We can do this with the `Data Cleansing` tool in the `Preparation` tab in the Ribbon.
36
37Ensure that the following options are enabled:
38
39- Replace Nulls
40 - Replace with Blanks (String Fields)
41 - Replace with 0 (Numeric Fields)
42- Remove Unwanted Characters
43 - Leading and Trailing Whitespace
44
45![Data Cleansing](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_cleansing.png)
46
47For our next step, we will transform the date fields from strings to datetime format. Add a `Datetime` tool for each field you want to transform - in the example below, I am using the tool twice for the "Started On" and "Submitted On" fields.
48
49![Data Transformation](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_transformation.png)
50
51Now that the dates are in the correct format, let's perform a calculation based on those fields. Start by adding a `Filter` tool, naming a new Output Column, and pasting the formula below into it (the two fields used in this formula must match the output of the `Datetime` tools above):
52
53```txt
54DateTimeDiff([SubmittedOn_Out],[StartedOn_Out], "days")
55```
56
57![Data Analysis](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_analysis.png)
58
59### Export Data
60
61Finalize the process by exporting the transformed data set to a new file, for use in the following visualization step.
62
63![Data Export](https://img.cleberg.net/blog/20240126-audit-dashboard/alteryx_export.png)
64
65## Power BI: Data Visualization
66
67### Import Data
68
69To start, open the Power BI Desktop application. Upon first use, Power BI will ask if you want to open an existing dashboard or import new data.
70
71As we are creating our first dashboard, let's import our data. In my example below, I'm importing data from the "Tracker" sheet of the Excel file I'm using for this project.
72
73During this process, I also imported the export from the Alteryx workflow above. Therefore, we have two different files available for use in our dashboard.
74
75![Excel Tracker](https://img.cleberg.net/blog/20240126-audit-dashboard/excel_tracker.png)
76
77![Power BI Excel Import](https://img.cleberg.net/blog/20240126-audit-dashboard/powerbi_import.png)
78
79### Add Visuals
80
81To create the dashboard below, you will need to follow the list instructions below and format as needed:
82
83![Audit Dashboard, pg. 1](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_01.png)
84![Audit Dashboard, pg. 2](https://img.cleberg.net/blog/20240126-audit-dashboard/dashboard_02.png)
85
86Instructions to create the visuals above:
87
88- `Text Box`: Explain the name and purpose of the dashboard. You can also add images and logos at the top of the dashboard.
89- `Donut Chart`: Overall status of the project.
90 - `Legend`: Status
91 - `Values`: Count of Status
92- `Stacked Column Chart`: Task count by assignee.
93 - `X-axis`: Preparer
94 - `Y-axis`: Count of Control ID
95 - `Legend`: Status
96- `Treemap`: Top N client submitters by average days to submit.
97 - `Details`: Preparer
98 - `Values`: Sum of Avg_DaysToSubmit
99- `Line Chart`: Projected vs. actual hours over time.
100- `Clustered Bar Chart`: Projected vs. actual hours per person.
101- `Slicer & Table` - Upcoming due dates.
102 - `Slicer`:
103 - `Values`: Date Due
104 - `Table`:
105 - `Columns`: Count of Control ID, Date Due, Preparer, Status
106
107### Format the Dashboard
108
109You can choose a theme in the View tab of the Ribbon. You can even browse for custom JSON files that define themes, such as ones found online or custom ones created by your organization.
110
111For each visual, you can click the `Format` button in the `Visualizations` side pane and explore the options. You can custom options such as:
112
113- Visual
114 - Legend
115 - Colors
116 - Data labels
117 - Category labels
118- General
119 - Properties
120 - Title
121 - Effects
122 - Header icons
123 - Tooltips
124 - Alt text
125
126You can always look online for inspiration when trying to decide how best to organize and style your dashboard.
127
128## Sharing the Results
129
130Generally, you have a few different options for sharing your dashboards with others:
131
1321. Export the dashboard as a PDF in the file menu of Power BI. This will export all tabs and visuals as they are set when the export button is pressed. You will lose all interactivity with this option.
1332. Send the full Power BI file to those you wish to share the dashboard. This will retain all settings and interactivity. However, you will also need to send the source files if they need to refresh the dashboard and you will need to re-send the files if you make updates.
1343. Store the dashboard in a synced location, such as a shared drive or Microsoft Teams. Depending on how a user configures their local Windows paths, the data source paths may not be compatible for all users with such a setup.
content/blog/2024-01-27-tableau-dashboard.md deleted −122
@@ -1,122 +0,0 @@
1+++
2date = 2024-01-27
3title = "Data Visualization: Mapping Omaha Crime Data with Tableau"
4description = "Learn how to utilize Tableau Public to build a dashboard to map crimes across Omaha."
5+++
6
7In this project, I am going to show you how to use Tableau Public for free to create simple dashboards.
8
9I will be creating simple visuals from an Omaha crime data set and combining them to create the dashboard below. You can view this dashboard interactively online here: [Omaha Crime Data (2015 - 2023)](https://public.tableau.com/app/profile/c.c7042/viz/OmahaCrimeData2015-2023/OmahaCrimeData2015-2023#1).
10
11![Tableau Dashboard](https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png)
12
13## Gather the Data
14
15You can download incident data from the Omaha Police Department on their [Incident Data Download](https://police.cityofomaha.org/crime-information/incident-data-download) page. They currently have files for the years 2015 through 2023.
16
17Each file will be downloaded as a CSV file, approximately 3 MB - 8 MB.
18
19## Clean and Transform the Data
20
21I have used Python to combine the files into a single CSV file, as well as adding a custom `datetime` column. You could do this step in any software you prefer, but I prefer Python as its free, easy to use, and has a plethora of support resources online.
22
23Start by opening a terminal, navigating to your Downloads directory, and creating a python script.
24
25```sh
26cd ~/Downloads
27nano data_processing.py
28```
29
30Within the Python script, paste the following:
31
32```python
33# Import modules
34import pandas as pd
35import glob
36import os
37
38# Import the data
39path = r"~/Downloads/*.csv"
40files = glob.glob(path)
41
42list = []
43
44for file in files:
45 df_tmp = pd.read_csv(file)
46 li.append(df_tmp)
47
48df = pd.concat(list, axis=0, ignore_index=True)
49
50# Create a combined datetime column
51df["datetime"] = pd.to_datetime(
52 df["date"] + " " + df["time"],
53 format="%m/%d/%Y %H:%M:%S"
54)
55df.head()
56
57# Export the combined data
58df.to_csv(r"~/Downloads/combined_incidents.csv")
59```
60
61Once pasted, save and close the file. You can execute the file like so:
62
63```sh
64python3 data_processing.py
65```
66
67After this, you should have a combined data file that contains all incidents between 2015 and 2023. Mine was approximately 55 MB.
68
69## Tableau Public
70
71[Tableau Public](https://public.tableau.com/) is a free-to-use web application that allows you to create visualizations by uploading data sources. Note that there's no way to keep the data and visualizations private, so don't upload anything private.
72
73After creating an account, you can click the `Create` > `Web Authoring` link to create your first visualization.
74
75![Create Button](https://img.cleberg.net/blog/20240127-tableau-dashboard/create_button.png)
76
77### Upload the Data
78
79Once you've opened your first project, Tableau will ask you to connect to your data. For this project, click the `Upload from computer` button and select the CSV file previously combined in the step above.
80
81![Connect Data](https://img.cleberg.net/blog/20240127-tableau-dashboard/connect_data.png)
82
83Once connected, you can refresh the preview of the data with the `Refresh Data Source` button in the toolbar.
84
85![Data Preview](https://img.cleberg.net/blog/20240127-tableau-dashboard/data_preview.png)
86
87If you need to edit any of the data types, column names, etc., you can do so now. Once complete, generate an extract so that you can start creating visualizations.
88
89### Create Visualizations
90
91To start, create a worksheet in the toolbar at the bottom of the screen.
92
93Within this screen, select a column from the `Data` side bar on the left and drag it into the `Columns` or `Rows` area of the canvas.
94
95See below for the map visualization. You can recreate this by adding the following fields:
96
97- `Columns`: Lon
98- `Rows`: Lat
99- `Marks`:
100 - Description
101 - Datetime
102- `Filters`: Datetime
103
104![Worksheet Editing](https://img.cleberg.net/blog/20240127-tableau-dashboard/worksheet_edit.png)
105
106You can repeat this process for each visualization you want to create. Explore your options by dragging data fields to different areas and by opening the field options to explore what operations can be performed on different data types (e.g., average, count, etc.).
107
108### Create Dashboard
109
110To create a dashboard, click the button on the toolbar at the bottom of the screen. Within the dashboard, drag each sheet from the left side bar onto the dashboard canvas.
111
112![Dashboard Editing](https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard_edit.png)
113
114### Formatting
115
116You can explore a ton of different formatting options throughout the worksheets and dashboard. Specifically for maps, you can alter the map layers, background, and visible features through the `Map` menu in the top file menu of the editing screen.
117
118In the finished dashboard below, I opted for a dark mode with a map that showed county lines and city names.
119
120There's a ton of other options available to be used in a dashboard like this, but this project shows a quick preview of what you can do in Tableau Public.
121
122![Tableau Dashboard](https://img.cleberg.net/blog/20240127-tableau-dashboard/dashboard.png)
content/blog/2024-02-13-ubuntu-emergency-mode.md deleted −71
@@ -1,71 +0,0 @@
1+++
2date = 2024-02-13
3title = "Stuck in Ubuntu's Emergency Mode? Try Fixing the Fstab File"
4description = "Learn how to fix a missing disk issue that can lock you in Ubuntu's emergency mode."
5+++
6
7### The Problem
8
9I recently [migrated my hard drives to a ZFS pool](/blog/zfs/) and found myself
10stuck in Ubuntu's emergency mode after the first reboot I performed after
11creating the ZFS pool.
12
13My server was stuck in the boot process and showed the following error on the
14screen:
15
16```txt
17You are in emergency mode.
18After logging in, type "journalctl -xb" to view system logs,
19"systemctl reboot" to reboot, "systemctl default"
20or ^D to try again to boot into default mode".
21```
22
23After rebooting the server and watching the logs scroll on a monitor, I noticed
24the root cause was related to a very long search for certain drives. I kept
25seeing errors like this:
26
27```txt
28[ TIME ] Timed out waiting of device dev-disk-by/[disk-uuid]
29```
30
31I realized that I had not removed the `/etc/fstab` references that asked Ubuntu
32to mount two disks on boot, but I had recently changed those disks to be part of
33my ZFS pool instead. Therefore, Ubuntu was trying to identify and mount a disk
34that was not available.
35
36Now that we have an idea of the issue, let's move to solution.
37
38### The Solution
39
40In order to fix the issue, I waited until I was allowed to type the root user's
41password, and then I executed the following command:
42
43```sh
44nano /etc/fstab
45```
46
47Within the `fstab` file, I needed to comment/remove the following lines at the
48bottom of the file. You can comment-out a line by prepending a `#` symbol at the
49beginning of the line. You can also delete the line entirely.
50
51```conf
52# What it looked like when running into the issue:
53UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
54UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
55
56# What I changed it to, in order to fix the issue:
57# UUID=B64E53824E5339F7 /mnt/white-01 ntfs-3g uid=1000,gid=1000 0 0
58# UUID=E69867E59867B32B /mnt/white-02 ntfs-3g uid=1000,gid=1000 0 0
59```
60
61Once removing the lines above from the `/etc/fstab` file, save and exit the file
62by hitting the `Ctrl` + `x` key combo.
63
64You can now hit `Ctrl` + `D` to continue, or reboot:
65
66```sh
67systemctl reboot
68```
69
70Once rebooted, I was able to watch the machine boot properly and launch to the
71TTY login screen without errors!
content/blog/2024-02-21-self-hosting-otter-wiki.md deleted −135
@@ -1,135 +0,0 @@
1+++
2date = 2024-02-21
3title = "Self-Hosting An Otter Wiki"
4description = "Learn how to easily self-host An Otter Wiki."
5+++
6
7## An Otter Wiki
8
9[An Otter Wiki](https://otterwiki.com/) is an easy to use wiki software that
10takes almost no effort to set up and maintain.
11
12## Installation
13
14To install An Otter Wiki, I'm going to use Docker Compose to create the
15container and then use Nginx as a reverse proxy to allow external access via a
16subdomain I own.
17
18### Docker Compose
19
20Start by creating a directory for the container's files.
21
22```sh
23mkdir ~/otterwiki
24```
25
26Next, create the `docker-compose.yml` file to define the container's parameters.
27
28```sh
29nano ~/otterwiki/docker-compose.yml
30```
31
32Within the file, paste the following content. You can read the project's
33documentation if you want to further override or customize the container.
34
35```conf
36version: '3'
37services:
38 otterwiki:
39 image: redimp/otterwiki:2
40 restart: unless-stopped
41 # Internal port must be assigned to port 80
42 # External port can be customized
43 ports:
44 - 8337:80
45 volumes:
46 - ./app-data:/app-data
47```
48
49Once the file is saved and closed, you can launch the container.
50
51```sh
52cd ~/otterwiki
53sudo docker-compose up -d
54```
55
56The container is now available at `localhost:8337`. Next, we will use Nginx to
57serve this app externally.
58
59### Nginx
60
61To access the app externally, let's set up a reverse proxy. I'll start by
62creating the Nginx configuration file for my wiki.
63
64```sh
65sudo nano /etc/nginx/sites-available/wiki
66```
67
68Within the file, I have saved the following content. This assumes you already
69have a TLS/SSL cert to use with this subdomain. If not, simply remove the
70`ssl_*` variables, remove the `80` server block, and change the `443`
71server block to `80` to serve the app without SSL.
72
73```conf
74server {
75 if ($host ~ ^[^.]+\.example\.com$) {
76 return 301 https://$host$request_uri;
77 }
78
79 listen [::]:80;
80 listen 80;
81 server_name wiki.example.com;
82 return 404;
83}
84
85server {
86 listen [::]:443 ssl http2;
87 listen 443 ssl http2;
88 server_name wiki.example.com;
89 access_log /var/log/nginx/wiki.access.log;
90 error_log /var/log/nginx/wiki.error.log;
91
92 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
93 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
94 include /etc/letsencrypt/options-ssl-nginx.conf;
95 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
96
97 location / {
98 set $upstream_wiki http://127.0.0.1:8337;
99 proxy_pass $upstream_wiki;
100
101 proxy_set_header Host $http_host;
102 proxy_set_header X-Real-IP $remote_addr;
103 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
104 proxy_set_header X-Forwarded-Host $http_host;
105 }
106}
107```
108
109Save and close the configuration file. On Nginx, we need to symlink the file to
110enable it.
111
112```sh
113sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/wiki
114```
115
116Once enabled, restart the Nginx server to start serving the app externally.
117
118```sh
119sudo systemctl restart nginx.service
120```
121
122## Screenshots
123
124In the screenshot below, you can see the initial launch screen for An Otter
125Wiki. Start by creating your admin account and configure the app as necessary.
126
127![An Otter Wiki - Initial Screen](https://img.cleberg.net/blog/20240221-otterwiki/otterwiki.png)
128
129You can also see the default editing screen for creating and editing pages. It's
130as easy as typing Markdown and hitting the save button.
131
132![An Otter Wiki - Editing Screen](https://img.cleberg.net/blog/20240221-otterwiki/otterwiki-edit.png)
133
134Overall, I like An Otter Wiki and will be looking forward to seeing how much use
135I get out of it going forward.
content/blog/_index.md deleted −4
@@ -1,4 +0,0 @@
1+++
2title = "Blog"
3sort_by = "date"
4+++\ No newline at end of file
content/salary/index.md deleted −54
@@ -1,54 +0,0 @@
1+++
2title = "Salary"
3description = "A summary of my salaries throughout my career."
4+++
5
6## Salary Transparency
7
8The data below details the base salary information for each job I've
9held. This information is posted publicly to ensure others in my
10position have a solid reference point when determining if their current
11or proposed salary is appropriate.
12
13While sites like Glassdoor are locking salary data behind a paywall,
14LinkedIn is discontinuing LinkedIn Salary, and helpful websites like Big
154 Transparency are extremely rare, I wanted to provide my personal data
16publicly and freely to those who need it.
17
18I have seen what can happen when great employees don't know the market
19values for their skills and I happily help those in my teams, so I'm
20happy to extend this information to the online community.
21
22As a final note, there are numerous reasons that people in the same role
23are paid differently (expertise, years of experience, certifications,
24education, etc.) and that the data in this table should only be used as
25a single point of reference, not the whole story.
26
27## Salary Data
28
29Note: When in a role that gives periodic raises, I will create a new
30record with the new base salary in the table below. See the KPMG records
31for an example of a raise while in the same role.
32
33| Title | Company | Location | Start | End | Salary |
34|------------------------------------------------|------------------------|----------------|:-------:|:-------:|----------|
35| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2023-10 | Current | $116,700 |
36| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2022-06 | 2023-10 | $110,000 |
37| Senior Technology Risk Consultant | Ernst & Young | Des Moines, IA | 2021-09 | 2022-06 | $89,500 |
38| Senior IT Auditor | Ameritas | Lincoln, NE | 2021-05 | 2021-09 | $72,000 |
39| IT Auditor | Ameritas | Lincoln, NE | 2020-04 | 2021-05 | $65,000 |
40| IS Auditor II | Nelnet | Lincoln, NE | 2019-12 | 2020-04 | $58,000 |
41| IS Auditor I | Nelnet | Lincoln, NE | 2019-06 | 2019-12 | $20/hour |
42| Internal Audit Intern | Ameritas | Lincoln, NE | 2018-02 | 2019-06 | $16/hour |
43| Teaching Assistant | University of Nebraska | Lincoln, NE | 2017-08 | 2018-05 | $7/hour |
44| Community Management Intern | Walgreens | Lincoln, NE | 2017-06 | 2018-02 | $14/hour |
45
46This page was inspired by [Xe](https://xeiaso.net/), and I'm quoting
47the following wording from them as I want to reiterate this piece:
48
49> Please consider publishing your salary data like this as well. By
50> open, voluntary transparency we can help to end stigmas around
51> discussing pay and help ensure that the next generations of people in
52> tech are treated fairly. Stigmas thrive in darkness but die in the
53> light of day. You can help end the stigma by playing your cards out in
54> the open like this.
content/services/index.md deleted −18
@@ -1,18 +0,0 @@
1+++
2title = "Services"
3description = "Publicly-available services hosted on cleberg.net."
4+++
5
6- [AnonymousOverflow](https://ao.cleberg.net) - A StackOverflow proxy
7- [CyberChef](https://cyberchef.cleberg.net) - The Cyber Swiss Army Knife
8- [FileArchive](https://files.cleberg.net) - An interesting file archive
9- [FlashPaper](https://paste.cleberg.net) - One-time encrypted password/secret sharing
10- [GotHub](https://gh.cleberg.net) - An alternative front-end for GitHub
11- [ifconfig.php](https://ip.cleberg.net) - A \"whatsmyip\" tool
12- [Invidious](https://invidious.cleberg.net) - A YouTube proxy
13- [Office](https://office.cleberg.net) - The world\'s smallest office suite
14- [SearXNG](https://search.cleberg.net) - A privacy-respecting, open metasearch engine
15
16See the [git
17log](https://git.cleberg.net/cleberg.net.git/log)
18if you want to see changes that have been made.
index.org added +28
@@ -0,0 +1,28 @@
1#+title: cleberg.net
2#+options: toc:nil
3#+options: \n:t
4
5#+begin_quote
6Do AIs dream in electric sheep?
7He hoped it had nanosecond nightmares.
8#+end_quote
9
10* Recent Blog Posts
11
12- 2024-02-21 [[./blog/self-hosting-otter-wiki/][Self-Hosting An Otter Wiki]]
13- 2024-02-13 [[./blog/ubuntu-emergency-mode/][Stuck in Ubuntu's Emergency Mode? Try Fixing the Fstab File]]
14- 2024-02-06 [[./blog/zfs/][How to Create a ZFS Pool on Ubuntu Linux]]
15
16[[./blog/][All Posts →]]
17
18* Everything Else
19
20- [[./salary/][Salary]]
21- [[./services/][Services]]
22- [[./wiki/][Wiki]]
23
24* Contact
25
26- hello [at] cleberg.net ([[./gpg.txt][GPG]])
27- [[https://matrix.to/#/@cyborg:matrix.tchncs.de][@cyborg:@matrix.tchncs.de]]
28- [[https://git.cleberg.net/cleberg.net.git][Git]]
salary/index.org added +50
@@ -0,0 +1,50 @@
1#+title: Salary
2#+description: A historical log of my jobs and salaries.
3
4* Salary Transparency
5
6The data below details the base salary information for each job I've held. This
7information is posted publicly to ensure others in my position have a solid
8reference point when determining if their current or proposed salary is
9appropriate.
10
11While sites like Glassdoor are locking salary data behind a paywall, LinkedIn is
12discontinuing LinkedIn Salary, and helpful websites like Big 4 Transparency are
13extremely rare, I wanted to provide my personal data publicly and freely to
14those who need it.
15
16I have seen what can happen when great employees don't know the market values
17for their skills and I happily help those in my teams, so I'm happy to extend
18this information to the online community.
19
20As a final note, there are numerous reasons that people in the same role are
21paid differently (expertise, years of experience, certifications, education,
22etc.) and that the data in this table should only be used as a single point of
23reference, not the whole story.
24
25* Salary Data
26
27Note: When in a role that gives periodic raises, I will create a new record with
28the new base salary in the table below. See the KPMG records for an example of a
29raise while in the same role.
30
31| Title | Company | Location | Start | End | Salary |
32|------------------------------------------------+------------------------+----------------+---------+---------+----------|
33| Senior IT & Security Auditor | Gemini | Remote | 2024-03 | Current | $122,000 |
34| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2023-10 | 2024-03 | $116,700 |
35| Senior Associate, Technology Assurance - Audit | KPMG | Omaha, NE | 2022-06 | 2023-10 | $110,000 |
36| Senior Technology Risk Consultant | Ernst & Young | Des Moines, IA | 2021-09 | 2022-06 | $89,500 |
37| Senior IT Auditor | Ameritas | Lincoln, NE | 2021-05 | 2021-09 | $72,000 |
38| IT Auditor | Ameritas | Lincoln, NE | 2020-04 | 2021-05 | $65,000 |
39| IS Auditor II | Nelnet | Lincoln, NE | 2019-12 | 2020-04 | $58,000 |
40| IS Auditor I | Nelnet | Lincoln, NE | 2019-06 | 2019-12 | $20/hour |
41| Internal Audit Intern | Ameritas | Lincoln, NE | 2018-02 | 2019-06 | $16/hour |
42| Teaching Assistant | University of Nebraska | Lincoln, NE | 2017-08 | 2018-05 | $7/hour |
43| Community Management Intern | Walgreens | Lincoln, NE | 2017-06 | 2018-02 | $14/hour |
44
45This page was inspired by [[https://xeiaso.net/][Xe]], and I'm quoting the following wording from them as
46I want to reiterate this piece:
47
48#+begin_quote
49Please consider publishing your salary data like this as well. By open, voluntary transparency we can help to end stigmas around discussing pay and help ensure that the next generations of people in tech are treated fairly. Stigmas thrive in darkness but die in the light of day. You can help end the stigma by playing your cards out in the open like this.
50#+end_quote
services/index.org added +14
@@ -0,0 +1,14 @@
1#+title: Services
2#+description: Publicly-available services hosted on cleberg.net.
3
4- [[https://ao.cleberg.net/][AnonymousOverflow]] - A StackOverflow proxy
5- [[https://cyberchef.cleberg.net/][CyberChef]] - The Cyber Swiss Army Knife
6- [[https://files.cleberg.net/][FileArchive]] - An interesting file archive
7- [[https://paste.cleberg.net/][FlashPaper]] - One-time encrypted password/secret sharing
8- [[https://gh.cleberg.net/][GotHub]] - An alternative front-end for GitHub
9- [[https://ip.cleberg.net/][ifconfig.php]] - A "whatsmyip" tool
10- [[https://invidious.cleberg.net/][Invidious]] - A YouTube proxy
11- [[https://office.cleberg.net/][Office]] - The world's smallest office suite
12- [[https://search.cleberg.net/][SearXNG]] - A privacy-respecting, open metasearch engine
13
14See the [[https://git.cleberg.net/cleberg.net.git/log][git log]] if you want to see changes that have been made.
static/gpg.txt deleted −52
@@ -1,52 +0,0 @@
1-----BEGIN PGP PUBLIC KEY BLOCK-----
2
3mQINBGN0Z8YBEAC6gWrNuB0nyAVkIuCsErIqpO7ngH0HV2sZD54t0ge9on0Fb+QA
4S/oRSK3bcwgR09lnIwPDWpQqlPpzKib2bZ9USBMD0lcgPb0hMr0fafs/+/FCRQV1
5Pvc/e37n2/KHRW2KoiM4sOFT9LYxi7LBGgXffusx6uVBzrNUacTFFd/pMNkPd5uX
6/J4IT+QbDh1bKw4CtLNvoh7xTcfdpzdqpHfNkjm+24pEnbrv6QrSpBzol4uulaB+
75I8JF938/FBHBndVMT9UQcGRtX+ttZTp9U7m0TzCAsyrVV/V8rEbcGTMlU+yyUjR
8TDRX4Dwj79IfL+vLZgFGLd8GwO08AirNkHYaZwzvYi42I5Bh8cX1L2IippYL8YYF
9ASX26pXC6mxTwC/hjCA7rzyIMD5sf1UailYm5MvrhEujRYOPWDU+kex2t2HJpzvN
10vYSVIS0sKi7iHr82VA+/spR6Ii07/84zZPQ1h/sXJLaDolQV2pU7WJBPyFO957Er
11BggugSpB1U/blgU+B9vOR+2sMEReRQwVF7aVhQ2Wy5wOZRHlmTZVMNO5SawXkiTp
12Bt4IwhThzMcUQstXxf5V44pPsB5uQE3n4WdROfJTOiB2cZJZOiXe3iPiiTsVsBc2
13tcqxJmwplMS4jNddJ9Nh0NTCKNk3Yt0iZzWlQDKAzR1uSGW9fQGAdh/UZQARAQAB
14tCVDaHJpc3RpYW4gQ2xlYmVyZyA8aGVsbG9AY2xlYmVyZy5uZXQ+iQJRBBMBCAA7
15FiEEOReXP7FZu7hhlFOFaUUaUXrAyzcFAmN0Z8YCGwMFCwkIBwICIgIGFQoJCAsC
16BBYCAwECHgcCF4AACgkQaUUaUXrAyzcrIQ//ZzCvcNKi2exZ4MfH68q9cIrJtHA3
17+FQ/3yTwAMiXmaTctHTcME+4Um12TCJMH/PwOkmxjolln5LfNHC8emcNs9Uaoga7
181Cy8ihqKbQMaZN1ZHrgaddeTs7+N1+/u8yAZPXo+N6pmLaeuPfn1MMhcejTbo+C/
19c22/yj97ym6Iuc9oodnbHb2rxhGKReQLJmqzBHWd0syFbYDopmhY2MjMawVirPG7
20vt6EbiFHkWLBY08FYbOQWT4bXdSHlLxT3q5m+j0HdVjHxd/vOk+dc3FPtK8ZpTVC
21qKa2oQ6dl4fjp8GVbu4f4SMDDJRR1iOoLTNEhKDSZHkZdjH0/LQRLsf49cqbff1Y
22mpxJcNv1/RmgzctTmBAoGt2RS8mT97H8AUW7v7PGs3eS/OkrY1e4cuTrmcafEaMy
23J2L7aUxDVVUCvRKsONXx+zSLin7ByRHypqRfxGqnpQUDPW4Oty6CewsSXktDjIUx
24FXzMwfuNXKS/KHqcUxamE0rL8O2d3CJI4YAbWu2Y0GtBetfuaaOgi56cYqv86zGu
25os0oga1ZakiCeRPgEaipPaZZMf6pCvucF2VyaUHwIOMcSVEH5nhHO4McgYLSZucx
26na5qcLk06EP+1HjmifGqPmkxHTW/ez5x6m/QT9jOzyRu94p3IG5DT1Lh8kl6gid7
27YntCGrwcsvw8zKi5Ag0EY3RnxgEQANj1c0ZS3npmtj1V5m+vsq1mK2g4K66senuC
28Gf+wTudYgcUzBhcTtEwzFJTwFFUMkfpgmkgYrlMB7iH+85YxiDR2sBhMg56haJXn
29LGnSaWqHVE4P0yyHI/6QasYxdJWfYkSH9m3B8yW1VKs4eSSL6S/U/qdHzDRRZCEX
30lNhvplrWcWv6Ifk/aYJ+njwn+jhXT4j8lbhem5oCbg4olY84bs+rfvWHTrWdGD1N
31sHkbLEwLgnmJou7vkVY9vkIMhbgpvNp1CEx0zOGTMhlPrCYBCFUGR7Bnoqwwl/C4
32CTBwZxBE5bNBpsEdySYhmHW+YHRHIxeboLmOyoc0CpT/zhu2y5JxqJBiGEDYhTM/
33V2nNqKGDgUenXqjwoBw5oCNQJ35NNgFgEbphhs71W7VVwEPRdppcem33TcpFBeoE
34vTzCfK6xJA82ASJHKz0hajEQchJ5jJZYo4wRZJGFv2jMIhq24DLIqxgjZeBKLEzl
35RigfuuCrAsQBIMtTUSUUvxjB/b+UDYssqi9Q+8F7FcwqE9egaj7xVQprTAYcGW7S
36zxfjzFSbJSl+b4WQSewFtmFIx62RYemBH+YPxbwOhQAuRWgEg0E6xeuILu22xydf
37DEkbk1GEXH3ilYxl4LhFy2EmUbvB8rxh5Vk3A1KVaBe/ndPhwznTe7X9ypRHzQiV
38Jkc2vVGjABEBAAGJAjYEGAEIACAWIQQ5F5c/sVm7uGGUU4VpRRpResDLNwUCY3Rn
39xgIbDAAKCRBpRRpResDLN60xD/9pEDZK7SXZy/nhVdHMKn2UyF7F8dEXYxILmXp0
4053zDDVN5D7luHjcI5pYynWqlgXWPAaCOthWtexr3r3b8fVSc7yxi497fFa3jiVkn
41VNYjDCa4SK5H6Rdf6I5rv+SS1PPmiVFKL7xuWcJ9YucHACdeH0UUmPCMmHHDc9SY
42MGUPjadFxjCGtBz8itS+ufDiZsTU9ov7XRE+pVyrDlMSVD1Ex02vMRtr+UD9lkYb
43KGs32TEDw28+OgvSZc81P6/BuxmG2XxgvU5OOrlVFOs4VU5P0TnZVxbVuKyO4fkZ
44sxSApn+U4lU89dM0M1l1S2NZtvkWTIW2jFvzwXDCPxs5GAXAGtnEO8gjGICo2QV3
45VovHMTmPz7Bo87fqGZkfZatzvAeHW92FIhQBOdgSiCwSDRPVVdPTpQ27BaUrfH8p
46aS81RhlCWgKptFKj4+MAqGEf9V6jUIKn5EvdSxCZ5XXHmaBL/kXxTPaDJiK8nMKv
47dr/iwtb23SwNrBLQkYcI+6aZj4jcqr5T0H2EsC8h9jQWMG+S9/BzhJOI/QeepHvD
48VJQve5jozXbz+iBRLrWbEgbNydL/LT9iQCWaWt2HCPQ7bPrxKqya8fUMPxi3VCZZ
49AuXpJzywCyVuzpuChuvdMflHbmx1r8uJjSmUCJf6ngt0TQMXiwL3vsECTV7ZtVge
500jZ4tA==
51=pl2G
52-----END PGP PUBLIC KEY BLOCK-----
static/styles.css +468 −208
@@ -1,127 +1,59 @@
1/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 1/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/
2
3/* Document
4 ========================================================================== */
5
6/**
7 * 1. Correct the line height in all browsers.
8 * 2. Prevent adjustments of font size after orientation changes in iOS.
9 */
10
11html { 2html {
12 line-height: 1.15; /* 1 */ 3 line-height: 1.15;
13 -webkit-text-size-adjust: 100%; /* 2 */ 4 -webkit-text-size-adjust: 100%;
14} 5}
15 6
16/* Sections
17 ========================================================================== */
18
19/**
20 * Remove the margin in all browsers.
21 */
22
23body { 7body {
24 margin: 0; 8 margin: 0;
25} 9}
26 10
27/**
28 * Render the `main` element consistently in IE.
29 */
30
31main { 11main {
32 display: block; 12 display: block;
33} 13}
34 14
35/**
36 * Correct the font size and margin on `h1` elements within `section` and
37 * `article` contexts in Chrome, Firefox, and Safari.
38 */
39
40h1 { 15h1 {
41 font-size: 2em; 16 font-size: 2em;
42 margin: 0.67em 0; 17 margin: 0.67em 0;
43} 18}
44 19
45/* Grouping content
46 ========================================================================== */
47
48/**
49 * 1. Add the correct box sizing in Firefox.
50 * 2. Show the overflow in Edge and IE.
51 */
52
53hr { 20hr {
54 box-sizing: content-box; /* 1 */ 21 box-sizing: content-box;
55 height: 0; /* 1 */ 22 height: 0;
56 overflow: visible; /* 2 */ 23 overflow: visible;
57} 24}
58 25
59/**
60 * 1. Correct the inheritance and scaling of font size in all browsers.
61 * 2. Correct the odd `em` font sizing in all browsers.
62 */
63
64pre { 26pre {
65 font-family: monospace, monospace; /* 1 */ 27 font-family: monospace, monospace;
66 font-size: 1em; /* 2 */ 28 font-size: 1em;
67} 29}
68 30
69/* Text-level semantics
70 ========================================================================== */
71
72/**
73 * Remove the gray background on active links in IE 10.
74 */
75
76a { 31a {
77 background-color: transparent; 32 background-color: initial;
78} 33}
79 34
80/**
81 * 1. Remove the bottom border in Chrome 57-
82 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 */
84
85abbr[title] { 35abbr[title] {
86 border-bottom: none; /* 1 */ 36 border-bottom: none;
87 text-decoration: underline; /* 2 */ 37 text-decoration: underline;
88 text-decoration: underline dotted; /* 2 */ 38 text-decoration: underline dotted;
89} 39}
90 40
91/**
92 * Add the correct font weight in Chrome, Edge, and Safari.
93 */
94
95b, 41b,
96strong { 42strong {
97 font-weight: bolder; 43 font-weight: bolder;
98} 44}
99 45
100/**
101 * 1. Correct the inheritance and scaling of font size in all browsers.
102 * 2. Correct the odd `em` font sizing in all browsers.
103 */
104
105code, 46code,
106kbd, 47kbd,
107samp { 48samp {
108 font-family: monospace, monospace; /* 1 */ 49 font-family: monospace, monospace;
109 font-size: 1em; /* 2 */ 50 font-size: 1em;
110} 51}
111 52
112/**
113 * Add the correct font size in all browsers.
114 */
115
116small { 53small {
117 font-size: 80%; 54 font-size: 80%;
118} 55}
119 56
120/**
121 * Prevent `sub` and `sup` elements from affecting the line height in
122 * all browsers.
123 */
124
125sub, 57sub,
126sup { 58sup {
127 font-size: 75%; 59 font-size: 75%;
@@ -138,62 +70,31 @@ sup {
138 top: -0.5em; 70 top: -0.5em;
139} 71}
140 72
141/* Embedded content
142 ========================================================================== */
143
144/**
145 * Remove the border on images inside links in IE 10.
146 */
147
148img { 73img {
149 border-style: none; 74 border-style: none;
150} 75}
151 76
152/* Forms
153 ========================================================================== */
154
155/**
156 * 1. Change the font styles in all browsers.
157 * 2. Remove the margin in Firefox and Safari.
158 */
159
160button, 77button,
161input, 78input,
162optgroup, 79optgroup,
163select, 80select,
164textarea { 81textarea {
165 font-family: inherit; /* 1 */ 82 font-family: inherit;
166 font-size: 100%; /* 1 */ 83 font-size: 100%;
167 line-height: 1.15; /* 1 */ 84 line-height: 1.15;
168 margin: 0; /* 2 */ 85 margin: 0;
169} 86}
170 87
171/**
172 * Show the overflow in IE.
173 * 1. Show the overflow in Edge.
174 */
175
176button, 88button,
177input { 89input {
178 /* 1 */
179 overflow: visible; 90 overflow: visible;
180} 91}
181 92
182/**
183 * Remove the inheritance of text transform in Edge, Firefox, and IE.
184 * 1. Remove the inheritance of text transform in Firefox.
185 */
186
187button, 93button,
188select { 94select {
189 /* 1 */
190 text-transform: none; 95 text-transform: none;
191} 96}
192 97
193/**
194 * Correct the inability to style clickable types in iOS and Safari.
195 */
196
197button, 98button,
198[type="button"], 99[type="button"],
199[type="reset"], 100[type="reset"],
@@ -201,10 +102,6 @@ button,
201 -webkit-appearance: button; 102 -webkit-appearance: button;
202} 103}
203 104
204/**
205 * Remove the inner border and padding in Firefox.
206 */
207
208button::-moz-focus-inner, 105button::-moz-focus-inner,
209[type="button"]::-moz-focus-inner, 106[type="button"]::-moz-focus-inner,
210[type="reset"]::-moz-focus-inner, 107[type="reset"]::-moz-focus-inner,
@@ -213,10 +110,6 @@ button::-moz-focus-inner,
213 padding: 0; 110 padding: 0;
214} 111}
215 112
216/**
217 * Restore the focus styles unset by the previous rule.
218 */
219
220button:-moz-focusring, 113button:-moz-focusring,
221[type="button"]:-moz-focusring, 114[type="button"]:-moz-focusring,
222[type="reset"]:-moz-focusring, 115[type="reset"]:-moz-focusring,
@@ -224,133 +117,68 @@ button:-moz-focusring,
224 outline: 1px dotted ButtonText; 117 outline: 1px dotted ButtonText;
225} 118}
226 119
227/**
228 * Correct the padding in Firefox.
229 */
230
231fieldset { 120fieldset {
232 padding: 0.35em 0.75em 0.625em; 121 padding: 0.35em 0.75em 0.625em;
233} 122}
234 123
235/**
236 * 1. Correct the text wrapping in Edge and IE.
237 * 2. Correct the color inheritance from `fieldset` elements in IE.
238 * 3. Remove the padding so developers are not caught out when they zero out
239 * `fieldset` elements in all browsers.
240 */
241
242legend { 124legend {
243 box-sizing: border-box; /* 1 */ 125 box-sizing: border-box;
244 color: inherit; /* 2 */ 126 color: inherit;
245 display: table; /* 1 */ 127 display: table;
246 max-width: 100%; /* 1 */ 128 max-width: 100%;
247 padding: 0; /* 3 */ 129 padding: 0;
248 white-space: normal; /* 1 */ 130 white-space: normal;
249} 131}
250 132
251/**
252 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
253 */
254
255progress { 133progress {
256 vertical-align: baseline; 134 vertical-align: baseline;
257} 135}
258 136
259/**
260 * Remove the default vertical scrollbar in IE 10+.
261 */
262
263textarea { 137textarea {
264 overflow: auto; 138 overflow: auto;
265} 139}
266 140
267/**
268 * 1. Add the correct box sizing in IE 10.
269 * 2. Remove the padding in IE 10.
270 */
271
272[type="checkbox"], 141[type="checkbox"],
273[type="radio"] { 142[type="radio"] {
274 box-sizing: border-box; /* 1 */ 143 box-sizing: border-box;
275 padding: 0; /* 2 */ 144 padding: 0;
276} 145}
277 146
278/**
279 * Correct the cursor style of increment and decrement buttons in Chrome.
280 */
281
282[type="number"]::-webkit-inner-spin-button, 147[type="number"]::-webkit-inner-spin-button,
283[type="number"]::-webkit-outer-spin-button { 148[type="number"]::-webkit-outer-spin-button {
284 height: auto; 149 height: auto;
285} 150}
286 151
287/**
288 * 1. Correct the odd appearance in Chrome and Safari.
289 * 2. Correct the outline style in Safari.
290 */
291
292[type="search"] { 152[type="search"] {
293 -webkit-appearance: textfield; /* 1 */ 153 -webkit-appearance: textfield;
294 outline-offset: -2px; /* 2 */ 154 outline-offset: -2px;
295} 155}
296 156
297/**
298 * Remove the inner padding in Chrome and Safari on macOS.
299 */
300
301[type="search"]::-webkit-search-decoration { 157[type="search"]::-webkit-search-decoration {
302 -webkit-appearance: none; 158 -webkit-appearance: none;
303} 159}
304 160
305/**
306 * 1. Correct the inability to style clickable types in iOS and Safari.
307 * 2. Change font properties to `inherit` in Safari.
308 */
309
310::-webkit-file-upload-button { 161::-webkit-file-upload-button {
311 -webkit-appearance: button; /* 1 */ 162 -webkit-appearance: button;
312 font: inherit; /* 2 */ 163 font: inherit;
313} 164}
314 165
315/* Interactive
316 ========================================================================== */
317
318/*
319 * Add the correct display in Edge, IE 10+, and Firefox.
320 */
321
322details { 166details {
323 display: block; 167 display: block;
324} 168}
325 169
326/*
327 * Add the correct display in all browsers.
328 */
329
330summary { 170summary {
331 display: list-item; 171 display: list-item;
332} 172}
333 173
334/* Misc
335 ========================================================================== */
336
337/**
338 * Add the correct display in IE 10+.
339 */
340
341template { 174template {
342 display: none; 175 display: none;
343} 176}
344 177
345/**
346 * Add the correct display in IE 10.
347 */
348
349[hidden] { 178[hidden] {
350 display: none; 179 display: none;
351} 180}
352 181
353/* CUSTOM CSS */
354:root { 182:root {
355 --bg: #fff; 183 --bg: #fff;
356 --bg-bright: #eee; 184 --bg-bright: #eee;
@@ -368,8 +196,19 @@ template {
368body { 196body {
369 background-color: var(--bg); 197 background-color: var(--bg);
370 color: var(--fg); 198 color: var(--fg);
371 font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, 199 font-family:
372 helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, 200 -apple-system,
201 BlinkMacSystemFont,
202 avenir next,
203 avenir,
204 segoe ui,
205 helvetica neue,
206 helvetica,
207 Cantarell,
208 Ubuntu,
209 roboto,
210 noto,
211 arial,
373 sans-serif; 212 sans-serif;
374 font-size: 1rem; 213 font-size: 1rem;
375 line-height: 1.5; 214 line-height: 1.5;
@@ -390,6 +229,7 @@ footer {
390} 229}
391 230
392footer p { 231footer p {
232 font-size: 90%;
393 margin: 0; 233 margin: 0;
394} 234}
395 235
@@ -433,8 +273,13 @@ table {
433 border-collapse: collapse; 273 border-collapse: collapse;
434 color: var(--fg); 274 color: var(--fg);
435 border-color: var(--bg-bright); 275 border-color: var(--bg-bright);
436 font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; 276 font-family:
437 font-size: 0.9rem; 277 Menlo,
278 Consolas,
279 Monaco,
280 Liberation Mono,
281 Lucida Console,
282 monospace;
438} 283}
439 284
440thead, 285thead,
@@ -448,7 +293,13 @@ td {
448pre > code, 293pre > code,
449time, 294time,
450code { 295code {
451 font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; 296 font-family:
297 Menlo,
298 Consolas,
299 Monaco,
300 Liberation Mono,
301 Lucida Console,
302 monospace;
452 font-size: 1rem; 303 font-size: 1rem;
453} 304}
454 305
@@ -510,7 +361,7 @@ details {
510} 361}
511 362
512summary { 363summary {
513 font-weight: bold; 364 font-weight: 700;
514} 365}
515 366
516.footnote-definition { 367.footnote-definition {
@@ -545,3 +396,412 @@ summary {
545 --code: #ffb454; 396 --code: #ffb454;
546 } 397 }
547} 398}
399
400/* Org Styles */
401p.verse {
402 margin-left: 3%;
403}
404.timestamp {
405 color: var(--bg-light);
406}
407.timestamp-kwd {
408 color: var(--timestamp-kwd);
409}
410.org-right {
411 margin-left: auto;
412 margin-right: 0px;
413 text-align: right;
414}
415.org-left {
416 margin-left: 0px;
417 margin-right: auto;
418 text-align: left;
419}
420.org-center {
421 margin-left: auto;
422 margin-right: auto;
423 text-align: center;
424}
425.underline {
426 text-decoration: underline;
427}
428.todo {
429 font-family: monospace;
430 color: red;
431}
432.done {
433 font-family: monospace;
434 color: green;
435}
436.priority {
437 font-family: monospace;
438 color: orange;
439}
440.tag {
441 background-color: #eee;
442 font-family: monospace;
443 padding: 2px;
444 font-size: 80%;
445 font-weight: normal;
446}
447
448.code-highlighted {
449 background-color: var(--highlight);
450}
451.org-info-js_info-navigation {
452 border-style: none;
453}
454#org-info-js_console-label {
455 font-size: 10px;
456 font-weight: bold;
457 white-space: nowrap;
458}
459.org-info-js_search-highlight {
460 background-color: var(--highlight);
461 color: var(--fg);
462 font-weight: bold;
463}
464
465caption.t-above {
466 caption-side: top;
467}
468caption.t-bottom {
469 caption-side: bottom;
470}
471td,
472th {
473 vertical-align: top;
474}
475th.org-right {
476 text-align: center;
477}
478th.org-left {
479 text-align: center;
480}
481th.org-center {
482 text-align: center;
483}
484td.org-right {
485 text-align: right;
486}
487td.org-left {
488 text-align: left;
489}
490td.org-center {
491 text-align: center;
492}
493dt {
494 font-weight: bold;
495}
496.footpara {
497 display: inline;
498}
499.footdef {
500 margin-bottom: 1em;
501}
502.figure {
503 padding: 1em;
504}
505.figure p {
506 text-align: center;
507}
508.equation-container {
509 display: table;
510 text-align: center;
511 width: 100%;
512}
513.equation {
514 vertical-align: middle;
515}
516.equation-label {
517 display: table-cell;
518 text-align: right;
519 vertical-align: middle;
520}
521.inlinetask {
522 padding: 10px;
523 border: 2px solid var(--bg);
524 margin: 10px;
525 background: var(--task);
526}
527#org-div-home-and-up {
528 text-align: right;
529 font-size: 70%;
530 white-space: nowrap;
531}
532
533pre.src {
534 position: relative;
535 overflow: auto;
536}
537pre.src:before {
538 display: none;
539 position: absolute;
540 top: -8px;
541 right: 12px;
542 padding: 3px;
543 color: var(--fg);
544 background-color: var(--bg-lightest-opac);
545}
546pre.src:hover:before {
547 display: inline;
548 margin-top: 14px;
549}
550
551/* Languages per Org manual */
552pre.src-asymptote:before {
553 content: "Asymptote";
554}
555pre.src-awk:before {
556 content: "Awk";
557}
558pre.src-authinfo::before {
559 content: "Authinfo";
560}
561pre.src-C:before {
562 content: "C";
563}
564/* pre.src-C++ doesn't work in CSS */
565pre.src-clojure:before {
566 content: "Clojure";
567}
568pre.src-css:before {
569 content: "CSS";
570}
571pre.src-D:before {
572 content: "D";
573}
574pre.src-ditaa:before {
575 content: "ditaa";
576}
577pre.src-dot:before {
578 content: "Graphviz";
579}
580pre.src-calc:before {
581 content: "Emacs Calc";
582}
583pre.src-emacs-lisp:before {
584 content: "Emacs Lisp";
585}
586pre.src-fortran:before {
587 content: "Fortran";
588}
589pre.src-gnuplot:before {
590 content: "gnuplot";
591}
592pre.src-haskell:before {
593 content: "Haskell";
594}
595pre.src-hledger:before {
596 content: "hledger";
597}
598pre.src-java:before {
599 content: "Java";
600}
601pre.src-js:before {
602 content: "Javascript";
603}
604pre.src-latex:before {
605 content: "LaTeX";
606}
607pre.src-ledger:before {
608 content: "Ledger";
609}
610pre.src-lisp:before {
611 content: "Lisp";
612}
613pre.src-lilypond:before {
614 content: "Lilypond";
615}
616pre.src-lua:before {
617 content: "Lua";
618}
619pre.src-matlab:before {
620 content: "MATLAB";
621}
622pre.src-mscgen:before {
623 content: "Mscgen";
624}
625pre.src-ocaml:before {
626 content: "Objective Caml";
627}
628pre.src-octave:before {
629 content: "Octave";
630}
631pre.src-org:before {
632 content: "Org mode";
633}
634pre.src-oz:before {
635 content: "OZ";
636}
637pre.src-plantuml:before {
638 content: "Plantuml";
639}
640pre.src-processing:before {
641 content: "Processing.js";
642}
643pre.src-python:before {
644 content: "Python";
645}
646pre.src-R:before {
647 content: "R";
648}
649pre.src-ruby:before {
650 content: "Ruby";
651}
652pre.src-sass:before {
653 content: "Sass";
654}
655pre.src-scheme:before {
656 content: "Scheme";
657}
658pre.src-screen:before {
659 content: "Gnu Screen";
660}
661pre.src-sed:before {
662 content: "Sed";
663}
664pre.src-sh:before {
665 content: "shell";
666}
667pre.src-sql:before {
668 content: "SQL";
669}
670pre.src-sqlite:before {
671 content: "SQLite";
672}
673/* additional languages in org.el's org-babel-load-languages alist */
674pre.src-forth:before {
675 content: "Forth";
676}
677pre.src-io:before {
678 content: "IO";
679}
680pre.src-J:before {
681 content: "J";
682}
683pre.src-makefile:before {
684 content: "Makefile";
685}
686pre.src-maxima:before {
687 content: "Maxima";
688}
689pre.src-perl:before {
690 content: "Perl";
691}
692pre.src-picolisp:before {
693 content: "Pico Lisp";
694}
695pre.src-scala:before {
696 content: "Scala";
697}
698pre.src-shell:before {
699 content: "Shell Script";
700}
701pre.src-ebnf2ps:before {
702 content: "ebfn2ps";
703}
704/* additional language identifiers per "defun org-babel-execute"
705 in ob-*.el */
706pre.src-cpp:before {
707 content: "C++";
708}
709pre.src-abc:before {
710 content: "ABC";
711}
712pre.src-coq:before {
713 content: "Coq";
714}
715pre.src-groovy:before {
716 content: "Groovy";
717}
718/* additional language identifiers from org-babel-shell-names in
719 ob-shell.el: ob-shell is the only babel language using a lambda to put
720 the execution function name together. */
721pre.src-bash:before {
722 content: "bash";
723}
724pre.src-csh:before {
725 content: "csh";
726}
727pre.src-ash:before {
728 content: "ash";
729}
730pre.src-dash:before {
731 content: "dash";
732}
733pre.src-ksh:before {
734 content: "ksh";
735}
736pre.src-mksh:before {
737 content: "mksh";
738}
739pre.src-posh:before {
740 content: "posh";
741}
742/* Additional Emacs modes also supported by the LaTeX listings package */
743pre.src-ada:before {
744 content: "Ada";
745}
746pre.src-asm:before {
747 content: "Assembler";
748}
749pre.src-caml:before {
750 content: "Caml";
751}
752pre.src-delphi:before {
753 content: "Delphi";
754}
755pre.src-html:before {
756 content: "HTML";
757}
758pre.src-idl:before {
759 content: "IDL";
760}
761pre.src-mercury:before {
762 content: "Mercury";
763}
764pre.src-metapost:before {
765 content: "MetaPost";
766}
767pre.src-modula-2:before {
768 content: "Modula-2";
769}
770pre.src-pascal:before {
771 content: "Pascal";
772}
773pre.src-ps:before {
774 content: "PostScript";
775}
776pre.src-prolog:before {
777 content: "Prolog";
778}
779pre.src-simula:before {
780 content: "Simula";
781}
782pre.src-tcl:before {
783 content: "tcl";
784}
785pre.src-tex:before {
786 content: "TeX";
787}
788pre.src-plain-tex:before {
789 content: "Plain TeX";
790}
791pre.src-verilog:before {
792 content: "Verilog";
793}
794pre.src-vhdl:before {
795 content: "VHDL";
796}
797pre.src-xml:before {
798 content: "XML";
799}
800pre.src-nxml:before {
801 content: "XML";
802}
803/* add a generic configuration mode; LaTeX export needs an additional
804 (add-to-list 'org-latex-listings-langs '(conf " ")) in .emacs */
805pre.src-conf:before {
806 content: "Configuration File";
807}
static/styles.min.css deleted −1
@@ -1 +0,0 @@
1/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:initial}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--bg:#fff;--bg-bright:#eee;--fg:#111;--link:#00f;--code:#f00}@media(prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{background-color:var(--bg);color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif;font-size:1rem;line-height:1.5;max-width:45em;margin:0 auto;padding:0 1rem}.site-nav,footer{border-color:var(--fg)}footer{border-top:1px dotted;padding-top:1rem;margin:1rem 0}footer p{margin:0}.site-nav{border-bottom:1px dotted}.site-nav ul{list-style-type:none;display:flex;align-items:center;padding:0}.site-nav ul li{margin-right:.5rem}h1,h2,h3,h4{color:var(--fg)}a,a:visited{color:var(--link);text-decoration:none}a:hover{text-decoration:underline}img{width:100%}table{border-collapse:collapse;color:var(--fg);border-color:var(--bg-bright);font-family:Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace}thead,th,tr,td{border:1px solid var(--bg-bright);padding:.25rem}pre>code,time,code{font-family:Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;font-size:1rem}pre{background-color:var(--bg-bright)!important;border-radius:5px;margin:.5rem 0;padding:.5rem;overflow-x:auto}:not(pre)>code{color:var(--code);font-family:inherit}aside{background-color:var(--bg-bright);padding:1rem}blockquote{background-color:var(--bg-bright);border-left:10px solid var(--link);border-radius:5px;margin:1rem 0;padding:.5rem 1rem}blockquote p{margin:0}.post{display:flex}.post time{flex-shrink:0;margin-right:.5rem}.post-metadata{border:1px dotted var(--fg);border-top:none;padding:1rem}.post-metadata h1{margin:0}.post-metadata p{margin:0}details{margin-top:1rem}summary{font-weight:700}.footnote-definition{display:flex;justify-content:flex-start;align-items:top;margin:1rem 0}.footnote-definition sup{font-size:inherit;top:0;line-height:inherit;padding-right:.5rem}.footnote-definition sup::after{content:"."}.footnote-definition p{display:inline-block;margin:0}@media(prefers-color-scheme:dark){:root{--bg:#111111;--bg-bright:#2a2a2a;--fg:#c9c9c9;--link:#f07178;--code:#ffb454}}\ No newline at end of file
static/syntax-theme-dark.css added +280
@@ -0,0 +1,280 @@
1/*
2 * theme "ayu" generated by syntect
3 */
4
5.z-code {
6 color: #bfbab0;
7 background-color: #0f1419;
8}
9
10.z-comment, .z-punctuation.z-definition.z-comment {
11 color: #5c6773;
12font-style: italic;
13}
14.z-variable {
15 color: #bfbab0;
16}
17.z-string, .z-constant.z-other.z-symbol {
18 color: #c2d94c;
19}
20.z-constant.z-numeric {
21 color: #f29718;
22}
23.z-string.z-regexp, .z-constant.z-character.z-escape {
24 color: #95e6cb;
25}
26.z-constant.z-language {
27 color: #f29718;
28}
29.z-constant.z-character, .z-constant.z-other {
30 color: #f29718;
31}
32.z-variable.z-member {
33 color: #ec5f67;
34}
35.z-keyword, .z-keyword.z-operator.z-word {
36 color: #ff7733;
37}
38.z-keyword.z-operator {
39 color: #f29668;
40}
41.z-punctuation.z-separator, .z-punctuation.z-terminator {
42 color: #bfbab0;
43}
44.z-punctuation.z-section {
45 color: #bfbab0;
46}
47.z-punctuation.z-accessor {
48 color: #f29668;
49}
50.z-punctuation.z-definition.z-annotation {
51 color: #bfbab0;
52}
53.z-variable.z-other.z-dollar.z-only.z-js, .z-variable.z-other.z-object.z-dollar.z-only.z-js, .z-variable.z-type.z-dollar.z-only.z-js, .z-support.z-class.z-dollar.z-only.z-js {
54 color: #e6b673;
55}
56.z-storage {
57 color: #ff7733;
58}
59.z-storage.z-type {
60 color: #ff7733;
61}
62.z-entity.z-name.z-function {
63 color: #ffb454;
64}
65.z-entity.z-name, .z-entity.z-name.z-tag, .z-entity.z-name.z-label) {
66 color: #59c2ff;
67}
68.z-entity.z-other.z-inherited-class {
69 color: #59c2ff;
70text-decoration: underline;
71}
72.z-variable.z-parameter {
73 color: #f29718;
74}
75.z-variable.z-language {
76 color: #39bae6;
77font-style: italic;
78}
79.z-entity.z-name.z-tag, .z-meta.z-tag.z-sgml {
80 color: #39bae6;
81}
82.z-punctuation.z-definition.z-tag.z-end, .z-punctuation.z-definition.z-tag.z-begin, .z-punctuation.z-definition.z-tag {
83 color: #39bae6;
84}
85.z-entity.z-other.z-attribute-name {
86 color: #ffb454;
87}
88.z-variable.z-function, .z-variable.z-annotation {
89 color: #ffb454;
90}
91.z-support.z-function, .z-support.z-macro {
92 color: #f07178;
93}
94.z-support.z-constant {
95 color: #f29668;
96font-style: italic;
97}
98.z-support.z-type, .z-support.z-class {
99 color: #39bae6;
100font-style: italic;
101}
102.z-invalid {
103 color: #ff3333;
104}
105.z-invalid.z-deprecated {
106 color: #ffffff;
107 background-color: #ff7733;
108}
109.z-meta.z-diff, .z-meta.z-diff.z-header {
110 color: #c594c5;
111}
112.z-source.z-ruby .z-variable.z-other.z-readwrite {
113 color: #ffb454;
114}
115.z-source.z-css .z-entity.z-name.z-tag, .z-source.z-sass .z-entity.z-name.z-tag, .z-source.z-scss .z-entity.z-name.z-tag, .z-source.z-less .z-entity.z-name.z-tag, .z-source.z-stylus .z-entity.z-name.z-tag {
116 color: #59c2ff;
117}
118.z-source.z-css .z-support.z-type, .z-source.z-sass .z-support.z-type, .z-source.z-scss .z-support.z-type, .z-source.z-less .z-support.z-type, .z-source.z-stylus .z-support.z-type {
119 color: #5c6773;
120}
121.z-support.z-type.z-property-name {
122 color: #39bae6;
123}
124.z-constant.z-numeric.z-line-number.z-find-in-files {
125 color: #5c6773;
126}
127.z-constant.z-numeric.z-line-number.z-match {
128 color: #ff7733;
129}
130.z-entity.z-name.z-filename.z-find-in-files {
131 color: #c2d94c;
132}
133.z-message.z-error {
134 color: #ff3333;
135}
136.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
137 color: #39bae6;
138}
139.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
140 color: #39bae6;
141}
142.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
143 color: #39bae6;
144}
145.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
146 color: #39bae6;
147}
148.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
149 color: #39bae6;
150}
151.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
152 color: #39bae6;
153}
154.z-source.z-json .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
155 color: #39bae6;
156}
157.z-source.z-json .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
158 color: #39bae6;
159}
160.z-source.z-json .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
161 color: #39bae6;
162}
163.z-markup.z-heading {
164 color: #ff7733;
165font-weight: bold;
166}
167.z-string.z-other.z-link, .z-markup.z-underline.z-link {
168 color: #95e6cb;
169text-decoration: underline;
170font-style: italic;
171}
172.z-punctuation.z-definition.z-image {
173 color: #ffb454;
174}
175.z-markup.z-italic {
176 color: #f07178;
177font-style: italic;
178}
179.z-markup.z-bold {
180 color: #f07178;
181font-weight: bold;
182}
183.z-markup.z-italic .z-markup.z-bold, .z-markup.z-bold .z-markup.z-italic {
184font-weight: bold;
185font-style: italic;
186}
187.z-markup.z-raw {
188 background-color: #bfbab0;
189}
190.z-markup.z-raw.z-inline {
191 background-color: #bfbab0;
192}
193.z-meta.z-separator {
194 color: #5c6773;
195 background-color: #bfbab0;
196font-weight: bold;
197}
198.z-markup.z-quote {
199 color: #f29718;
200font-style: italic;
201}
202.z-markup.z-list.z-numbered.z-bullet, .z-markup.z-list .z-punctuation.z-definition.z-list_item {
203 color: #95e6cb;
204}
205.z-markup.z-inserted {
206 color: #c2d94c;
207}
208.z-markup.z-changed {
209 color: #39bae6;
210}
211.z-markup.z-deleted {
212 color: #f07178;
213}
214.z-markup.z-strike {
215 color: #e6b673;
216}
217.z-markup.z-table {
218 color: #39bae6;
219 background-color: #bfbab0;
220}
221.z-text.z-html.z-markdown .z-markup.z-raw.z-inline {
222 color: #f29668;
223}
224.z-text.z-html.z-markdown .z-meta.z-dummy.z-line-break {
225 color: #5c6773;
226}
227.z-markup.z-raw.z-block.z-fenced.z-markdown {
228 color: #bfbab0;
229 background-color: #bfbab0;
230}
231.z-punctuation.z-definition.z-fenced.z-markdown, .z-variable.z-language.z-fenced.z-markdown {
232 color: #5c6773;
233 background-color: #bfbab0;
234}
235.z-variable.z-language.z-fenced.z-markdown {
236 color: #5c6773;
237}
238.z-markup.z-inserted.z-git_gutter {
239 color: #c2d94c;
240}
241.z-markup.z-changed.z-git_gutter {
242 color: #39bae6;
243}
244.z-markup.z-deleted.z-git_gutter {
245 color: #ff3333;
246}
247.z-markup.z-ignored.z-git_gutter {
248 color: #20272c;
249}
250.z-markup.z-untracked.z-git_gutter {
251 color: #20272c;
252}
253.z-gutter_color {
254 color: #ffffff;
255}
256.z-acejump.z-label.z-blue {
257 color: #ffffff;
258 background-color: #39bae6;
259}
260.z-acejump.z-label.z-green {
261 color: #ffffff;
262 background-color: #c2d94c;
263}
264.z-acejump.z-label.z-orange {
265 color: #ffffff;
266 background-color: #ff7733;
267}
268.z-acejump.z-label.z-purple {
269 color: #ffffff;
270 background-color: #f07178;
271}
272.z-sublimelinter.z-mark.z-warning {
273 color: #39bae6;
274}
275.z-sublimelinter.z-gutter-mark {
276 color: #ffffff;
277}
278.z-sublimelinter.z-mark.z-error {
279 color: #ff3333;
280}
static/syntax-theme-light.css added +407
@@ -0,0 +1,407 @@
1/*
2 * theme "GitHub" generated by syntect
3 */
4
5.z-code {
6 color: #323232;
7 background-color: #ffffff;
8}
9
10.z-comment {
11 color: #969896;
12font-style: italic;
13}
14.z-string {
15 color: #183691;
16}
17.z-regexp-operator {
18 color: #a71d5d;
19}
20.z-string.z-regexp.z-characterclass .z-punctuation.z-definition.z-string.z-begin, .z-string.z-regexp.z-characterclass .z-punctuation.z-definition.z-string.z-end {
21 color: #a71d5d;
22}
23.z-constant.z-numeric {
24 color: #0086b3;
25}
26.z-constant.z-language {
27 color: #0086b3;
28}
29.z-constant.z-character, .z-constant.z-other, .z-variable.z-other.z-constant {
30 color: #0086b3;
31}
32.z-variable {
33 color: #323232;
34}
35.z-keyword {
36 color: #a71d5d;
37font-weight: bold;
38}
39.z-bitwise-operator {
40 color: #a71d5d;
41font-weight: bold;
42}
43.z-storage {
44 color: #a71d5d;
45font-weight: bold;
46}
47.z-storage.z-type {
48 color: #a71d5d;
49font-weight: bold;
50}
51.z-entity.z-name.z-class {
52 color: #0086b3;
53}
54.z-entity.z-other.z-inherited-class {
55 color: #0086b3;
56}
57.z-entity.z-name.z-function {
58 color: #795da3;
59font-weight: bold;
60}
61.z-variable.z-parameter {
62 color: #323232;
63}
64.z-entity.z-name.z-tag {
65 color: #63a35c;
66}
67.z-entity.z-other.z-attribute-name {
68 color: #795da3;
69}
70.z-support.z-function {
71 color: #62a35c;
72}
73.z-support.z-constant {
74 color: #0086b3;
75}
76.z-support.z-type, .z-support.z-class {
77 color: #0086b3;
78}
79.z-support.z-other.z-variable {
80 color: #323232;
81}
82.z-invalid, .z-invalid.z-illegal, .z-invalid.z-deprecated {
83 color: #b52a1d;
84 background-color: #f5f5f5;
85font-weight: bold;
86}
87.z-entity.z-name.z-filename.z-find-in-files {
88 color: #323232;
89font-weight: bold;
90}
91.z-constant.z-numeric.z-line-number.z-find-in-files, .z-constant.z-numeric.z-line-number.z-match.z-find-in-files {
92 color: #b3b3b3;
93}
94.z-meta.z-diff.z-header {
95 color: #969896;
96 background-color: #ffffff;
97font-style: italic;
98}
99.z-meta.z-diff.z-header .z-punctuation.z-definition.z-from-file.z-diff {
100 color: #bd2c00;
101 background-color: #ffecec;
102font-weight: bold;
103font-style: italic;
104}
105.z-meta.z-diff.z-header .z-punctuation.z-definition.z-to-file.z-diff {
106 color: #55a532;
107 background-color: #eaffea;
108font-weight: bold;
109font-style: italic;
110}
111.z-meta.z-diff.z-range {
112 color: #969896;
113font-weight: bold;
114font-style: italic;
115}
116.z-markup.z-deleted {
117 background-color: #ffecec;
118}
119.z-markup.z-deleted .z-punctuation.z-definition.z-inserted {
120 color: #bd2c00;
121font-weight: bold;
122}
123.z-markup.z-inserted {
124 background-color: #eaffea;
125}
126.z-markup.z-inserted .z-punctuation.z-definition.z-inserted {
127 color: #55a532;
128font-weight: bold;
129}
130.z-markup.z-deleted.z-git_gutter {
131 color: #bd2c00;
132}
133.z-markup.z-inserted.z-git_gutter {
134 color: #55a532;
135}
136.z-markup.z-changed.z-git_gutter {
137 color: #0086b3;
138}
139.z-markup.z-ignored.z-git_gutter {
140 color: #b3b3b3;
141}
142.z-markup.z-untracked.z-git_gutter {
143 color: #b3b3b3;
144}
145.z-source.z-css .z-punctuation.z-definition.z-entity {
146 color: #323232;
147}
148.z-source.z-css .z-entity.z-other.z-attribute-name.z-pseudo-class, .z-source.z-css .z-entity.z-other.z-attribute-name.z-pseudo-element {
149 color: #a71d5d;
150}
151.z-source.z-css .z-meta.z-value, .z-source.z-css .z-support.z-constant, .z-source.z-css .z-support.z-function {
152 color: #323232;
153}
154.z-source.z-css .z-constant.z-other.z-color {
155 color: #ed6a43;
156}
157.z-source.z-scss .z-punctuation.z-definition.z-entity {
158 color: #323232;
159}
160.z-source.z-scss .z-entity.z-other.z-attribute-name.z-pseudo-class, .z-source.z-scss .z-entity.z-other.z-attribute-name.z-pseudo-element {
161 color: #a71d5d;
162}
163.z-source.z-scss .z-support.z-constant.z-property-value, .z-source.z-scss .z-support.z-function {
164 color: #323232;
165}
166.z-source.z-scss .z-variable {
167 color: #a71d5d;
168}
169.z-variable.z-language.z-this.z-js {
170 color: #ed6a43;
171}
172.z-source.z-js .z-entity.z-name.z-function {
173 color: #323232;
174}
175.z-source.z-js .z-meta.z-function .z-entity.z-name.z-function, .z-source.z-js .z-entity.z-name.z-function .z-meta.z-function {
176 color: #795da3;
177font-weight: bold;
178}
179.z-entity.z-name.z-type.z-new.z-js {
180 color: #795da3;
181}
182.z-variable.z-language.z-prototype.z-js {
183 color: #0086b3;
184}
185.z-source.z-js .z-support.z-function {
186 color: #0086b3;
187}
188.z-support.z-type.z-object.z-console.z-js {
189 color: #795da3;
190}
191.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
192 color: #183691;
193font-weight: bold;
194}
195.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
196 color: #323232;
197}
198.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
199 color: #183691;
200font-weight: bold;
201}
202.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
203 color: #323232;
204}
205.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
206 color: #183691;
207font-weight: bold;
208}
209.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
210 color: #323232;
211}
212.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
213 color: #183691;
214font-weight: bold;
215}
216.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
217 color: #323232;
218}
219.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
220 color: #183691;
221font-weight: bold;
222}
223.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
224 color: #323232;
225}
226.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
227 color: #183691;
228font-weight: bold;
229}
230.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
231 color: #323232;
232}
233.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
234 color: #183691;
235font-weight: bold;
236}
237.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
238 color: #323232;
239}
240.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
241 color: #183691;
242font-weight: bold;
243}
244.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
245 color: #323232;
246}
247.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
248 color: #183691;
249font-weight: bold;
250}
251.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
252 color: #323232;
253}
254.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
255 color: #183691;
256font-weight: bold;
257}
258.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
259 color: #323232;
260}
261.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
262 color: #183691;
263font-weight: bold;
264}
265.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
266 color: #323232;
267}
268.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
269 color: #183691;
270font-weight: bold;
271}
272.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
273 color: #323232;
274}
275.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
276 color: #183691;
277font-weight: bold;
278}
279.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
280 color: #323232;
281}
282.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
283 color: #183691;
284font-weight: bold;
285}
286.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
287 color: #323232;
288}
289.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
290 color: #183691;
291font-weight: bold;
292}
293.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
294 color: #323232;
295}
296.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
297 color: #183691;
298font-weight: bold;
299}
300.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
301 color: #323232;
302}
303.z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
304 color: #183691;
305font-weight: bold;
306}
307.z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
308 color: #323232;
309}
310.z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
311 color: #183691;
312font-weight: bold;
313}
314.z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
315 color: #323232;
316}
317.z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
318 color: #183691;
319font-weight: bold;
320}
321.z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
322 color: #323232;
323}
324.z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
325 color: #183691;
326font-weight: bold;
327}
328.z-meta .z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
329 color: #323232;
330}
331.z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
332 color: #183691;
333font-weight: bold;
334}
335.z-meta.z-structure.z-dictionary.z-json .z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
336 color: #323232;
337}
338.z-source.z-python .z-keyword {
339font-weight: bold;
340}
341.z-source.z-python .z-storage {
342font-weight: bold;
343}
344.z-source.z-python .z-storage.z-type {
345font-weight: bold;
346}
347.z-source.z-python .z-entity.z-name.z-function {
348 color: #323232;
349font-weight: bold;
350}
351.z-source.z-php .z-entity.z-name.z-type.z-class {
352 color: #323232;
353font-weight: bold;
354}
355.z-variable.z-language.z-ruby {
356 color: #ed6a43;
357}
358.z-entity.z-name.z-type.z-module.z-ruby {
359 color: #795da3;
360font-weight: bold;
361}
362.z-entity.z-name.z-type.z-class.z-ruby {
363 color: #795da3;
364font-weight: bold;
365}
366.z-entity.z-other.z-inherited-class.z-ruby {
367 color: #795da3;
368font-weight: bold;
369}
370.z-punctuation.z-definition.z-heading.z-markdown, .z-punctuation.z-definition.z-italic.z-markdown, .z-punctuation.z-definition.z-bold.z-markdown, .z-punctuation.z-definition.z-string.z-begin.z-markdown, .z-punctuation.z-definition.z-string.z-end.z-markdown, .z-punctuation.z-definition.z-metadata.z-markdown, .z-punctuation.z-definition.z-link.z-markdown, .z-punctuation.z-definition.z-blockquote.z-markdown, .z-punctuation.z-definition.z-raw.z-markdown {
371 color: #a71d5d;
372}
373.z-text.z-html.z-markdown .z-meta.z-separator {
374 color: #b3b3b3;
375}
376.z-text.z-html.z-markdown .z-markup.z-heading {
377font-weight: bold;
378}
379.z-text.z-html.z-markdown .z-markup.z-raw.z-block {
380 color: #323232;
381}
382.z-text.z-html.z-markdown .z-markup.z-raw.z-inline {
383 color: #323232;
384}
385.z-text.z-html.z-markdown .z-meta.z-link, .z-text.z-html.z-markdown .z-meta.z-image {
386 color: #4183c4;
387}
388.z-text.z-html.z-markdown .z-markup.z-underline.z-link, .z-text.z-html.z-markdown .z-constant.z-other.z-reference {
389font-style: italic;
390}
391.z-text.z-html.z-markdown .z-markup.z-list {
392 color: #ed6a43;
393}
394.z-text.z-html.z-markdown .z-markup.z-bold {
395font-weight: bold;
396}
397.z-text.z-html.z-markdown .z-markup.z-italic {
398font-style: italic;
399}
400.z-text.z-html.z-markdown .z-markup.z-bold .z-markup.z-italic {
401font-weight: bold;
402font-style: italic;
403}
404.z-text.z-html.z-markdown .z-markup.z-italic .z-markup.z-bold {
405font-weight: bold;
406font-style: italic;
407}
templates/base.html deleted −48
@@ -1,48 +0,0 @@
1<!doctype html>
2<html lang="en">
3<head>
4 {% block title %}
5 <title>cleberg.net</title>
6 {% endblock %}
7 <meta name="viewport" content="width=device-width, initial-scale=1.0">
8 <meta name="theme-color" content="#111" media="(prefers-color-scheme: dark)">
9 <meta name="theme-color" content="#fff" media="(prefers-color-scheme: light)">
10 {% if page %}
11 {% if page.description %}
12 <meta name="description" content="{{ page.description }}">
13 {% elif config.description %}
14 <meta name="description" content="{{ config.description }}">
15 {% endif %}
16 {% elif config.description %}
17 <meta name="description" content="{{ config.description }}" />
18 {% endif %}
19
20 {% block rss %}
21 <link rel="alternate" type="application/atom+xml" title="RSS" href="{{ get_url(path="atom.xml", trailing_slash=false) }}">
22 {% endblock %}
23
24 {% block css %}
25 <link rel="stylesheet" href="/styles.min.css">
26 {% endblock css %}
27
28 {% block extra_head %}{% endblock extra_head %}
29</head>
30<body>
31 <nav class="site-nav" aria-label="site-nav" role="navigation">
32 <ul>
33 <li><a href="/">Home</a></li>
34 <li><a href="/blog/">Blog</a></li>
35 <li><a href="/services/">Services</a></li>
36 <li><a href="/wiki/">Wiki</a></li>
37 </ul>
38 </nav>
39
40 <main>{% block content %}{% endblock content %}</main>
41
42 <footer>
43 <p>Last build: {{ now() | date(format="%Y-%m-%d %H:%M:%S") }}</p>
44 <p><a href="https://git.cleberg.net/cleberg.net.git" target="_blank" rel="noopener">Source Code</a></p>
45 <p><a href="/atom.xml">Atom RSS Feed</a></p>
46 </footer>
47</body>
48</html>
templates/index.html deleted −40
@@ -1,40 +0,0 @@
1{% extends "base.html" %}
2
3{% block content %}
4<section>
5 <h1>cleberg.net</h1>
6 <blockquote>Do AIs dream in electric sheep?<br>He hoped it had nanosecond nightmares.</blockquote>
7</section>
8<section>
9 <h2>Recent Blog Posts</h2>
10 {% set section = get_section(path="blog/_index.md") %}
11 {% set i = 0 %}
12 {% for page in section.pages %}
13 {% if i < 3%}
14 {% set_global i = i + 1%}
15 <div class="post">
16 <time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date }}</time>
17 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
18 </div>
19 {% endif %}
20 {% endfor %}
21 <br>
22 <a href="/blog/">All Posts &rarr;</a>
23</section>
24<section>
25 <h2>Everything Else</h2>
26 <ul>
27 <li><a href="/salary/">Salary</a></li>
28 <li><a href="/services/">Services</a></li>
29 <li><a href="/wiki/">Wiki</a></li>
30 </ul>
31</section>
32<section>
33 <h2>Contact</h2>
34 <ul>
35 <li>hello [at] cleberg.net (<a href="/gpg.txt">GPG</a>)</li>
36 <li><a href="https://matrix.to/#/@cyborg:matrix.tchncs.de" target="_blank" rel="noopener">@cyborg:@matrix.tchncs.de</a></li>
37 <li><a href="https://git.cleberg.net/cleberg.net.git" target="_blank" rel="noopener">Git</a></li>
38 </ul>
39</section>
40{% endblock content %}
templates/page.html deleted −48
@@ -1,48 +0,0 @@
1{% extends "base.html" %}
2
3{% block title %}
4<title>{{ page.title }} | cleberg.net</title>
5{% endblock %}
6
7{% block css %}
8<link rel="stylesheet" href="/syntax-theme-dark.css" media="(prefers-color-scheme: dark)">
9<link rel="stylesheet" href="/syntax-theme-light.css" media="(prefers-color-scheme: light)">
10<link rel="stylesheet" href="/styles.css">
11{% endblock css %}
12
13{% block content %}
14<section class="post-metadata">
15 <h1>{{ page.title }}</h1>
16 {% if page.date %}
17 <p><time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date }}</time></p>
18 {% endif %}
19 <p>{{ page.word_count }} words; {{ page.reading_time }} minute(s)</p>
20{% if page.toc %}
21 <details open>
22 <summary>Table of Contents</summary>
23 <nav class="page-nav" aria-label="page-nav" role="navigation">
24 <ul>
25 {% for h1 in page.toc %}
26 <li>
27 <a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
28 {% if h1.children %}
29 <ul>
30 {% for h2 in h1.children %}
31 <li>
32 <a href="{{ h2.permalink | safe }}">{{ h2.title }}</a>
33 </li>
34 {% endfor %}
35 </ul>
36 {% endif %}
37 </li>
38 {% endfor %}
39 </ul>
40 </nav>
41 </details>
42{% endif %}
43</section>
44
45<article role="article">
46{{ page.content | safe }}
47</article>
48{% endblock content %}
templates/section.html deleted −18
@@ -1,18 +0,0 @@
1{% extends "base.html" %}
2
3{% block content %}
4<h1>{{ section.title }}</h1>
5<p>Use <code>Ctrl + F</code> to search blog post titles for keywords.</p>
6<p>You can also add the <a href="/atom.xml">Atom RSS Feed</a> to your feed reader.</p>
7{% set post_date = "1970-01-01" | date(format="%Y") %}
8{% for page in section.pages %}
9 {% if post_date != page.date | date(format="%Y") %}
10 {% set_global post_date = page.date | date(format="%Y") %}
11 <h2>{{ post_date }}</h2>
12 {% endif %}
13 <div class="post">
14 <time datetime="{{ page.date | date(format='%Y-%m-%d %H:%M:%S') }}">{{ page.date }}</time>
15 <a href="/blog/{{ page.slug }}/">{{ page.title }}</a>
16 </div>
17{% endfor %}
18{% endblock content %}
content/wiki/index.md → wiki/index.org renamed +41 −37
@@ -1,79 +1,83 @@
1+++ 1#+title: Wiki
2title = "Wiki" 2#+description: An informal wiki of sorts.
3description = "An informal wiki of sorts."
4+++
5 3
6An informal wiki of sorts. 4An informal wiki of sorts.
7 5
8## Digital Garden 6* Digital Garden
9 7
10> At times, wilderness is exactly what readers want: a rich collection 8#+begin_quote
11> of resources and links. At times, rigid formality suits readers 9At times, wilderness is exactly what readers want: a rich collection of
12> perfectly, providing precisely the information they want, no more and 10resources and links. At times, rigid formality suits readers perfectly,
13> no less. Indeed, individual hypertexts and Web sites may contain 11providing precisely the information they want, no more and no less.
14> sections that tend toward each extreme. 12Indeed, individual hypertexts and Web sites may contain sections that
13tend toward each extreme.
15 14
16> Often, however, designers should strive for the comfort, interest, and 15#+end_quote
17> habitability of parks and gardens: places that invite visitors to
18> remain, and that are designed to engage and delight them, to invite
19> them to linger, to explore, and to reflect.
20 16
21[Hypertext Garden](https://www.eastgate.com/garden/) 17#+begin_quote
18Often, however, designers should strive for the comfort, interest, and
19habitability of parks and gardens: places that invite visitors to
20remain, and that are designed to engage and delight them, to invite them
21to linger, to explore, and to reflect.
22 22
23## Git 23#+end_quote
24
25[[https://www.eastgate.com/garden/][Hypertext Garden]]
26
27* Git
24 28
25I want to get rid of all local modifications and go back to the working 29I want to get rid of all local modifications and go back to the working
26tree of the most recent commit: 30tree of the most recent commit:
27 31
28```sh 32#+begin_src sh
29git restore . 33git restore .
30``` 34#+end_src
31 35
32Revert a specified commit: 36Revert a specified commit:
33 37
34```sh 38#+begin_src sh
35git revert commit-id 39git revert commit-id
36``` 40#+end_src
37 41
38Reset the repository to a specific commit in the git log: 42Reset the repository to a specific commit in the git log:
39 43
40```sh 44#+begin_src sh
41git reset --mixed commit-id 45git reset --mixed commit-id
42``` 46#+end_src
43 47
44I need to commit and push changes to a remote that has been changed 48I need to commit and push changes to a remote that has been changed
45since my most recent pull: 49since my most recent pull:
46 50
47```sh 51#+begin_src sh
48git pull --rebase 52git pull --rebase
49``` 53#+end_src
50 54
51## Hardware 55* Hardware
52 56
53### Laptops 57** Laptops
54 58
55#### macOS 59*** macOS
56 60
57| Category | Details | 61| Category | Details |
58| -------- | --------------- | 62|----------+-----------------|
59| Model | Macbook Pro 16" | 63| Model | Macbook Pro 16” |
60| CPU | Apple M2 Pro | 64| CPU | Apple M2 Pro |
61| RAM | 16GB | 65| RAM | 16GB |
62| Storage | 512GB SSD | 66| Storage | 512GB SSD |
63 67
64#### Linux 68*** Linux
65 69
66| Category | Details | 70| Category | Details |
67| -------- | ------------------------------------------- | 71|----------+---------------------------------------------|
68| Model | Lenovo ThinkPad E15 Gen 4, model 21ED0048US | 72| Model | Lenovo ThinkPad E15 Gen 4, model 21ED0048US |
69| CPU | AMD Ryzen 5 5625U with Radeon Graphics | 73| CPU | AMD Ryzen 5 5625U with Radeon Graphics |
70| RAM | 16 GB | 74| RAM | 16 GB |
71| Storage | 256 GB SSD | 75| Storage | 256 GB SSD |
72 76
73### Servers 77** Servers
74 78
75| Category | Details | 79| Category | Details |
76| ------------------ | -------------------------------------- | 80|--------------------+----------------------------------------|
77| Case | Rosewill RSV-R4100U 4U | 81| Case | Rosewill RSV-R4100U 4U |
78| Motherboard | NZXT B550 | 82| Motherboard | NZXT B550 |
79| CPU | AMD Ryzen 7 5700G with Radeon Graphics | 83| CPU | AMD Ryzen 7 5700G with Radeon Graphics |
@@ -82,7 +86,7 @@ git pull --rebase
82| Storage (HDD Bay) | 48TB HDD | 86| Storage (HDD Bay) | 48TB HDD |
83| PSU | Corsair RM850 PSU | 87| PSU | Corsair RM850 PSU |
84 88
85### Networking Equipment 89** Networking Equipment
86 90
87- UDM-Pro 91- UDM-Pro
88- USW-24-PoE 92- USW-24-PoE
@@ -96,9 +100,9 @@ git pull --rebase
96- USW 24-Port Patch Panel 100- USW 24-Port Patch Panel
97- USW Switch Lite 8 PoE 101- USW Switch Lite 8 PoE
98 102
99## Software 103* Software
100 104
101### Laptop 105** Laptop
102 106
103Alpine 3.18.2; no DE. 107Alpine 3.18.2; no DE.
104 108
@@ -128,7 +132,7 @@ I have Sway installed and configured, but only launch it when I must.
128- zola 132- zola
129- zsh 133- zsh
130 134
131### Server 135** Server
132 136
133Ubuntu 22.04.1; no DE. 137Ubuntu 22.04.1; no DE.
134 138